chainlesschain 0.45.81 → 0.47.0

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 (71) hide show
  1. package/README.md +10 -0
  2. package/bin/chainlesschain.js +0 -0
  3. package/package.json +1 -1
  4. package/src/assets/web-panel/.build-hash +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-C1AnPdMx.js → Analytics-DgypYeUB.js} +2 -2
  6. package/src/assets/web-panel/assets/AppLayout-Bzf3mSZI.js +1 -0
  7. package/src/assets/web-panel/assets/AppLayout-DQyDwGut.css +1 -0
  8. package/src/assets/web-panel/assets/{Backup-D31iZX3l.js → Backup-Ba9UybpT.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DiXJ3TuK.js → Chat-BwXskT21.js} +1 -1
  10. package/src/assets/web-panel/assets/Cowork-CXuhlHew.css +1 -0
  11. package/src/assets/web-panel/assets/Cowork-UmOe7qvE.js +7 -0
  12. package/src/assets/web-panel/assets/{Cron-DBt1ueXh.js → Cron-JHS-rc-4.js} +2 -2
  13. package/src/assets/web-panel/assets/{Dashboard-HPh9FcPt.js → Dashboard-B95cMCO7.js} +2 -2
  14. package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +1 -0
  15. package/src/assets/web-panel/assets/{Git-hwQ1oZHj.js → Git-CSYO0_zk.js} +2 -2
  16. package/src/assets/web-panel/assets/{Logs-4D9p6PRM.js → Logs-Hxw_K0km.js} +2 -2
  17. package/src/assets/web-panel/assets/{McpTools-CyAUjbbs.js → McpTools-DIE75TrB.js} +2 -2
  18. package/src/assets/web-panel/assets/{Memory-BMqOR7S-.js → Memory-C4KVnLlp.js} +2 -2
  19. package/src/assets/web-panel/assets/{Notes-Cmas8i4E.js → Notes-DuzrHMAk.js} +2 -2
  20. package/src/assets/web-panel/assets/{Organization-DnSa58Tl.js → Organization-DTq6uF82.js} +4 -4
  21. package/src/assets/web-panel/assets/{P2P-BxksIBWs.js → P2P-C0hjlhsR.js} +2 -2
  22. package/src/assets/web-panel/assets/{Permissions-Bq5Qn2s3.js → Permissions-Ec0NH-xC.js} +4 -4
  23. package/src/assets/web-panel/assets/{Projects-B7EM0uPg.js → Projects-U8D0asCS.js} +2 -2
  24. package/src/assets/web-panel/assets/{Providers-DAwgG5KV.js → Providers-BngtTLvJ.js} +2 -2
  25. package/src/assets/web-panel/assets/{RssFeed-HSZoRXvS.js → RssFeed-B9NbwCKM.js} +3 -3
  26. package/src/assets/web-panel/assets/{Security-Cz17qBny.js → Security-BL5Rkr1T.js} +3 -3
  27. package/src/assets/web-panel/assets/{Services-D2EsLq-v.js → Services-D4MJzLld.js} +2 -2
  28. package/src/assets/web-panel/assets/{Skills-C9v-f3vZ.js → Skills-CQTOMDwF.js} +1 -1
  29. package/src/assets/web-panel/assets/{Tasks-yMEcU0n7.js → Tasks-DepbJMnL.js} +1 -1
  30. package/src/assets/web-panel/assets/{Templates-l7SvlKuB.js → Templates-C24PVZPu.js} +1 -1
  31. package/src/assets/web-panel/assets/{Wallet-BHWhLWn9.js → Wallet-PQoSpN_P.js} +3 -3
  32. package/src/assets/web-panel/assets/{WebAuthn-kWhFYaUK.js → WebAuthn-BcuyQ4Lr.js} +4 -4
  33. package/src/assets/web-panel/assets/WorkflowEditor-C-SvXbHW.js +1 -0
  34. package/src/assets/web-panel/assets/WorkflowEditor-D5bX6woe.css +1 -0
  35. package/src/assets/web-panel/assets/{antd-D6h4fDFf.js → antd-DEjZPGMj.js} +82 -82
  36. package/src/assets/web-panel/assets/index-CwvzTTw_.js +2 -0
  37. package/src/assets/web-panel/assets/{markdown-BZsB-Dsv.js → markdown-CusdXFxb.js} +1 -1
  38. package/src/assets/web-panel/index.html +2 -2
  39. package/src/commands/cowork.js +867 -0
  40. package/src/gateways/ws/action-protocol.js +182 -2
  41. package/src/gateways/ws/message-dispatcher.js +5 -0
  42. package/src/gateways/ws/ws-server.js +21 -0
  43. package/src/lib/cowork-cron.js +474 -0
  44. package/src/lib/cowork-evomap-adapter.js +121 -0
  45. package/src/lib/cowork-learning.js +438 -0
  46. package/src/lib/cowork-mcp-tools.js +182 -0
  47. package/src/lib/cowork-observe-html.js +108 -0
  48. package/src/lib/cowork-observe.js +160 -0
  49. package/src/lib/cowork-share.js +322 -0
  50. package/src/lib/cowork-task-runner.js +317 -3
  51. package/src/lib/cowork-task-templates.js +101 -13
  52. package/src/lib/cowork-template-marketplace.js +205 -0
  53. package/src/lib/cowork-workflow.js +571 -0
  54. package/src/lib/provider-options.js +133 -0
  55. package/src/lib/skill-loader.js +65 -0
  56. package/src/lib/sub-agent-context.js +54 -2
  57. package/src/lib/sub-agent-profiles.js +164 -0
  58. package/src/lib/todo-manager.js +108 -0
  59. package/src/lib/turn-context.js +95 -0
  60. package/src/lib/web-fetch.js +224 -0
  61. package/src/lib/workflow-expr.js +318 -0
  62. package/src/repl/agent-repl.js +4 -0
  63. package/src/runtime/agent-core.js +135 -3
  64. package/src/runtime/coding-agent-contract-shared.cjs +131 -0
  65. package/src/runtime/coding-agent-policy.cjs +30 -0
  66. package/src/assets/web-panel/assets/AppLayout-YdvJBMHH.js +0 -1
  67. package/src/assets/web-panel/assets/AppLayout-cxfKLu-m.css +0 -1
  68. package/src/assets/web-panel/assets/Cowork-BnrHWwZw.js +0 -7
  69. package/src/assets/web-panel/assets/Cowork-CcSoS3eX.css +0 -1
  70. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +0 -1
  71. package/src/assets/web-panel/assets/index-ByUk2Wmr.js +0 -2
