chatccc 0.2.258 → 0.2.260

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.
@@ -0,0 +1,230 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { randomUUID } from "node:crypto";
10
+ import { mkdir } from "node:fs/promises";
11
+ import { homedir } from "node:os";
12
+ import { dirname, join, resolve } from "node:path";
13
+ import { pathToFileURL } from "node:url";
14
+ import { engineManager } from "../engines/engine-specs.js";
15
+ import { config, RAW_STREAM_LOGS_DIR } from "../config.js";
16
+ import { createRawStreamLog } from "./raw-stream-log.js";
17
+ let injectedSdk = null;
18
+ const knownSessions = new Map();
19
+ export function __setDshSdkModuleForTest(module) {
20
+ injectedSdk = module;
21
+ }
22
+ export function createDshAdapter(options = {}) {
23
+ return {
24
+ displayName: "DeepSeek Harness",
25
+ sessionDescPrefix: "DSH Session:",
26
+ responseStallDetectionEnabled: true,
27
+ async createSession(cwd) {
28
+ await engineManager.getEntryPath("dsh");
29
+ const sessionId = `dsh-${randomUUID()}`;
30
+ knownSessions.set(sessionId, { sessionId, cwd, lastModified: Date.now(), model: options.model ?? "deepseek-v4-flash" });
31
+ return { sessionId };
32
+ },
33
+ async *prompt(sessionId, userText, cwd, signal, promptOptions) {
34
+ const entryPath = await engineManager.getEntryPath("dsh");
35
+ const installationDir = resolve(dirname(entryPath), "../../../..");
36
+ const sdk = injectedSdk ?? await import(__rewriteRelativeImportExtension(pathToFileURL(entryPath).href));
37
+ const sessionRoot = join(homedir(), ".chatccc", "dsh-sessions");
38
+ await mkdir(sessionRoot, { recursive: true });
39
+ const runtime = new sdk.DeepSeekHarness({
40
+ launch: {
41
+ command: process.execPath,
42
+ args: [
43
+ join(installationDir, "node_modules", "@deepseek-ai", "dsh-sdk-jsonrpc-demo", "lib", "bin.js"),
44
+ join(installationDir, "dsh-runtime.cordis.yml"),
45
+ ],
46
+ cwd,
47
+ env: {
48
+ ...process.env,
49
+ DSH_CWD: cwd,
50
+ DSH_SESSION_ROOT: sessionRoot,
51
+ ...(options.apiKey ? { DEEPSEEK_API_KEY: options.apiKey } : {}),
52
+ ...(options.baseUrl ? { DEEPSEEK_BASE_URL: options.baseUrl } : {}),
53
+ },
54
+ },
55
+ cwd,
56
+ provider: options.provider ?? "deepseek-official",
57
+ model: options.model ?? "deepseek-v4-flash",
58
+ ...(options.maxTokens ? { maxTokens: options.maxTokens } : {}),
59
+ });
60
+ const queue = new AsyncMessageQueue();
61
+ const rawLogConfig = config.rawStreamLogs.dsh;
62
+ let rawLog = null;
63
+ try {
64
+ rawLog = await createRawStreamLog({
65
+ enabled: rawLogConfig.enabled,
66
+ rootDir: RAW_STREAM_LOGS_DIR,
67
+ tool: "dsh",
68
+ sessionId,
69
+ label: "prompt",
70
+ maxBytesPerTurn: rawLogConfig.maxBytesPerTurn,
71
+ retentionDays: rawLogConfig.retentionDays,
72
+ });
73
+ }
74
+ catch (error) {
75
+ console.error(`[DSH raw stream log] create failed: ${error.message}`);
76
+ }
77
+ let closed = false;
78
+ let completed = false;
79
+ const closeRuntime = () => {
80
+ if (closed)
81
+ return;
82
+ closed = true;
83
+ void runtime.close();
84
+ };
85
+ promptOptions?.onSessionCreated?.(closeRuntime);
86
+ const onAbort = () => closeRuntime();
87
+ signal?.addEventListener("abort", onAbort, { once: true });
88
+ const task = (async () => {
89
+ try {
90
+ await runtime.start();
91
+ const result = await runtime.run(userText, {
92
+ sessionId,
93
+ onNotification: (notification) => {
94
+ rawLog?.writeLine(JSON.stringify(notification));
95
+ const message = notificationToMessage(notification);
96
+ if (message)
97
+ queue.push(message);
98
+ },
99
+ });
100
+ if (result.finalResponse) {
101
+ rawLog?.writeLine(JSON.stringify({ type: "run.result", result }));
102
+ queue.push({ type: "assistant", blocks: [{ type: "text_final", text: result.finalResponse }], isFinalResponse: true });
103
+ }
104
+ completed = true;
105
+ knownSessions.set(sessionId, { sessionId, cwd, lastModified: Date.now(), model: options.model ?? "deepseek-v4-flash" });
106
+ queue.end();
107
+ }
108
+ catch (error) {
109
+ queue.fail(error instanceof Error ? error : new Error(String(error)));
110
+ }
111
+ finally {
112
+ signal?.removeEventListener("abort", onAbort);
113
+ await runtime.close().catch(() => { });
114
+ await rawLog?.close({ keep: rawLogConfig.keepCompleted || signal?.aborted === true || !completed });
115
+ closed = true;
116
+ }
117
+ })();
118
+ try {
119
+ for await (const message of queue)
120
+ yield message;
121
+ await task;
122
+ }
123
+ finally {
124
+ closeRuntime();
125
+ }
126
+ },
127
+ async getSessionInfo(sessionId) {
128
+ return knownSessions.get(sessionId) ?? { sessionId };
129
+ },
130
+ async closeSession() {
131
+ // Each prompt owns and closes its runtime. Durable state lives under ~/.chatccc/dsh-sessions.
132
+ },
133
+ };
134
+ }
135
+ function notificationToMessage(notification) {
136
+ if (notification.method === "session.status") {
137
+ return notification.params.status === "running"
138
+ ? { type: "system", blocks: [{ type: "agent_status", status: "responding" }] }
139
+ : null;
140
+ }
141
+ if (notification.method === "subagent.started") {
142
+ return { type: "system", blocks: [{ type: "agent_progress", phase: "reasoning" }] };
143
+ }
144
+ if (notification.method !== "session.event")
145
+ return null;
146
+ const event = asRecord(notification.params.event);
147
+ if (!event)
148
+ return null;
149
+ const data = asRecord(event.data);
150
+ if (!data)
151
+ return null;
152
+ const blocks = [];
153
+ if (event.type === "assistant/chunk") {
154
+ const chunk = asRecord(data.chunk);
155
+ if (chunk?.type === "text-delta" && typeof chunk.text === "string" && chunk.text) {
156
+ blocks.push({ type: "text", text: chunk.text });
157
+ }
158
+ }
159
+ else if (event.type === "tool/call") {
160
+ const raw = typeof data.arguments === "string" ? data.arguments : "{}";
161
+ let input = raw;
162
+ try {
163
+ input = JSON.parse(raw);
164
+ }
165
+ catch { /* retain raw provider arguments */ }
166
+ blocks.push({
167
+ type: "tool_use",
168
+ ...(typeof data.callId === "string" ? { id: data.callId } : {}),
169
+ name: typeof data.name === "string" ? data.name : "tool",
170
+ input,
171
+ });
172
+ }
173
+ else if (event.type === "tool/result") {
174
+ const message = asRecord(data.message);
175
+ const callId = typeof message?.toolCallId === "string"
176
+ ? message.toolCallId
177
+ : typeof message?.callId === "string"
178
+ ? message.callId
179
+ : "";
180
+ blocks.push({
181
+ type: "tool_result",
182
+ tool_use_id: callId,
183
+ content: message?.content ?? data.message,
184
+ ...(data.error ? { is_error: true } : {}),
185
+ });
186
+ }
187
+ else if (event.type === "turn/start" || event.type === "step/start") {
188
+ blocks.push({ type: "agent_progress", phase: "reasoning" });
189
+ }
190
+ return blocks.length ? { type: "assistant", blocks } : null;
191
+ }
192
+ function asRecord(value) {
193
+ return typeof value === "object" && value !== null && !Array.isArray(value)
194
+ ? value
195
+ : null;
196
+ }
197
+ class AsyncMessageQueue {
198
+ values = [];
199
+ waiters = [];
200
+ done = false;
201
+ error = null;
202
+ push(value) {
203
+ if (this.done)
204
+ return;
205
+ this.values.push(value);
206
+ this.waiters.shift()?.();
207
+ }
208
+ end() {
209
+ this.done = true;
210
+ while (this.waiters.length)
211
+ this.waiters.shift()?.();
212
+ }
213
+ fail(error) {
214
+ this.error = error;
215
+ this.end();
216
+ }
217
+ async *[Symbol.asyncIterator]() {
218
+ while (true) {
219
+ if (this.values.length) {
220
+ yield this.values.shift();
221
+ continue;
222
+ }
223
+ if (this.error)
224
+ throw this.error;
225
+ if (this.done)
226
+ return;
227
+ await new Promise((resolvePromise) => this.waiters.push(resolvePromise));
228
+ }
229
+ }
230
+ }
@@ -67,6 +67,10 @@ export function updateAgentActivity(tracker, block, now = Date.now()) {
67
67
  if (tracker.activeTools.size > 0)
68
68
  return false;
69
69
  switch (block.type) {
70
+ case "agent_progress":
71
+ return setActivity(tracker, { kind: "thinking", startedAt: now });
72
+ case "text_reset":
73
+ return setActivity(tracker, { kind: "responding", startedAt: now });
70
74
  case "agent_status":
71
75
  return setActivity(tracker, {
72
76
  kind: block.status === "compacting" ? "compacting" : "responding",
@@ -1,9 +1,10 @@
1
- export const AGENT_TOOLS = ["claude", "cursor", "codex", "ccc"];
1
+ export const AGENT_TOOLS = ["claude", "cursor", "codex", "ccc", "dsh"];
2
2
  export const AGENT_TOOL_OPTIONS = [
3
3
  { id: "ccc", label: "CCC" },
4
4
  { id: "claude", label: "Claude" },
5
5
  { id: "cursor", label: "Cursor" },
6
6
  { id: "codex", label: "Codex" },
7
+ { id: "dsh", label: "DeepSeek Harness" },
7
8
  ];
8
9
  export function isAgentTool(value) {
9
10
  return typeof value === "string" && AGENT_TOOLS.includes(value);
@@ -16,6 +16,7 @@ export const CARD_ACTION_CMD_MAP = {
16
16
  "new cursor": "/new cursor",
17
17
  "new codex": "/new codex",
18
18
  "new ccc": "/new ccc",
19
+ "new dsh": "/new dsh",
19
20
  restart: "/restart",
20
21
  update: "/update",
21
22
  state: "/state",
package/dist/src/cards.js CHANGED
@@ -137,6 +137,7 @@ export function buildHelpCard(userText, opts = {}) {
137
137
  "发送 **/new cursor** 创建新 Cursor 会话",
138
138
  "发送 **/new codex** 创建新 Codex 会话",
139
139
  "发送 **/new ccc** 创建新 CCC Agent 会话",
140
+ "发送 **/new dsh** 创建新 DeepSeek Harness 会话",
140
141
  "发送 **/forget** 重置当前会话(忘掉上下文,沿用当前工作目录,不切换)",
141
142
  "发送 **/plan** 以规划模式提问(只读,不执行写操作)",
142
143
  "发送 **/ask** 以问答模式提问(只读,不执行写操作)",
@@ -157,6 +158,7 @@ export function buildHelpCard(userText, opts = {}) {
157
158
  { text: "新建 Cursor 会话(/new cursor)", value: JSON.stringify({ cmd: "new cursor" }), type: "primary" },
158
159
  { text: "新建 Codex 会话(/new codex)", value: JSON.stringify({ cmd: "new codex" }), type: "primary" },
159
160
  { text: "新建 CCC Agent 会话(/new ccc)", value: JSON.stringify({ cmd: "new ccc" }), type: "primary" },
161
+ { text: "新建 DeepSeek Harness 会话(/new dsh)", value: JSON.stringify({ cmd: "new dsh" }), type: "primary" },
160
162
  { text: "重启 ChatCCC(/restart)", value: JSON.stringify({ cmd: "restart" }), type: "danger" },
161
163
  { text: "更新并重启(/update)", value: JSON.stringify({ cmd: "update" }), type: "danger" },
162
164
  { text: "切换工作路径(/cd)", value: JSON.stringify({ cmd: "cd" }), type: "default" },
@@ -253,6 +255,8 @@ function sessionToolLabel(tool) {
253
255
  return "Codex";
254
256
  if (tool === "ccc")
255
257
  return "CCC Agent";
258
+ if (tool === "dsh")
259
+ return "DeepSeek Harness";
256
260
  return "Claude Code";
257
261
  }
258
262
  function pushSessionGroup(lines, title, sessions, formatSession, index) {
@@ -271,22 +275,24 @@ export function buildSessionsCard(sessions, opts = {}) {
271
275
  const defaultToolLabel = opts.defaultToolLabel ?? "Claude Code";
272
276
  const fixedPrivateSession = opts.fixedPrivateSession ?? false;
273
277
  // 按 tool 分组排序:Claude Code 在前,Cursor 其次,Codex 最后
274
- const claudeCodeSessions = sessions.filter(s => s.tool !== "cursor" && s.tool !== "codex" && s.tool !== "ccc");
278
+ const claudeCodeSessions = sessions.filter(s => s.tool === "claude");
275
279
  const cursorSessions = sessions.filter(s => s.tool === "cursor");
276
280
  const codexSessions = sessions.filter(s => s.tool === "codex");
277
281
  const cccSessions = sessions.filter(s => s.tool === "ccc");
282
+ const dshSessions = sessions.filter(s => s.tool === "dsh");
278
283
  const hasClaudeCode = claudeCodeSessions.length > 0;
279
284
  const hasCursor = cursorSessions.length > 0;
280
285
  const hasCodex = codexSessions.length > 0;
281
286
  const hasCcc = cccSessions.length > 0;
287
+ const hasDsh = dshSessions.length > 0;
282
288
  if (sessions.length === 0) {
283
289
  return JSON.stringify({
284
290
  config: { wide_screen_mode: true },
285
291
  header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
286
292
  elements: [
287
293
  { tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
288
- ? `当前没有会话记录。\n\n直接发送普通消息即可创建飞书私聊专属 ${defaultToolLabel} 会话;默认 Agent 变化后,下一条普通消息会创建对应 Agent 的新空会话。发送 **/new**、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 会另外创建会话群。`
289
- : `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor**、**/new codex** 或 **/new ccc** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
294
+ ? `当前没有会话记录。\n\n直接发送普通消息即可创建飞书私聊专属 ${defaultToolLabel} 会话;默认 Agent 变化后,下一条普通消息会创建对应 Agent 的新空会话。发送 **/new**、**/new claude**、**/new cursor**、**/new codex**、**/new ccc** 或 **/new dsh** 会另外创建会话群。`
295
+ : `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor**、**/new codex**、**/new ccc** 或 **/new dsh** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
290
296
  { tag: "hr" },
291
297
  { tag: "action", actions: [{ tag: "button", text: { tag: "plain_text", content: "收起" }, type: "default", value: { action: "close" } }] },
292
298
  ],
@@ -322,6 +328,8 @@ export function buildSessionsCard(sessions, opts = {}) {
322
328
  pushSessionGroup(lines, "Codex 会话", codexSessions, formatSession, idx);
323
329
  if (hasCcc)
324
330
  pushSessionGroup(lines, "CCC Agent 会话", cccSessions, formatSession, idx);
331
+ if (hasDsh)
332
+ pushSessionGroup(lines, "DeepSeek Harness 会话", dshSessions, formatSession, idx);
325
333
  return JSON.stringify({
326
334
  config: { wide_screen_mode: true },
327
335
  header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
@@ -58,6 +58,9 @@ export function getAllModelsForTool(tool, cfg = config) {
58
58
  collect(cfg.ccc.model);
59
59
  collect(cfg.ccc.alternativeModel);
60
60
  }
61
+ else if (tool === "dsh") {
62
+ collect(cfg.dsh.model);
63
+ }
61
64
  return Array.from(seen).slice(0, 100);
62
65
  }
63
66
  export const CLAUDE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
@@ -291,6 +294,7 @@ function loadConfig() {
291
294
  cursor: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
292
295
  codex: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
293
296
  ccc: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
297
+ dsh: { enabled: false, maxBytesPerTurn: 50 * 1024 * 1024, retentionDays: 7, keepCompleted: false },
294
298
  },
295
299
  claude: { enabled: false, defaultAgent: true, model: "", subagentModel: "", effort: "", apiKey: "", baseUrl: "", maxTurn: 0 },
296
300
  cursor: {
@@ -316,6 +320,15 @@ function loadConfig() {
316
320
  compactionTimeoutMs: DEFAULT_CCC_COMPACTION_TIMEOUT_MS,
317
321
  contextWindow: DEFAULT_CCC_CONTEXT_WINDOW_TOKENS,
318
322
  },
323
+ dsh: {
324
+ enabled: false,
325
+ defaultAgent: false,
326
+ apiKey: "",
327
+ baseUrl: DEFAULT_CCC_DEEPSEEK_BASE_URL,
328
+ model: "deepseek-v4-flash",
329
+ provider: "deepseek-official",
330
+ maxTokens: 49152,
331
+ },
319
332
  };
320
333
  if (!IS_TEST_ENV) {
321
334
  migrateLegacyData();
@@ -368,6 +381,7 @@ function loadConfig() {
368
381
  const cursorRaw = (parsed.cursor ?? {});
369
382
  const codexRaw = (parsed.codex ?? {});
370
383
  const cccRaw = (parsed.ccc ?? {});
384
+ const dshRaw = (parsed.dsh ?? {});
371
385
  const webUiRaw = (parsed.webUi ?? {});
372
386
  const chromeDevtoolsRaw = (parsed.chromeDevtools ?? {});
373
387
  const rawStreamLogsRaw = typeof parsed.rawStreamLogs === "object" && parsed.rawStreamLogs !== null
@@ -408,17 +422,20 @@ function loadConfig() {
408
422
  const cursorEnabled = resolveEnabled(cursorRaw.enabled, cursorNonEmpty);
409
423
  const codexEnabled = resolveEnabled(codexRaw.enabled, codexNonEmpty);
410
424
  const cccEnabled = resolveCccEnabled(cccRaw.enabled, cccRaw.DEEPSEEK_API_KEY);
425
+ const dshEnabled = resolveEnabled(dshRaw.enabled, () => Boolean(typeof dshRaw.apiKey === "string" && dshRaw.apiKey.trim()));
411
426
  const chromeDevtoolsPort = Number(chromeDevtoolsRaw.port);
412
427
  const explicitDefaultTool = typeof claude.defaultAgent === "boolean" && claude.defaultAgent && claudeEnabled ? "claude" :
413
428
  typeof cursorRaw.defaultAgent === "boolean" && cursorRaw.defaultAgent && cursorEnabled ? "cursor" :
414
429
  typeof codexRaw.defaultAgent === "boolean" && codexRaw.defaultAgent && codexEnabled ? "codex" :
415
430
  typeof cccRaw.defaultAgent === "boolean" && cccRaw.defaultAgent && cccEnabled ? "ccc" :
416
- null;
431
+ typeof dshRaw.defaultAgent === "boolean" && dshRaw.defaultAgent && dshEnabled ? "dsh" :
432
+ null;
417
433
  const fallbackDefaultTool = claudeEnabled ? "claude" :
418
434
  cursorEnabled ? "cursor" :
419
435
  codexEnabled ? "codex" :
420
436
  cccEnabled ? "ccc" :
421
- "claude";
437
+ dshEnabled ? "dsh" :
438
+ "claude";
422
439
  const defaultTool = explicitDefaultTool ?? fallbackDefaultTool;
423
440
  return {
424
441
  feishu: {
@@ -462,6 +479,7 @@ function loadConfig() {
462
479
  cursor: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.cursor),
463
480
  codex: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.codex),
464
481
  ccc: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.ccc),
482
+ dsh: normalizeRawStreamAgentLogConfig(rawStreamLogsRaw.dsh),
465
483
  },
466
484
  claude: {
467
485
  enabled: claudeEnabled,
@@ -509,6 +527,15 @@ function loadConfig() {
509
527
  compactionTimeoutMs: normalizePositiveInteger(cccRaw.compactionTimeoutMs, DEFAULT_CCC_COMPACTION_TIMEOUT_MS),
510
528
  contextWindow: normalizePositiveInteger(cccRaw.contextWindow, DEFAULT_CCC_CONTEXT_WINDOW_TOKENS),
511
529
  },
530
+ dsh: {
531
+ enabled: dshEnabled,
532
+ defaultAgent: defaultTool === "dsh",
533
+ apiKey: normalizeOptionalConfigField(dshRaw.apiKey, { label: "dsh.apiKey" }),
534
+ baseUrl: normalizeOptionalConfigField(dshRaw.baseUrl, { label: "dsh.baseUrl", fallback: DEFAULT_CCC_DEEPSEEK_BASE_URL }),
535
+ model: normalizeOptionalConfigField(dshRaw.model, { label: "dsh.model", fallback: "deepseek-v4-flash" }),
536
+ provider: normalizeOptionalConfigField(dshRaw.provider, { label: "dsh.provider", fallback: "deepseek-official" }),
537
+ maxTokens: normalizePositiveInteger(dshRaw.maxTokens, 49152),
538
+ },
512
539
  };
513
540
  }
514
541
  /**
@@ -781,6 +808,7 @@ export const CURSOR_SESSION_PREFIX = "Cursor Session:";
781
808
  export const CODEX_SESSION_PREFIX = "Codex Session:";
782
809
  /** 群描述中用于识别 CCC Agent 会话的前缀 */
783
810
  export const CCC_SESSION_PREFIX = "CCC Session:";
811
+ export const DSH_SESSION_PREFIX = "DSH Session:";
784
812
  /** 根据 tool 名称返回对应的群描述前缀 */
785
813
  export function sessionPrefixForTool(tool) {
786
814
  if (tool === "cursor")
@@ -789,6 +817,8 @@ export function sessionPrefixForTool(tool) {
789
817
  return CODEX_SESSION_PREFIX;
790
818
  if (tool === "ccc")
791
819
  return CCC_SESSION_PREFIX;
820
+ if (tool === "dsh")
821
+ return DSH_SESSION_PREFIX;
792
822
  return CLAUDE_SESSION_PREFIX;
793
823
  }
794
824
  /** 根据 tool 名称返回用于状态展示的标签 */
@@ -799,6 +829,8 @@ export function toolDisplayName(tool) {
799
829
  return "Codex";
800
830
  if (tool === "ccc")
801
831
  return "CCC Agent";
832
+ if (tool === "dsh")
833
+ return "DeepSeek Harness";
802
834
  return "Claude Code";
803
835
  }
804
836
  /** 解析 /new 未指定工具时使用的默认 Agent。旧配置缺省 defaultAgent 时保持 Claude 优先。 */