@ai-workbench/remote-agent 0.8.5 → 0.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +33 -8
  2. package/dist/acp.d.ts +22 -2
  3. package/dist/acp.js +143 -25
  4. package/dist/approval-label.d.ts +1 -1
  5. package/dist/approval-label.js +15 -3
  6. package/dist/broker-runtime.js +40 -8
  7. package/dist/broker.js +5 -1
  8. package/dist/claude-local.d.ts +37 -0
  9. package/dist/claude-local.js +207 -0
  10. package/dist/client.js +71 -2
  11. package/dist/conversation-details.d.ts +0 -1
  12. package/dist/conversation-details.js +37 -7
  13. package/dist/employee-endpoint.d.ts +91 -0
  14. package/dist/employee-endpoint.js +141 -0
  15. package/dist/employee-exec.d.ts +53 -0
  16. package/dist/employee-exec.js +423 -0
  17. package/dist/employee-location.d.ts +66 -0
  18. package/dist/employee-location.js +51 -0
  19. package/dist/employee-mcp.d.ts +42 -0
  20. package/dist/employee-mcp.js +181 -0
  21. package/dist/employee-native.d.ts +18 -0
  22. package/dist/employee-native.js +38 -0
  23. package/dist/employee-provider.d.ts +42 -0
  24. package/dist/employee-provider.js +326 -0
  25. package/dist/employee-relay.d.ts +18 -0
  26. package/dist/employee-relay.js +49 -0
  27. package/dist/employee-shell.d.ts +26 -0
  28. package/dist/employee-shell.js +89 -0
  29. package/dist/employee-ssh.d.ts +22 -0
  30. package/dist/employee-ssh.js +123 -0
  31. package/dist/employee-tools.d.ts +117 -0
  32. package/dist/employee-tools.js +292 -0
  33. package/dist/employee-workspace.d.ts +26 -0
  34. package/dist/employee-workspace.js +61 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.js +148 -28
  37. package/dist/input.d.ts +11 -0
  38. package/dist/input.js +61 -23
  39. package/dist/local-user-turn.d.ts +24 -0
  40. package/dist/local-user-turn.js +103 -0
  41. package/dist/presentation.d.ts +1 -1
  42. package/dist/presentation.js +46 -2
  43. package/dist/provider.d.ts +36 -2
  44. package/dist/provider.js +33 -0
  45. package/dist/readiness.js +1 -1
  46. package/dist/recovery.d.ts +14 -1
  47. package/dist/recovery.js +11 -2
  48. package/dist/remote-details.d.ts +7 -0
  49. package/dist/remote-details.js +11 -1
  50. package/helpers/ripgrep/LICENSE-vscode +13 -0
  51. package/helpers/ripgrep/PROVENANCE.md +9 -0
  52. package/helpers/ripgrep/darwin-arm64/rg +0 -0
  53. package/helpers/ripgrep/darwin-x64/rg +0 -0
  54. package/helpers/ripgrep/manifest.json +56 -0
  55. package/helpers/ripgrep/notices/COPYING-ripgrep +3 -0
  56. package/helpers/ripgrep/notices/DEPENDENCIES.json +946 -0
  57. package/helpers/ripgrep/notices/LICENCE-PCRE2.md +103 -0
  58. package/helpers/ripgrep/notices/LICENSE-MIT-ripgrep +21 -0
  59. package/helpers/ripgrep/notices/UNLICENSE-ripgrep +24 -0
  60. package/helpers/ripgrep/notices/aho-corasick-1.1.3/COPYING +3 -0
  61. package/helpers/ripgrep/notices/aho-corasick-1.1.3/LICENSE-MIT +21 -0
  62. package/helpers/ripgrep/notices/aho-corasick-1.1.3/UNLICENSE +24 -0
  63. package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-APACHE +176 -0
  64. package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-MIT +23 -0
  65. package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-APACHE +201 -0
  66. package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-MIT +27 -0
  67. package/helpers/ripgrep/notices/bstr-1.12.0/COPYING +8 -0
  68. package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-APACHE +201 -0
  69. package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-MIT +21 -0
  70. package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-APACHE +201 -0
  71. package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-MIT +25 -0
  72. package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-APACHE +201 -0
  73. package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-MIT +25 -0
  74. package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-APACHE +201 -0
  75. package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-MIT +27 -0
  76. package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-THIRD-PARTY +593 -0
  77. package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-APACHE +201 -0
  78. package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-MIT +27 -0
  79. package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-APACHE +201 -0
  80. package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-MIT +27 -0
  81. package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-APACHE +201 -0
  82. package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-MIT +27 -0
  83. package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-APACHE +201 -0
  84. package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-MIT +27 -0
  85. package/helpers/ripgrep/notices/encoding_rs-0.8.35/COPYRIGHT +17 -0
  86. package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-APACHE +202 -0
  87. package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-MIT +25 -0
  88. package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-WHATWG +26 -0
  89. package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/COPYING +8 -0
  90. package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-APACHE +201 -0
  91. package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-MIT +21 -0
  92. package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-APACHE +201 -0
  93. package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-MIT +25 -0
  94. package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-APACHE +201 -0
  95. package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-MIT +26 -0
  96. package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-APACHE +201 -0
  97. package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-MIT +25 -0
  98. package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-APACHE +176 -0
  99. package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-MIT +23 -0
  100. package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-APACHE +201 -0
  101. package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-MIT +25 -0
  102. package/helpers/ripgrep/notices/lexopt-0.3.1/LICENSE +19 -0
  103. package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-APACHE +176 -0
  104. package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-MIT +25 -0
  105. package/helpers/ripgrep/notices/log-0.4.28/LICENSE-APACHE +201 -0
  106. package/helpers/ripgrep/notices/log-0.4.28/LICENSE-MIT +25 -0
  107. package/helpers/ripgrep/notices/memchr-2.7.6/COPYING +3 -0
  108. package/helpers/ripgrep/notices/memchr-2.7.6/LICENSE-MIT +21 -0
  109. package/helpers/ripgrep/notices/memchr-2.7.6/UNLICENSE +24 -0
  110. package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-APACHE +201 -0
  111. package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-MIT +26 -0
  112. package/helpers/ripgrep/notices/msvc_spectre_libs-0.1.3/LICENSE +21 -0
  113. package/helpers/ripgrep/notices/pcre2-0.2.11/COPYING +3 -0
  114. package/helpers/ripgrep/notices/pcre2-0.2.11/LICENSE-MIT +21 -0
  115. package/helpers/ripgrep/notices/pcre2-0.2.11/UNLICENSE +24 -0
  116. package/helpers/ripgrep/notices/pcre2-sys-0.2.10/COPYING +3 -0
  117. package/helpers/ripgrep/notices/pcre2-sys-0.2.10/LICENSE-MIT +21 -0
  118. package/helpers/ripgrep/notices/pcre2-sys-0.2.10/UNLICENSE +24 -0
  119. package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-APACHE +201 -0
  120. package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-MIT +25 -0
  121. package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-APACHE +176 -0
  122. package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-MIT +23 -0
  123. package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-APACHE +176 -0
  124. package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-MIT +23 -0
  125. package/helpers/ripgrep/notices/r-efi-5.3.0/AUTHORS +74 -0
  126. package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-APACHE +201 -0
  127. package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-MIT +25 -0
  128. package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-APACHE +201 -0
  129. package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-MIT +25 -0
  130. package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-APACHE +201 -0
  131. package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-MIT +25 -0
  132. package/helpers/ripgrep/notices/regex-syntax-0.8.8/src/unicode_tables/LICENSE-UNICODE +57 -0
  133. package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-APACHE +176 -0
  134. package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-BOOST +23 -0
  135. package/helpers/ripgrep/notices/same-file-1.0.6/COPYING +3 -0
  136. package/helpers/ripgrep/notices/same-file-1.0.6/LICENSE-MIT +21 -0
  137. package/helpers/ripgrep/notices/same-file-1.0.6/UNLICENSE +24 -0
  138. package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-APACHE +176 -0
  139. package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-MIT +23 -0
  140. package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-APACHE +176 -0
  141. package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-MIT +23 -0
  142. package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-APACHE +176 -0
  143. package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-MIT +23 -0
  144. package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-APACHE +176 -0
  145. package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-MIT +23 -0
  146. package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-APACHE +13 -0
  147. package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-MIT +21 -0
  148. package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-APACHE +176 -0
  149. package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-MIT +23 -0
  150. package/helpers/ripgrep/notices/termcolor-1.4.1/COPYING +3 -0
  151. package/helpers/ripgrep/notices/termcolor-1.4.1/LICENSE-MIT +21 -0
  152. package/helpers/ripgrep/notices/termcolor-1.4.1/UNLICENSE +24 -0
  153. package/helpers/ripgrep/notices/textwrap-0.16.2/LICENSE +21 -0
  154. package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-APACHE +201 -0
  155. package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-MIT +25 -0
  156. package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/jemalloc/COPYING +27 -0
  157. package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-APACHE +201 -0
  158. package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-MIT +25 -0
  159. package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-APACHE +176 -0
  160. package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-MIT +23 -0
  161. package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-UNICODE +39 -0
  162. package/helpers/ripgrep/notices/walkdir-2.5.0/COPYING +3 -0
  163. package/helpers/ripgrep/notices/walkdir-2.5.0/LICENSE-MIT +21 -0
  164. package/helpers/ripgrep/notices/walkdir-2.5.0/UNLICENSE +24 -0
  165. package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-APACHE +201 -0
  166. package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
  167. package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-MIT +23 -0
  168. package/helpers/ripgrep/notices/winapi-util-0.1.11/COPYING +3 -0
  169. package/helpers/ripgrep/notices/winapi-util-0.1.11/LICENSE-MIT +21 -0
  170. package/helpers/ripgrep/notices/winapi-util-0.1.11/UNLICENSE +24 -0
  171. package/helpers/ripgrep/notices/windows-link-0.2.1/license-apache-2.0 +201 -0
  172. package/helpers/ripgrep/notices/windows-link-0.2.1/license-mit +21 -0
  173. package/helpers/ripgrep/notices/windows-sys-0.61.2/license-apache-2.0 +201 -0
  174. package/helpers/ripgrep/notices/windows-sys-0.61.2/license-mit +21 -0
  175. package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-APACHE +201 -0
  176. package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
  177. package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-MIT +23 -0
  178. package/locale/en.json +1 -1
  179. package/locale/zh.json +2 -2
  180. package/package.json +29 -11
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
- 在 DSH「插件 → 添加插件」填 `@ai-workbench/remote-agent@0.8.5`(由编排发布后可用);交付前可由官方插件管理安装固定 tgz。
1
+ **`@ai-workbench/remote-agent@0.8.10` 发布形态包;编排放行用于 leo 真机自验 Claude 线、设置界面和 D24 重开。Codex 线因主机适配器 aw.2 实装失败(UNSAFE_MODE_MISMATCH),待 S 修复后另验,当前未验证可用。由编排统一发布,X 未执行 publish。**
2
2
 