@@ -7,14 +7,16 @@
7
7
  * @module cowork-task-runner
8
8
  */
9
9
 
10
- import { existsSync, mkdirSync, appendFileSync } from "node:fs";
10
+ import { existsSync, mkdirSync, appendFileSync, readFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { SubAgentContext } from "./sub-agent-context.js";
13
- import { getTemplate } from "./cowork-task-templates.js";
13
+ import { getTemplate, setUserTemplates } from "./cowork-task-templates.js";
14
+ import { mountTemplateMcpTools } from "./cowork-mcp-tools.js";
15
+ import { listUserTemplates } from "./cowork-template-marketplace.js";
14
16
 
15
17
  // ─── Dependencies (overridable for testing) ──────────────────────────────────
16
18
 
17
- export const _deps = { existsSync, mkdirSync, appendFileSync };
19
+ export const _deps = { existsSync, mkdirSync, appendFileSync, readFileSync };
18
20
 
19
21
  // ─── Constants ────────────────────────────────────────────────────────────────
20
22
 
@@ -63,18 +65,53 @@ export async function runCoworkTask(options = {}) {
63
65
  }
64
66
  }
65
67
 
68
+ // Merge user-installed templates (marketplace) into the registry before resolving
69
+ try {
70
+ setUserTemplates(listUserTemplates(cwd));
71
+ } catch (_e) {
72
+ // Non-fatal — marketplace absence should not break task execution
73
+ }
74
+
66
75
  // Resolve template
