Copilot SDK harness
外部插件 @openclaw/copilot 通过 GitHub Copilot CLI(@github/copilot-sdk)运行内嵌的订阅 Copilot 智能体轮次,而不是使用 OpenClaw 的内置运行框架。Copilot CLI 会话拥有底层智能体循环:原生工具执行、原生压缩(infiniteSessions),以及 copilotHome 下由 CLI 管理的线程状态。OpenClaw 仍然负责聊天频道、会话文件、模型选择、动态工具(桥接)、审批、媒体投递、可见聊天记录、/btw 附带问题(参见附带问题(/btw))以及 openclaw doctor。
标记为顺序执行的直接桥接工具会等待同一尝试中较早的工具调用完成,并推迟后续调用,直到它们结束。其他工具调用可以并发运行。
关于更广泛的模型/提供商/运行时划分,请先阅读智能体运行时。
要求¶
- 安装了
@openclaw/copilot插件的 OpenClaw。 - 如果配置使用
plugins.allow,请包含copilot(插件声明的 manifest id)。仅添加 npm 包名@openclaw/copilot的允许列表条目不会匹配,并且会使插件保持被阻止,即使设置了agentRuntime.id: "copilot"也不行。 - 一个能够驱动 Copilot CLI 的 GitHub Copilot 订阅,或者用于无头/定时(cron)运行的
gitHubToken环境变量 / auth 配置文件条目。 - 一个可写的
copilotHome目录。当 OpenClaw 提供智能体目录时,默认值为<agentDir>/copilot;否则为~/.openclaw/agents/<agentId>/copilot。
openclaw doctor 会运行该插件的 doctor 契约,以检查会话状态所有权和未来的配置迁移。它不会探测 Copilot CLI 环境。
安装¶
Copilot 运行时作为外部插件提供,因此核心 openclaw 包不会携带 @github/copilot-sdk 或其平台特定的 @github/copilot-sdk-<platform>-<arch> 运行时包。安装期间请保持可选依赖启用,以便包含原生运行时。请仅将此插件安装给选择使用此运行时的智能体:
设置向导会在你首次选择 github-copilot/* 模型并且你的配置通过 agentRuntime: { id: "copilot" } 将该模型(或其提供商)路由到 Copilot 运行时的时候自动安装该插件;参见快速入门。如果没有这样的选择,OpenClaw 会使用其内置的 GitHub Copilot 提供商,并且永远不会安装此插件。
运行时按以下顺序解析 SDK:
import("@github/copilot-sdk"),来自已安装的@openclaw/copilot包。- 回退目录
~/.openclaw/npm-runtime/copilot/(旧的按需安装目标)。
缺少 SDK 时会显示一个错误,错误代码为 COPILOT_SDK_MISSING,并附上上面的重新安装命令。
快速入门¶
将某个模型(或提供商)固定到运行框架:
{
agents: {
defaults: {
model: "github-copilot/auto",
models: {
"github-copilot/auto": {
agentRuntime: { id: "copilot" },
},
},
},
},
}
在单个模型条目上设置 agentRuntime.id,则仅将该模型路由到运行框架;在提供商上设置,则将该提供商下的所有模型路由到运行框架。
github-copilot/auto 是通用起点。具名的 Copilot 模型取决于账户和组织策略;在固定某个具名模型之前,请确认你已认证的 Copilot CLI 确实暴露了该模型。
支持的提供商¶
该运行框架支持标准的 github-copilot 提供商(由 extensions/github-copilot 拥有),以及自定义的 models.providers 条目,前提是模型具有非空 baseUrl 且 api 采用以下形状之一:
anthropic-messagesazure-openai-responsesollama(兼容 OpenAI 的 completions)openai-completionsopenai-responses
原生提供商 ID(openai、anthropic、google、ollama)仍由各自的原生运行时所有。如需通过 Copilot BYOK 路由某个端点,请改用不同的自定义提供商 ID。
Copilot BYOK 端点必须是公开的 HTTPS URL。该运行框架会为 Copilot SDK 的每次尝试提供一个环回代理,然后通过 OpenClaw 受保护的 fetch 路径转发提供商流量,因此 DNS 固定和 SSRF 策略仍然由 OpenClaw 掌控。对于本地 Ollama、LM Studio 或局域网模型服务器,请使用 OpenClaw 原生运行框架。
BYOK¶
Copilot BYOK 使用 SDK 的会话级自定义提供商契约。OpenClaw 传递解析后的模型端点、API 密钥、bearer-token 模式、请求头、模型 ID 以及上下文/输出限制;提供商传输逻辑仍位于 SDK 中,而不是核心中。
{
agents: {
defaults: {
model: "custom-proxy/llama-3.1-8b",
models: {
"custom-proxy/llama-3.1-8b": {
agentRuntime: { id: "copilot" },
},
},
},
},
models: {
mode: "merge",
providers: {
"custom-proxy": {
baseUrl: "https://api.example.com/v1",
apiKey: "${CUSTOM_PROXY_API_KEY}",
api: "openai-responses",
authHeader: true,
models: [{ id: "llama-3.1-8b", name: "Llama 3.1 8B" }],
},
},
},
}
BYOK 会话与订阅会话以及其他 BYOK 端点或凭据按不同的键区分。轮换密钥、请求头、模型或端点会启动一个新的 Copilot SDK 会话,而不是恢复不兼容的状态。
认证¶
优先级(在每个智能体执行 runCopilotAttempt 期间生效):
- 尝试输入上的显式
useLoggedInUser: true—— 使用智能体copilotHome下 Copilot CLI 已登录的用户。 - 尝试输入上的显式
gitHubToken(需要profileId+profileVersion)。用于需要绕过 auth 配置文件解析的直接 CLI 调用和测试。 - 契约解析的
resolvedApiKey+authProfileId—— 生产环境的主路径。核心会在调用运行框架之前解析智能体配置的github-copilotauth 配置文件(src/infra/provider-usage.auth.ts:resolveProviderAuths),因此github-copilot:<profile>auth 配置文件可以在无头、cron 或多配置文件设置中无需环境变量即可端到端工作。 - 运行框架环境变量回退,按以下顺序检查(第一个非空值生效;空字符串视为不存在)。这适用于显式选择的 Copilot 运行框架。
github-copilot提供商仅接受COPILOT_GITHUB_TOKEN作为其自动环境凭据: OPENCLAW_GITHUB_TOKEN—— 运行框架特定的覆盖项;允许你为 OpenClaw 运行框架固定一个令牌,而不会影响系统级的gh/ Copilot CLI 配置。COPILOT_GITHUB_TOKEN—— 标准的 Copilot SDK / CLI 环境变量。GH_TOKEN—— 标准的ghCLI 环境变量。GITHUB_TOKEN—— 通用的 GitHub 令牌回退。
合成的池配置 ID 为 env:<NAME>;配置版本是 Token 的不可逆 sha256 指纹,因此轮换 env 值会使客户端池干净地失效。
- 当没有可用的 Token 信号时,默认使用
useLoggedInUser。
每个 agent 都有自己的 copilotHome,因此 Copilot CLI Token、会话和配置永远不会在同一台机器的 agent 之间泄漏。默认值为 <agentDir>/copilot(将 SDK 状态与 OpenClaw 的 models.json / openclaw-agent.sqlite 分开放置);如果没有提供 agent 目录,则为 ~/.openclaw/agents/<agentId>/copilot。可以在尝试输入中使用 copilotHome: <path> 覆盖为自定义位置(例如,用于迁移的共享挂载点)。
运行框架的真实环境测试使用 OPENCLAW_COPILOT_AGENT_LIVE_TOKEN 作为直接 Token。共享的真实环境测试设置会在将真实认证配置暂存到隔离的测试主目录后,清除 COPILOT_GITHUB_TOKEN、GH_TOKEN 和 GITHUB_TOKEN;因此,通过专用变量传入的 gh auth token 值可以避免误跳过,而不会泄漏到无关的测试套件中。
配置面¶
运行框架从每次尝试的输入(runCopilotAttempt({...}))以及 extensions/copilot/src/ 内的一小组环境变量默认值中读取配置:
| 字段 | 用途 |
|---|---|
copilotHome |
每个 agent 的 CLI 状态目录(默认值见上文)。 |
model |
字符串或 { provider, id, api?, baseUrl?, headers?, authHeader? }。省略时使用 agent 的正常模型选择;运行框架会验证解析出的 provider 是否受支持。 |
reasoningEffort |
"low" \| "medium" \| "high" \| "xhigh"。映射自 OpenClaw 在 auto-reply/thinking.ts 中的 ThinkLevel / ReasoningLevel 解析结果。 |
infiniteSessionConfig |
SDK infiniteSessions 块的可选覆盖,由 harness.compact 驱动。保持默认即可。 |
hooksConfig |
可选的 Copilot SDK 原生 SessionHooks 配置,用于工具/MCP、用户提示、会话和错误回调。与 OpenClaw 的可移植生命周期钩子相互独立。 |
permissionPolicy |
SDK 内置工具类型(shell、write、read、url、mcp、memory、hook)的 onPermissionRequest 处理程序的可选覆盖。默认以 rejectAllPolicy 作为安全网;至于它为何实际上永远不会触发,请参阅 权限与 ask_user。 |
enableSessionTelemetry |
可选的 SDK 会话遥测标志。 |
OpenClaw 插件钩子不需要任何 Copilot 特定的尝试配置。运行框架通过标准的运行框架辅助函数运行 before_prompt_build、llm_input、llm_output 和 agent_end。SDK 压缩成功后,还会运行 before_compaction 和 after_compaction。桥接的 OpenClaw 工具会运行 before_tool_call 并上报 after_tool_call;hooksConfig 保留给没有可移植等价物的原生 SDK 专属回调。
OpenClaw 中的其他部分无需了解这些字段。其他插件、渠道和核心代码只看到标准的 AgentHarnessAttemptParams / AgentHarnessAttemptResult 结构。
压缩¶
当 harness.compact 运行时,Copilot SDK 运行框架会:
- 恢复被跟踪的 SDK 会话,但不继续处理待处理的工作。
- 调用 SDK 的会话级历史压缩 RPC。
- 返回 SDK 压缩结果,且不会在工作区下写入兼容性标记文件。
OpenClaw 侧的转录日志(见下文)会持续接收压缩后的消息,因此面向用户的聊天历史保持一致。
转录持久化¶
runCopilotAttempt 为每次尝试提供一个转录日志(createAttemptTranscriptJournal,位于 extensions/copilot/src/attempt-transcript-journal.ts),将每一轮的消息持久化到 OpenClaw 会话转录中。日志标识符以轮次为作用域,而非内容为作用域:初始用户轮次的键为 ${runId}:user,SDK 来源事件的键为 copilot-sdk:${sdkSessionId}:${eventId};结合已声明的事件 ID 以及转录存储处的幂等扫描,重新发出的先前轮次条目不会产生重复。
助手轮次及其工具结果会以结构完整的组的形式写入日志,因此组与组之间发生崩溃时仍会留下有效的转录前缀。before_message_write 钩子可以对内容进行删改,但不能更改角色或工具拓扑;结构上具有破坏性的重写会丢弃整个组,而不是持久化一个虚假的重放。
持久化失败采用故障关闭(fail closed)策略。首次写入失败会将日志标记为失败,中止正在进行的 SDK 会话,并将该尝试的重放标记为未验证,因此下一次运行会创建全新的 SDK 会话,而不是信任不完整的转录。只有追加后的转录更新通知是尽力而为的,并会记录日志。
附带问题(/btw)¶
/btw 在此 harness 中并非原生功能。createCopilotAgentHarness() 有意让 harness.runSideQuestion 保持未定义(在 extensions/copilot/harness.test.ts 的 describe("runSideQuestion") 中有断言),因此 OpenClaw 的 /btw 分发器(src/agents/btw.ts)会回退到与非 Codex 运行时相同的路径:直接以简短的附带问题提示词调用配置的模型提供方,并通过 streamSimple 流式返回(不创建 CLI 会话,也不占用额外的池槽位)。
这样可确保 Copilot CLI 会话保留给代理的主回合循环,并让 /btw 的行为与其他非 Codex 运行时保持一致。
Doctor¶
Copilot 插件通过其 manifest 和 doctor 合约提供 doctor 修复元数据:
- 空的
legacyConfigRules(目前没有已弃用字段)。 - 无操作的
normalizeCompatibilityConfig(保留它,以便未来的字段弃用有一个稳定的树内位置)。 - 其 manifest 声明一个
sessionRouteStateOwners条目:provider 为github-copilot,runtime 为copilot,CLI 会话键为copilot,认证配置文件前缀为github-copilot:。
限制¶
- harness 认领
github-copilot以及无属主的自定义 BYOK provider id。即使将agentRuntime.id强制设为copilot,manifest 所拥有的原生 provider id 仍保留在其所属的运行时上。 - 没有 TUI 界面;PI 的 TUI 仍是缺少对等界面的运行时的回退方案。
- 当代理切换到
copilot时,PI 会话状态不会迁移。选择是按尝试进行的;现有 PI 会话仍然有效。 ask_user使用与 provider 无关的网关问题运行时。Control UI 与其他 OpenClaw 问题一样显示相同的问题卡片,受支持的频道会渲染选择按钮,并且下一条排队等待的纯文本消息会在 SDK 请求返回之前解析该网关记录。
权限与 ask_user¶
对于桥接的 OpenClaw 工具,权限强制执行发生在工具包装器内部,而不是通过 SDK 的 onPermissionRequest 回调。PI 使用的同一个 wrapToolWithBeforeToolCallHook(src/agents/agent-tools.before-tool-call.ts)由 createOpenClawCodingTools 应用于每个编码工具:循环检测、受信任插件策略、before-tool-call 钩子,以及通过网关(plugin.approval.request)进行的两阶段插件审批,所有这些都走与原生 PI 尝试完全相同的代码路径。
Copilot 工具桥返回的每个 SDK 工具都带有以下标记:
overridesBuiltInTool: true— 替换 Copilot CLI 中同名内置工具(edit、read、write、bash 等),使每次工具调用都路由回 OpenClaw。skipPermission: true— 告诉 SDK 在调用工具之前不要触发onPermissionRequest({kind: "custom-tool"})。包装后的execute()已经执行了更完善的 OpenClaw 策略检查;SDK 级别的提示要么会短路 OpenClaw 的强制检查(全部允许),要么会阻止每次工具调用(全部拒绝)——两者都不符合与 PI 的对等性。
树内 Codex harness 采用相同的拆分方式:桥接的 OpenClaw 工具会被包装(extensions/codex/src/app-server/dynamic-tools.ts),codex-app-server 自身的原生审批类别(item/commandExecution/requestApproval、item/fileChange/requestApproval、item/permissions/requestApproval)通过 plugin.approval.request 路由(extensions/codex/src/app-server/approval-bridge.ts)。Copilot SDK 的对应方案——对任何到达 onPermissionRequest 的非 custom-tool 类别采用故障关闭的 rejectAllPolicy——是同样的安全网,而且它实际上永远不会触发,因为 overridesBuiltInTool: true 会取代所有内置工具。
embedded、Codex 和 Copilot harness 共享 buildEmbeddedAttemptToolRunContext,用于提供发起方客户端能力、工具绑定、发送者与角色身份、频道路由、任务建议,以及被允许审查审批的设备。这可以在选择后端或恢复回合时保持这些信息不变。extensions/copilot/src/tool-bridge.ts 中的 Copilot 桥在调用 createOpenClawCodingTools 之前,会添加自己的会话与工作区映射、认证、模型上下文和执行回调。
runAttempt 通过共享的 resolveSandboxContext 接缝解析沙箱上下文,向 SDK 传入有效的工作目录,并将 sandbox 及子代理生成的工作区转发到工具桥。该桥还会在 SDK 边界转发其可以强制执行的受限工具构建控制项:includeCoreTools、运行时工具允许列表和 toolConstructionPlan。
该桥还使用来自 openclaw/plugin-sdk/agent-harness-tool-runtime 的共享 harness 工具表面助手,以实现与 PI 的对等。当工具搜索启用时,SDK 看到的是紧凑的控制工具加上一个隐藏的目录执行器,而不是每个 OpenClaw 工具的 schema。在 before_prompt_build 收窄目录之后,共享的 Tool Search 目录和特定模式的调用指令会进入 SDK 开发者提示词。被拒绝的条目不会被公布,空目录也不会添加任何发现指令。当代码模式启用时,该助手会构建与其他 agent harness 相同的代码模式控制表面和目录生命周期。本地模型精简默认值、与运行时兼容的 schema 过滤以及目录清理都保留在共享助手内。
对于 Copilot,tools.toolSearch.mode: "directory" 使用结构化的 tools 语义:通过 tool_search 或 tool_describe 发现,然后通过带有 id 和 args 的 tool_call 执行。隐藏的 OpenClaw 目录名称不会注册为 SDK 工具处理器,也不能被直接调用。固定的 Copilot SDK 1.0.13 通过 Tool.defer 支持对已注册工具声明的原生延迟;这是一个单独的 SDK 目录,而不是针对被省略的 OpenClaw 工具的解析器。OpenClaw 保留其紧凑的桥,而不是向 SDK 注册每一个隐藏的 schema。代理配置不会被重写,embedded harness 仍保留其直接按目录名进行水合的方式。
会话级 GitHub token¶
Copilot SDK 契约区分 客户端级 GitHub token(CopilotClientOptions.gitHubToken,用于认证 CLI 进程本身)和 会话级 token(SessionConfig.gitHubToken,决定该会话的内容排除、模型路由和配额;在 createSession 和 resumeSession 中均生效)。执行框架通过 resolveCopilotAuth 一次性解析认证,并在认证模式为 gitHubToken 时同时设置这两个字段(显式的 auth.gitHubToken,或来自已配置 github-copilot 认证配置文件的契约解析 resolvedApiKey)。当解析后的模式为 useLoggedInUser 时,会话级字段将被省略,以便 SDK 继续从已登录身份派生身份。
ask_user 使用 SessionConfig.onUserInputRequest。桥接器将 SDK 选项或无选项的自由文本提示注册为网关问题,对于固定选项请求接受选项索引或标签,并在 SDK 请求允许时接受自由格式答案。中止 OpenClaw 尝试会取消网关记录并返回空的 SDK 答案。
相关¶
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw