无界Harness文档
无界Harness文档
欢迎快速开始

基础

工作区与成员Issue频道项目标签工作流收件箱与订阅

智能体

认识智能体创建和配置智能体分配 issue 给智能体对话小队Skills

自动化

自动化运行方式

知识与能力

文档知识能力中心连接器

治理

治理

Local Run Provider

为 wujie run 接入新的本地 CLI provider 的工程规范。

Local Run Provider

Local run 是 wujie run <issue> -- <command...> 背后的本地执行能力。它把一个本地 CLI 会话绑定到 Wujie issue,负责创建 local run、启动本地 agent CLI、同步执行日志、同步用户输入和最终回复评论。

需要先讨论、后建 Issue 时,使用:

wujie discuss -- codex
# 短别名
wujie ds -- codex

讨论开始时没有 Issue,也没有 local run。用户与 provider 完成方案讨论后,可以输入 /wujiecreateissue,或使用 Codex、Claude、Cursor、Kiro、AGY 原生 Plan 模式的“在当前上下文实现 / 清空上下文实现”动作。

在交互终端中,只有当前编辑行从行首完整达到 /wujie 时,Wujie 才会显示内联命令候选;空输入、仅输入 / 或更短前缀都不显示。普通字符、CSI-u 字符、退格和 bracketed paste 结束都会重新检查精确触发值,因此从 /wujiecr 连续退格回 /wujie、关闭后清空重输或任意分块输入都能再次打开菜单。粘贴期间不会提前暂停 provider;Wujie 先把完整的 ESC[201~ 结束帧写入 PTY,再等待回显并打开菜单。OSC、DCS、APC、PM、SOS 控制字符串支持 BEL 或 ST 终止,CPR、DA、DSR、模式报告和窗口尺寸等 CSI 终端回复也会被完整识别;这些回复始终原样转发给 provider,但不会进入当前用户输入状态,也不会关闭已经打开的菜单。因此完成一次正常问答后,第一次输入 /wujie 就能稳定触发。菜单打开时仍会忽略终端焦点切换和 SGR/X10 鼠标事件,不关闭菜单、不改变输入状态,也不把这些事件延迟转发给 provider。

触发时,Wujie 会先把输入写入 provider PTY;检测到新的可见输出后再等待 10ms 静默,最多等待 100ms,然后才暂停 provider 并绘制菜单。因此终端会先完整回显 /wujie;provider 没有输出或持续输出到上限时也会继续,不会卡住菜单。菜单按注册顺序展示全部候选,当前项保留 > 标记并使用 ANSI 亮蓝色文字(94,以 39 复位前景色),不使用反色或背景色;↑/↓ 循环选择,Enter 执行,Esc 关闭并保留已输入内容。Tab 只补全、不执行:Wujie 会先用 Ctrl+U 清空 provider 当前编辑行,再写回完整、规范化的 /wujiecreateissue,因此结果不依赖 provider 在暂停前消费了多少输入。非交互终端或无法暂停 provider 时不会显示菜单,但仍可手动输入完整命令或使用 /wujie… + Tab 补全。

候选菜单和 Issue 确认卡共用输入框下方的专用插入区。打开时保存原光标,先向下越过两行 provider 自有区域,再在新的锚点插入所需行;这两行只是 top margin,不属于 Wujie 区域,也不会额外插入或在关闭时删除。菜单预留“候选数 + 帮助行”,确认卡从终端可用高度中扣除原光标行和这两行 margin 后使用剩余区域。重绘和异常清理使用同一锚点;关闭、完成、取消或失败时只删除插入行并恢复打开前的光标行列,不覆盖 provider 历史,也不进入 alternate screen。确认卡先滚动检查完整方案,再选择项目、负责人、标签等 Issue 字段,最后确认创建。普通 /wujiecreateissue 可以选择继续当前 CLI、交给 Agent/Squad 或只创建 Issue;provider 原生动作会锁定原生执行方式,不再重复询问。

这份文档面向 Wujie 代码贡献者。它不是 CLI 使用教程,而是未来接入 Copilot、Hermes、OpenCode 等本地 CLI 时必须遵守的工程规范。

架构边界

Local run 分成两层:

层级负责什么不应该负责什么
通用生命周期解析 issue、创建 local run、heartbeat、注入环境变量、最终状态上报、通用 PTY runner、消息 reporter任何 provider 特有协议、session 文件规则、日志解析
Provider启动对应 CLI、接入该 CLI 的事件源、把事件映射成 local run message、处理该 CLI 的 session/resume 特性创建 local run、更新最终状态、改前端展示类型

通用入口在 server/cmd/wujie/cmd_run.go。Provider 注册和基础接口在 server/cmd/wujie/cmd_run_provider.go。外部插件 provider 的 manifest、发现和执行逻辑在 server/cmd/wujie/cmd_run_plugin.go。新增 provider 时,不要把 CLI 特有逻辑写回 cmd_run.go。

前置讨论仍遵守这个边界:父 Wujie CLI 持有 discussion、Issue 和 local run 生命周期;provider 子进程只收到稳定 session ID 和 workspace 运行环境。选择继续当前上下文时,父进程只切换 reporter、usage 和 heartbeat 的绑定目标,不修改已运行子进程的环境,也不注入 Issue 描述或要求重新读取 Issue。可选 v2 handoff 只把 issue_id / issue_key / run_id 作为绑定结果返回给 adapter,不传上下文正文。

Issue 前置讨论

wujie discuss 的生命周期是:

  1. 创建或按 provider_session_id 恢复一个未完成 discussion。
  2. 以原生交互方式运行 provider;WUJIE_LOCALRUN_SESSION_ID 使用稳定的 discussion ID。
  3. 服务端只保存用户可见的逐轮问答与最终方案;thinking、tool use、tool result、状态事件不进入讨论记录。
  4. /wujiecreateissue 或 provider 原生 implement-plan 动作触发 Wujie 统一维护的内联终端卡片。卡片先越过两行 provider 自有区域,再按扣除这两行后的终端可用高度预留插入区;首屏可滚动查看完整方案,之后编辑执行方式、标题、项目、负责人和标签;状态、优先级、起止日期、父 Issue 放在“更多设置”中。项目、负责人和标签支持过滤,CLI flags 和最近项目只作为可编辑初值。窄终端使用纵向、自适应布局,不依赖固定列宽;Shift+Tab 返回。普通 /wujiecreateissue 可选择“继续讨论”,按 Ctrl+C 也会返回讨论;provider 原生动作不提供“继续讨论”,按 Ctrl+C 会结束当前 provider,但保留未完成 discussion,之后可从原生 session 恢复。
  5. 服务端使用已保存的方案消息创建 Issue,保证 Issue description 与确认时的 Markdown 一致,并把 discussion 作为来源关联到 Issue。
  6. 选择“当前上下文”时,父进程创建 local run、切换 reporter 目标,然后让原会话直接执行;选择 Agent/Squad 时走既有 Issue 分配执行;选择“只创建”时结束本地 provider。

表单创建失败时会保留全部输入。普通 /wujiecreateissue 提供重试、返回修改和继续讨论;provider 原生动作只提供重试和返回修改,加载失败页只提供重试。两种路径都可按 Ctrl+C 取消:普通入口返回讨论,原生入口结束 provider 并以成功状态保存 discussion。成功、取消、重试和错误退出都会幂等删除插入区并恢复原光标。Issue 创建成功后,父 CLI 会等 provider 恢复、放行原生 handoff,并使用独立于命令菜单回显的稳定窗口等待重绘输出结束,再输出 【标题】Issue MYT-22 创建成功 和 Issue 链接;链接使用与候选菜单一致的 ANSI 亮蓝色 94。继续运行时,父 CLI 会临时为 provider PTY 扣除底部两行,并从提示首次真正显示开始提供至少 20 秒的保护窗口;窗口内 provider 继续输出或清屏后会在输出稳定时补画提示。窗口到期只会释放底部两行并恢复完整 PTY 高度,不主动擦除当前提示;之后提示若被 provider 覆盖则不再恢复。只创建或交给其他执行方时,则等 provider 退出并完成 PTY 输出后再提示。该提示只写本地终端,不保存到 discussion,也不写入 provider PTY;提示本身不会触发强制重绘。非交互终端会自动使用可访问的逐项输入模式,保持原有输出方式;API、表单或 hook 的真实错误仍按失败返回。

Web 和 Desktop 的 Issue 详情页会在来源频道旁显示讨论来源入口。讨论详情只展示已保存的可见问答,不展示推理或工具事件。

未完成的讨论不会因 CLI 退出而丢失。只要 provider 支持恢复并返回同一 session ID,再次执行相应的 wujie discuss -- <provider resume ...> 就会恢复同一个 discussion;CLI 同时会恢复服务端最后保存的方案,因此可以直接继续确认。

Provider 合约

每个 provider 实现 localRunProvider:

type localRunProvider interface {
	Name() string
	Run(args []string, cwd string, env localCLIEnv, reporter *localRunReporter, usageReporter *localRunUsageReporter) (int, error)
}

注册 provider 时,把实现加入 localRunProviders:

var localRunProviders = []localRunProvider{
	codexLocalRunProvider{},
	claudeLocalRunProvider{},
	newCLIProvider{},
}

Provider 的 Run 方法必须遵守这些规则:

  • args[0] 是用户传入的 CLI 可执行文件路径;不要改写为固定命令名。
  • cwd 是 local run 的工作目录;子进程、hook server、transcript resolver 都必须以它为准。
  • env 必须通过 localCLIProcessEnv 注入到子进程,避免泄漏父进程的真实 workspace token。
  • 前置讨论期间只依赖 WUJIE_LOCALRUN_SESSION_ID;不要假定 WUJIE_RUN_ID 或 WUJIE_ISSUE_ID 已存在。
  • reporter 是唯一允许写 local run message 的出口。Provider 不直接调用 HTTP API。
  • Provider 可以使用 runProviderPTY 复用通用 PTY 行为;如果 CLI 需要特殊代理、hook 或 websocket,可以自行管理子进程,但仍要复用环境变量和 reporter 约定。

外部插件形态

外部 provider 插件由 manifest 和本地 adapter 可执行文件组成:

如果你要从零开发 external provider 插件,先读 LocalRun 插件开发;本节只记录 Wujie 代码库侧的工程边界。

~/.wujie/localrun-providers/
  copilot/
    provider.json
    wujie-localrun-copilot

provider.json 示例:

{
  "schema_version": 1,
  "name": "copilot",
  "display_name": "GitHub Copilot",
  "command": "./wujie-localrun-copilot",
  "protocol": "wujie-localrun-provider.v2",
  "capabilities": {
    "interactive_pty": true,
    "structured_events": true,
    "issue_context": "env",
    "usage": false,
    "native_plan_handoff": false
  }
}

用户可以通过 wujie local-run provider install ./copilot-provider 安装,也可以在本地配置中手动引用开发中的 manifest:

{
  "localrun_providers": {
    "hermes": {
      "manifest_path": "/Users/me/dev/hermes-wujie-provider/provider.json",
      "enabled": true
    },
    "grok": {
      "manifest_path": "/Users/me/dev/wujie/localrun-providers/grok/provider.json",
      "enabled": true
    }
  }
}

仓库内置样板插件:

  • Hermes:localrun-providers/hermes/
  • Grok Build:localrun-providers/grok/

安装示例:

wujie local-run provider install ./localrun-providers/grok
wujie local-run provider list
wujie run OPE-xxxx -- grok

运行方式 provider 查找顺序是:

  1. 内置 provider,例如 codex、claude、agy、cursor、kiro-cli
  2. 当前 profile/config 的 localrun_providers
  3. 当前 profile/config state dir 下的 localrun-providers/*/provider.json

外部插件不能覆盖内置 provider 名称;发现重名时直接报错。

插件 adapter 把标准事件以 NDJSON 写到 stdout。Wujie 主 CLI 读取事件并通过 reporter 写入 local run message。普通 Issue-first 执行模式下,插件进程会收到 WUJIE_RUN_ID、WUJIE_ISSUE_ID、WUJIE_WORKSPACE_ID、WUJIE_SERVER_URL、WUJIE_TOKEN,以及 WUJIE_LOCALRUN_PROVIDER_* 元数据。

协议版本直接决定前置讨论能力:

  • wujie-localrun-provider.v1 始终保持 Issue-first 合约,不能用于 wujie discuss。
  • wujie-localrun-provider.v2 必须支持前置讨论;Issue 创建前只提供稳定的 WUJIE_LOCALRUN_SESSION_ID,不能依赖 WUJIE_RUN_ID / WUJIE_ISSUE_ID。
  • v2 的 native_plan_handoff 是可选能力。声明后,Adapter 先发 handoff_ready,Wujie 完成绑定后再回 handoff_decision;控制帧只携带绑定 ID,不携带或重复注入方案正文。

执行日志语义

Local run 的执行日志必须对齐对应平台 Agent backend,而不是发明一套前端专用事件。

Provider对齐对象主要事件
Codexserver/pkg/agent/codex.gotext、tool_use、tool_result;工具名使用 exec_command、patch_apply
Claudeserver/pkg/agent/claude.gotext、thinking、tool_use、tool_result;工具名保留 Claude 原始名称,如 Bash、Read、Edit
Cursor内置 transcript mappertext、tool_use、tool_result、user_input、final;从原生 session transcript 同步
AGY内置 transcript mappertool_use、user_input、final;只把 PLANNER_RESPONSE 作为模型回复
Kiro CLIserver/pkg/agent/kiro.gotext、tool_use、tool_result;从 ~/.kiro/sessions/cli/*.jsonl 同步 kiro-cli chat 会话
Hermes外部插件 localrun-providers/hermesuser_input、tool_use、tool_result、final;hook 事件转 NDJSON
Grok Build外部插件 localrun-providers/groktext、thinking、tool_use、tool_result、user_input、final;从 ~/.grok/sessions/<url-encoded-cwd>/<sessionId>/updates.jsonl 同步;usage 来自 turn_completed

Reporter 消息字段:

reporter.Post(localCLIMessage{
	Type:    "tool_use",
	Tool:    "Bash",
	Input:   map[string]any{"command": "go test ./cmd/wujie"},
	Source:  "provider-source",
	SourceKey: "stable-provider-event-id",
})

通用规则:

  • text:agent 面向用户的文本输出。
  • thinking:只有平台 backend 已经写入该类型的 provider 才能写,例如 Claude。
  • tool_use:工具开始执行,Tool 必须使用平台 backend 的同名工具语义。
  • tool_result:工具执行结果,Output 应匹配平台 backend 的字符串化方式。
  • error:只有平台 backend 也会把同类错误写成 task message 时才写。否则错误应该进入 local run 最终状态的 error 字段。
  • 不新增 event、session、thread 等前端专属类型。生命周期事件通常不进入执行日志。

Source 和 SourceKey 用于幂等。SourceKey 必须稳定、唯一,并来自 provider 的真实事件 ID、session ID、line UUID、tool call ID 等确定性信息。不要用当前时间、随机数或递增内存计数作为唯一依据。

评论同步

Local run 额外保留两个不属于平台执行日志的消息类型:

类型用途
user_input把用户在本地 CLI 里输入的普通消息同步成 Wujie 用户评论
final把 agent 对该用户输入的最终回复同步成本地 Codex/Claude 回复评论

评论同步规则:

  • Provider 注入的 issue context 不写 user_input,也不写 final。
  • Slash command 不写 user_input,也不应该让后续输出写成 final。
  • Tool result 不是用户输入,不能写成评论。
  • final 只在当前 turn 有可评论的 user_input 时写。
  • text / thinking / tool 日志仍然可以进入执行日志,即使当前 turn 不写评论。

Provider 需要自己识别该 CLI 的用户输入和最终回复边界。如果 CLI 没有明确 final 事件,可以用该 CLI 的完整 assistant message 作为 final,但不能把 token delta 或状态文案当最终回复。

Session 和 Transcript 跟踪

不要通过“扫描 cwd 下最新 session 文件”来追踪本地 CLI。这个策略会被新的终端会话污染:同一个项目目录里,用户在另一个终端开启新会话时,旧 local run 可能误读新 session 文件。

Provider 必须使用可验证的会话来源:

  • CLI 提供 remote/app-server/stdout stream:优先直接从协议事件读取。
  • CLI 提供 hook:用 hook 上报的 session_id / transcript_path 精确追踪。
  • CLI 只提供 transcript 文件:必须有明确的 session ID 或启动时返回的文件路径;不能按“最新文件”猜测。

Claude 当前使用 SessionStart hook 获取准确的 JSONL transcript。恢复会话、清空上下文、新开 session 时,都以 hook 上报为准。为了兼容 CLI 继续写旧 session 文件的情况,tracker 可以同时跟踪多个 hook 上报过的 session,但不能跟踪没有被 hook 明确确认过的文件。

新 Provider Checklist

接入新的本地 CLI 前,先完成能力调研:

  • CLI 是否支持交互式 TUI?
  • 是否支持 stream JSON、app-server、remote proxy、hook、transcript JSONL 或其它结构化事件源?
  • 是否支持启动时传入 bootstrap prompt?
  • 是否支持 resume / clear / new session?这些行为会如何改变 session ID 或 transcript 文件?
  • 是否能在没有 Issue/Run ID 时启动,并使用 WUJIE_LOCALRUN_SESSION_ID 保持 discussion 会话稳定?
  • 是否支持原生 Plan 模式,并能在真正执行前暴露 current-context / clear-context 选择?
  • 是否有用户输入、assistant final、tool use、tool result 的稳定 ID?

实现时按这个顺序做:

  1. 新增 provider 文件,例如 cmd_run_copilot.go,只放 provider 特有逻辑。
  2. 实现 localRunProvider,注册到 localRunProviders。
  3. 启动 CLI 时复用 localCLIProcessEnv,并明确校验 Wujie 必须托管的 CLI flag。
  4. 选择结构化事件源,禁止依赖终端文本解析作为主要日志来源。
  5. 编写 mapper,把事件映射到平台 backend 已使用的消息语义。
  6. 为 bootstrap、slash command、普通 user input、final reply、tool use/result、error/session 切换写测试。
  7. 跑 go test ./cmd/wujie 和 local run handler 相关测试。

如果是外部插件 provider,还需要:

  1. 提供 provider.json;只支持 Issue-first 时使用 v1,需要前置讨论时使用 v2。
  2. Adapter stdout 只能输出标准 NDJSON 事件;普通诊断日志写 stderr。
  3. 使用稳定的 source / source_key,禁止用当前时间、随机数或内存序号作为唯一依据。
  4. 通过 wujie local-run provider install 或 localrun_providers 手动配置接入。

新增 provider 时,前端通常不需要改动。如果需要前端改动,先检查是否是 provider 泄漏了专属事件类型;只有平台已有通用展示能力不足时,才考虑扩展前端。

当前 Provider

Codex

Codex local run 通过 Codex app-server 和本地 websocket proxy 采集结构化事件。执行日志主语义对齐平台 Codex backend:

  • command execution → tool_use/tool_result,tool=exec_command
  • file change → tool_use/tool_result,tool=patch_apply
  • agent message → text

Local run 额外使用 user_input 和 final 做评论同步。为了兼容 Codex app-server 事件差异,mapper 保留少量 fallback,但用户可见主语义仍按平台 Codex backend。

Codex app-server 和本地 websocket proxy 是 TUI 进程的 sidecar。TUI 退出后,provider 必须关闭 proxy、断开 active websocket,并终止 app-server 进程组来释放端口和子进程资源。不要通过解析 slash command 来判断退出:/clear 会启动新的 thread,/resume 会切回旧 thread,它们都属于同一个 local run 会话内的 session/thread 切换,不能触发 sidecar 清理。

Claude

Claude local run 保留原生 TUI,通过临时 --settings 注入 SessionStart hook。hook 调用隐藏命令 wujie __claude-session-hook,把 Claude 上报的 session 信息转发给本地 hook server。tracker 只读取 hook 明确上报的 transcript 文件。

前置讨论会在没有显式 --permission-mode 时追加 --permission-mode plan,并临时注入 PostToolUse: ExitPlanMode hook。Claude 批准原生 Plan 动作后,hook 会把原始方案写为结构化 plan 消息并同步等待 Issue/Run 绑定;绑定成功后返回原 hook,让同一个 Claude session 继续。原生确认卡按 Ctrl+C 时,hook 阻止该动作并结束 Claude provider。这里不增加 Claude 专属 PreToolUse、上下文注入或 /clear 编排;hook 控制响应不携带 Issue 描述,Claude 不会重新读取 Issue。

执行日志语义对齐平台 Claude backend:

  • text block → text
  • thinking block → thinking
  • tool use block → tool_use,保留 Claude 原始工具名
  • tool result block → tool_result,通过 tool_use_id 回填原始工具名

这个实现避免了按项目目录猜最新 session 文件,也避免了清空上下文、恢复会话或另一个终端新开会话时串线。

Cursor、Kiro 与 AGY

这三个内置 provider 在前置讨论中同样使用各自原生 Plan 模式:

  • Cursor 在用户没有显式 --mode 时追加 --mode=plan。
  • Kiro CLI 启动 kiro-cli chat 后发送原生 /plan。
  • AGY 在用户没有显式 --mode 时追加 --mode=plan。

它们从各自结构化 session/transcript 识别 provider 生成的 implement-plan 动作。父 CLI 在确认卡和 Issue/Run 绑定期间暂停原 provider 子进程,绑定后恢复同一个进程和 session;原生确认卡按 Ctrl+C 时会终止 provider,保留 discussion,并忽略终止信号产生的退出码。这个路径不解析 TUI 渲染文本,也不会把 Issue description 重新注入子进程。

本页目录

Local Run Provider架构边界Issue 前置讨论Provider 合约外部插件形态执行日志语义评论同步Session 和 Transcript 跟踪新 Provider Checklist当前 ProviderCodexClaudeCursor、Kiro 与 AGY