67
76
  const template = getTemplate(templateId);
68
77
 
69
78
  // Build the task prompt with template context + files
70
79
  const taskParts = [template.systemPromptExtension];
71
80
 
81
+ // N2: apply learning-layer patch for this template if one exists
82
+ try {
83
+ const { loadUserTemplate } = await import("./cowork-learning.js");
84
+ const override = loadUserTemplate(cwd, template.id);
85
+ if (override?.systemPromptExtension) {
86
+ taskParts.push(
87
+ `\n## 历史学习补丁 (learning patch)\n${override.systemPromptExtension}`,
88
+ );
89
+ }
90
+ } catch (_e) {
91
+ // Non-fatal — learning overrides are optional
92
+ }
93
+
72
94
  if (files.length > 0) {
73
95
  taskParts.push(`\n## 用户提供的文件\n${files.join("\n")}`);
74
96
  }
75
97
 
76
98
  const task = taskParts.join("\n");
77
99
 
100
+ // Mount template-declared MCP servers (best-effort, failures are tolerated)
101
+ const mcp = await mountTemplateMcpTools(template, {
102
+ onWarn: (msg) => {
103
+ if (onProgress) onProgress({ type: "mcp-warning", message: msg });
104
+ },
105
+ });
106
+ if (onProgress && (mcp.mounted.length > 0 || mcp.skipped.length > 0)) {
107
+ onProgress({
108
+ type: "mcp-mounted",
109
+ mounted: mcp.mounted,
110
+ skipped: mcp.skipped.map((s) => s.name),
111
+ toolCount: mcp.extraToolDefinitions.length,
112
+ });
113
+ }
114
+
78
115
  // Create isolated sub-agent context