3
3
  # 远程ACP插件
4
4
 
5
5
  一个插件提供 **Claude(远程主机)** 和 **Codex(远程主机)**,通过员工主机上的官方CLI运行。Codex保留单次模式;Claude固定会话模式。只用官方模型列表、插件管理和配置入口,不提供自制面板、问答或登录按钮。登录和原生工作区绑定唯一归连接插件L。
6
6
 
7
- 当前为公开npm发布准备版0.8.5,包名采用`@ai-workbench/remote-agent`以涵盖两个品牌;发布前自测从固定tgz绝对路径安装。X仅准备,不执行实际发布,编排统一发布后才可按包名安装。开发fixture逐字节保留L0.7.13固定包,公开类型与SSH导出从该包消费。运行时仍要求L的`getContractInfo()`同时返回API0.7.0、implemented、native-local-v1;旧L包、draft.3或只有版本号的候选会明确报缺项。依赖范围为`~0.7.13`,L由官方插件管理单独安装,不打进X。组件验证不代表真实开通、模型或leo E通过;HanaMesh另验。
7
+ 当前为0.8.10,包名采用`@ai-workbench/remote-agent`以涵盖两个品牌;本轮按编排放行准备发布形态包,供 leo 真机自验;X 不执行实际发布,由编排统一发布。开发fixture逐字节保留L0.7.17固定包,公开类型与SSH导出从该包消费。运行时仍要求L的`getContractInfo()`同时返回API0.7.0、implemented、native-local-v1;旧L包、draft.3或只有版本号的不完整实现会明确报缺项。依赖范围为`~0.7.17`,L由官方插件管理单独安装,不打进X。组件验证不代表真实开通、模型或leo E通过;HanaMesh另验。
8
8
 
9
9
  ## 员工路径
10
10
 
11
- 从官方插件管理安装,照常使用默认工作区/会话/模型入口。L0.7.13沿唯一“远程主机连接”设置节记录文件来源;本统一版X只继续L已明确确认的legacy同名远端映射。旧映射需在L逐工作区确认,本地或新主机位置仍为pending,分别由L明确停止为WORKSPACE_EXECUTION_UNAVAILABLE/WORKSPACE_LOCATION_PROVIDER_REQUIRED,不退回同名目录或主机代跑。D20新执行位置尚未接通。本地文件不自动上传,X不猜目录、不选首项。首次说明使用官方普通回复;先经官方会话提交、持久化并读回核对,才向L确认。持久记录不等于员工已看见,GUI另验;日志丢失后的未知claim由L保留,不宣称端到端恰好一次。
11
+ 从官方插件管理安装,照常使用默认工作区/会话/模型入口。L0.7.17沿唯一“远程主机连接”设置节记录文件来源;主机来源沿用L既有解析;本地来源接入N0.4.4/S0.4.11员工端点。旧映射需在L逐工作区确认,新主机位置仍按L现有合同停止为WORKSPACE_LOCATION_PROVIDER_REQUIRED。本地六类工具经Claude公开toolAliases路由到员工电脑:cwd是员工选定目录,Git使用本机仓库,不回退主机同名目录。本地真实员工输入先取得官方当前轮次CAS预期,再调用L0.7.17 acceptLocalWorkspaceUserTurn;核回包后才打开D19和本轮主机目录/执行。每轮localAcceptanceId冻结进原详情sink,迟到结果不借新轮ID;LOCAL_USER_TURN_CHANGED立即停止本轮,不重接纳或退legacy。已用固定真包完成本地D19组件联测;真实N/S员工端仍NOT_RUN,产品验收以 leo 实际执行结果为准。本地文件不自动上传,X不猜目录、不选首项。首次说明使用官方普通回复;先经官方会话提交、持久化并读回核对,才向L确认。持久记录不等于员工已看见,GUI另验;日志丢失后的未知claim由L保留,不宣称端到端恰好一次。
12
12
 
13
13
  未登录时两个模型均回复:**需要登录,请到设置 → 远程主机连接 点登录**。无L或缺正式能力时直接说明缺项,不让员工填写主机、用户、路径或密钥。
14
14
 
@@ -24,13 +24,13 @@
24
24
 
25
25
  `usage_update`的used/size是上下文占用,不能当作本轮输入/输出计费量;远端真实prompt响应usage及Claude quota令牌计数是会话累计,按保存在当前实例/恢复标识中的可信基线减差后进入DSH本轮用量;重复值不重复结算。恢复缺少基线、计数缺失、回退或来源改变时明确不计入该轮,重建可信基线,不能把历史用量算成本轮。
26
26
 
27
- 会话模式跨轮保留同一ACP客户端和主机进程;关闭实际agent/session或插件时精确清理。重开恢复优先协商resume,无能力时明确使用load,不静默新建。恢复标识绑定品牌、员工目标、DSH会话和L工作区;复制/分叉不能沿用父远端会话。客户端丢失留下的孤儿由管理员确认处理,不自动结束或重开。无TTL或隐式淘汰上限。
27
+ 会话模式跨轮保留同一ACP客户端和主机进程;关闭实际agent/session或插件时精确清理。重开恢复优先协商resume,无能力时明确使用load,不静默新建。恢复标识绑定品牌、员工目标、DSH会话和L工作区;复制/分叉不能沿用父远端会话。主机来源沿用D24同实例自动恢复/安全重建。本地来源重开后原MCP socket失效,先由既有S同实例条件流程结束旧broker再重建,不代答旧审批;未获真实清理确认时保留未知,不自动重放。无TTL或隐式淘汰上限。
28
28
 
29
29
  Codex单次模式首版仅文本,不转接中途审批;图片或系统/开发者上下文需使用会话模式。文件传输、远端VFS与自动同步不在本轮。其他模型不锁定、不拦截、不记录。
30
30
 
