@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.
- package/README.en.md +8 -8
- package/README.md +11 -9
- package/data/build/dist/client/bundle.js +169 -35
- package/data/build/dist/client/bundle.js.map +1 -1
- package/data/build/dist/client/locales/peerHostWorkspace.d.ts.map +1 -1
- package/data/build/dist/client/locales/peerHostWorkspace.js +16 -2
- package/data/build/dist/client/locales/peerHostWorkspace.js.map +1 -1
- package/data/build/dist/client/peer-host-native-projection.d.ts +4 -3
- package/data/build/dist/client/peer-host-native-projection.d.ts.map +1 -1
- package/data/build/dist/client/peer-host-native-projection.js +7 -6
- package/data/build/dist/client/peer-host-native-projection.js.map +1 -1
- package/data/build/dist/client/peer-host-native-store-projection.js +9 -21
- package/data/build/dist/client/peer-host-native-store-projection.js.map +1 -1
- package/data/build/dist/client/peer-host-workspace-tab.d.ts.map +1 -1
- package/data/build/dist/client/peer-host-workspace-tab.js +139 -10
- package/data/build/dist/client/peer-host-workspace-tab.js.map +1 -1
- package/data/build/dist/client/skill-command.d.ts +1 -1
- package/data/build/dist/client/skill-command.d.ts.map +1 -1
- package/data/build/dist/client/skill-command.js +25 -2
- package/data/build/dist/client/skill-command.js.map +1 -1
- package/data/build/dist/client/skill-reference-dom.js +1 -1
- package/data/build/dist/client/skill-reference-dom.js.map +1 -1
- package/data/build/dist/client/terminal/styles.js +2 -2
- package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.d.ts +28 -0
- package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.d.ts.map +1 -0
- package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.js +109 -0
- package/data/build/dist/dsh-capabilities/host/sidebar-session-compat.js.map +1 -0
- package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts +18 -1
- package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/acp-cli-driver.js +26 -8
- package/data/build/dist/host/cli-adapters/acp-cli-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/attachment-utils.d.ts +16 -5
- package/data/build/dist/host/cli-adapters/attachment-utils.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/attachment-utils.js +152 -42
- package/data/build/dist/host/cli-adapters/attachment-utils.js.map +1 -1
- package/data/build/dist/host/cli-adapters/claude-driver.d.ts +14 -8
- package/data/build/dist/host/cli-adapters/claude-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/claude-driver.js +101 -76
- package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/codex-driver.js +74 -6
- package/data/build/dist/host/cli-adapters/codex-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-acp-loader.d.ts +23 -0
- package/data/build/dist/host/cli-adapters/command-code-acp-loader.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/command-code-acp-loader.js +39 -0
- package/data/build/dist/host/cli-adapters/command-code-acp-loader.js.map +1 -0
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +17 -6
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-driver.js +430 -20
- package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts +1 -1
- package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/command-code-subscription.js +7 -4
- package/data/build/dist/host/cli-adapters/command-code-subscription.js.map +1 -1
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +28 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js.map +1 -1
- package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js +23 -1
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js.map +1 -1
- package/data/build/dist/host/cli-adapters/feature.js +1 -1
- package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
- package/data/build/dist/host/cli-adapters/grok-driver.d.ts +4 -2
- package/data/build/dist/host/cli-adapters/grok-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/grok-driver.js +208 -9
- package/data/build/dist/host/cli-adapters/grok-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -3
- package/data/build/dist/host/cli-adapters/json-rpc-process.js.map +1 -1
- package/data/build/dist/host/cli-adapters/kimi-driver.d.ts +4 -0
- package/data/build/dist/host/cli-adapters/kimi-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/kimi-driver.js +11 -4
- package/data/build/dist/host/cli-adapters/kimi-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/mcode-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/mcode-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/new-api-subscription.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/new-api-subscription.js +11 -58
- package/data/build/dist/host/cli-adapters/new-api-subscription.js.map +1 -1
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +5 -2
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/opencode-driver.js +136 -18
- package/data/build/dist/host/cli-adapters/opencode-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/pi-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/pi-driver.js +2 -1
- package/data/build/dist/host/cli-adapters/pi-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/process-utils.d.ts +10 -0
- package/data/build/dist/host/cli-adapters/process-utils.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/process-utils.js +20 -0
- package/data/build/dist/host/cli-adapters/process-utils.js.map +1 -1
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/provider-subscription.js +42 -8
- package/data/build/dist/host/cli-adapters/provider-subscription.js.map +1 -1
- package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/registry.js +5 -3
- package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +2 -9
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js.map +1 -1
- package/data/build/dist/host/cli-adapters/skill-filesystem.d.ts +25 -0
- package/data/build/dist/host/cli-adapters/skill-filesystem.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/skill-filesystem.js +161 -0
- package/data/build/dist/host/cli-adapters/skill-filesystem.js.map +1 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +1 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js +4 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/zcode-driver.d.ts +8 -2
- package/data/build/dist/host/cli-adapters/zcode-driver.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/zcode-driver.js +166 -55
- package/data/build/dist/host/cli-adapters/zcode-driver.js.map +1 -1
- package/data/build/dist/host/cli-adapters/zcode-interactions.d.ts +18 -0
- package/data/build/dist/host/cli-adapters/zcode-interactions.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/zcode-interactions.js +90 -0
- package/data/build/dist/host/cli-adapters/zcode-interactions.js.map +1 -0
- package/data/build/dist/host/cli-adapters/zcode-provider-config.d.ts +26 -0
- package/data/build/dist/host/cli-adapters/zcode-provider-config.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/zcode-provider-config.js +102 -0
- package/data/build/dist/host/cli-adapters/zcode-provider-config.js.map +1 -0
- package/data/build/dist/host/cli-adapters/zcode-subscription.d.ts +2 -7
- package/data/build/dist/host/cli-adapters/zcode-subscription.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/zcode-subscription.js.map +1 -1
- package/data/build/dist/host/cli-adapters/zcode-telemetry.d.ts +17 -0
- package/data/build/dist/host/cli-adapters/zcode-telemetry.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/zcode-telemetry.js +124 -0
- package/data/build/dist/host/cli-adapters/zcode-telemetry.js.map +1 -0
- package/data/build/dist/host/cli-bridge/injections.d.ts +2 -9
- package/data/build/dist/host/cli-bridge/injections.d.ts.map +1 -1
- package/data/build/dist/host/cli-bridge/injections.js +7 -17
- package/data/build/dist/host/cli-bridge/injections.js.map +1 -1
- package/data/build/dist/host/features/peer-host.d.ts.map +1 -1
- package/data/build/dist/host/features/peer-host.js +23 -0
- package/data/build/dist/host/features/peer-host.js.map +1 -1
- package/data/build/dist/host/index.d.ts.map +1 -1
- package/data/build/dist/host/index.js +3 -0
- package/data/build/dist/host/index.js.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.d.ts +2 -0
- package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.d.ts.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js +5 -0
- package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.d.ts.map +1 -1
- package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js +3 -1
- package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js.map +1 -1
- package/data/build/dist/host/rpc.d.ts.map +1 -1
- package/data/build/dist/host/rpc.js +5 -0
- package/data/build/dist/host/rpc.js.map +1 -1
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +4 -0
- package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/version.d.ts +1 -1
- package/data/build/dist/shared/contracts/version.js +1 -1
- package/package.json +3 -1
- package/profile/README.md +2 -2
- package/profile/package.json +2 -2
- package/profile/version.json +1 -1
- 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
|
-
|
|
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
|
|
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
|