79
116
  const subAgent = SubAgentContext.create({
80
117
  role: `cowork-${template.id}`,
@@ -87,6 +124,10 @@ export async function runCoworkTask(options = {}) {
87
124
  cwd,
88
125
  onProgress,
89
126
  signal,
127
+ extraToolDefinitions: mcp.extraToolDefinitions,
128
+ externalToolDescriptors: mcp.externalToolDescriptors,
129
+ externalToolExecutors: mcp.externalToolExecutors,
130
+ mcpClient: mcp.mcpClient,
90
131
  });
91
132
 
92
133
  const taskId = subAgent.id;
@@ -128,6 +169,279 @@ export async function runCoworkTask(options = {}) {
128
169
  };
129
170
  _appendHistory(cwd, entry, userMessage);
130
171
  return entry;
172
+ } finally {
173
+ await mcp.cleanup();
174
+ }
175
+ }
176
+
177
+ // ─── Parallel Runner (Orchestrator) ──────────────────────────────────────────
178
+
179
+ /**
180
+ * Run a cowork task using the Orchestrator for multi-agent parallel execution.
181
+ *
182
+ * @param {object} options - Same as runCoworkTask, plus:
183
+ * @param {number} [options.agents] - Number of parallel agents (default 3, max 10)
184
+ * @param {string} [options.strategy] - Routing strategy (default "round-robin")
185
+ * @param {function} [options.onProgress] - Progress callback
186
+ * @param {AbortSignal} [options.signal] - Cancellation signal
187
+ * @returns {Promise<{ taskId: string, status: string, result: object }>}
188
+ */
189
+ export async function runCoworkTaskParallel(options = {}) {
190
+ const {
191
+ templateId = null,
192
+ userMessage,
193
+ files = [],
194
+ cwd = process.cwd(),
195
+ agents = 3,
196
+ strategy,
197
+ onProgress = null,
198
+ signal = null,
199
+ } = options;
200
+
201
+ if (!userMessage || typeof userMessage !== "string") {
202
+ throw new Error("userMessage is required");
203
+ }
204
+
205
+ if (files.length > 0) {
206
+ const missing = files.filter((f) => !_deps.existsSync(f));
207
+ if (missing.length > 0) {
208
+ throw new Error(`File(s) not found: ${missing.join(", ")}`);
209
+ }
210
+ }
211
+
212
+ const template = getTemplate(templateId);
213
+
214
+ // Build full task description for the orchestrator
215
+ const taskParts = [
216
+ `[Cowork Template: ${template.name}]`,
217
+ template.systemPromptExtension,
218
+ `\n## 用户需求\n${userMessage}`,
219
+ ];
220
+ if (files.length > 0) {
221
+ taskParts.push(`\n## 用户提供的文件\n${files.join("\n")}`);
222
+ }
223
+ const fullTask = taskParts.join("\n");
224
+
225
+ try {
226
+ const { Orchestrator, TASK_SOURCE } = await import("./orchestrator.js");
227
+
228
+ const orch = new Orchestrator({
229
+ cwd,
230
+ maxParallel: Math.min(parseInt(agents, 10) || 3, 10),
231
+ ciCommand: "echo ok",
232
+ agents: strategy ? { strategy } : undefined,
233
+ verbose: false,
234
+ });
235
+
236
+ // Wire progress events
237
+ if (onProgress) {
238
+ orch.on("task:added", (t) =>
239
+ onProgress({
240
+ type: "orchestrator-started",
241
+ taskId: t.id,
242
+ subtaskCount: 0,
243
+ }),
244
+ );
245
+ orch.on("task:decomposed", (t) =>
246
+ onProgress({
247
+ type: "orchestrator-decomposed",
248
+ taskId: t.id,
249
+ subtaskCount: t.subtasks?.length || 0,
250
+ }),
251
+ );
252
+ orch.on("agents:dispatched", (ev) =>
253
+ onProgress({
254
+ type: "agents-dispatched",
255
+ agentCount: ev.agents?.length || 0,
256
+ }),
257
+ );
258
+ orch.on("agent:output", (ev) =>
259
+ onProgress({
260
+ type: "agent-progress",
261
+ agentIndex: ev.agentIndex,
262
+ status: ev.status,
263
+ output: ev.output?.slice(0, 200),
264
+ }),
265
+ );
266
+ }
267
+
268
+ // Handle cancellation
269
+ if (signal) {
270
+ signal.addEventListener(
271
+ "abort",
272
+ () => {
273
+ orch.stopCronWatch();
274
+ },
275
+ { once: true },
276
+ );
277
+ }
278
+
279
+ const orchResult = await orch.addTask(fullTask, {
280
+ source: TASK_SOURCE.CLI,
281
+ cwd,
282
+ runCI: false,
283
+ notify: false,
284
+ });
285
+
286
+ const entry = {
287
+ taskId: orchResult.id,
288
+ status: orchResult.status === "completed" ? "completed" : "failed",
289
+ templateId: template.id,
290
+ templateName: template.name,
291
+ parallel: true,
292
+ agentCount: agents,
293
+ result: {
294
+ summary:
295
+ orchResult.agentResults
296
+ ?.map((r) => r.output?.slice(0, 500))
297
+ .join("\n---\n") || "Parallel execution completed",
298
+ artifacts: [],
299
+ tokenCount: 0,
300
+ toolsUsed: [],
301
+ iterationCount: orchResult.retries || 0,
302
+ subtaskCount: orchResult.subtasks?.length || 0,
303
+ },
304
+ };
305
+ _appendHistory(cwd, entry, userMessage);
306
+ return entry;
307
+ } catch (err) {
308
+ const entry = {
309
+ taskId: `cowork-parallel-${Date.now()}`,
310
+ status: "failed",
311
+ templateId: template.id,
312
+ templateName: template.name,
313
+ parallel: true,
314
+ result: {
315
+ summary: `Parallel task failed: ${err.message}`,
316
+ artifacts: [],
317
+ tokenCount: 0,
318
+ toolsUsed: [],
319
+ iterationCount: 0,
320
+ subtaskCount: 0,
321
+ },
322
+ };
323
+ _appendHistory(cwd, entry, userMessage);
324
+ return entry;
325
+ }
326
+ }
327
+
328
+ // ─── Debate Runner (Multi-perspective Review) ───────────────────────────────
329
+
330
+ /**
331
+ * Run a cowork task in debate mode — multiple reviewer perspectives converge
332
+ * into a final verdict via moderator synthesis.
333
+ *
334
+ * @param {object} options
335
+ * @param {string|null} options.templateId - Should be "code-review" or null
336
+ * @param {string} options.userMessage - Target description / review instructions
337
+ * @param {string[]} [options.files] - File paths to review (concatenated as code body)
338
+ * @param {string[]} [options.perspectives] - Override template perspectives
339
+ * @param {string} [options.cwd] - Working directory for history
340
+ * @param {object} [options.llmOptions] - LLM provider/model/key
341
+ * @param {function} [options.onProgress] - Progress callback
342
+ * @returns {Promise<{ taskId, status, result }>}
343
+ */
344
+ export async function runCoworkDebate(options = {}) {
345
+ const {
346
+ templateId = "code-review",
347
+ userMessage,
348
+ files = [],
349
+ perspectives,
350
+ cwd = process.cwd(),
351
+ llmOptions = {},
352
+ onProgress = null,
353
+ } = options;
354
+
355
+ if (!userMessage || typeof userMessage !== "string") {
356
+ throw new Error("userMessage is required");
357
+ }
358
+
359
+ if (files.length > 0) {
360
+ const missing = files.filter((f) => !_deps.existsSync(f));
361
+ if (missing.length > 0) {
362
+ throw new Error(`File(s) not found: ${missing.join(", ")}`);
363
+ }
364
+ }
365
+
366
+ const template = getTemplate(templateId);
367
+ const reviewPerspectives = perspectives ||
368
+ template.debatePerspectives || [
369
+ "performance",
370
+ "security",
371
+ "maintainability",
372
+ ];
373
+
374
+ // Build code body from files (or from userMessage if no files provided)
375
+ let code = "";
376
+ if (files.length > 0) {
377
+ const chunks = files.map((f) => {
378
+ try {
379
+ return `// ===== ${f} =====\n${_deps.readFileSync(f, "utf-8")}`;
380
+ } catch (err) {
381
+ return `// ===== ${f} (read error: ${err.message}) =====`;
382
+ }
383
+ });
384
+ code = chunks.join("\n\n");
385
+ } else {
386
+ code = userMessage;
387
+ }
388
+
389
+ const taskId = `cowork-debate-${Date.now()}`;
390
+
391
+ if (onProgress) {
392
+ onProgress({ type: "debate-started", perspectives: reviewPerspectives });
393
+ }
394
+
395
+ try {
396
+ const { startDebate } = await import("./cowork/debate-review-cli.js");
397
+ const debateResult = await startDebate({
398
+ target: userMessage,
399
+ code,
400
+ perspectives: reviewPerspectives,
401
+ llmOptions,
402
+ });
403
+
404
+ if (onProgress) {
405
+ onProgress({ type: "debate-completed", verdict: debateResult.verdict });
406
+ }
407
+
408
+ const entry = {
409
+ taskId,
410
+ status: "completed",
411
+ templateId: template.id,
412
+ templateName: template.name,
413
+ mode: "debate",
414
+ result: {
415
+ summary: debateResult.summary,
416
+ verdict: debateResult.verdict,
417
+ consensusScore: debateResult.consensusScore,
418
+ reviews: debateResult.reviews,
419
+ perspectives: debateResult.perspectives,
420
+ artifacts: [],
421
+ tokenCount: 0,
422
+ toolsUsed: [],
423
+ iterationCount: debateResult.reviews.length + 1,
424
+ },
425
+ };
426
+ _appendHistory(cwd, entry, userMessage);
427
+ return entry;
428
+ } catch (err) {
429
+ const entry = {
430
+ taskId,
431
+ status: "failed",
432
+ templateId: template.id,
433
+ templateName: template.name,
434
+ mode: "debate",
435
+ result: {
436
+ summary: `Debate failed: ${err.message}`,
437
+ artifacts: [],
438
+ tokenCount: 0,
439
+ toolsUsed: [],
440
+ iterationCount: 0,
441
+ },
442
+ };
443
+ _appendHistory(cwd, entry, userMessage);
444
+ return entry;
131
445
  }
132
446
  }