31
31
  ## 管理员配置
32
32
 
33
- 在官方设置中使用“打开配置文件”,修改**已有同id插件条目**的config。保留官方settingsNs,不注册额外设置节;原生提供方编辑卡是否可编辑以官方实际能力为准。不要新增重复insert条目,不修改官方默认服务或布局。
33
+ 在官方 **设置 → 内置插件 → 远程助手参数** 查看和修改本插件全部七项参数。页面走官方ConfigForms与settings.plugins.tab,不新增连接设置节、不改DSH核心。每项列明默认值、范围、生效和调整后果;固定never及正确性约束明确列为不可关闭。当前profile不可写、服务未就绪或远程Web不支持持久写入时禁写并说明原因。保存携带编辑起点revision,冲突保留草稿、不自动重试;恢复继承值发unset,不写死默认。原生模型Edit卡未增加表单;“打开配置文件”仍可用,但不作为参数可见性的替代。
34
34
 
35
35
  | 设置 | 默认 | 范围与用途 |
36
36
  |---|---|---|
@@ -40,8 +40,9 @@ Codex单次模式首版仅文本,不转接中途审批;图片或系统/开
40
40
  | brokerControlMaxBytes | 1048576 | 正整数,主机会话管理回复容量,默认1MiB |
41
41
  | oneShotSandbox | workspace-write | Codex单次可选read-only/workspace-write/danger-full-access |
42
42
  | oneShotApproval | never | 当前单次通道无中途交互审批;不代表自动批准 |
43
+ | employeeShellKillGraceMs | 1000 | 0至2147483647的整数;取消后外层SSH清理宽限毫秒。取消结果仍未知,不将宽限作为员工端退出证明 |
43
44
 
44
- 六项沿用owner已定默认,无新增设置或策略阈值;非法值拒绝,删除覆盖遵循官方配置继承。SSH可执行程序从当前PATH发现,不写死某一系统路径。原始错误码保留在官方失败代码/技术详情,原始stderr、凭据和URL不展开。
45
+ 原六项默认保留;D20新增employeeShellKillGraceMs供管理员调节自己的SSH清理。非法值拒绝,删除覆盖遵循官方配置继承。SSH可执行程序从当前PATH发现,不写死某一系统路径。原始错误码保留在官方失败代码/技术详情,原始stderr、凭据和URL不展开。
45
46
 
46
47
  仅新增中文文案和验证;保留已有英文名称及既有locale机制。提供方名称不附状态后缀,由原注册replace更新,不自造刷新或客户端品牌分支。
47
48
 
@@ -59,7 +60,7 @@ D19(0.8.1):相邻正文增量合成连续文本块,工作区首次说明
59
60
  技术详情由 L 0.7.11 的唯一“远程主机连接”设置节展示,正文只放模型文本。命令目录保留完整描述;未知事件和工具输入输出保留字段、JSON类型及处理结果,未获准原文明确脱敏,数字用量可核对。没有 `remoteConversationDetails:1` 时明确提示“技术详情暂不可用”,本轮不发送模型请求。X 在官方当前 profile 的 `ai-workbench-remote-agent/diagnostics/` 保存私有诊断日志(目录700、文件600、落盘同步);写入/归属/顺序/容量失败中止本轮,不自动重发、换代重投、过期或淘汰。结果未知时只读核对原事件完整回执,未找到仍保持未知。X本轮无新增设置/阈值;L的单事件容量和分页设置在L设置节可见。
60
61
 
61
62
 
62
- 0.8.2 登录检查原交付需要 L `~0.7.11`(本统一版peer为`~0.7.13`),失败来源及固定分支保留到 L“登录前技术详情”(`loginDiagnostics:1`),员工正文保持原文。按 L 明确的本品牌 queued/checking/authorizing 状态订阅等待;不以全局checking猜测在途,不新增刷新、轮询、超时或重试。每轮ensureReady仍是合同规定的显式检查,L的新可信结果可以替代旧检查,较早返回不得覆盖它。
63
+ 0.8.2 登录检查原交付需要 L `~0.7.11`(本统一版peer为`~0.7.17`),失败来源及固定分支保留到 L“登录前技术详情”(`loginDiagnostics:1`),员工正文保持原文。按 L 明确的本品牌 queued/checking/authorizing 状态订阅等待;不以全局checking猜测在途,不新增刷新、轮询、超时或重试。每轮ensureReady仍是合同规定的显式检查,L的新可信结果可以替代旧检查,较早返回不得覆盖它。
63
64
 
64
65
  L安全规范的officialReason原串完整保留;旧/不支持格式明确记OFFICIAL_REASON_UNSUPPORTED与字节数/SHA,秘密原文不保存。L0.7.11的LOGIN_INSTANCE_MISMATCH记录通过observedLoginInstanceId与原始login.instanceId保存实际观测ID,事件及connection仍绑定当前可信ID/代次;L技术详情和X私有日志均保留真实双ID,不归一、不清空快照。复合未知官方原因以固定summary完整保存OFFICIAL_REASON_UNSUPPORTED及bytes/SHA,不交原文。X私有原快照先同步落盘,再追加L;ACK未知只读核实原事件,跨代不重写/重发。此版本的工程验证不替代leo员工路径复验,真实故障根因尚未证实。
65
66
 
