Macro 用户态代码模式实战:用 Deno 运行真实 Macro SDK 的 ExecuteCode 工具

发布时间:2026/10/30 1:43:30
Macro 用户态代码模式实战:用 Deno 运行真实 Macro SDK 的 ExecuteCode 工具 后端前端协同办公即时通讯企业应用CRMAI Agent人工智能【免费下载链接】macroMacro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — -linked together with shared AI memory.项目地址https://gitcode.com/GitHub_Trending/macro3/macro点击查看免费下载在 Macro 的 Code Mode 中模型不必逐条调用原生工具而是直接编写一段 JavaScript ES 模块在 Deno 沙箱里操作真实的 Macro SDK。本文聚焦**用户态user scope**这一入口它不是 Agent 会话内部的执行通道而是面向公共 MCP 等已登录但不在会话中的客户端。读完后你将掌握ExecuteCode的程序契约、参数上限、沙箱边界、鉴权与凭证传递方式以及DescribeCodeTools/ReadCodeDocs构成的发现流程。一、用户态 Code Mode 的定位同一套运行时不同的身份来源仓库里有两条 Code Mode 装配路径共用同一份程序契约与运行时说明但上下文类型不同维度会话态Session用户态User工具集合toolset()user_toolset()上下文CodeModeToolContext含AgentSessionUserCodeModeToolContext仅 service cognition服务抽象SessionCodeModeUserCodeMode回执ExecutionReceipt含execution_idUserExecutionReceipt不落库评审reviews允许明确拒绝工具集合的定义在 toolset.rs/// Code mode for clients outside agent sessions, such as the public MCP. pub fn user_toolset() - AsyncToolCollectionUserCodeModeToolContext { AsyncToolCollection::new() .add_tool::UserExecuteCode, UserCodeModeToolContext() .add_tool::DescribeCodeTools, UserCodeModeToolContext() .add_tool::ReadCodeDocs, UserCodeModeToolContext() }也就是说用户态对外只暴露三个工具执行、发现、读文档。测试 toolset/test.rs 进一步断言了这一点并确认ExecuteCode的输入 schema 中没有execution_idassert_eq!(names, [DescribeCodeTools, ExecuteCode, ReadCodeDocs]); let schema serde_json::to_value(tools.tools[ExecuteCode].input_schema).unwrap(); assert!(schema[properties].get(execution_id).is_none()); assert!(schema[properties].get(source).is_some());会话态需要execution_id、turn_id、acting_as等字段来绑定哪一轮对话、以谁的身份执行用户态没有这些概念——身份来自请求本身携带的登录凭证因此字段被刻意裁掉避免生成代码伪造身份。在服务层这两条路径最终都汇入agent_harness_service。会话态使用RunCredentials先过 owner 审批门再为 Agent 铸造令牌用户态使用UserRunCredentials直接为登录用户铸造令牌见 code_mode.rs/// Credentials for public MCP runs: the signed-in user, with no session or agent. /// The MCP client asks the user before each tool call, so there is no owner gate. pub struct UserRunCredentials { pub signer: MacroApiTokenSigner, pub hosts: BTreeMapString, String, }注释直接说明了为什么用户态没有 owner gateMCP 客户端在每次工具调用前已经向用户征得同意。这是理解为什么用户态更简洁的关键设计取舍。二、程序契约main({macro})与{data}返回2.1 关联文档原文要义overview.md 用一段话概括了全部契约Use the real Macro SDK, prebundled for Deno. Write an ES module exporting async main({macro}); optionally import {Macro} from macro-inc/sdk or Handlebars from handlebars. The provided macro is already authenticated as the signed-in user; main also receives token and hosts for explicit construction. Never expose the token. Return plain JSON as {data}. Nothing is saved between executions.拆解成可执行规则必须是 ES 模块且导出async的main函数没有默认导出、没有 CommonJS。macro已鉴权直接用它即可无需自己构造客户端。可选 import{Macro}来自macro-inc/sdkHandlebars来自handlebars两者均已预打包。main还能收到token和hosts用于显式构造Macro实例。绝不暴露 token不打印、不返回、不写入。返回纯 JSON形状为{data: ...}。执行之间不保留任何状态。2.2 Rust 侧的输入类型UserExecuteCode 只有两个字段并开启了deny_unknown_fields#[derive(Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct UserExecuteCode { pub source: String, #[serde(default default_timeout)] pub timeout_ms: u64, }deny_unknown_fields意味着传入execution_id、acting_as、turn_id这类会话态字段会直接反序列化失败而不是被静默忽略。这既是安全边界也是给调用方的明确信号用户态没有以某个 Agent 身份运行这件事。2.3 返回值校验为什么reviews会被判失败main的返回值并非原样透传。执行结束后由 settle() 按契约校验用户态传入的是Reviews::Rejectedif reviews Reviews::Rejected { // Shared guides end with reviews: []; an empty list asks for nothing. if let Some(object) value.as_object_mut() object.get(reviews) Some(serde_json::json!([])) { object.remove(reviews); } let valid value .as_object() .is_some_and(|object| object.len() 1 object.contains_key(data)); return if valid { (ExecutionStatus::Succeeded, Some(value), None) } else { ( ExecutionStatus::Failed, Some(value), Some(main must return exactly {data: JSON}; reviews are unavailable here..into()), ) }; }两个容易踩的坑共享指南如 templates.md的程序示例普遍以reviews: []结尾。用户态会先把空数组这个无害形式删掉因此照抄这些示例仍然能成功。一旦真的返回了非空reviews例如[{kind:email, id:x}]结果为Failed错误信息包含reviews are unavailable。测试 domain/user/test.rs 固定了这个行为assert_eq!(receipt.status, ExecutionStatus::Failed); assert!(receipt.error.unwrap().contains(reviews are unavailable));原因是评审栈需要在会话里展示而用户态没有任何地方可以呈现评审所以UserCodeMode的注释写明 with no session, journal or reviews。2.4 一个最小可用程序// 工具入参source export async function main({macro}) { const me await macro.users.me(); return {data: {userId: me.id}}; }注意两点macro已经是登录用户身份不需要传任何 token返回对象只能有data一个键。三、参数取值与准入校验3.1 两个入参的边界参数类型约束默认值依据sourcestring非空、去除空白后不能为空UTF-8 长度 ≤ 64 KiB无必填validate_requesttimeout_msu641 ≤timeout_ms≤ 30000含排队与 API 调用30000default_timeout校验发生在铸造凭证之前因此非法请求不会消耗签名开销pub(super) fn validate_request(request: ExecuteRequest) - Result(), CodeModeError { if request.source.trim().is_empty() || request.source.len() Limits::default().max_source_bytes || request.timeout_ms 0 || request.timeout_ms 30_000 { return Err(CodeModeError::Invalid(Provide a nonempty JavaScript module exporting main() of at most 64 KiB and timeout_ms between 1 and 30000..into())); } Ok(()) }测试 invalid_requests_are_rejected_before_minting_a_credential 覆盖了空白源码timeout 超限两种情况并断言凭证签发器未被调用。3.2 服务端还有一层更宽的硬限制Limits::default()定义在 code_execution/models.rs限制项默认值含义max_running8同时运行的 Deno 进程数max_queued16允许排队等待进程槽的执行数max_duration30s单次执行最长生命周期max_source_bytes64 KiB源码上限max_frame_bytes256 KiB单帧上限含最终返回给模型的 JSONmax_output_bytes2 MiB单次执行 stdout stderr 合计预算max_events1024常规事件数模型可见的返回值受 256 KiB 约束。这意味着把 300 封邮件正文全量返回会被判为Limit失败——正确做法是返回精简证据ID、时间、片段。公共 MCP 还拥有独立的准入预算避免外部客户端挤占 Agent 会话的槽位见 agent_harness_service/src/main.rsmax_running: 4, max_queued: 8。四、运行时沙箱全新进程、白名单出网、无持久化4.1 Nothing is saved between executions 的实现shared/runtime.md 明确每次执行都是全新进程30 秒含排队、64 KiB 源码、256 KiB 最终 JSON 帧依赖预打包API 流量仅限已配置的 Macro 服务主机不允许文件写入、任意 import、子进程或环境变量访问。DenoRunner::execute每次调用都会新建临时目录并写入四个文件见 deno.rstokio::fs::write(root.join(main.mjs), job.request.source).await?; tokio::fs::write(bootstrap, BOOTSTRAP).await?; tokio::fs::write(root.join(sdk.mjs), SDK).await?; tokio::fs::write(root.join(handlebars.mjs), HANDLEBARS).await?; tokio::fs::write( root.join(imports.json), r#{imports:{macro-inc/sdk:./sdk.mjs,handlebars:./handlebars.mjs}}#, ).await?;imports.json就是预打包的落地方式macro-inc/sdk与handlebars被 import map 指向本地内联文件因此任何网络包安装都不需要也不允许。执行结束后临时目录随tempfile::TempDir生命周期销毁进程被reap()回收reap(mut child).await; // Drop the directory only after the process is reaped. drop(directory);4.2 Deno 权限旗标启动参数在 deno.rs 中一次性收紧.args([ run, --no-prompt, --no-config, --no-lock, --no-remote, --no-npm, --deny-write, --deny-env, --deny-run, --deny-ffi, --deny-sys, --deny-import, ]) .arg(network) .arg(format!(--allow-read{}, root.display())) .arg(format!(--import-map{}, root.join(imports.json).display())) .arg(format!(--v8-flags--max-old-space-size{}, self.heap_mb)) .arg(bootstrap)对照 runtime.md 的表述逐条对应--deny-write→ 没有文件系统写入--no-remote --no-npm --deny-import import map → 没有任意 import 与包下载--deny-run→ 没有子进程--deny-envenv_clear()→ 没有环境变量访问注释说明APP_SECRETS_JSON、AWS 凭证、代理设置等都无法泄漏到生成代码--allow-readscratch→ 只能读取本次临时目录。网络权限按需生成从environment.hosts提取 host:port 组成白名单为空时直接--deny-net。4.3 Deno 版本被强校验DENO_VERSION 2.9.6启动时执行deno --version并比对前缀不匹配则拒绝启动。这避免了本地能跑、线上行为不同的隐性差异。4.4 运行时还做了哪些防护deno.rs 的桥接层有两条值得注意的实现输出预算stdout/stderr 各按LinesCodec限长读取累计超过max_output_bytes立即返回FailureCode::Limit同时为 SDK 诊断保留 64 KiB 独立额度避免内部诊断吃掉用户程序的输出额度。Token 脱敏每行在 JSON 解析前先做字符串替换把本次凭证替换为[REDACTED]覆盖值和对象键且不破坏 JSON 结构fn redact_token(line: String, token: str) - String { if token.is_empty() { line } else { line.replace(token, [REDACTED]) } }注释也诚实地说明了边界这是防误泄而非密码学保证——程序本身持有凭证刻意编码后传出不在防护范围内。这正是文档反复强调Never expose the token的原因最终仍需程序自律服务端只兜底。五、macro从哪来一次性的凭证传递5.1 凭证只走私有管道bootstrap.mjs 是可信引导脚本顺序固定const context await new Response(Deno.stdin.readable).json(); // ... 安装 fetch 观测与追踪头注入 ... const macro new Macro({token: context.token, hosts: context.hosts}); const session context.sessionId ? macro.agentSessions.byId(context.sessionId) : undefined; const execution session context.executionId ? session.codeExecutions.byId(context.executionId) : undefined; const program new URL(./main.mjs, import.meta.url).href; const { main } await import(program); if (typeof main ! function) throw new Error(Export an async main({macro, session, execution}) function.); const value await main({...context, macro, session, execution}); emit({type: result, value: value ?? null});由此可得几个文档未明说但很重要的事实main收到的参数是{...context, macro, session, execution}因此token、hosts、traceHeaders、sessionId、executionId都会出现。用户态下sessionId/executionId为None所以session与execution为undefined——不要写execution.reviews.create那是会话态独有的。凭证从 stdin 单向传入写入后立即drop(stdin)不经过 argv也不落盘deno.rs。动态 import 的路径由字符串拼出而非字面量注释说明这样能让整个模块图在运行时加载、从而受 scratch 读权限约束。5.2 凭证的生命周期用户态令牌由MacroApiTokenSigner::mint_for(user, 60)铸造注释写明与 30 秒 deadline 一致外加时钟余量且不提供 refresh credential。凭证签发点在 domain/user.rsvalidate_request(request)?; let environment self.credentials.issue(user).await?;注意user来自request.user_id已认证请求而非来自生成代码——UserExecutionCredentials的注释写得很直白The user comes from the authenticated request, never from generated code.5.3hosts决定了两件事sdk_hosts()在 code_mode.rs 中把各服务 URL 收敛为一张表document-content、static-content、storage、search、properties、auth、agent-harness、email、calendar、cognition、notification、scheduled-action、static-files、connection、contacts、unfurl。这张表同时是SDK 构造参数new Macro({token, hosts})Deno 出网白名单--allow-nethost:port,...。也就是说API 流量仅限已配置的 Macro 服务主机不是策略声明而是进程级网络权限。六、回执结构activity、activity_truncated、result、error用户态返回 UserExecutionReceiptpub struct UserExecutionReceipt { pub status: ExecutionStatus, pub activity: VecExecutionActivity, pub activity_truncated: bool, pub result: OptionValue, pub error: OptionString, }含义逐项说明statusSucceeded/Failed/Cancelled/TimedOut等终态。activity观测到的 SDK 操作序列不含参数与响应observing SDK operations, without arguments or responses。它由 bootstrap 中的observeRequests产生并以{type:progress}帧回传。activity_truncatedSDK 操作数超过记录上限bootstrap 中为 128 次 started超出只发一次{truncated:true}。result只有main显式返回的值console.log不会进入结果。error失败时的安全文本。测试 runs_as_the_authenticated_user_and_returns_data_with_activity 把这条链路固定下来程序拿到的是本次签发的凭证、session_id与execution_id均为None最终activity里出现一条Get Agent Coding Preferences且状态为Completed。result与error可以同时非空当返回形状不合法例如带了非空reviews时statusFailed但result保留原始返回值便于定位问题。七、发现流程先描述任务再读符号ExecuteCode的描述里有一句强约束见 user/execute_code.mdBefore writing a program, call DescribeCodeTools with task set to what the program must do, unless the entity indexes it needs are already in this conversation, then read the symbols the program uses with ReadCodeDocs, all together in one call. The SDK is large; do not guess method names or options.这条建议有实际成本依据SDK 目录有 30 个实体、1799 条符号引用见 documentation.json 的统计猜测方法名的失败率远高于一次发现调用。7.1DescribeCodeTools的三个参数参数约束作用task可选自然语言交给 Jev 排序返回最相关实体的索引entities are most relevant firstnames≤ 5 个实体名直接点名要索引如documents、email、coretopics≤ 4 个直接拉取工作流指南search/ai/templates/examples约束在 DescribeCodeTools::describe 中硬编码超限返回Invalid。用户态还额外有一条限制if host CodeHost::User self.topics.iter().any(|topic| matches!(topic, CodeGuideTopic::Templates | CodeGuideTopic::Examples)) { return Err(CodeModeError::Invalid(Only the search and ai guides are available here..into())); }为什么只放行search与ai因为templates指南的核心示例是execution.reviews.create(...)而用户态根本没有execution句柄examples指向会话态的SKILL.md同样包含评审与turn_id语义。与其给出不可执行的指南不如明确拒绝——这正是用户态与共享文档之间的一致性问题测试 toolset/test.rs 同时验证了共享契约一致、会话态描述含execution.reviews.create而用户态不含。7.2task的选择算法与预算documentation_for_task在 documentation.rs 中实现调用rank_entities把每个实体的name作为问题、把description拼进提示让 Jev 输出概率只保留score 0.5且有限的实体按分数降序、同名按字典序稳定排序单个结果总预算DOCUMENTATION_BYTES 32 KiB超出预算的实体进入omitted调用方可再按名请求status非selected时unconfigured/cancelled/provider_error/timeoutentities为空此时应按 user/discovery.md 的指示pick entities from the catalog and request them by name, up to five together。超时策略也不同这里是 5 秒The agent is waiting on this call而每轮提示注入路径只用 1 秒且失败即回退到紧凑目录。7.3ReadCodeDocs按符号读引用pub struct ReadCodeDocs { pub symbols: VecString, }数量必须在 1..10否则Invalid按请求顺序返回unknown收集目录里不存在的符号omitted收集因 32 KiB 预算被跳过的符号第一条引用总是返回因为单条引用不会超过预算。测试 symbols_from_an_index_read_as_their_own_references 验证了三点方法级符号TaskNamespace.create可读、其引用里包含opts.name这类可继续追溯的符号、不存在的TaskNamespace.missing落入unknown。八、可运行的程序范式8.1 带边界的并发搜索guides/search.md 在用户态可用search指南被放行。它的关键手法是多查询并发 早停 保留失败分支export async function main({macro}) { const queries [launch decision, release readiness]; const outcomes await Promise.allSettled(queries.map(async query { const evidence []; let stoppedEarly false; for await (const document of macro.documents.search(query, {searchOn:content})) { if (evidence.length 5) { stoppedEarly true; break; } evidence.push({id:document.id, text:(await document.content()).slice(0,3000)}); } return {query, evidence, stoppedEarly}; })); return {data: outcomes.map((item, index) item.status fulfilled ? item.value : {query:queries[index], error:String(item.reason)})}; }请注意两点示例中去掉了reviews:[]用户态也不需要并且显式标注stoppedEarly——指南反复强调a short sample cannot establish corpus-wide completeness or absence即不能把提前停止的迭代器说成穷尽结果。8.2 工作区概览按类别小页并发shared/program.md 给出了一类高效范式export async function main({macro}) { const types [email, channel_thread, calendar_event]; const pages await Promise.all(types.map(type macro.workspace.page({includeTypes:[type], limit:3}) )); return {data: types.map((type, i) ({ type, hasMore: pages[i].hasMore, items: pages[i].items.map(item ({id:item.id, type:item.type, name:item.name ?? item.subject})) }))}; }其中两条约束来自同一份文档单个混合分页会被某一类实体占满所以要按类别分别取页每页只是样本不是类别总量因此只返回hasMore与精简元数据。任务属于文档需用includeTypes:[document]配合df:{l:{dst:task}}AST 的与|都是二元操作符单个条件不要包一层。8.3 结构化抽取与分类ai指南同样在用户态可用。典型组合是先把源文本取到本地再对文本做macro.ai.structuredCompletion配合toolset:{type:none}或对一批条目做macro.ai.decide。SKILL.md的提醒是check the returned outcome rather than assuming success——即必须检查返回结构里的结果状态而不是假定调用成功。8.4 用 Handlebars 做本地渲染用户态仍然可以import Handlebars from handlebars并使用它——只是不能创建评审栈。渲染本身不产生任何 API 调用import Handlebars from handlebars; export async function main() { const render Handlebars.compile(Hi {{name}},\n\n{{body}}, {strict:true, noEscape:true}); const drafts [ {name:Alex, body:Launch update}, {name:Sam, body:Release readiness} ].map(v ({recipient: v.name, text: render(v)})); return {data: {drafts, note: Rendered locally; nothing was sent.}}; }noEscape:true的用途见 templates.mdMarkdown 正文需要保留 Markdown 语法若目标是 HTML 则应使用默认转义。九、失败处理与重试纪律runtime.md里最重要的一句是SDK writes, including sending messages and email, take effect immediately; completed writes survive cancellation or errors. Inspect state before retrying, never blindly replay writes.配合代码看这一点是被架构强化的取消只停进程不回滚写入。Outcome::Cancelled对应的错误文案是 Execution cancelled. Unfinished calls may have taken effect.service.rs。超时同理30 秒是含排队的墙钟预算超时不代表没有副作用。用户态没有幂等键。会话态可以用execution_id重放同一份源码拿回已存回执Reusing your existing ID with identical source returns its saved receipt without running again但UserExecuteCode没有这个字段所以用户态重试必然是重新执行。这正是先查状态再重试在用户态更为关键的原因。失败分类可用于决策。FailureCode区分Runtime/Protocol/Limit/InternalLimit说明要缩小返回体积或降低并发Protocol通常意味着输出接错了形状Runtime才是程序逻辑问题。排查顺序建议先看status与error再看activity判断写操作是否已经发生最后才决定是否重试对邮件、消息等不可逆操作重试前应先用macro.email.search/macro.channels之类只读调用确认状态。十、可观测性与日志console.log/info/debug/warn/error都被 bootstrap 重写为{type:log}帧经EventKind::Log进入事件流它们只用于诊断不会成为模型可见结果。用户态执行会打一条 spancode_mode.user_execution字段包含source_bytes、timeout_ms、status、activity见 domain/user.rs。注意skip_all与不记录源码避免把用户数据写进追踪。生成的代码发出的 SDK 请求会带上traceparent/tracestate并加一个标记头x-macro-code-execution: 1bootstrap用于在服务端区分程序发起与人发起的调用。十一、把代码读下去推荐入口按阅读顺序整理关键文件便于继续深入契约与提示词user/overview.md、user/execute_code.md、user/discovery.md、shared/program.md、shared/runtime.md工具装配与参数inbound/toolset.rs用户态服务与凭证domain/user.rs、domain/user/test.rs返回值校验与准入domain/service.rsSDK 文档选择domain/documentation.rs运行时与沙箱code_execution/models.rs、deno.rs、bootstrap.mjs生产装配services/agent_harness_service/src/code_mode.rs、services/agent_harness_service/src/public_mcp.rs小结用户态 Code Mode 的本质是把真实 SDK 真实身份塞进一个一次性 Deno 进程。文档用一段话规定了程序形状ES 模块、main({macro})、{data}、不泄露 token、不跨执行保存状态而仓库用三层机制把这些规定变成硬约束——输入层的deny_unknown_fields与 64 KiB/30 秒校验执行层的 deny-write/deny-env/白名单出网与 token 脱敏输出层的{data}形状校验与 256 KiB 帧上限。理解这三层就能判断一段程序能不能跑、跑完会不会留下东西、失败后该不该重试。赞分享后端前端协同办公即时通讯企业应用CRMAI Agent人工智能【免费下载链接】macroMacro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — -linked together with shared AI memory.项目地址https://gitcode.com/GitHub_Trending/macro3/macro点击查看免费下载相关推荐Macro Agent Code Mode 用户侧发现机制实战用 DescribeCodeTools 索引真实 SDK 再执行 ExecuteCodeMacro Agent Code Mode 用户侧发现机制实战用 DescribeCodeTools 索引真实 SDK 再执行 ExecuteCode 在 M后端前端协同办公即时通讯企业应用CRMAI Agent人工智能proc-macro-workshop 实战案例3 个真实工业级应用解析proc macro workshop 实战案例3 个真实工业级应用解析 想要掌握 Rust 过程宏编程的核心技能吗proc macro workshop示例工程教程3个macOS用户常犯的错误配置如何避免网络资源下载工具失效3个macOS用户常犯的错误配置如何避免网络资源下载工具失效 你是否遇到过这样的情况下载了一个功能强大的网络资源下载工具准备大展身手下载心仪的视频或音乐桌面应用网络音视频上一篇FFmpeg libav码率控制CBR、VBR、ABR模式详解下一篇VirtualMotionCapture虚拟摄像机使用教程创建专业级拍摄效果创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询