133
447
 
@@ -158,6 +158,7 @@ ${ERROR_RECOVERY_PROMPT}`,
158
158
  name: "数据分析",
159
159
  category: "data",
160
160
  acceptsFiles: true,
161
+ parallelStrategy: "auto",
161
162
  fileTypes: [".csv", ".xlsx", ".xls", ".json", ".tsv", ".sqlite", ".db"],
162
163
  systemPromptExtension: `你是数据分析专家。
163
164
 
@@ -194,6 +195,7 @@ ${ERROR_RECOVERY_PROMPT}`,
194
195
  category: "research",
195
196
  acceptsFiles: false,
196
197
  fileTypes: [],
198
+ parallelStrategy: "auto",
197
199
  shellPolicyOverrides: ["network-download"],
198
200
  systemPromptExtension: `你是信息检索与调研专家。
199
201
 
@@ -270,6 +272,7 @@ ${ERROR_RECOVERY_PROMPT}`,
270
272
  name: "代码辅助",
271
273
  category: "development",
272
274
  acceptsFiles: true,
275
+ parallelStrategy: "auto",
273
276
  fileTypes: [
274
277
  ".js",
275
278
  ".ts",
@@ -460,6 +463,32 @@ ${OPEN_SOURCE_FIRST_PROMPT}
460
463
  ${FILE_HANDLING_PROMPT}
461
464
  ${ERROR_RECOVERY_PROMPT}`,
