@jingyi0605/codingns4dsh 0.2.1-beta.1 → 0.2.1-beta.3

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 (217) hide show
  1. package/README.en.md +25 -14
  2. package/README.md +21 -17
  3. package/data/build/dist/client/bundle.js +366 -81
  4. package/data/build/dist/client/bundle.js.map +1 -1
  5. package/data/build/dist/client/locales/fileWorkspace.d.ts.map +1 -1
  6. package/data/build/dist/client/locales/fileWorkspace.js +4 -0
  7. package/data/build/dist/client/locales/fileWorkspace.js.map +1 -1
  8. package/data/build/dist/client/locales/peerHostWorkspace.d.ts.map +1 -1
  9. package/data/build/dist/client/locales/peerHostWorkspace.js +16 -2
  10. package/data/build/dist/client/locales/peerHostWorkspace.js.map +1 -1
  11. package/data/build/dist/client/peer-host-native-projection.d.ts +4 -3
  12. package/data/build/dist/client/peer-host-native-projection.d.ts.map +1 -1
  13. package/data/build/dist/client/peer-host-native-projection.js +7 -6
  14. package/data/build/dist/client/peer-host-native-projection.js.map +1 -1
  15. package/data/build/dist/client/peer-host-native-store-projection.js +9 -21
  16. package/data/build/dist/client/peer-host-native-store-projection.js.map +1 -1
  17. package/data/build/dist/client/peer-host-workspace-tab.d.ts.map +1 -1
  18. package/data/build/dist/client/peer-host-workspace-tab.js +139 -10
  19. package/data/build/dist/client/peer-host-workspace-tab.js.map +1 -1
  20. package/data/build/dist/client/session-changed-files-view.d.ts.map +1 -1
  21. package/data/build/dist/client/session-changed-files-view.js +162 -58
  22. package/data/build/dist/client/session-changed-files-view.js.map +1 -1
  23. package/data/build/dist/client/skill-command.d.ts +1 -1
  24. package/data/build/dist/client/skill-command.d.ts.map +1 -1
  25. package/data/build/dist/client/skill-command.js +25 -2
  26. package/data/build/dist/client/skill-command.js.map +1 -1
  27. package/data/build/dist/client/skill-reference-dom.js +1 -1
  28. package/data/build/dist/client/skill-reference-dom.js.map +1 -1
  29. package/data/build/dist/client/terminal/styles.js +2 -2
  30. package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.d.ts +28 -0
  31. package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.d.ts.map +1 -0
  32. package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.js +109 -0
  33. package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.js.map +1 -0
  34. package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts +25 -3
  35. package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts.map +1 -1
  36. package/data/build/dist/host/cli-adapters/acp-cli-driver.js +120 -9
  37. package/data/build/dist/host/cli-adapters/acp-cli-driver.js.map +1 -1
  38. package/data/build/dist/host/cli-adapters/acp-elicitation.d.ts +31 -0
  39. package/data/build/dist/host/cli-adapters/acp-elicitation.d.ts.map +1 -0
  40. package/data/build/dist/host/cli-adapters/acp-elicitation.js +144 -0
  41. package/data/build/dist/host/cli-adapters/acp-elicitation.js.map +1 -0
  42. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts +2 -1
  43. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts.map +1 -1
  44. package/data/build/dist/host/cli-adapters/antigravity-driver.js +15 -2
  45. package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -1
  46. package/data/build/dist/host/cli-adapters/attachment-utils.d.ts +16 -5
  47. package/data/build/dist/host/cli-adapters/attachment-utils.d.ts.map +1 -1
  48. package/data/build/dist/host/cli-adapters/attachment-utils.js +152 -42
  49. package/data/build/dist/host/cli-adapters/attachment-utils.js.map +1 -1
  50. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +23 -8
  51. package/data/build/dist/host/cli-adapters/claude-driver.d.ts.map +1 -1
  52. package/data/build/dist/host/cli-adapters/claude-driver.js +227 -70
  53. package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
  54. package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts +7 -1
  55. package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts.map +1 -1
  56. package/data/build/dist/host/cli-adapters/codebuddy-driver.js +143 -6
  57. package/data/build/dist/host/cli-adapters/codebuddy-driver.js.map +1 -1
  58. package/data/build/dist/host/cli-adapters/codex-driver.d.ts.map +1 -1
  59. package/data/build/dist/host/cli-adapters/codex-driver.js +74 -6
  60. package/data/build/dist/host/cli-adapters/codex-driver.js.map +1 -1
  61. package/data/build/dist/host/cli-adapters/command-code-acp-loader.d.ts +23 -0
  62. package/data/build/dist/host/cli-adapters/command-code-acp-loader.d.ts.map +1 -0
  63. package/data/build/dist/host/cli-adapters/command-code-acp-loader.js +39 -0
  64. package/data/build/dist/host/cli-adapters/command-code-acp-loader.js.map +1 -0
  65. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +17 -6
  66. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts.map +1 -1
  67. package/data/build/dist/host/cli-adapters/command-code-driver.js +448 -26
  68. package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
  69. package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts +1 -1
  70. package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts.map +1 -1
  71. package/data/build/dist/host/cli-adapters/command-code-subscription.js +7 -4
  72. package/data/build/dist/host/cli-adapters/command-code-subscription.js.map +1 -1
  73. package/data/build/dist/host/cli-adapters/cursor-driver.d.ts +1 -1
  74. package/data/build/dist/host/cli-adapters/cursor-driver.js +2 -2
  75. package/data/build/dist/host/cli-adapters/cursor-driver.js.map +1 -1
  76. package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.js +1 -1
  77. package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.js.map +1 -1
  78. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts.map +1 -1
  79. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +28 -0
  80. package/data/build/dist/host/cli-adapters/dsh-message-projector.js.map +1 -1
  81. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts.map +1 -1
  82. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +23 -1
  83. package/data/build/dist/host/cli-adapters/dsh-tool-history.js.map +1 -1
  84. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  85. package/data/build/dist/host/cli-adapters/feature.js +5 -3
  86. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  87. package/data/build/dist/host/cli-adapters/gemini-driver.d.ts +2 -1
  88. package/data/build/dist/host/cli-adapters/gemini-driver.d.ts.map +1 -1
  89. package/data/build/dist/host/cli-adapters/gemini-driver.js +66 -11
  90. package/data/build/dist/host/cli-adapters/gemini-driver.js.map +1 -1
  91. package/data/build/dist/host/cli-adapters/grok-driver.d.ts +5 -2
  92. package/data/build/dist/host/cli-adapters/grok-driver.d.ts.map +1 -1
  93. package/data/build/dist/host/cli-adapters/grok-driver.js +268 -18
  94. package/data/build/dist/host/cli-adapters/grok-driver.js.map +1 -1
  95. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +2 -0
  96. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts.map +1 -1
  97. package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -3
  98. package/data/build/dist/host/cli-adapters/json-rpc-process.js.map +1 -1
  99. package/data/build/dist/host/cli-adapters/kimi-driver.d.ts +4 -0
  100. package/data/build/dist/host/cli-adapters/kimi-driver.d.ts.map +1 -1
  101. package/data/build/dist/host/cli-adapters/kimi-driver.js +11 -4
  102. package/data/build/dist/host/cli-adapters/kimi-driver.js.map +1 -1
  103. package/data/build/dist/host/cli-adapters/kiro-driver.js +1 -1
  104. package/data/build/dist/host/cli-adapters/kiro-driver.js.map +1 -1
  105. package/data/build/dist/host/cli-adapters/mcode-driver.d.ts +6 -2
  106. package/data/build/dist/host/cli-adapters/mcode-driver.d.ts.map +1 -1
  107. package/data/build/dist/host/cli-adapters/mcode-driver.js +95 -26
  108. package/data/build/dist/host/cli-adapters/mcode-driver.js.map +1 -1
  109. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.d.ts +55 -1
  110. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.d.ts.map +1 -1
  111. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js +148 -6
  112. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js.map +1 -1
  113. package/data/build/dist/host/cli-adapters/native-team-subagent.d.ts +7 -0
  114. package/data/build/dist/host/cli-adapters/native-team-subagent.d.ts.map +1 -1
  115. package/data/build/dist/host/cli-adapters/native-team-subagent.js.map +1 -1
  116. package/data/build/dist/host/cli-adapters/new-api-subscription.d.ts.map +1 -1
  117. package/data/build/dist/host/cli-adapters/new-api-subscription.js +11 -58
  118. package/data/build/dist/host/cli-adapters/new-api-subscription.js.map +1 -1
  119. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +5 -2
  120. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts.map +1 -1
  121. package/data/build/dist/host/cli-adapters/opencode-driver.js +136 -18
  122. package/data/build/dist/host/cli-adapters/opencode-driver.js.map +1 -1
  123. package/data/build/dist/host/cli-adapters/pi-driver.d.ts.map +1 -1
  124. package/data/build/dist/host/cli-adapters/pi-driver.js +2 -1
  125. package/data/build/dist/host/cli-adapters/pi-driver.js.map +1 -1
  126. package/data/build/dist/host/cli-adapters/process-utils.d.ts +10 -0
  127. package/data/build/dist/host/cli-adapters/process-utils.d.ts.map +1 -1
  128. package/data/build/dist/host/cli-adapters/process-utils.js +20 -0
  129. package/data/build/dist/host/cli-adapters/process-utils.js.map +1 -1
  130. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +3 -0
  131. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts.map +1 -1
  132. package/data/build/dist/host/cli-adapters/provider-subscription.js +42 -8
  133. package/data/build/dist/host/cli-adapters/provider-subscription.js.map +1 -1
  134. package/data/build/dist/host/cli-adapters/qoder-driver.d.ts +4 -4
  135. package/data/build/dist/host/cli-adapters/qoder-driver.d.ts.map +1 -1
  136. package/data/build/dist/host/cli-adapters/qoder-driver.js +31 -8
  137. package/data/build/dist/host/cli-adapters/qoder-driver.js.map +1 -1
  138. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  139. package/data/build/dist/host/cli-adapters/registry.js +5 -3
  140. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  141. package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts.map +1 -1
  142. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +2 -9
  143. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js.map +1 -1
  144. package/data/build/dist/host/cli-adapters/skill-filesystem.d.ts +25 -0
  145. package/data/build/dist/host/cli-adapters/skill-filesystem.d.ts.map +1 -0
  146. package/data/build/dist/host/cli-adapters/skill-filesystem.js +161 -0
  147. package/data/build/dist/host/cli-adapters/skill-filesystem.js.map +1 -0
  148. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +1 -0
  149. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts.map +1 -1
  150. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +7 -1
  151. package/data/build/dist/host/cli-adapters/standard-stream-driver.js.map +1 -1
  152. package/data/build/dist/host/cli-adapters/subagent-tool.d.ts.map +1 -1
  153. package/data/build/dist/host/cli-adapters/subagent-tool.js +40 -10
  154. package/data/build/dist/host/cli-adapters/subagent-tool.js.map +1 -1
  155. package/data/build/dist/host/cli-adapters/zcode-driver.d.ts +8 -2
  156. package/data/build/dist/host/cli-adapters/zcode-driver.d.ts.map +1 -1
  157. package/data/build/dist/host/cli-adapters/zcode-driver.js +166 -55
  158. package/data/build/dist/host/cli-adapters/zcode-driver.js.map +1 -1
  159. package/data/build/dist/host/cli-adapters/zcode-interactions.d.ts +18 -0
  160. package/data/build/dist/host/cli-adapters/zcode-interactions.d.ts.map +1 -0
  161. package/data/build/dist/host/cli-adapters/zcode-interactions.js +90 -0
  162. package/data/build/dist/host/cli-adapters/zcode-interactions.js.map +1 -0
  163. package/data/build/dist/host/cli-adapters/zcode-provider-config.d.ts +26 -0
  164. package/data/build/dist/host/cli-adapters/zcode-provider-config.d.ts.map +1 -0
  165. package/data/build/dist/host/cli-adapters/zcode-provider-config.js +102 -0
  166. package/data/build/dist/host/cli-adapters/zcode-provider-config.js.map +1 -0
  167. package/data/build/dist/host/cli-adapters/zcode-subscription.d.ts +2 -7
  168. package/data/build/dist/host/cli-adapters/zcode-subscription.d.ts.map +1 -1
  169. package/data/build/dist/host/cli-adapters/zcode-subscription.js.map +1 -1
  170. package/data/build/dist/host/cli-adapters/zcode-telemetry.d.ts +17 -0
  171. package/data/build/dist/host/cli-adapters/zcode-telemetry.d.ts.map +1 -0
  172. package/data/build/dist/host/cli-adapters/zcode-telemetry.js +124 -0
  173. package/data/build/dist/host/cli-adapters/zcode-telemetry.js.map +1 -0
  174. package/data/build/dist/host/cli-bridge/bridge-holder.d.ts +4 -1
  175. package/data/build/dist/host/cli-bridge/bridge-holder.d.ts.map +1 -1
  176. package/data/build/dist/host/cli-bridge/bridge-holder.js.map +1 -1
  177. package/data/build/dist/host/cli-bridge/bridge-server.d.ts +11 -3
  178. package/data/build/dist/host/cli-bridge/bridge-server.d.ts.map +1 -1
  179. package/data/build/dist/host/cli-bridge/bridge-server.js +8 -4
  180. package/data/build/dist/host/cli-bridge/bridge-server.js.map +1 -1
  181. package/data/build/dist/host/cli-bridge/command-code-mod.js +18 -2
  182. package/data/build/dist/host/cli-bridge/command-code-mod.js.map +1 -1
  183. package/data/build/dist/host/cli-bridge/dispatch.d.ts.map +1 -1
  184. package/data/build/dist/host/cli-bridge/dispatch.js +30 -5
  185. package/data/build/dist/host/cli-bridge/dispatch.js.map +1 -1
  186. package/data/build/dist/host/cli-bridge/injections.d.ts +2 -9
  187. package/data/build/dist/host/cli-bridge/injections.d.ts.map +1 -1
  188. package/data/build/dist/host/cli-bridge/injections.js +9 -19
  189. package/data/build/dist/host/cli-bridge/injections.js.map +1 -1
  190. package/data/build/dist/host/cli-bridge/mcp-stdio-entry.js +49 -7
  191. package/data/build/dist/host/cli-bridge/mcp-stdio-entry.js.map +1 -1
  192. package/data/build/dist/host/features/peer-host.d.ts.map +1 -1
  193. package/data/build/dist/host/features/peer-host.js +23 -0
  194. package/data/build/dist/host/features/peer-host.js.map +1 -1
  195. package/data/build/dist/host/index.d.ts +1 -1
  196. package/data/build/dist/host/index.d.ts.map +1 -1
  197. package/data/build/dist/host/index.js +4 -1
  198. package/data/build/dist/host/index.js.map +1 -1
  199. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.d.ts +2 -0
  200. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.d.ts.map +1 -1
  201. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js +5 -0
  202. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js.map +1 -1
  203. package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.d.ts.map +1 -1
  204. package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js +3 -1
  205. package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js.map +1 -1
  206. package/data/build/dist/host/rpc.d.ts.map +1 -1
  207. package/data/build/dist/host/rpc.js +5 -0
  208. package/data/build/dist/host/rpc.js.map +1 -1
  209. package/data/build/dist/shared/contracts/cli-adapter.d.ts +4 -0
  210. package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
  211. package/data/build/dist/shared/contracts/version.d.ts +1 -1
  212. package/data/build/dist/shared/contracts/version.js +1 -1
  213. package/package.json +3 -1
  214. package/profile/README.md +2 -2
  215. package/profile/package.json +2 -2
  216. package/profile/version.json +1 -1
  217. package/version.json +1 -1
