@jingyi0605/codingns4dsh 0.2.1-beta.2 → 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 (156) hide show
  1. package/README.en.md +8 -8
  2. package/README.md +11 -9
  3. package/data/build/dist/client/bundle.js +169 -35
  4. package/data/build/dist/client/bundle.js.map +1 -1
  5. package/data/build/dist/client/locales/peerHostWorkspace.d.ts.map +1 -1
  6. package/data/build/dist/client/locales/peerHostWorkspace.js +16 -2
  7. package/data/build/dist/client/locales/peerHostWorkspace.js.map +1 -1
  8. package/data/build/dist/client/peer-host-native-projection.d.ts +4 -3
  9. package/data/build/dist/client/peer-host-native-projection.d.ts.map +1 -1
  10. package/data/build/dist/client/peer-host-native-projection.js +7 -6
  11. package/data/build/dist/client/peer-host-native-projection.js.map +1 -1
  12. package/data/build/dist/client/peer-host-native-store-projection.js +9 -21
  13. package/data/build/dist/client/peer-host-native-store-projection.js.map +1 -1
  14. package/data/build/dist/client/peer-host-workspace-tab.d.ts.map +1 -1
  15. package/data/build/dist/client/peer-host-workspace-tab.js +139 -10
  16. package/data/build/dist/client/peer-host-workspace-tab.js.map +1 -1
  17. package/data/build/dist/client/skill-command.d.ts +1 -1
  18. package/data/build/dist/client/skill-command.d.ts.map +1 -1
  19. package/data/build/dist/client/skill-command.js +25 -2
  20. package/data/build/dist/client/skill-command.js.map +1 -1
  21. package/data/build/dist/client/skill-reference-dom.js +1 -1
  22. package/data/build/dist/client/skill-reference-dom.js.map +1 -1
  23. package/data/build/dist/client/terminal/styles.js +2 -2
  24. package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.d.ts +28 -0
  25. package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.d.ts.map +1 -0
  26. package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.js +109 -0
  27. package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.js.map +1 -0
  28. package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts +18 -1
  29. package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts.map +1 -1
  30. package/data/build/dist/host/cli-adapters/acp-cli-driver.js +26 -8
  31. package/data/build/dist/host/cli-adapters/acp-cli-driver.js.map +1 -1
  32. package/data/build/dist/host/cli-adapters/attachment-utils.d.ts +16 -5
  33. package/data/build/dist/host/cli-adapters/attachment-utils.d.ts.map +1 -1
  34. package/data/build/dist/host/cli-adapters/attachment-utils.js +152 -42
  35. package/data/build/dist/host/cli-adapters/attachment-utils.js.map +1 -1
  36. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +14 -8
  37. package/data/build/dist/host/cli-adapters/claude-driver.d.ts.map +1 -1
  38. package/data/build/dist/host/cli-adapters/claude-driver.js +101 -76
  39. package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
  40. package/data/build/dist/host/cli-adapters/codex-driver.d.ts.map +1 -1
  41. package/data/build/dist/host/cli-adapters/codex-driver.js +74 -6
  42. package/data/build/dist/host/cli-adapters/codex-driver.js.map +1 -1
  43. package/data/build/dist/host/cli-adapters/command-code-acp-loader.d.ts +23 -0
  44. package/data/build/dist/host/cli-adapters/command-code-acp-loader.d.ts.map +1 -0
  45. package/data/build/dist/host/cli-adapters/command-code-acp-loader.js +39 -0
  46. package/data/build/dist/host/cli-adapters/command-code-acp-loader.js.map +1 -0
  47. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +17 -6
  48. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts.map +1 -1
  49. package/data/build/dist/host/cli-adapters/command-code-driver.js +430 -20
  50. package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
  51. package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts +1 -1
  52. package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts.map +1 -1
  53. package/data/build/dist/host/cli-adapters/command-code-subscription.js +7 -4
  54. package/data/build/dist/host/cli-adapters/command-code-subscription.js.map +1 -1
  55. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts.map +1 -1
  56. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +28 -0
  57. package/data/build/dist/host/cli-adapters/dsh-message-projector.js.map +1 -1
  58. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts.map +1 -1
  59. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +23 -1
  60. package/data/build/dist/host/cli-adapters/dsh-tool-history.js.map +1 -1
  61. package/data/build/dist/host/cli-adapters/feature.js +1 -1
  62. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  63. package/data/build/dist/host/cli-adapters/grok-driver.d.ts +4 -2
  64. package/data/build/dist/host/cli-adapters/grok-driver.d.ts.map +1 -1
  65. package/data/build/dist/host/cli-adapters/grok-driver.js +208 -9
  66. package/data/build/dist/host/cli-adapters/grok-driver.js.map +1 -1
  67. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +2 -0
  68. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts.map +1 -1
  69. package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -3
  70. package/data/build/dist/host/cli-adapters/json-rpc-process.js.map +1 -1
  71. package/data/build/dist/host/cli-adapters/kimi-driver.d.ts +4 -0
  72. package/data/build/dist/host/cli-adapters/kimi-driver.d.ts.map +1 -1
  73. package/data/build/dist/host/cli-adapters/kimi-driver.js +11 -4
  74. package/data/build/dist/host/cli-adapters/kimi-driver.js.map +1 -1
  75. package/data/build/dist/host/cli-adapters/mcode-driver.js +2 -2
  76. package/data/build/dist/host/cli-adapters/mcode-driver.js.map +1 -1
  77. package/data/build/dist/host/cli-adapters/new-api-subscription.d.ts.map +1 -1
  78. package/data/build/dist/host/cli-adapters/new-api-subscription.js +11 -58
  79. package/data/build/dist/host/cli-adapters/new-api-subscription.js.map +1 -1
  80. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +5 -2
  81. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts.map +1 -1
  82. package/data/build/dist/host/cli-adapters/opencode-driver.js +136 -18
  83. package/data/build/dist/host/cli-adapters/opencode-driver.js.map +1 -1
  84. package/data/build/dist/host/cli-adapters/pi-driver.d.ts.map +1 -1
  85. package/data/build/dist/host/cli-adapters/pi-driver.js +2 -1
  86. package/data/build/dist/host/cli-adapters/pi-driver.js.map +1 -1
  87. package/data/build/dist/host/cli-adapters/process-utils.d.ts +10 -0
  88. package/data/build/dist/host/cli-adapters/process-utils.d.ts.map +1 -1
  89. package/data/build/dist/host/cli-adapters/process-utils.js +20 -0
  90. package/data/build/dist/host/cli-adapters/process-utils.js.map +1 -1
  91. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +3 -0
  92. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts.map +1 -1
  93. package/data/build/dist/host/cli-adapters/provider-subscription.js +42 -8
  94. package/data/build/dist/host/cli-adapters/provider-subscription.js.map +1 -1
  95. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  96. package/data/build/dist/host/cli-adapters/registry.js +5 -3
  97. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  98. package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts.map +1 -1
  99. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +2 -9
  100. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js.map +1 -1
  101. package/data/build/dist/host/cli-adapters/skill-filesystem.d.ts +25 -0
  102. package/data/build/dist/host/cli-adapters/skill-filesystem.d.ts.map +1 -0
  103. package/data/build/dist/host/cli-adapters/skill-filesystem.js +161 -0
  104. package/data/build/dist/host/cli-adapters/skill-filesystem.js.map +1 -0
  105. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +1 -0
  106. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts.map +1 -1
  107. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +4 -0
  108. package/data/build/dist/host/cli-adapters/standard-stream-driver.js.map +1 -1
  109. package/data/build/dist/host/cli-adapters/zcode-driver.d.ts +8 -2
  110. package/data/build/dist/host/cli-adapters/zcode-driver.d.ts.map +1 -1
  111. package/data/build/dist/host/cli-adapters/zcode-driver.js +166 -55
  112. package/data/build/dist/host/cli-adapters/zcode-driver.js.map +1 -1
  113. package/data/build/dist/host/cli-adapters/zcode-interactions.d.ts +18 -0
  114. package/data/build/dist/host/cli-adapters/zcode-interactions.d.ts.map +1 -0
  115. package/data/build/dist/host/cli-adapters/zcode-interactions.js +90 -0
  116. package/data/build/dist/host/cli-adapters/zcode-interactions.js.map +1 -0
  117. package/data/build/dist/host/cli-adapters/zcode-provider-config.d.ts +26 -0
  118. package/data/build/dist/host/cli-adapters/zcode-provider-config.d.ts.map +1 -0
  119. package/data/build/dist/host/cli-adapters/zcode-provider-config.js +102 -0
  120. package/data/build/dist/host/cli-adapters/zcode-provider-config.js.map +1 -0
  121. package/data/build/dist/host/cli-adapters/zcode-subscription.d.ts +2 -7
  122. package/data/build/dist/host/cli-adapters/zcode-subscription.d.ts.map +1 -1
  123. package/data/build/dist/host/cli-adapters/zcode-subscription.js.map +1 -1
  124. package/data/build/dist/host/cli-adapters/zcode-telemetry.d.ts +17 -0
  125. package/data/build/dist/host/cli-adapters/zcode-telemetry.d.ts.map +1 -0
  126. package/data/build/dist/host/cli-adapters/zcode-telemetry.js +124 -0
  127. package/data/build/dist/host/cli-adapters/zcode-telemetry.js.map +1 -0
  128. package/data/build/dist/host/cli-bridge/injections.d.ts +2 -9
  129. package/data/build/dist/host/cli-bridge/injections.d.ts.map +1 -1
  130. package/data/build/dist/host/cli-bridge/injections.js +7 -17
  131. package/data/build/dist/host/cli-bridge/injections.js.map +1 -1
  132. package/data/build/dist/host/features/peer-host.d.ts.map +1 -1
  133. package/data/build/dist/host/features/peer-host.js +23 -0
  134. package/data/build/dist/host/features/peer-host.js.map +1 -1
  135. package/data/build/dist/host/index.d.ts.map +1 -1
  136. package/data/build/dist/host/index.js +3 -0
  137. package/data/build/dist/host/index.js.map +1 -1
  138. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.d.ts +2 -0
  139. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.d.ts.map +1 -1
  140. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js +5 -0
  141. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js.map +1 -1
  142. package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.d.ts.map +1 -1
  143. package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js +3 -1
  144. package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js.map +1 -1
  145. package/data/build/dist/host/rpc.d.ts.map +1 -1
  146. package/data/build/dist/host/rpc.js +5 -0
  147. package/data/build/dist/host/rpc.js.map +1 -1
  148. package/data/build/dist/shared/contracts/cli-adapter.d.ts +4 -0
  149. package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
  150. package/data/build/dist/shared/contracts/version.d.ts +1 -1
  151. package/data/build/dist/shared/contracts/version.js +1 -1
  152. package/package.json +3 -1
  153. package/profile/README.md +2 -2
  154. package/profile/package.json +2 -2
  155. package/profile/version.json +1 -1
  156. 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,14 +191,7 @@ function commandCodePermissionArgs(permission) {
181
191
  * usage 和原生组件映射全部交给公共消息投影层。
182
192
  */
183
193
  export class CommandCodeDriver {
184
- // Command Code 的公开 json 输出只有结果事件;没有 DSH 可回写的权限或问题 wire。
185
- // `--tools-enable ask_user_question` 只是 CLI 内部工具开关,不能伪造为 question-request。
186
- descriptor = {
187
- id: 'command-code',
188
- name: 'Command Code',
189
- protocol: 'command',
190
- capabilities: ['models', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage', 'history'],
191
- };
194
+ descriptor;
192
195
  /** 驱动自己维护 Provider turn 边界,Host 可以把工具边界映射为 DSH step。 */
193
196
  supportsSegmentedTurns = true;
194
197
  homeDirectory;
@@ -199,6 +202,11 @@ export class CommandCodeDriver {
199
202
  autoContinueMaxAttempts;
200
203
  autoContinuePrompt;
201
204
  history;
205
+ acpDriver;
206
+ respondPermission;
207
+ respondQuestion;
208
+ /** 按规范化 cwd 缓存 Skill 摘要;forceReload 用于响应目录变更。 */
209
+ skillCatalogs = new Map();
202
210
  cachedBinary = null;
203
211
  cachedEnvironment;
204
212
  processes = new Set();
@@ -213,6 +221,36 @@ export class CommandCodeDriver {
213
221
  this.autoContinuePrompt = options.autoContinuePrompt?.trim() || AUTO_CONTINUE_PROMPT;
214
222
  const readStatus = options.readStatus ?? ((workspacePath) => this.readCommandCodeStatus(workspacePath));
215
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
+ }
216
254
  }
217
255
  /** 读取 Command Code 原生会话目录,供 Provider History 层使用。 */
218
256
  detectSessions(workspacePath) {
@@ -221,6 +259,43 @@ export class CommandCodeDriver {
221
259
  detectSessionsDetailed(workspacePath) {
222
260
  return this.history.detectSessionsDetailed(workspacePath);
223
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
+ }
224
299
  startSession(workspacePath, options = {}) {
225
300
  return this.history.startSession(workspacePath, options);
226
301
  }
@@ -265,10 +340,8 @@ export class CommandCodeDriver {
265
340
  const direct = this.detectCommand(command);
266
341
  if (direct !== null)
267
342
  return direct;
268
- if (!this.lookupAfterDetectionFailure)
269
- continue;
270
343
  const resolved = resolveCommandPath(command, this.runSpawnSync);
271
- if (resolved === null)
344
+ if (resolved === null || resolved === command)
272
345
  continue;
273
346
  const fallback = this.detectCommand(resolved);
274
347
  if (fallback !== null)
@@ -276,9 +349,7 @@ export class CommandCodeDriver {
276
349
  }
277
350
  return { installed: false, version: null, command: null };
278
351
  }
279
- lookupAfterDetectionFailure = false;
280
352
  detectCommand(command) {
281
- this.lookupAfterDetectionFailure = false;
282
353
  try {
283
354
  const result = this.runSpawnSync(command, ['--version'], { encoding: 'utf8', timeout: 3_000, windowsHide: true, shell: WINDOWS, env: commandEnvironment(command) });
284
355
  const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim();
@@ -288,11 +359,9 @@ export class CommandCodeDriver {
288
359
  this.cachedEnvironment = commandEnvironment(command);
289
360
  return { installed: true, version, command };
290
361
  }
291
- this.lookupAfterDetectionFailure = result.status === null;
292
362
  }
293
363
  catch {
294
364
  // PATH 中不存在候选命令属于正常的未安装状态。
295
- this.lookupAfterDetectionFailure = true;
296
365
  }
297
366
  return null;
298
367
  }
@@ -366,6 +435,8 @@ export class CommandCodeDriver {
366
435
  }
367
436
  }
368
437
  async probeSession(input) {
438
+ if (this.acpDriver !== undefined)
439
+ return this.acpDriver.probeSession(input);
369
440
  throwIfCommandCodeProbeAborted(input.signal);
370
441
  const providerSessionId = input.providerSessionId?.trim();
371
442
  if (!providerSessionId)
@@ -422,11 +493,30 @@ export class CommandCodeDriver {
422
493
  return lastFailure ?? { state: 'missing', reason: 'Command Code 原生会话文件不存在', ...(safeRawStoreRef ? { rawStoreRef: safeRawStoreRef } : {}) };
423
494
  }
424
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
+ }
425
502
  const binary = this.cachedBinary ?? (await this.detect()).command;
426
503
  if (binary === null)
427
504
  throw new Error('Command Code 未安装');
428
505
  const segmented = input.splitToolSteps === true;
429
- 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
+ }
430
520
  let suspended = false;
431
521
  const onAbort = () => { this.requestGracefulStop(turn); };
432
522
  input.signal?.addEventListener('abort', onAbort, { once: true });
@@ -444,12 +534,14 @@ export class CommandCodeDriver {
444
534
  }
445
535
  }
446
536
  dispose() {
537
+ this.acpDriver?.dispose();
447
538
  for (const turn of [...this.turns.values()])
448
539
  this.disposeTurn(turn);
449
540
  this.turns.clear();
450
541
  for (const child of this.processes)
451
542
  terminateChildProcess(child);
452
543
  this.processes.clear();
544
+ this.skillCatalogs.clear();
453
545
  this.cachedBinary = null;
454
546
  }
455
547
  /** 丢弃等待下一个 DSH step 的 `-p` 运行,避免旧进程继续占用新一轮输出。 */
@@ -459,18 +551,18 @@ export class CommandCodeDriver {
459
551
  this.disposeTurn(turn);
460
552
  }
461
553
  /** 只有 Host 显式声明续段时才复用常驻进程;新的用户回合必须重新启动。 */
462
- acquireTurn(input, binary) {
554
+ acquireTurn(input, binary, skillPaths) {
463
555
  const existing = this.turns.get(input.sessionId);
464
556
  if (existing !== undefined) {
465
557
  if (input.resumeSegmentedTurn === true && !existing.disposed && !existing.finished)
466
558
  return existing;
467
559
  this.disposeTurn(existing);
468
560
  }
469
- const turn = this.startTurn(input, binary);
561
+ const turn = this.startTurn(input, binary, skillPaths);
470
562
  this.turns.set(input.sessionId, turn);
471
563
  return turn;
472
564
  }
473
- startTurn(input, binary) {
565
+ startTurn(input, binary, skillPaths) {
474
566
  const cwd = input.cwd ?? process.cwd();
475
567
  const requestedProviderSessionId = input.providerSessionId?.trim() || undefined;
476
568
  // 旧 synthetic ID 可能只对应 checkpoint;回退时必须以当前 DSH 会话 ID
@@ -499,6 +591,7 @@ export class CommandCodeDriver {
499
591
  transcriptPath,
500
592
  providerSessionId,
501
593
  rawStoreRef: canResume ? input.rawStoreRef?.trim() || undefined : transcriptPath,
594
+ skillPaths,
502
595
  cwd,
503
596
  queue: createEventQueue(),
504
597
  currentMessageId: undefined,
@@ -618,6 +711,8 @@ export class CommandCodeDriver {
618
711
  if (input.enableAskUserQuestion === true || input.runtimeEnv?.CMD_TOOLS_ASK_USER_QUESTION_ENABLE === 'true') {
619
712
  args.push('--tools-enable', 'ask_user_question');
620
713
  }
714
+ for (const path of turn.skillPaths)
715
+ args.push('--skill', path);
621
716
  // 子代理托管开启时加载桥接 mod:内建 agent 调用会被转投成 DSH 原生子会话。
622
717
  args.push(...commandCodeBridgeArgs(input.sessionId));
623
718
  for (const directory of new Set((input.attachments ?? []).map((attachment) => dirname(attachment.path))))
@@ -631,6 +726,10 @@ export class CommandCodeDriver {
631
726
  }
632
727
  /** 中断当前会话并清理 CLI 进程;不存在活动运行时视为幂等成功。 */
633
728
  async interrupt(sessionId) {
729
+ if (this.acpDriver !== undefined) {
730
+ await this.acpDriver.interrupt(sessionId);
731
+ return;
732
+ }
634
733
  const turn = this.turns.get(sessionId);
635
734
  if (turn === undefined)
636
735
  return;
@@ -808,6 +907,7 @@ export class CommandCodeDriver {
808
907
  this.processes.delete(child);
809
908
  terminateChildProcess(child);
810
909
  }
910
+ turn.attachmentCleanup?.();
811
911
  // 首轮 transcript 已经是 canonical 文件,必须保留给下一轮 --resume 及冷恢复。
812
912
  }
813
913
  }
@@ -1374,6 +1474,200 @@ function workspaceSlug(workspacePath) {
1374
1474
  .replace(/^-+|-+$/g, '')
1375
1475
  .toLowerCase();
1376
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
+ }
1377
1671
  function buildMaxTurnsReachedMessage(autoContinueCount, maxTurns) {
1378
1672
  const autoContinueSuffix = autoContinueCount > 0
1379
1673
  ? `,自动续跑 ${autoContinueCount} 次后仍未完成`
@@ -1531,6 +1825,122 @@ function readJson(path) { if (!existsSync(path))
1531
1825
  catch {
1532
1826
  return null;
1533
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
+ }
1534
1944
  function isRecord(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); }
1535
1945
  function emptyCatalog() { return { groups: [], currentModel: null, currentEffort: null }; }
1536
1946
  //# sourceMappingURL=command-code-driver.js.map