462
465
  },
466
+
467
+ "code-review": {
468
+ id: "code-review",
469
+ name: "代码评审",
470
+ category: "development",
471
+ acceptsFiles: true,
472
+ fileTypes: [
473
+ ".js",
474
+ ".ts",
475
+ ".py",
476
+ ".go",
477
+ ".rs",
478
+ ".java",
479
+ ".kt",
480
+ ".cpp",
481
+ ".c",
482
+ ".rb",
483
+ ".php",
484
+ ".vue",
485
+ ],
486
+ mode: "debate",
487
+ debatePerspectives: ["performance", "security", "maintainability"],
488
+ systemPromptExtension: `你是多视角代码评审的协调者。
489
+ 将代码分发给多个专业评审员独立评审,最后综合输出裁决。
490
+ `,
491
+ },
463
492
  };
464
493
 
465
494
  /**
@@ -467,23 +496,49 @@ ${ERROR_RECOVERY_PROMPT}`,
467
496
  * @param {string|null} templateId
468
497
  * @returns {object} Template definition
469
498
  */
499
+ /**
500
+ * Extra template registry for installed user templates. The marketplace
501
+ * loader populates this at CLI startup via `setUserTemplates()`. Keeping
502
+ * it local lets `getTemplate()` / `getTemplatesForUI()` stay synchronous
503
+ * while still returning user-installed templates.
504
+ */
505
+ let _userTemplates = {};
506
+
507
+ /** Called by the marketplace / CLI to register installed user templates. */
508
+ export function setUserTemplates(templates) {
509
+ _userTemplates = {};
510
+ if (Array.isArray(templates)) {
511
+ for (const tpl of templates) {
512
+ if (tpl?.id) _userTemplates[tpl.id] = tpl;
513
+ }
514
+ }
515
+ }
516
+
517
+ /** Read-only accessor for tests. */
518
+ export function getUserTemplates() {
519
+ return { ..._userTemplates };
520
+ }
521
+
470
522
  export function getTemplate(templateId) {
471
- if (!templateId || !TASK_TEMPLATES[templateId]) {
472
- return {
473
- id: "free",
474
- name: "自由模式",
475
- category: "general",
476
- acceptsFiles: true,
477
- fileTypes: [],
478
- systemPromptExtension: `你是一个全能助手,可以处理用户提出的任何日常任务。
523
+ if (templateId && TASK_TEMPLATES[templateId]) {
524
+ return TASK_TEMPLATES[templateId];
525
+ }
526
+ if (templateId && _userTemplates[templateId]) {
527
+ return _userTemplates[templateId];
528
+ }
529
+ return {
530
+ id: "free",
531
+ name: "自由模式",
532
+ category: "general",
533
+ acceptsFiles: true,
534
+ fileTypes: [],
535
+ systemPromptExtension: `你是一个全能助手,可以处理用户提出的任何日常任务。
479
536
  根据任务类型自动选择最合适的工具和方法。
480
537
 
481
538
  ${OPEN_SOURCE_FIRST_PROMPT}
482
539
  ${FILE_HANDLING_PROMPT}
483
540
  ${ERROR_RECOVERY_PROMPT}`,
484
- };
485
- }
486
- return TASK_TEMPLATES[templateId];
541
+ };
487
542
  }
488
543
 
489
544
  /**
@@ -491,7 +546,7 @@ ${ERROR_RECOVERY_PROMPT}`,
491
546
  * @returns {string[]}
492
547
  */
493
548
  export function listTemplateIds() {
494
- return Object.keys(TASK_TEMPLATES);
549
+ return [...Object.keys(TASK_TEMPLATES), ...Object.keys(_userTemplates)];
495
550
  }
496
551
 
497
552
  // ─── UI Metadata ─────────────────────────────────────────────────────────────
@@ -571,6 +626,15 @@ const UI_METADATA = {
571
626
  description: "文档翻译、内容总结、论文分析",
572
627
  examples: ["翻译 PDF 摘要", "总结长文档要点", "解释代码工作原理"],
573
628
  },
629
+ "code-review": {
630
+ icon: "SafetyCertificateOutlined",
631
+ description: "多视角评审(性能 / 安全 / 可维护性),综合裁决",
632
+ examples: [
633
+ "评审这个函数的安全问题",
634
+ "从性能角度审查这段代码",
635
+ "评审 PR 的可维护性",
636
+ ],
637
+ },
574
638
  };
575
639
 
576
640
  /**
@@ -580,8 +644,17 @@ const UI_METADATA = {
580
644
  * @returns {object[]}
581
645
  */
582
646
  export function getTemplatesForUI() {
583
- return Object.values(TASK_TEMPLATES).map((tpl) => {
647
+ const builtIn = Object.values(TASK_TEMPLATES).map((tpl) => {
584
648
  const ui = UI_METADATA[tpl.id] || {};
649
+ return { tpl, ui, source: "builtin" };
650
+ });
651
+ const userInstalled = Object.values(_userTemplates).map((tpl) => ({
652
+ tpl,
653
+ // User templates carry their own ui metadata inline
654
+ ui: { icon: tpl.icon, description: tpl.description, examples: tpl.examples },
655
+ source: "user",
656
+ }));
657
+ return [...builtIn, ...userInstalled].map(({ tpl, ui, source }) => {
585
658
  return {
586
659
  id: tpl.id,
587
660
  name: tpl.name,
@@ -589,10 +662,25 @@ export function getTemplatesForUI() {
589
662
  category: tpl.category,
590
663
  description: ui.description || "",
591
664
  examples: ui.examples || [],
665
+ source,
592
666
  acceptsFiles: tpl.acceptsFiles,
667
+ parallelStrategy: tpl.parallelStrategy || "none",
668
+ mode: tpl.mode || "agent",
669
+ ...(tpl.debatePerspectives
670
+ ? { debatePerspectives: tpl.debatePerspectives }
671
+ : {}),
593
672
  ...(tpl.shellPolicyOverrides
594
673
  ? { shellPolicyOverrides: tpl.shellPolicyOverrides }
595
674
  : {}),
675
+ ...(Array.isArray(tpl.mcpServers) && tpl.mcpServers.length > 0
676
+ ? {
677
+ mcpServers: tpl.mcpServers.map((s) => ({
678
+ name: s.name,
679
+ command: s.command,
680
+ args: Array.isArray(s.args) ? s.args : [],
681
+ })),
682
+ }
683
+ : {}),
596
684
  };
597
685
  });
598
686
  }