@@ -6,9 +6,11 @@ import readline from 'node:readline';
6
6
  import { firstToolText, serializeToolValue } from './tool-observation.js';
7
7
  import { usageChunk } from './rpc-driver-utils.js';
8
8
  import { commandEnvironment, resolveCommandPath, terminateChildProcess } from './process-utils.js';
9
- import { promptWithAttachmentPaths } from './attachment-utils.js';
9
+ import { prepareAttachmentPaths, promptWithAttachmentPaths } from './attachment-utils.js';
10
+ import { parseSkillFrontmatter } from './skill-filesystem.js';
10
11
  import { commandCodeBridgeArgs, commandCodeBridgeEnvironment } from '../cli-bridge/injections.js';
11
12
  import { getSubagentBridge } from '../cli-bridge/bridge-holder.js';
13
+ import { AcpCliDriver } from './acp-cli-driver.js';
12
14
  import { CommandCodeHistory, } from './command-code-history.js';
13
15
  const WINDOWS = process.platform === 'win32';
14
16
  const COMMAND_CODE_BINARIES = WINDOWS
@@ -156,6 +158,14 @@ function readDeclaredEfforts(homeDirectory) {
156
158
  const DEFAULT_MAX_TURNS = 500;
157
159
  const DEFAULT_AUTO_CONTINUE_ATTEMPTS = 3;
158
160
  const AUTO_CONTINUE_PROMPT = '继续';
161
+ /** Command Code ACP 自由文本兼容 loader 的 URL;只注入子进程,不改写安装包。 */
162
+ const COMMAND_CODE_ACP_LOADER_URL = new URL('./command-code-acp-loader.js', import.meta.url).href;
163
+ function commandCodeAcpEnvironment() {
164
+ // file URL 会把空格编码成 %20,避免 Windows 的 NODE_OPTIONS 按空格拆路径。
165
+ const loaderOption = `--loader=${COMMAND_CODE_ACP_LOADER_URL}`;
166
+ const existing = process.env.NODE_OPTIONS?.trim();
167
+ return { NODE_OPTIONS: existing === undefined || existing === '' ? loaderOption : `${existing} ${loaderOption}` };
168
+ }
159
169
  /** CLI 在 -p 模式撞到 --max-turns 时的退出码(MAX_TURNS_REACHED)。 */
160
170
  const COMMAND_CODE_MAX_TURNS_EXIT_CODE = 8;
161
171
  /** 发送 SIGINT 后等待 CLI 自己收尾的时间;超时再强制清理进程树。 */
@@ -181,12 +191,7 @@ function commandCodePermissionArgs(permission) {
181
191
  * usage 和原生组件映射全部交给公共消息投影层。
182
192
  */
183
193
  export class CommandCodeDriver {
184
- descriptor = {
185
- id: 'command-code',
186
- name: 'Command Code',
187
- protocol: 'command',
188
- capabilities: ['models', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage', 'history'],
189
- };
194
+ descriptor;
190
195
  /** 驱动自己维护 Provider turn 边界,Host 可以把工具边界映射为 DSH step。 */
191
196
  supportsSegmentedTurns = true;
192
197
  homeDirectory;
@@ -197,6 +202,11 @@ export class CommandCodeDriver {
197
202
  autoContinueMaxAttempts;
198
203
  autoContinuePrompt;
199
204
  history;
205
+ acpDriver;
206
+ respondPermission;
207
+ respondQuestion;
208
+ /** 按规范化 cwd 缓存 Skill 摘要;forceReload 用于响应目录变更。 */
209
+ skillCatalogs = new Map();
200
210
  cachedBinary = null;
201
211
  cachedEnvironment;
202
212
  processes = new Set();
@@ -211,6 +221,36 @@ export class CommandCodeDriver {
211
221
  this.autoContinuePrompt = options.autoContinuePrompt?.trim() || AUTO_CONTINUE_PROMPT;
212
222
  const readStatus = options.readStatus ?? ((workspacePath) => this.readCommandCodeStatus(workspacePath));
213
223
  this.history = new CommandCodeHistory(this.homeDirectory, { readStatus });
224
+ const legacyCapabilities = ['models', 'skills', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage', 'history'];
225
+ const acpCapabilities = [...legacyCapabilities, 'permission', 'questions'];
226
+ this.descriptor = { id: 'command-code', name: 'Command Code', protocol: options.enableAcp === true ? 'acp' : 'command', capabilities: options.enableAcp === true ? acpCapabilities : legacyCapabilities };
227
+ if (options.enableAcp === true) {
228
+ this.acpDriver = new AcpCliDriver({
229
+ binaries: this.binaries,
230
+ spawnSync: this.runSpawnSync,
231
+ spawn: this.runSpawn,
232
+ args: ['acp'],
233
+ buildArgs: (input) => commandCodeAcpArgs(input),
234
+ runtimeModelSelection: false,
235
+ configureSession: configureCommandCodeAcpSession,
236
+ sessionEnvironment: commandCodeAcpEnvironment(),
237
+ id: 'command-code',
238
+ name: 'Command Code',
239
+ capabilities: acpCapabilities,
240
+ readQuestionRequest: readCommandCodeQuestionRequest,
241
+ probeReason: 'Command Code ACP 不公开可安全读取的会话索引',
242
+ });
243
+ this.respondPermission = (sessionId, response) => this.acpDriver.respondPermission(sessionId, response);
244
+ this.respondQuestion = (sessionId, response) => this.acpDriver.respondQuestion(sessionId, response);
245
+ }
246
+ else {
247
+ this.acpDriver = undefined;
248
+ // 保持旧实例的运行时能力声明:未启用 ACP 时不暴露这两个可回写接口。
249
+ this.respondPermission = () => { throw new Error('Command Code ACP 未启用'); };
250
+ this.respondQuestion = () => { throw new Error('Command Code ACP 未启用'); };
251
+ delete this.respondPermission;
252
+ delete this.respondQuestion;
253
+ }
214
254
  }
215
255
  /** 读取 Command Code 原生会话目录,供 Provider History 层使用。 */
216
256
  detectSessions(workspacePath) {
@@ -219,6 +259,43 @@ export class CommandCodeDriver {
219
259
  detectSessionsDetailed(workspacePath) {
220
260
  return this.history.detectSessionsDetailed(workspacePath);
221
261
  }
262
+ /** 读取 Command Code 与 `.agents` 兼容目录中的 Skill 摘要,不返回本地路径。 */
263
+ async listSkills(input) {
264
+ if (input.signal?.aborted)
265
+ throw new Error('Skill 目录读取已取消');
266
+ const command = this.cachedBinary ?? (await this.detect()).command;
267
+ if (command === null)
268
+ return [];
269
+ const cwd = resolve(input.cwd ?? process.cwd());
270
+ const entries = this.readSkillCatalog(cwd, input.forceReload === true);
271
+ return entries.map(({ path: _path, ...descriptor }) => descriptor);
272
+ }
273
+ /** 只为显式 `/skill-name` 或 `$skill-name` 下发匹配的 Skill 目录。 */
274
+ async resolveSkillPaths(input) {
275
+ const names = commandCodeSkillNames(input.prompt);
276
+ if (names.length === 0)
277
+ return [];
278
+ if (input.signal?.aborted)
279
+ throw new Error('Skill 目录读取已取消');
280
+ const entries = this.readSkillCatalog(resolve(input.cwd ?? process.cwd()), false);
281
+ const byName = new Map(entries.map((entry) => [entry.name, entry]));
282
+ const paths = [];
283
+ for (const name of names) {
284
+ const entry = byName.get(name);
285
+ if (entry?.enabled !== true || paths.includes(entry.path))
286
+ continue;
287
+ paths.push(entry.path);
288
+ }
289
+ return paths;
290
+ }
291
+ readSkillCatalog(cwd, forceReload) {
292
+ const cached = this.skillCatalogs.get(cwd);
293
+ if (!forceReload && cached !== undefined)
294
+ return cached;
295
+ const entries = scanCommandCodeSkills(cwd, this.homeDirectory);
296
+ this.skillCatalogs.set(cwd, entries);
297
+ return entries;
298
+ }
222
299
  startSession(workspacePath, options = {}) {
223
300
  return this.history.startSession(workspacePath, options);
224
301
  }
@@ -263,10 +340,8 @@ export class CommandCodeDriver {
263
340
  const direct = this.detectCommand(command);
264
341
  if (direct !== null)
265
342
  return direct;
266
- if (!this.lookupAfterDetectionFailure)
267
- continue;
268
343
  const resolved = resolveCommandPath(command, this.runSpawnSync);
269
- if (resolved === null)
344
+ if (resolved === null || resolved === command)
270
345
  continue;
271
346
  const fallback = this.detectCommand(resolved);
272
347
  if (fallback !== null)
@@ -274,9 +349,7 @@ export class CommandCodeDriver {
274
349
  }
275
350
  return { installed: false, version: null, command: null };
276
351
  }
277
- lookupAfterDetectionFailure = false;
278
352
  detectCommand(command) {
279
- this.lookupAfterDetectionFailure = false;
280
353
  try {
281
354
  const result = this.runSpawnSync(command, ['--version'], { encoding: 'utf8', timeout: 3_000, windowsHide: true, shell: WINDOWS, env: commandEnvironment(command) });
282
355
  const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim();
@@ -286,11 +359,9 @@ export class CommandCodeDriver {
286
359
  this.cachedEnvironment = commandEnvironment(command);
287
360
  return { installed: true, version, command };
288
361
  }
289
- this.lookupAfterDetectionFailure = result.status === null;
290
362
  }
291
363
  catch {
292
364
  // PATH 中不存在候选命令属于正常的未安装状态。
293
- this.lookupAfterDetectionFailure = true;
294
365
  }
295
366
  return null;
296
367
  }
@@ -364,6 +435,8 @@ export class CommandCodeDriver {
364
435
  }
365
436
  }
366
437
  async probeSession(input) {
438
+ if (this.acpDriver !== undefined)
439
+ return this.acpDriver.probeSession(input);
367
440
  throwIfCommandCodeProbeAborted(input.signal);
368
441
  const providerSessionId = input.providerSessionId?.trim();
369
442
  if (!providerSessionId)
@@ -420,11 +493,30 @@ export class CommandCodeDriver {
420
493
  return lastFailure ?? { state: 'missing', reason: 'Command Code 原生会话文件不存在', ...(safeRawStoreRef ? { rawStoreRef: safeRawStoreRef } : {}) };
421
494
  }
422
495
  async *executeTurn(input) {
496
+ const skillPaths = await this.resolveSkillPaths(input);
497
+ const turnInput = skillPaths.length === 0 ? input : { ...input, skillPaths };
498
+ if (this.acpDriver !== undefined) {
499
+ yield* this.acpDriver.executeTurn(turnInput);
500
+ return;
501
+ }
423
502
  const binary = this.cachedBinary ?? (await this.detect()).command;
424
503
  if (binary === null)
425
504
  throw new Error('Command Code 未安装');
426
505
  const segmented = input.splitToolSteps === true;
427
- const turn = segmented ? this.acquireTurn(input, binary) : this.startTurn(input, binary);
506
+ const existing = this.turns.get(input.sessionId);
507
+ const resuming = segmented && input.resumeSegmentedTurn === true && existing !== undefined && !existing.disposed && !existing.finished;
508
+ const prepared = resuming ? undefined : await prepareAttachmentPaths(input);
509
+ let turn;
510
+ try {
511
+ const preparedInput = prepared?.input ?? input;
512
+ turn = segmented ? this.acquireTurn(preparedInput, binary, skillPaths) : this.startTurn(preparedInput, binary, skillPaths);
513
+ if (prepared !== undefined)
514
+ turn.attachmentCleanup = prepared.cleanup;
515
+ }
516
+ catch (error) {
517
+ prepared?.cleanup();
518
+ throw error;
519
+ }
428
520
  let suspended = false;
429
521
  const onAbort = () => { this.requestGracefulStop(turn); };
430
522
  input.signal?.addEventListener('abort', onAbort, { once: true });
@@ -442,12 +534,14 @@ export class CommandCodeDriver {
442
534
  }
443
535
  }
444
536
  dispose() {
537
+ this.acpDriver?.dispose();
445
538
  for (const turn of [...this.turns.values()])
446
539
  this.disposeTurn(turn);
447
540
  this.turns.clear();
448
541
  for (const child of this.processes)
449
542
  terminateChildProcess(child);
450
543
  this.processes.clear();
544
+ this.skillCatalogs.clear();
451
545
  this.cachedBinary = null;
452
546
  }
453
547
  /** 丢弃等待下一个 DSH step 的 `-p` 运行,避免旧进程继续占用新一轮输出。 */
@@ -457,18 +551,18 @@ export class CommandCodeDriver {
457
551
  this.disposeTurn(turn);
458
552
  }
459
553
  /** 只有 Host 显式声明续段时才复用常驻进程;新的用户回合必须重新启动。 */
460
- acquireTurn(input, binary) {
554
+ acquireTurn(input, binary, skillPaths) {
461
555
  const existing = this.turns.get(input.sessionId);
462
556
  if (existing !== undefined) {
463
557
  if (input.resumeSegmentedTurn === true && !existing.disposed && !existing.finished)
464
558
  return existing;
465
559
  this.disposeTurn(existing);
466
560
  }
467
- const turn = this.startTurn(input, binary);
561
+ const turn = this.startTurn(input, binary, skillPaths);
468
562
  this.turns.set(input.sessionId, turn);
469
563
  return turn;
470
564
  }
471
- startTurn(input, binary) {
565
+ startTurn(input, binary, skillPaths) {
472
566
  const cwd = input.cwd ?? process.cwd();
473
567
  const requestedProviderSessionId = input.providerSessionId?.trim() || undefined;
474
568
  // 旧 synthetic ID 可能只对应 checkpoint;回退时必须以当前 DSH 会话 ID
@@ -497,6 +591,7 @@ export class CommandCodeDriver {
497
591
  transcriptPath,
498
592
  providerSessionId,
499
593
  rawStoreRef: canResume ? input.rawStoreRef?.trim() || undefined : transcriptPath,
594
+ skillPaths,
500
595
  cwd,
501
596
  queue: createEventQueue(),
502
597
  currentMessageId: undefined,
@@ -616,6 +711,8 @@ export class CommandCodeDriver {
616
711
  if (input.enableAskUserQuestion === true || input.runtimeEnv?.CMD_TOOLS_ASK_USER_QUESTION_ENABLE === 'true') {
617
712
  args.push('--tools-enable', 'ask_user_question');
618
713
  }
714
+ for (const path of turn.skillPaths)
715
+ args.push('--skill', path);
619
716
  // 子代理托管开启时加载桥接 mod:内建 agent 调用会被转投成 DSH 原生子会话。
620
717
  args.push(...commandCodeBridgeArgs(input.sessionId));
621
718
  for (const directory of new Set((input.attachments ?? []).map((attachment) => dirname(attachment.path))))
@@ -629,6 +726,10 @@ export class CommandCodeDriver {
629
726
  }
630
727
  /** 中断当前会话并清理 CLI 进程;不存在活动运行时视为幂等成功。 */
631
728
  async interrupt(sessionId) {
729
+ if (this.acpDriver !== undefined) {
730
+ await this.acpDriver.interrupt(sessionId);
731
+ return;
732
+ }
632
733
  const turn = this.turns.get(sessionId);
633
734
  if (turn === undefined)
634
735
  return;
@@ -806,6 +907,7 @@ export class CommandCodeDriver {
806
907
  this.processes.delete(child);
807
908
  terminateChildProcess(child);
808
909
  }
910
+ turn.attachmentCleanup?.();
809
911
  // 首轮 transcript 已经是 canonical 文件,必须保留给下一轮 --resume 及冷恢复。
810
912
  }
811
913
  }
@@ -1024,10 +1126,20 @@ function commandCodeEventChunks(event, state) {
1024
1126
  }
1025
1127
  else if (isToolResult(type)) {
1026
1128
  // 子代理托管命中时,mod 用 block 结束内建 agent 调用,真正的执行已经发生在
1027
- // DSH 原生子会话里;这里必须投影成完成态,而不是普通 hook 拦截的失败态。
1028
- const redirected = type.includes('blocked') && consumeBridgeRedirect(state, event);
1029
- const failed = !redirected && (type.includes('error') || type.includes('fail') || type.includes('denied') || type.includes('blocked'));
1030
- const tool = readToolChunk(event, failed ? 'failed' : 'completed');
1129
+ // DSH 原生子会话里;这里必须投影为桥接返回的 running/completed/failed 状态,
1130
+ // 而不是把后台创建误报为完成或把普通 hook 拦截误报为托管成功。
1131
+ const redirected = type.includes('blocked') ? consumeBridgeRedirect(state, event) : undefined;
1132
+ const failed = redirected === undefined && (type.includes('error') || type.includes('fail') || type.includes('denied') || type.includes('blocked'));
1133
+ // 后台托管的 hook_blocked 只代表“已创建子会话”。如果这里投影成 completed,
1134
+ // 父 Agent 会跳过 wait/read,子会话随后失败也就没有机会回到父会话。
1135
+ const redirectedStatus = redirected === undefined
1136
+ ? undefined
1137
+ : redirected.completed === false || redirected.status === 'running'
1138
+ ? 'running'
1139
+ : redirected.ok && redirected.status !== 'failed' && redirected.status !== 'interrupted'
1140
+ ? 'completed'
1141
+ : 'failed';
1142
+ const tool = readToolChunk(event, redirectedStatus ?? (failed ? 'failed' : 'completed'));
1031
1143
  if (tool !== null) {
1032
1144
  chunks.push(tool);
1033
1145
  // 工具完成后的正文属于下一条 assistant 消息。即使 CLI 没有发送
@@ -1051,15 +1163,15 @@ function readFinalText(event) {
1051
1163
  /** 该 blocked 事件是否来自桥接转投;命中后消费一次记录,避免重复投影。 */
1052
1164
  function consumeBridgeRedirect(state, event) {
1053
1165
  if (state.bridgeSessionId === '')
1054
- return false;
1166
+ return undefined;
1055
1167
  const callId = firstToolText(event.callId, event.call_id, event.toolCallId, event.tool_call_id, event.toolUseId, event.tool_use_id, event.id);
1056
1168
  if (callId === undefined || callId === '')
1057
- return false;
1169
+ return undefined;
1058
1170
  try {
1059
- return getSubagentBridge()?.consumeRedirect(state.bridgeSessionId, callId) !== undefined;
1171
+ return getSubagentBridge()?.consumeRedirect(state.bridgeSessionId, callId);
1060
1172
  }
1061
1173
  catch {
1062
- return false;
1174
+ return undefined;
1063
1175
  }
1064
1176
  }
1065
1177
  function beginMessage(state) {
@@ -1362,6 +1474,200 @@ function workspaceSlug(workspacePath) {
1362
1474
  .replace(/^-+|-+$/g, '')
1363
1475
  .toLowerCase();
1364
1476
  }
1477
+ /** 从用户文本提取显式 Skill mention,和 Codex 的 `/name`、`$name` 语义保持一致。 */
1478
+ function commandCodeSkillNames(prompt) {
1479
+ const names = [];
1480
+ const seen = new Set();
1481
+ for (const match of prompt.matchAll(/(?:^|\s)(?:\$|\/)([A-Za-z0-9][A-Za-z0-9._-]*)(?=\s|$)/gu)) {
1482
+ const name = match[1]?.trim();
1483
+ if (name === undefined || name === '' || seen.has(name))
1484
+ continue;
1485
+ seen.add(name);
1486
+ names.push(name);
1487
+ }
1488
+ return names;
1489
+ }
1490
+ /**
1491
+ * 按 Command Code 的优先级扫描项目级、用户级和额外 Skill 根目录。
1492
+ *
1493
+ * 只把合法的 `SKILL.md` 摘要交给 Client;绝对路径保留在 Host,供显式 mention
1494
+ * 通过 `--skill` 下发。重名时高优先级目录胜出,避免同一 Skill 被重复注入。
1495
+ */
1496
+ function scanCommandCodeSkills(cwd, homeDirectory) {
1497
+ const projectRoot = commandCodeProjectRoot(cwd);
1498
+ const userHome = basename(homeDirectory) === '.commandcode' ? dirname(homeDirectory) : homeDirectory;
1499
+ const disabledSkills = readCommandCodeDisabledSkills(projectRoot, homeDirectory);
1500
+ const roots = [
1501
+ join(projectRoot, '.commandcode', 'skills'),
1502
+ ...commandCodeProjectAgentsSkillRoots(cwd, userHome),
1503
+ join(homeDirectory, 'skills'),
1504
+ join(userHome, '.agents', 'skills'),
1505
+ ...readCommandCodeConfiguredSkillRoots(projectRoot, homeDirectory),
1506
+ ];
1507
+ const entries = [];
1508
+ const seenNames = new Set();
1509
+ const visitedDirectories = new Set();
1510
+ for (const root of roots)
1511
+ scanCommandCodeSkillRoot(root, entries, seenNames, visitedDirectories, disabledSkills);
1512
+ return entries;
1513
+ }
1514
+ /** Command Code 会从当前目录向上查找项目 `.agents/skills`,最多跨十级。 */
1515
+ function commandCodeProjectAgentsSkillRoots(cwd, userHome) {
1516
+ const roots = [];
1517
+ let current = resolve(cwd);
1518
+ for (let depth = 0; depth <= 10; depth += 1) {
1519
+ if (current === resolve(userHome))
1520
+ break;
1521
+ roots.push(join(current, '.agents', 'skills'));
1522
+ const parent = dirname(current);
1523
+ if (parent === current)
1524
+ break;
1525
+ current = parent;
1526
+ }
1527
+ return roots;
1528
+ }
1529
+ function commandCodeProjectRoot(cwd) {
1530
+ let current = resolve(cwd);
1531
+ const fallback = current;
1532
+ while (true) {
1533
+ if (existsSync(join(current, '.git')))
1534
+ return current;
1535
+ const parent = dirname(current);
1536
+ if (parent === current)
1537
+ return fallback;
1538
+ current = parent;
1539
+ }
1540
+ }
1541
+ function readCommandCodeConfiguredSkillRoots(projectRoot, homeDirectory) {
1542
+ const settingsLayers = [
1543
+ { path: join(projectRoot, '.commandcode', 'settings.local.json'), base: projectRoot },
1544
+ { path: join(projectRoot, '.commandcode', 'settings.json'), base: projectRoot },
1545
+ { path: join(homeDirectory, 'settings.json'), base: basename(homeDirectory) === '.commandcode' ? dirname(homeDirectory) : homeDirectory },
1546
+ ];
1547
+ const layer = settingsLayers.find(({ path }) => Array.isArray(readJson(path)?.skills));
1548
+ if (layer === undefined)
1549
+ return [];
1550
+ const settings = readJson(layer.path);
1551
+ if (!Array.isArray(settings?.skills))
1552
+ return [];
1553
+ const roots = [];
1554
+ for (const value of settings.skills) {
1555
+ if (typeof value !== 'string' || value.trim() === '')
1556
+ continue;
1557
+ const configured = value.trim();
1558
+ const userHome = basename(homeDirectory) === '.commandcode' ? dirname(homeDirectory) : homeDirectory;
1559
+ roots.push(configured.startsWith('~/')
1560
+ ? resolve(userHome, configured.slice(2))
1561
+ : resolve(layer.base, configured));
1562
+ }
1563
+ return roots;
1564
+ }
1565
+ function scanCommandCodeSkillRoot(root, entries, seenNames, visitedDirectories, disabledSkills) {
1566
+ if (!existsSync(root))
1567
+ return;
1568
+ let canonicalRoot;
1569
+ try {
1570
+ if (!statSync(root).isDirectory())
1571
+ return;
1572
+ canonicalRoot = realpathSync(root);
1573
+ }
1574
+ catch {
1575
+ return;
1576
+ }
1577
+ const walk = (directory, depth) => {
1578
+ if (depth > 32)
1579
+ return;
1580
+ let canonicalDirectory;
1581
+ try {
1582
+ canonicalDirectory = realpathSync(directory);
1583
+ }
1584
+ catch {
1585
+ return;
1586
+ }
1587
+ if (visitedDirectories.has(canonicalDirectory))
1588
+ return;
1589
+ visitedDirectories.add(canonicalDirectory);
1590
+ const ownSkillFile = join(canonicalDirectory, 'SKILL.md');
1591
+ try {
1592
+ if (statSync(ownSkillFile).isFile()) {
1593
+ const entry = parseCommandCodeSkillFile(ownSkillFile);
1594
+ if (entry !== null && !seenNames.has(entry.name)) {
1595
+ seenNames.add(entry.name);
1596
+ entries.push(disabledSkills.has(entry.name) ? { ...entry, enabled: false } : entry);
1597
+ }
1598
+ return;
1599
+ }
1600
+ }
1601
+ catch {
1602
+ // 当前目录不是 Skill 目录,继续检查可能的分组子目录。
1603
+ }
1604
+ let children;
1605
+ try {
1606
+ children = readdirSync(canonicalDirectory, { withFileTypes: true });
1607
+ }
1608
+ catch {
1609
+ return;
1610
+ }
1611
+ for (const child of children) {
1612
+ if (child.name === '.git' || child.name === 'node_modules')
1613
+ continue;
1614
+ const childPath = join(canonicalDirectory, child.name);
1615
+ if (child.isDirectory()) {
1616
+ walk(childPath, depth + 1);
1617
+ }
1618
+ }
1619
+ };
1620
+ // 首次遍历也使用规范路径,确保额外目录的符号链接遵守去重规则。
1621
+ walk(canonicalRoot, 0);
1622
+ }
1623
+ /** disabledSkills 是用户设置与项目设置的并集,保持 Command Code 的禁用语义。 */
1624
+ function readCommandCodeDisabledSkills(projectRoot, homeDirectory) {
1625
+ const disabled = new Set();
1626
+ const paths = [
1627
+ join(homeDirectory, 'settings.json'),
1628
+ join(projectRoot, '.commandcode', 'settings.json'),
1629
+ join(projectRoot, '.commandcode', 'settings.local.json'),
1630
+ ];
1631
+ for (const path of paths) {
1632
+ const settings = readJson(path);
1633
+ if (!Array.isArray(settings?.disabledSkills))
1634
+ continue;
1635
+ for (const value of settings.disabledSkills) {
1636
+ if (typeof value === 'string' && /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(value.trim()))
1637
+ disabled.add(value.trim());
1638
+ }
1639
+ }
1640
+ return disabled;
1641
+ }
1642
+ function parseCommandCodeSkillFile(path) {
1643
+ let source;
1644
+ try {
1645
+ source = readFileSync(path, 'utf8');
1646
+ }
1647
+ catch {
1648
+ return null;
1649
+ }
1650
+ const match = source.match(/^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u);
1651
+ if (match === null)
1652
+ return null;
1653
+ const fields = parseSkillFrontmatter(match[1] ?? '');
1654
+ if (fields === null)
1655
+ return null;
1656
+ const name = typeof fields.name === 'string' ? fields.name.trim() : '';
1657
+ const description = typeof fields.description === 'string' ? fields.description.trim() : '';
1658
+ const directoryName = basename(dirname(path));
1659
+ if (name.length > 64 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name)
1660
+ || name !== directoryName
1661
+ || description === '')
1662
+ return null;
1663
+ return {
1664
+ id: name,
1665
+ name,
1666
+ description,
1667
+ enabled: fields['user-invocable'] !== false,
1668
+ path: dirname(path),
1669
+ };
1670
+ }
1365
1671
  function buildMaxTurnsReachedMessage(autoContinueCount, maxTurns) {
1366
1672
  const autoContinueSuffix = autoContinueCount > 0
1367
1673
  ? `,自动续跑 ${autoContinueCount} 次后仍未完成`
@@ -1519,6 +1825,122 @@ function readJson(path) { if (!existsSync(path))
1519
1825
  catch {
1520
1826
  return null;
1521
1827
  } }
1828
+ /** 保留启动选择作为进程复用条件;ACP 的实际会话状态由原生协议显式设置。 */
1829
+ function commandCodeAcpArgs(input) {
1830
+ const args = ['acp', ...commandCodePermissionArgs(input.permission)];
1831
+ if (input.plan === true && !args.includes('--plan'))
1832
+ args.push('--plan');
1833
+ if (input.enableAskUserQuestion === true || input.runtimeEnv?.CMD_TOOLS_ASK_USER_QUESTION_ENABLE === 'true') {
1834
+ args.push('--tools-enable', 'ask_user_question');
1835
+ }
1836
+ for (const path of input.skillPaths ?? [])
1837
+ args.push('--skill', path);
1838
+ if (input.modelId !== undefined && input.modelId !== 'provider-default')
1839
+ args.push('--model', input.modelId);
1840
+ const effort = input.effortId?.trim().toLowerCase();
1841
+ if (effort !== undefined && VALID_EFFORTS.has(effort))
1842
+ args.push('--effort', effort);
1843
+ return args;
1844
+ }
1845
+ /**
1846
+ * Command Code 1.74.3 的 acp 子命令不执行普通 CLI action 的参数初始化。
1847
+ * 仅传 --model/--effort/--permission-mode 会被静默忽略,恢复的会话还会沿用旧模型。
1848
+ * 必须在 prompt 前设置线程状态,且不能吞掉模型或权限设置失败。
1849
+ */
1850
+ async function configureCommandCodeAcpSession(rpc, sessionId, input) {
1851
+ const requestOptions = { signal: input.signal, killOnAbort: false };
1852
+ if (input.modelId !== undefined && input.modelId !== 'provider-default') {
1853
+ await rpc.request('session/set_model', { sessionId, modelId: input.modelId }, requestOptions);
1854
+ }
1855
+ await rpc.request('session/set_mode', { sessionId, modeId: commandCodeAcpMode(input) }, requestOptions);
1856
+ const effort = input.effortId?.trim().toLowerCase();
1857
+ if (effort !== undefined && VALID_EFFORTS.has(effort)) {
1858
+ await rpc.request('session/set_config_option', { sessionId, configId: 'effort', value: effort }, requestOptions);
1859
+ }
1860
+ }
1861
+ /** ACP 使用权限引擎的模式 ID,与普通 CLI 的 accept-edits/yolo 别名不同。 */
1862
+ function commandCodeAcpMode(input) {
1863
+ const permission = input.permission;
1864
+ if (input.plan === true || permission?.sandboxMode === 'read-only')
1865
+ return 'plan';
1866
+ if (permission?.approvalPolicy !== 'never')
1867
+ return 'default';
1868
+ if (permission.sandboxMode === 'danger-full-access')
1869
+ return 'bypass';
1870
+ if (permission.sandboxMode === 'workspace-write')
1871
+ return 'auto-accept';
1872
+ return 'default';
1873
+ }
1874
+ /** Command Code 将 ask_user_question 编译成 `session/request_permission`。 */
1875
+ function readCommandCodeQuestionRequest(message) {
1876
+ const method = message.method?.toLowerCase();
1877
+ if (method !== 'session/request_permission' || (typeof message.id !== 'string' && typeof message.id !== 'number'))
1878
+ return null;
1879
+ const params = isRecord(message.params) ? message.params : {};
1880
+ const tool = isRecord(params.toolCall) ? params.toolCall : isRecord(params.tool_call) ? params.tool_call : {};
1881
+ const kind = firstToolText(tool.kind, params.kind)?.toLowerCase();
1882
+ const toolName = firstToolText(tool.name, tool.toolName, tool.tool_name);
1883
+ if (kind !== 'other' && toolName !== 'ask_user_question')
1884
+ return null;
1885
+ const rawInput = isRecord(tool.rawInput) ? tool.rawInput : isRecord(tool.raw_input) ? tool.raw_input : {};
1886
+ const questionText = firstToolText(rawInput.question, rawInput.prompt, params.question, params.prompt);
1887
+ const rawOptions = Array.isArray(rawInput.options)
1888
+ ? rawInput.options
1889
+ : Array.isArray(params.questionOptions)
1890
+ ? params.questionOptions
1891
+ : [];
1892
+ if (questionText === undefined || rawOptions.length === 0)
1893
+ return null;
1894
+ const options = rawOptions.flatMap((rawOption) => {
1895
+ if (typeof rawOption === 'string' && rawOption.trim() !== '')
1896
+ return [{ label: rawOption.trim() }];
1897
+ if (!isRecord(rawOption))
1898
+ return [];
1899
+ const label = firstToolText(rawOption.label, rawOption.name, rawOption.title, rawOption.value);
1900
+ if (label === undefined)
1901
+ return [];
1902
+ const description = firstToolText(rawOption.description, rawOption.detail);
1903
+ return [{ label, ...(description === undefined ? {} : { description }) }];
1904
+ });
1905
+ if (options.length === 0)
1906
+ return null;
1907
+ const protocolOptions = Array.isArray(params.options) ? params.options : [];
1908
+ const optionIds = options.map((_, index) => {
1909
+ const protocolOption = isRecord(protocolOptions[index]) ? protocolOptions[index] : {};
1910
+ return firstToolText(protocolOption.optionId, protocolOption.option_id, protocolOption.id) ?? `option_${index}`;
1911
+ });
1912
+ const requestId = String(message.id);
1913
+ const questionId = `command-code-question-${requestId}`;
1914
+ const header = firstToolText(rawInput.header, params.header);
1915
+ return {
1916
+ requestId,
1917
+ rpcId: message.id,
1918
+ questions: [{
1919
+ id: questionId,
1920
+ question: questionText,
1921
+ ...(header === undefined ? {} : { header }),
1922
+ options,
1923
+ }],
1924
+ respond: (response) => {
1925
+ const answer = response.answers.find((item) => item.id === questionId) ?? response.answers[0];
1926
+ const custom = answer?.custom?.trim() ?? '';
1927
+ const selected = answer?.selected[0]?.trim() ?? '';
1928
+ const selectedIndex = options.findIndex((option) => option.label === selected);
1929
+ if (custom !== '') {
1930
+ // `optionId` 使用保留值,未加载兼容桥时会安全地取消,不会误选第一项。
1931
+ // loader 会从 _meta 读取原文,并把它作为 ask_user_question 的工具答案。
1932
+ return {
1933
+ outcome: { outcome: 'selected', optionId: '__codingns_custom__' },
1934
+ answers: [{ questionIndex: 0, selectedOptions: [custom] }],
1935
+ _meta: { 'codingns/questionAnswer': custom },
1936
+ };
1937
+ }
1938
+ if (selected === '' || selectedIndex < 0)
1939
+ return { outcome: { outcome: 'cancelled' } };
1940
+ return { outcome: { outcome: 'selected', optionId: optionIds[selectedIndex] } };
1941
+ },
1942
+ };
1943
+ }
1522
1944
  function isRecord(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); }
1523
1945
  function emptyCatalog() { return { groups: [], currentModel: null, currentEffort: null }; }
1524
1946
  //# sourceMappingURL=command-code-driver.js.map