@@ -71,4 +72,28 @@ D21(0.8.5):两个品牌的官方模型菜单从 L 的 `modelCatalogTarget:
71
72
 
72
73
  每轮先核对目录代次和所选模型,再经 ACP 逐项配置模型及推理等级,收到完整配置确认后才发送输入。Claude 别名必须有当前会话的具体型号证据;没有就明确拒绝。真实回复追加所选值和远端确认值,并在恢复标识与技术详情保留,确认配置不等于验证推理效果。连接/身份/目录变化清除旧确认;远端清理待定或结果未知时阻止派发,不自动清理重试或另开替代会话。Codex 单次模式当前不能确认此选择,会在发送前明确拒绝。新增设置 0;本轮工程证据、真实只读链路与产品未验项见 `docs/evidence/2026-10-22-X2-D21/`。
73
74
 
74
- 0.8.5统一包对齐L `~0.7.13`。D8模型目录双事件长期监听保留;同epoch的recovery提示/失败消息变化不触发执行状态失效,真实实例/epoch/phase/可信目标/登录变化仍失效。完整D9两品牌正文/状态/就绪门呈现按编排00:50允许的范围记下一版,本版不宣称已消费完成;L的connectionMessages.administratorAfterMs仍只由L管理,X新增设置0。
75
+ 0.8.5统一包原依赖L `~0.7.13`;当前0.8.10对齐L `~0.7.17`。D8模型目录双事件长期监听保留;同epoch的recovery提示/失败消息变化不触发执行状态失效,真实实例/epoch/phase/可信目标/登录变化仍失效。完整D9两品牌正文/状态/就绪门呈现按编排00:50允许的范围记下一版,本版不宣称已消费完成;L的connectionMessages.administratorAfterMs仍只由L管理,X新增设置0。
76
+
77
+ D18(0.8.6):Codex 需要主机自有 adapter `2.2.2-aw.1` 的明确新建会话上下文政策,官方 CLI 仍为未改的0.160.1。系统/开发者要求仅在新建时生效;恢复时与初建摘要相同则正常恢复且省略新建专用字段,不声称恢复接口会更新指令。要求不同或清空时,在同一真实回复说明“会话要求已变化,已新建 Codex 会话继续处理,原会话仍保留。”,并新建远端承接公开对话,保留旧标识和查询记录;旧/非法初建摘要明确拒绝,不能猜测。
78
+
79
+ 新会话承接公开用户/助手文本与核验图片,内部reasoning留在旧记录并记录排除数量,官方offload图片保持占位语义、不重读字节。工具角色、工具块、file和未知类型没有可靠公开投影时明确在新建/发送前拒绝;不序列化内部恢复或工具数据代替正文。新建后重新核对模型/等级并重置用量基线;技术详情只记原因、旧/新标识、摘要及承接范围,不保存系统上下文原文。该政策当前仅Codex,Claude保持上述原策略。新增设置0;证据及主机离线闭包见 `docs/evidence/2026-10-22-X2-D18/DELIVERY.md`。S安装和真实员工路径仍由编排安排。
80
+
81
+ 0.8.6仅完成D18与既有恢复的集成,D9完整呈现仍待X后续交付;系统/开发者上下文只支持公开text,非text在任何远端生命周期前明确拒绝。
82
+
83
+ ## D20 Claude范围与依赖
84
+
85
+ Read/Write/Edit/Glob/Grep由员工DSH同UID后端执行;Bash经L严格SSH到S同代桥再经N员工SSH端点执行,命令正文只走stdin。每次调用核官方会话唯一工作区、L实例/epoch/绑定/目录身份、N真实prepare及S实时target。取消、SSH255或退出证明缺失记录UNKNOWN,不重放。只关闭自己的绑定执行器,不撤其他工作区或设备服务。cwd不是权限根,绝对路径和上级访问遵循真实OS权限;不增加目录白名单。
86
+
87
+ 每轮同一官方回复明确:子代理和harness内部直接工具调用尚未证明可在员工端执行,可能仍在主机;已识别的原生未改道工具/Agent审批拒绝。PDF等二进制读取及后台命令明确不支持,不静默主机代跑。六类组件/MCP/配置转交证明不等于真实Claude原生alias执行或真实员工链路通过。实际Git依赖员工环境中已有Git,缺失会返回实际命令失败,不在主机替代,也不自动安装。
88
+
89
+ 本地目前仅支持macOS(arm64/x64);Grep固定携带微软@vscode/ripgrep-universal1.18.0的两架构原字节并核SHA,无员工PATH搜索或安装下载。主机需S0.4.11,N可选peer~0.4.4(缺失明确报能力缺项),L当前~0.7.17,本地接纳与D19工程路径已接,真实员工端门仍开放。无DSH/CLI修改、额外设置节或npm发布。
90
+
91
+ ## 合并保留的 Codex D20 路线
92
+
93
+ D20(0.8.8开发记录):本地来源的Codex原生文件与命令通过固定官方CLI0.160.1的实验执行环境接口改到员工电脑,cwd来自员工已选择的原生工作区,Git使用本机现有仓库。模型、登录及全局CODEX_HOME助手配置仍由远程主机提供;工作区AGENTS与所测原生子代理跟随员工执行环境。员工执行组件随标准npm依赖安装后自动发现、核版本和SHA;当前只支持已验证的Apple芯片Mac,其他平台明确拒绝,没有PATH替代或让员工手填路径的步骤。
94
+
95
+ 本地会话需要N0.4.4正式公共服务、S0.4.11员工SSH桥和自有主机适配器2.2.2-aw.2。S现有aw.1保护安装器不能直接消费aw.2,aw.2保护消费仍须提供方交付。已消费正式L0.7.17本地user-turn品牌接纳:仅实际本轮human输入可建立CAS预期,接纳先于ensureReady的主机状态命令及本轮模型目录;每轮详情sink固定自己的localAcceptanceId,append通过第二参数传递。换轮冲突立即停本轮,不重试或借历史/legacy接纳。本轮主机 aw.2 实装失败,原因是 UNSAFE_MODE_MISMATCH,S 正在修复;Codex 线待主机修复后另验。真实员工端链路仍为NOT_RUN,工程真包消费不代表真实路径或GUI通过。
96
+
97
+ 当前明确不支持本地单次模式、断开后的冷恢复/load/fork、快捷命令、主机文件报告及附加MCP/hooks/plugins。主机同路径的启用项目配置、会话层项目发现/信任覆盖也会明确拒绝;配置检查不静默清空或改写原配置。普通原生shell/patch/fs使用所选员工环境,未覆盖的辅助入口保留具体原因到员工回复与技术详情。位置、身份或代次改变只撤回对应执行器,在途结果保持UNKNOWN,不自动重放。新增产品设置0,执行通道和桥队列沿已有`acpMaxMessageBytes`控制容量。当前工程进度、首红和开放门见`docs/evidence/2026-10-23-X2-D20-088/STATUS.md`。
98
+
99
+ 0.8.10仅增加公开插件参数页,不改变上述七项执行语义。mode/ACP/frame/control/grace的修改不改变运行中当轮;保留会话下一轮配置签名不一致会停止,须新建会话。ACP容量还在下一次独立目录查询读取新值。sandbox与approval仅单次通道适用,当前单次仍明确拒绝执行。GUI与真实链路门仍NOT_RUN;本次由编排在 leo 验证 Claude 线、设置界面和 D24 重开,Codex 线另验。完整逐项盘点见 docs/evidence/2026-10-23-X-settings-0810/INVENTORY.md。
package/dist/acp.d.ts CHANGED
@@ -6,6 +6,7 @@ import { StageTiming } from './stage-timing.ts';
6
6
  import { type AcpRecovery } from './recovery.ts';
7
7
  import { type SanitizedObservation } from './conversation-details.ts';
8
8
  import { type ModelConfigurationSnapshot } from './model-configuration.ts';
9
+ import type { EmployeeRouting } from './employee-relay.ts';
9
10
  export type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable';
10
11
  export interface AcpHooks {
11
12
  timing?: StageTiming;
@@ -15,9 +16,20 @@ export interface AcpHooks {
15
16
  onNeed?: (need: Need) => void;
16
17
  onDetail?: (detail: Readonly<SanitizedObservation>) => void;
17
18
  broker?: BrokerTransport;
19
+ employeeExecution?: (params: unknown, signal?: AbortSignal) => Promise<Record<string, unknown>>;
20
+ }
21
+ export interface EmployeeExecutionMeta {
22
+ version: 1;
23
+ bindingId: string;
24
+ workspaceId: string;
25
+ cwd: string;
26
+ acpMaxMessageBytes: number;
18
27
  }
19
28
  export interface AcpStartContext {
20
29
  systemPrompt?: string;
30
+ codexNewSessionOnly?: true;
31
+ employeeExecution?: EmployeeExecutionMeta;
32
+ employeeRouting?: EmployeeRouting;
21
33
  }
22
34
  export type AcpStartMethod = 'session/new' | 'session/resume' | 'session/load';
23
35
  /** ACP v1 with explicitly negotiated SDK 2.0 lifecycle/context extensions. */
@@ -35,6 +47,10 @@ export declare class AcpSession {
35
47
  private updating;
36
48
  private cwd?;
37
49
  private systemPrompt;
50
+ private codexNewSessionOnly;
51
+ private creatingReplacement;
52
+ private employeeMeta?;
53
+ private employeeNegotiated;
38
54
  private configuration?;
39
55
  private configRevision;
40
56
  private selected?;
@@ -66,12 +82,16 @@ export declare class AcpSession {
66
82
  get capabilities(): Readonly<acp.AgentCapabilities>;
67
83
  get startMethod(): AcpStartMethod | undefined;
68
84
  get checkpoint(): AcpRecovery | undefined;
69
- restore(cwd: string, saved: AcpRecovery, context: AcpStartContext): void;
85
+ restore(cwd: string, saved: AcpRecovery, context: AcpStartContext, verifiedSameContext?: boolean): void;
70
86
  validatePrompt(prompt: acp.ContentBlock[]): void;
71
87
  validateContext(context: AcpStartContext): void;
88
+ validateNewSessionContext(context: AcpStartContext): void;
72
89
  setHooks(hooks: AcpHooks): void;
73
90
  get closed(): Promise<void>;
74
- start(cwd: string, resumeId?: string, signal?: AbortSignal, context?: AcpStartContext): Promise<string>;
91
+ private validateNewContext;
92
+ start(cwd: string, resumeId?: string, signal?: AbortSignal, context?: AcpStartContext, prompt?: acp.ContentBlock[]): Promise<string>;
93
+ /** Codex context is immutable after creation; keep the old remote session intact. */
94
+ newContextSession(context: AcpStartContext, signal?: AbortSignal): Promise<string>;
75
95
  updateContext(context: AcpStartContext, signal?: AbortSignal): Promise<AcpStartMethod | undefined>;
76
96
  prompt(input: string | acp.ContentBlock[], signal?: AbortSignal): Promise<acp.StopReason>;
77
97
  close(): void;
package/dist/acp.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as acp from '@agentclientprotocol/sdk/unstable';
2
2
  import { NeedError } from "./remote.js";
3
+ import { employeeFailure } from "./presentation.js";
3
4
  import { StageTiming } from "./stage-timing.js";
4
5
  import { isolateRpcIds } from "./rpc-namespace.js";
5
- import { recoveryCapabilities } from "./recovery.js";
6
+ import { recoveryCapabilities, supportsCodexContextPolicy } from "./recovery.js";
6
7
  import { sanitizeObservation } from "./conversation-details.js";
7
8
  import { ModelConfiguration, ModelConfigurationError } from "./model-configuration.js";
8
9
  /** ACP v1 with explicitly negotiated SDK 2.0 lifecycle/context extensions. */
@@ -20,6 +21,10 @@ export class AcpSession {
20
21
  updating = false;
21
22
  cwd;
22
23
  systemPrompt = '';
24
+ codexNewSessionOnly = false;
25
+ creatingReplacement = false;
26
+ employeeMeta;
27
+ employeeNegotiated = false;
23
28
  configuration;
24
29
  configRevision = 0;
25
30
  selected;
@@ -101,20 +106,20 @@ export class AcpSession {
101
106
  timing = new StageTiming();
102
107
  stage(phase, operation) { return (this.hooks.timing ?? this.timing).run(phase, operation); }
103
108
  get diagnostics() { return this.reports.map(entry => sanitizeObservation(entry)); }
104
- detail(observation) {
109
+ detail(observation, hooks = this.hooks) {
105
110
  const safe = sanitizeObservation(observation);
106
111
  this.reports.push(safe);
107
112
  try {
108
- this.hooks.onDetail?.(safe);
113
+ hooks.onDetail?.(safe);
109
114
  }
110
115
  catch (cause) {
111
116
  this.reportingFailure ??= { cause };
112
117
  this.connection?.close();
113
118
  }
114
119
  }
115
- need(need) {
116
- this.detail({ kind: 'need', severity: 'action-required', code: need.code, summary: need.message, payload: { requires: need.requires } });
117
- this.hooks.onNeed?.(need);
120
+ need(need, hooks = this.hooks) {
121
+ this.detail({ kind: 'need', severity: 'action-required', code: need.code, summary: need.message, payload: { requires: need.requires } }, hooks);
122
+ hooks.onNeed?.(need);
118
123
  }
119
124
  checkReporting() { if (this.reportingFailure)
120
125
  throw new NeedError('CONVERSATION_DETAILS_FAILED', '技术详情未能记录;本轮未继续发送', '可用的技术详情记录渠道'); }
@@ -123,6 +128,12 @@ export class AcpSession {
123
128
  this.timing.bind(detail => this.detail(detail));
124
129
  const app = acp.client({ name: '@ai-workbench/remote-agent' })
125
130
  .onNotification(acp.methods.client.session.update, ({ params: notification }) => {
131
+ if (this.creatingReplacement) {
132
+ if (notification.sessionId !== this.sessionId && notification.update.sessionUpdate === 'config_option_update')
133
+ this.pendingConfigurations.set(notification.sessionId, notification.update.configOptions);
134
+ this.detail({ kind: 'session', severity: 'info', code: 'ACP_IDLE_UPDATE', summary: '新会话建立期间的轨迹,未作为正文处理', method: 'session/update', payload: notification });
135
+ return;
136
+ }
126
137
  if (this.sessionId && notification.sessionId !== this.sessionId) {
127
138
  this.detail({ kind: 'session', severity: 'warning', code: 'ACP_FOREIGN_SESSION_UPDATE', summary: '收到不属于当前远端会话的通知,未作为正文处理', method: 'session/update', payload: notification });
128
139
  return;
@@ -152,13 +163,14 @@ export class AcpSession {
152
163
  this.detail({ kind: notification.update.sessionUpdate === 'available_commands_update' ? 'commands' : 'session', severity: 'info', code: 'ACP_IDLE_UPDATE', summary: '远端会话建立、恢复或空闲时的轨迹', method: 'session/update', payload: notification });
153
164
  })
154
165
  .onRequest(acp.methods.client.session.requestPermission, async ({ params: request, signal: rpcSignal, requestId }) => {
166
+ const hooks = this.hooks, detail = (value) => this.detail(value, hooks), need = (value) => this.need(value, hooks);
155
167
  if (request.sessionId !== this.sessionId || !this.busy || this.turnSignal?.aborted)
156
168
  return { outcome: { outcome: 'cancelled' } };
157
- this.hooks.broker?.acknowledge(acp.methods.agent.session.prompt);
169
+ hooks.broker?.acknowledge(acp.methods.agent.session.prompt);
158
170
  const toolName = request.toolCall.name ?? record(request.toolCall._meta?.claudeCode)?.toolName;
159
- this.detail({ kind: 'tool', severity: 'info', code: 'ACP_PERMISSION_REQUEST', summary: '远端工具审批标识', method: 'session/request_permission', payload: { sessionId: request.sessionId, toolCall: { toolCallId: request.toolCall.toolCallId, ...(typeof toolName === 'string' ? { name: toolName } : {}) } } });
171
+ detail({ kind: 'tool', severity: 'info', code: 'ACP_PERMISSION_REQUEST', summary: '远端工具审批标识', method: 'session/request_permission', payload: { sessionId: request.sessionId, toolCall: { toolCallId: request.toolCall.toolCallId, ...(typeof toolName === 'string' ? { name: toolName } : {}) } } });
160
172
  if (toolName === 'AskUserQuestion') {
161
- this.need({ code: 'INTERACTION_UNSUPPORTED', message: '首版不支持:远端提问;已取消,未提交答案', requires: '可承载此提问的官方交互接口' });
173
+ need({ code: 'INTERACTION_UNSUPPORTED', message: '首版不支持:远端提问;已取消,未提交答案', requires: '可承载此提问的官方交互接口' });
162
174
  return { outcome: { outcome: 'cancelled' } };
163
175
  }
164
176
  const eligible = request.options.filter(option => {
@@ -166,11 +178,11 @@ export class AcpSession {
166
178
  const sameKind = request.options.filter(candidate => candidate.kind === option.kind);
167
179
  const unique = single && sameKind.length === 1 && request.options.filter(candidate => candidate.optionId === option.optionId).length === 1;
168
180
  if (!unique)
169
- this.detail({ kind: 'tool', severity: 'info', code: 'APPROVAL_OPTION_NOT_OFFERED', summary: '此远端审批选项未映射到单次审批', method: 'session/request_permission', payload: option });
181
+ detail({ kind: 'tool', severity: 'info', code: 'APPROVAL_OPTION_NOT_OFFERED', summary: '此远端审批选项未映射到单次审批', method: 'session/request_permission', payload: option });
170
182
  return unique;
171
183
  });
172
184
  if (!eligible.length) {
173
- this.need({ code: 'APPROVAL_OPTION_UNSUPPORTED', message: '远端没有可唯一映射的单次审批选项,此动作未获授权', requires: '唯一对应的允许一次或拒绝一次选项' });
185
+ need({ code: 'APPROVAL_OPTION_UNSUPPORTED', message: '远端没有可唯一映射的单次审批选项,此动作未获授权', requires: '唯一对应的允许一次或拒绝一次选项' });
174
186
  return { outcome: { outcome: 'cancelled' } };
175
187
  }
176
188
  const presented = { ...request, options: eligible };
@@ -178,7 +190,7 @@ export class AcpSession {
178
190
  const id = this.sessionId;
179
191
  let outcome = 'unavailable';
180
192
  const approvalSignal = this.turnSignal ? AbortSignal.any([this.turnSignal, rpcSignal]) : rpcSignal;
181
- const broker = this.hooks.broker;
193
+ const broker = hooks.broker;
182
194
  let generation;
183
195
  // A transport disconnect withdraws only the UI question, not the ACP RPC.
184
196
  // Late UI answers lose their lease; a fresh online attachment asks again.
@@ -192,7 +204,7 @@ export class AcpSession {
192
204
  const abort = () => interrupted.resolve('cancelled');
193
205
  signal.addEventListener('abort', abort, { once: true });
194
206
  try {
195
- outcome = await Promise.race([this.hooks.approve?.(presented, signal) ?? Promise.resolve('unavailable'), interrupted.promise]);
207
+ outcome = await Promise.race([hooks.approve?.(presented, signal) ?? Promise.resolve('unavailable'), interrupted.promise]);
196
208
  }
197
209
  finally {
198
210
  signal.removeEventListener('abort', abort);
@@ -203,7 +215,7 @@ export class AcpSession {
203
215
  }
204
216
  }
205
217
  catch {
206
- this.need({ code: 'APPROVAL_FAILED', message: '审批渠道失败或连接已结束;此动作未获授权', requires: '可用的 DSH 审批渠道和原连接' });
218
+ need({ code: 'APPROVAL_FAILED', message: '审批渠道失败或连接已结束;此动作未获授权', requires: '可用的 DSH 审批渠道和原连接' });
207
219
  }
208
220
  const kind = outcome === 'allowed-once' ? 'allow_once' : outcome === 'rejected' ? 'reject_once' : undefined;
209
221
  const option = kind && eligible.find(item => item.kind === kind);
@@ -212,7 +224,7 @@ export class AcpSession {
212
224
  broker.authorizeResponse(requestId, generation);
213
225
  return { outcome: { outcome: 'selected', optionId: option.optionId } };
214
226
  }
215
- this.need({ code: outcome === 'allowed-once' ? 'APPROVAL_OPTION_UNSUPPORTED' : 'APPROVAL_UNAVAILABLE',
227
+ need({ code: outcome === 'allowed-once' ? 'APPROVAL_OPTION_UNSUPPORTED' : 'APPROVAL_UNAVAILABLE',
216
228
  message: '此动作未获授权;审批已取消、不可用或没有可映射的单次选项', requires: '本次动作的有效审批' });
217
229
  return { outcome: { outcome: 'cancelled' } };
218
230
  })
@@ -222,6 +234,20 @@ export class AcpSession {
222
234
  })
223
235
  .onNotification(acp.methods.client.elicitation.complete, () => {
224
236
  this.need({ code: 'INTERACTION_UNSUPPORTED', message: '首版不支持:远端提问完成通知;未提交答案', requires: '可承载此提问的官方交互接口' });
237
+ })
238
+ .onRequest('_aw/employee-exec', (params) => params, async ({ params, signal }) => {
239
+ if (!this.employeeNegotiated || !this.employeeMeta || !this.hooks.employeeExecution)
240
+ throw new acp.RequestError(-32601, 'EMPLOYEE_EXECUTION_UNSUPPORTED');
241
+ try {
242
+ return await this.hooks.employeeExecution(params, signal);
243
+ }
244
+ catch (error) {
245
+ if (error instanceof NeedError) {
246
+ this.need(error);
247
+ throw new acp.RequestError(-32603, error.code);
248
+ }
249
+ throw error;
250
+ }
225
251
  })
226
252
  .onRequest('*', ({ method, params }) => {
227
253
  this.detail({ kind: 'extension', severity: 'action-required', code: 'ACP_UNKNOWN_REQUEST', summary: '未知远端交互请求未获授权', method, payload: params });
@@ -253,7 +279,7 @@ export class AcpSession {
253
279
  const update = record(params?.update);
254
280
  if (raw)
255
281
  this.hooks.broker?.processedMessage(raw);
256
- if (typeof raw?.method === 'string' && Object.hasOwn(raw, 'id') && this.hooks.broker && !this.hooks.broker.promptInFlight) {
282
+ if (typeof raw?.method === 'string' && Object.hasOwn(raw, 'id') && this.hooks.broker && !this.hooks.broker.promptInFlight && !(raw.method === '_aw/employee-exec' && this.hooks.employeeExecution && this.employeeMeta)) {
257
283
  const error = new NeedError('RECOVERY_UNSAFE_OUTPUT', '主机会话有不属于当前输入的旧审批,未自动回答', '安全结束旧会话并新建');
258
284
  this.hooks.broker.fail(error);
259
285
  throw error;
@@ -270,7 +296,9 @@ export class AcpSession {
270
296
  get capabilities() { return this.negotiated; }
271
297
  get startMethod() { return this.method; }
272
298
  get checkpoint() { return this.ready && !this.busy && !this.updating && !this.selecting && this.sessionId ? { sessionId: this.sessionId, capabilities: recoveryCapabilities(this.negotiated), ...(this.configuration ? { modelConfiguration: { configOptions: JSON.parse(JSON.stringify(this.configuration.snapshot.configOptions)), ...(this.resolvedEvidence ? { resolvedEvidence: { ...this.resolvedEvidence } } : {}) } } : {}) } : undefined; }
273
- restore(cwd, saved, context) {
299
+ restore(cwd, saved, context, verifiedSameContext = false) {
300
+ if (context.employeeRouting)
301
+ throw new NeedError('EMPLOYEE_CHECKPOINT_STALE', '本机工具通道需要重新建立,旧审批没有代答。', '按原主机实例安全结束后新建本机会话');
274
302
  if (this.startAttempted)
275
303
  throw new NeedError('SESSION_ALREADY_STARTED', '此客户端已建立会话', '新的客户端连接');
276
304
  this.startAttempted = true;
@@ -278,7 +306,11 @@ export class AcpSession {
278
306
  this.negotiated = recoveryCapabilities(saved.capabilities);
279
307
  this.cwd = cwd;
280
308
  this.systemPrompt = context.systemPrompt ?? '';
281
- this.ready = true;
309
+ this.codexNewSessionOnly = !!context.codexNewSessionOnly;
310
+ // A verified same-context reattach does not create or update native context.
311
+ // Old journals may lack new-only metadata; never manufacture it for them.
312
+ if (this.codexNewSessionOnly && !verifiedSameContext)
313
+ this.validateNewContext(this.systemPrompt, context.systemPrompt !== undefined);
282
314
  if (saved.modelConfiguration) {
283
315
  this.receiveConfiguration(saved.modelConfiguration.configOptions);
284
316
  this.resolvedEvidence = saved.modelConfiguration.resolvedEvidence;
@@ -289,6 +321,8 @@ export class AcpSession {
289
321
  }
290
322
  this.hooks.broker?.finishRecovery();
291
323
  this.detail({ kind: 'need', severity: 'info', code: 'ACP_CLIENT_REATTACHED', summary: '已接回原模型会话', payload: { sessionId: saved.sessionId } });
324
+ this.checkReporting();
325
+ this.ready = true;
292
326
  }
293
327
  validatePrompt(prompt) {
294
328
  const capability = this.negotiated.promptCapabilities;
@@ -299,39 +333,66 @@ export class AcpSession {
299
333
  validateContext(context) {
300
334
  if ((context.systemPrompt ?? '') === this.systemPrompt)
301
335
  return;
336
+ if (this.codexNewSessionOnly)
337
+ throw new NeedError('SYSTEM_PROMPT_NEW_SESSION_REQUIRED', 'Codex会话要求已变化,需要保留原会话并新建会话承接', '新会话及完整公开历史承接');
302
338
  if (!this.negotiated.sessionCapabilities?.systemPrompt)
303
339
  throw new NeedError('SYSTEM_PROMPT_UNSUPPORTED', '远端未声明系统与开发者上下文更新能力,不能丢弃这些要求继续', '支持 systemPrompt 的公开 ACP 能力');
304
340
  if (!this.negotiated.sessionCapabilities?.resume && !this.negotiated.loadSession)
305
341
  throw new NeedError('SYSTEM_PROMPT_UPDATE_UNSUPPORTED', '远端没有可更新原会话上下文的恢复或加载能力', '支持带 systemPrompt 的 session/resume 或 session/load');
306
342
  }
343
+ validateNewSessionContext(context) {
344
+ if (!this.codexNewSessionOnly)
345
+ throw new NeedError('SYSTEM_PROMPT_POLICY_UNSUPPORTED', '此会话没有使用Codex新建政策', 'Codex新建政策');
346
+ this.validateNewContext(context.systemPrompt ?? '', true);
347
+ }
307
348
  setHooks(hooks) {
308
349
  if (this.busy || this.updating || this.selecting)
309
350
  throw new NeedError('TURN_BUSY', '上一轮或配置更新结束前不能更换会话钩子', '上一轮结束');
310
351
  this.hooks = hooks;
311
352
  }
312
353
  get closed() { return this.connection.closed; }
313
- async start(cwd, resumeId, signal, context) {
354
+ validateNewContext(text, requirePolicy = false) {
355
+ if ((text || requirePolicy) && !this.negotiated.sessionCapabilities?.systemPrompt)
356
+ throw new NeedError('SYSTEM_PROMPT_UNSUPPORTED', '远端未声明系统与开发者上下文能力,不能丢弃这些要求继续', '支持 systemPrompt 的公开 ACP 能力');
357
+ if (this.codexNewSessionOnly && (text || requirePolicy)) {
358
+ if (!supportsCodexContextPolicy(this.negotiated))
359
+ throw new NeedError('SYSTEM_PROMPT_POLICY_UNSUPPORTED', '远端没有明确声明Codex上下文仅新建生效的兼容政策,本轮未发送', 'systemPrompt与精确的aiWorkbenchSystemPrompt新建政策');
360
+ }
361
+ }
362
+ async start(cwd, resumeId, signal, context, prompt) {
314
363
  if (this.startAttempted)
315
364
  throw new NeedError('SESSION_ALREADY_STARTED', '此连接已用于建立会话,不能转用工作区或身份', '新的独立远端连接');
316
365
  this.startAttempted = true;
366
+ this.employeeMeta = context?.employeeExecution;
317
367
  const abort = () => this.connection.close(new NeedError('ABORTED', '会话建立已取消', '重新连接主机'));
318
368
  signal?.throwIfAborted();
319
369
  signal?.addEventListener('abort', abort, { once: true });
320
370
  try {
321
371
  const hello = await this.stage('acp-initialize', () => this.connection.agent.request(acp.methods.agent.initialize, {
322
- protocolVersion: acp.PROTOCOL_VERSION, clientInfo: { name: '@ai-workbench/remote-agent', version: '0.8.5' },
323
- clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false, session: { notices: {} } },
372
+ protocolVersion: acp.PROTOCOL_VERSION, clientInfo: { name: '@ai-workbench/remote-agent', version: '0.8.10' },
373
+ clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false, session: { notices: {} }, ...(this.employeeMeta ? { _meta: { awEmployeeExecution: { version: 1 } } } : {}) },
324
374
  }, { cancellationSignal: signal }));
325
375
  this.hooks.broker?.acknowledge(acp.methods.agent.initialize);
326
376
  if (hello.protocolVersion !== acp.PROTOCOL_VERSION)
327
377
  throw new NeedError('ACP_VERSION_MISMATCH', 'ACP 协议版本不兼容', '支持 ACP v1 的远端 agent');
328
378
  this.checkReporting();
329
379
  this.negotiated = hello.agentCapabilities ?? {};
330
- const systemPrompt = context?.systemPrompt;
331
- if (systemPrompt && !this.negotiated.sessionCapabilities?.systemPrompt) {
332
- throw new NeedError('SYSTEM_PROMPT_UNSUPPORTED', '远端未声明系统与开发者上下文能力,不能丢弃这些要求继续', '支持 systemPrompt 的公开 ACP 能力');
380
+ if (this.employeeMeta) {
381
+ const policy = record(hello._meta?.awEmployeeExecution);
382
+ if (!this.hooks.employeeExecution || !policy || Object.keys(policy).length !== 5 || policy.version !== 1 || policy.nativeCliVersion !== '0.160.1' || policy.native !== true || policy.perRpcGate !== true || policy.noHostFallback !== true)
383
+ throw new NeedError('EMPLOYEE_EXECUTION_UNSUPPORTED', '当前主机尚不支持在你的电脑执行Codex原生工具,本轮没有执行。', 'Codex员工端原生执行适配器与明确能力声明');
384
+ this.employeeNegotiated = true;
385
+ this.detail({ kind: 'session', severity: 'info', code: 'EMPLOYEE_EXECUTION_SELECTED', summary: '文件和命令在员工电脑的选定目录执行,Git使用本机仓库;模型、登录和全局助手要求仍在主机。本机MCP、hooks、plugins、冷恢复、快捷命令和主机文件报告尚不支持。外层ACP冷恢复已拒绝;原生子代理回收/缺少存活父会话时的恢复仅做源码检查,未实测。', payload: { bindingId: this.employeeMeta.bindingId, workspaceId: this.employeeMeta.workspaceId, cwd: this.employeeMeta.cwd, nativeCliVersion: '0.160.1', noHostFallback: true, agentConfigSource: 'host CODEX_HOME', unsupportedLocalFeatures: ['mcp', 'hooks', 'plugins', 'cold-resume', 'slash-commands', 'host-file-reports'] } });
333
386
  }
334
- const params = { cwd, mcpServers: [], ...(systemPrompt ? { systemPrompt: { mode: 'append', content: [{ type: 'text', text: systemPrompt }] } } : {}) };
387
+ const systemPrompt = context?.systemPrompt;
388
+ this.codexNewSessionOnly = !!context?.codexNewSessionOnly;
389
+ this.validateNewContext(systemPrompt ?? '', this.codexNewSessionOnly && systemPrompt !== undefined);
390
+ if (prompt)
391
+ this.validatePrompt(prompt);
392
+ if (context?.employeeRouting && resumeId)
393
+ throw new NeedError('EMPLOYEE_CHECKPOINT_STALE', '本机工具连接已变化,不能沿用旧会话。', '安全结束原主机实例并重新建立本机工具通道');
394
+ const routing = context?.employeeRouting;
395
+ const params = { cwd, mcpServers: routing ? structuredClone(routing.mcpServers) : [], ...((routing || this.employeeMeta) ? { _meta: { ...(this.employeeMeta ? { awEmployeeExecution: this.employeeMeta } : {}), ...(routing ? { claudeCode: { options: { toolAliases: structuredClone(routing.toolAliases) } } } : {}) } } : {}), ...(systemPrompt && !(resumeId && this.codexNewSessionOnly) ? { systemPrompt: { mode: 'append', content: [{ type: 'text', text: systemPrompt }] } } : {}) };
335
396
  const configRevision = this.resetConfiguration();
336
397
  if (resumeId) {
337
398
  this.sessionId = resumeId;
@@ -375,6 +436,51 @@ export class AcpSession {
375
436
  signal?.removeEventListener('abort', abort);
376
437
  }
377
438
  }
439
+ /** Codex context is immutable after creation; keep the old remote session intact. */
440
+ async newContextSession(context, signal) {
441
+ this.checkReporting();
442
+ if (!this.ready || !this.sessionId || !this.cwd)
443
+ throw new NeedError('SESSION_REQUIRED', '我需要先建立远端会话', 'ACP 会话');
444
+ if (!this.codexNewSessionOnly)
445
+ throw new NeedError('SYSTEM_PROMPT_POLICY_UNSUPPORTED', '此会话没有使用Codex新建政策', 'Codex新建政策');
446
+ if (this.busy || this.updating || this.selecting)
447
+ throw new NeedError('TURN_BUSY', '上一项会话操作尚未完成', '上一项操作结束');
448
+ const text = context.systemPrompt ?? '';
449
+ this.validateNewSessionContext(context);
450
+ signal?.throwIfAborted();
451
+ const oldId = this.sessionId;
452
+ this.updating = true;
453
+ this.ready = false;
454
+ this.creatingReplacement = true;
455
+ this.pendingConfigurations.clear();
456
+ const abort = () => this.connection.close(new NeedError('ABORTED', '新会话建立已取消,远端状态需确认', '管理员核对远端状态'));
457
+ signal?.addEventListener('abort', abort, { once: true });
458
+ try {
459
+ const revision = this.resetConfiguration();
460
+ const result = await this.stage('acp-session-new', () => this.connection.agent.request(acp.methods.agent.session.new, { cwd: this.cwd, mcpServers: [], ...(this.employeeMeta ? { _meta: { awEmployeeExecution: this.employeeMeta } } : {}), ...(text ? { systemPrompt: { mode: 'append', content: [{ type: 'text', text }] } } : {}) }, { cancellationSignal: signal }));
461
+ if (!result.sessionId || result.sessionId === oldId)
462
+ throw new NeedError('SESSION_STATE_INVALID', '新会话没有返回独立的远端标识,原会话状态需确认', '独立的新远端会话标识');
463
+ this.sessionId = result.sessionId;
464
+ this.lifecycleConfiguration(result, revision);
465
+ this.hooks.broker?.acknowledge(acp.methods.agent.session.new);
466
+ this.checkReporting();
467
+ this.systemPrompt = text;
468
+ this.method = acp.methods.agent.session.new;
469
+ this.ready = true;
470
+ return this.sessionId;
471
+ }
472
+ catch (error) {
473
+ this.checkReporting();
474
+ if (error instanceof ModelConfigurationError)
475
+ throw error;
476
+ throw acpNeed(error);
477
+ }
478
+ finally {
479
+ this.creatingReplacement = false;
480
+ this.updating = false;
481
+ signal?.removeEventListener('abort', abort);
482
+ }
483
+ }
378
484
  async updateContext(context, signal) {
379
485
  this.checkReporting();
380
486
  if (!this.ready || !this.sessionId || !this.cwd)
@@ -472,6 +578,17 @@ export class AcpSession {
472
578
  export function acpNeed(error) {
473
579
  if (error instanceof NeedError)
474
580
  return error;
581
+ if (error instanceof acp.RequestError) {
582
+ // Host SDK1.9 uses Internal error + data.details for thrown adapter errors.
583
+ // Only fixed codes cross this boundary; arbitrary detail strings are never copied.
584
+ for (const value of [error.message, record(error.data)?.details]) {
585
+ if (typeof value !== 'string')
586
+ continue;
587
+ const code = value.split(':', 1)[0];
588
+ if (employeeAdapterCodes.has(code))
589
+ return new NeedError(code, employeeFailure(code), `员工电脑执行适配器明确拒绝:${code};RPC 错误码 ${error.code}`);
590
+ }
591
+ }
475
592
  if (error instanceof acp.RequestError && error.code === -32000)
476
593
  return new NeedError('LOGIN_REQUIRED', '需要登录,请到设置 → 远程主机连接 点登录', '该员工自己的远端模型登录');
477
594
  if (error instanceof acp.RequestError)
@@ -481,3 +598,4 @@ export function acpNeed(error) {
481
598
  function record(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined; }
482
599
  const knownUpdates = new Set(['user_message_chunk', 'agent_message_chunk', 'agent_thought_chunk', 'tool_call', 'tool_call_update', 'plan', 'plan_update', 'plan_removed', 'available_commands_update', 'current_mode_update', 'config_option_update', 'session_info_update', 'usage_update', 'notice', 'compaction_update', 'compaction_summary_chunk', 'subagent_update', 'session_message', 'session_message_chunk']);
483
600
  function safeLabel(value) { return value.replaceAll(/[\r\n\x00-\x1f\x7f]/g, ' '); }
601
+ const employeeAdapterCodes = new Set(["AW_EMPLOYEE_ADDITIONAL_DIRECTORIES_UNSUPPORTED", "AW_EMPLOYEE_SLASH_COMMAND_UNSUPPORTED", "AW_EMPLOYEE_HOST_FILE_REPORT_UNSUPPORTED", "AW_EMPLOYEE_CONFIG_DISCOVERY_OVERRIDE_UNSUPPORTED", "AW_EMPLOYEE_CONFIG_ADMISSION_INVALID", "AW_EMPLOYEE_CONFIG_ADMISSION_FAILED", "AW_EMPLOYEE_HOST_PROJECT_CONFIG_UNSUPPORTED", "AW_EMPLOYEE_EXECUTOR_MCP_UNSUPPORTED", "AW_EMPLOYEE_EXECUTOR_CONFIG_PROTOCOL_INVALID", "AW_EMPLOYEE_EXECUTOR_CONFIG_READ_FAILED", "AW_EMPLOYEE_AGENT_CONFIG_UNAVAILABLE", "AW_EMPLOYEE_BINDING_INVALID", "AW_EMPLOYEE_BINDING_MISMATCH", "AW_EMPLOYEE_CAPABILITY_REQUIRED", "AW_EMPLOYEE_CLOSE_FAILED", "AW_EMPLOYEE_CLOSE_RESPONSE_INVALID", "AW_EMPLOYEE_COLD_RESUME_UNSUPPORTED", "AW_EMPLOYEE_CWD_MISMATCH", "AW_EMPLOYEE_ENVIRONMENT_REGISTRATION_FAILED", "AW_EMPLOYEE_ENVIRONMENT_REQUIRED", "AW_EMPLOYEE_EXECUTION_CLOSED", "AW_EMPLOYEE_EXECUTION_ID_REQUIRED", "AW_EMPLOYEE_HOST_AUXILIARY_CONFIG_UNSUPPORTED", "AW_EMPLOYEE_HOST_AUXILIARY_UNSUPPORTED", "AW_EMPLOYEE_HOST_FALLBACK_FORBIDDEN", "AW_EMPLOYEE_MCP_UNSUPPORTED", "AW_EMPLOYEE_MESSAGE_INVALID", "AW_EMPLOYEE_MESSAGE_TOO_LARGE", "AW_EMPLOYEE_OPEN_RESPONSE_INVALID", "AW_EMPLOYEE_READ_MESSAGE_INVALID", "AW_EMPLOYEE_READ_RESPONSE_INVALID", "AW_EMPLOYEE_RELAY_ADDRESS_INVALID", "AW_EMPLOYEE_RELAY_FAILED", "AW_EMPLOYEE_REQUEST_INVALID", "AW_EMPLOYEE_SESSION_UNKNOWN", "AW_EMPLOYEE_WIRE_FAILED", "AW_EMPLOYEE_WRITE_RESPONSE_INVALID"]);
@@ -1,6 +1,6 @@
1
1
  import type { RequestPermissionRequest } from '@agentclientprotocol/sdk/unstable';
2
2
  /** ACP diff.oldText=null means a new file. Write alone may overwrite a file. */
3
- export declare function approvalLabel(request: RequestPermissionRequest): {
3
+ export declare function approvalLabel(request: RequestPermissionRequest, location?: 'host' | 'local'): {
4
4
  toolName: string;
5
5
  reason: string;
6
6
  } | undefined;
@@ -5,13 +5,25 @@ function display(value) {
5
5
  return safe && !safe.includes('[已脱敏:') ? safe : undefined;
6
6
  }
7
7
  /** ACP diff.oldText=null means a new file. Write alone may overwrite a file. */
8
- export function approvalLabel(request) {
8
+ export function approvalLabel(request, location = 'host') {
9
9
  const tool = request.toolCall;
10
10
  const diffs = tool.content?.filter(item => item.type === 'diff') ?? [];
11
- const name = tool.name ?? /^(Write|Edit|MultiEdit) /.exec(tool.title ?? '')?.[1];
11
+ const originalName = tool.name ?? tool._meta?.claudeCode?.toolName ?? /^(Write|Edit|MultiEdit) /.exec(tool.title ?? '')?.[1];
12
+ const employee = location === 'local' && originalName?.startsWith('mcp__employee__');
13
+ const name = employee ? originalName.slice('mcp__employee__'.length) : originalName;
12
14
  const fileAction = diffs.length > 0 || name === 'Write' || name === 'Edit' || name === 'MultiEdit';
13
15
  const paths = diffs.length ? diffs.map(diff => diff.path) : tool.locations?.map(location => location.path) ?? [];
14
16
  let title = tool.title ?? '';
17
+ const raw = employee && tool.rawInput && typeof tool.rawInput === 'object' && !Array.isArray(tool.rawInput) ? tool.rawInput : undefined;
18
+ if (employee && raw) {
19
+ if ((fileAction || name === 'Read') && !paths.length && typeof raw.file_path === 'string')
20
+ paths.push(raw.file_path);
21
+ if ((name === 'Glob' || name === 'Grep') && !paths.length)
22
+ paths.push(typeof raw.path === 'string' ? raw.path : '当前文件夹');
23
+ title = typeof raw.description === 'string' ? raw.description : typeof raw.command === 'string' ? raw.command : '';
24
+ if ((name === 'Glob' || name === 'Grep') && typeof raw.pattern === 'string')
25
+ title = raw.pattern;
26
+ }
15
27
  if (name && title.startsWith(name + ' '))
16
28
  title = title.slice(name.length + 1);
17
29
  else if (title === name || title === tool.toolCallId)
@@ -32,7 +44,7 @@ export function approvalLabel(request) {
32
44
  const files = safe.map((path, index) => names.filter(name => name === names[index]).length > 1 ? path : names[index]);
33
45
  return { toolName: action, reason: action + ' ' + files.join('、') };
34
46
  }
35
- const action = { read: '读取文件', edit: '修改文件', delete: '删除文件', move: '移动文件', search: '搜索', execute: '执行主机命令', think: '分析', fetch: '获取内容', other: '远程操作' }[tool.kind ?? ''] ?? '远程操作';
47
+ const action = (employee ? { Read: '读取文件', Glob: '查找文件', Grep: '搜索内容', Bash: '执行本机命令' }[name] : undefined) ?? { read: '读取文件', edit: '修改文件', delete: '删除文件', move: '移动文件', search: '搜索', execute: employee ? '执行本机命令' : '执行主机命令', think: '分析', fetch: '获取内容', other: '远程操作' }[tool.kind ?? ''] ?? '远程操作';
36
48
  const safePaths = paths.map(display);
37
49
  if (safePaths.some(path => path === undefined))
38
50
  return;