minimal-agent 0.11.0 → 0.12.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 (128) hide show
  1. package/package.json +4 -1
  2. package/plugins/workflow-runner/plugin.js +27 -15
  3. package/plugins/workflow-runner/plugin.ts +34 -20
  4. package/plugins/workflow-runner/src/agentCommon.js +13 -12
  5. package/plugins/workflow-runner/src/agentCommon.ts +22 -13
  6. package/plugins/workflow-runner/src/index.js +5 -3
  7. package/plugins/workflow-runner/src/index.ts +31 -13
  8. package/plugins/workflow-runner/src/multimodal.js +2 -1
  9. package/plugins/workflow-runner/src/multimodal.ts +2 -1
  10. package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
  11. package/plugins/workflow-runner/src/stepExecutors/skill.ts +12 -7
  12. package/plugins/workflow-runner/src/stepExecutors/tool.js +68 -57
  13. package/plugins/workflow-runner/src/stepExecutors/tool.ts +88 -66
  14. package/plugins/workflow-runner/src/toolRuntime.js +48 -0
  15. package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
  16. package/plugins/workflow-runner/src/types.ts +26 -7
  17. package/plugins/workflow-runner/test/context-files.test.ts +47 -16
  18. package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
  19. package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
  20. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
  21. package/src/cli/print.js +19 -4
  22. package/src/cli/print.ts +17 -4
  23. package/src/cli/streamJson.js +19 -4
  24. package/src/cli/streamJson.ts +17 -4
  25. package/src/config/configDefaults.js +25 -0
  26. package/src/config/configDefaults.ts +33 -0
  27. package/src/config/configFile.js +2 -21
  28. package/src/config/configFile.ts +11 -24
  29. package/src/config/configManagement.js +1 -1
  30. package/src/config/configManagement.ts +2 -2
  31. package/src/context/compact.js +4 -0
  32. package/src/context/compact.ts +4 -0
  33. package/src/context/contextBundle.js +38 -13
  34. package/src/context/contextBundle.ts +47 -13
  35. package/src/harness/stallGuard.js +19 -4
  36. package/src/harness/stallGuard.ts +30 -7
  37. package/src/loop.js +95 -11
  38. package/src/loop.ts +125 -16
  39. package/src/main.js +8 -1
  40. package/src/main.tsx +7 -1
  41. package/src/memory/constants.js +10 -0
  42. package/src/memory/constants.ts +13 -0
  43. package/src/memory/episodicMemory.js +274 -2
  44. package/src/memory/episodicMemory.ts +306 -1
  45. package/src/memory/fileStore.js +24 -4
  46. package/src/memory/fileStore.ts +22 -2
  47. package/src/memory/memoryService.js +79 -12
  48. package/src/memory/memoryService.ts +108 -9
  49. package/src/memory/repository.js +415 -85
  50. package/src/memory/repository.ts +437 -86
  51. package/src/memory/turnJournal.js +95 -0
  52. package/src/memory/turnJournal.ts +120 -0
  53. package/src/memory/types.ts +16 -0
  54. package/src/plugins/pluginApi.ts +30 -6
  55. package/src/plugins/pluginRunner.js +6 -2
  56. package/src/plugins/pluginRunner.ts +12 -7
  57. package/src/prompts/policyRegistry.js +27 -0
  58. package/src/prompts/policyRegistry.ts +27 -0
  59. package/src/prompts/promptAssembly.js +39 -7
  60. package/src/prompts/promptAssembly.ts +145 -59
  61. package/src/session/runAgentTurn.js +43 -21
  62. package/src/session/runAgentTurn.ts +68 -26
  63. package/src/session/runTurn.js +11 -46
  64. package/src/session/runTurn.ts +12 -46
  65. package/src/session/runtimeStore.js +149 -0
  66. package/src/session/runtimeStore.ts +197 -0
  67. package/src/session/sessionState.js +5 -0
  68. package/src/session/sessionState.ts +14 -0
  69. package/src/tools/agent/agent.js +143 -14
  70. package/src/tools/agent/agent.ts +149 -14
  71. package/src/tools/agent/agentTaskService.js +60 -0
  72. package/src/tools/agent/agentTaskService.ts +113 -0
  73. package/src/tools/agent/agentTypes.js +5 -0
  74. package/src/tools/agent/agentTypes.ts +5 -0
  75. package/src/tools/bash/bash.js +31 -11
  76. package/src/tools/bash/bash.ts +29 -10
  77. package/src/tools/capabilities.js +5 -0
  78. package/src/tools/capabilities.ts +11 -1
  79. package/src/tools/edit/edit.js +7 -4
  80. package/src/tools/edit/edit.ts +7 -4
  81. package/src/tools/edit/multi-edit.js +4 -2
  82. package/src/tools/edit/multi-edit.ts +4 -2
  83. package/src/tools/glob/glob.js +156 -40
  84. package/src/tools/glob/glob.ts +185 -46
  85. package/src/tools/index.js +35 -17
  86. package/src/tools/index.ts +81 -20
  87. package/src/tools/lsp/lsp.js +165 -22
  88. package/src/tools/lsp/lsp.ts +206 -36
  89. package/src/tools/powershell/powershell.js +116 -26
  90. package/src/tools/powershell/powershell.ts +155 -30
  91. package/src/tools/presentresult/presentresult.js +10 -0
  92. package/src/tools/presentresult/presentresult.ts +10 -0
  93. package/src/tools/read/read.js +244 -29
  94. package/src/tools/read/read.ts +391 -59
  95. package/src/tools/shared/diffSummary.js +44 -0
  96. package/src/tools/shared/diffSummary.ts +63 -0
  97. package/src/tools/shared/fileActivity.js +2 -1
  98. package/src/tools/shared/fileActivity.ts +3 -0
  99. package/src/tools/shared/permanentDeleteGuard.js +26 -0
  100. package/src/tools/shared/permanentDeleteGuard.ts +34 -0
  101. package/src/tools/shared/toolOutputStore.js +97 -0
  102. package/src/tools/shared/toolOutputStore.ts +110 -0
  103. package/src/tools/skill/skill.js +86 -0
  104. package/src/tools/skill/skill.ts +94 -0
  105. package/src/tools/snip/snip.js +67 -0
  106. package/src/tools/snip/snip.ts +80 -0
  107. package/src/tools/tasks/backgroundTaskService.js +79 -28
  108. package/src/tools/tasks/backgroundTaskService.ts +165 -49
  109. package/src/tools/tasks/taskManagement.js +163 -0
  110. package/src/tools/tasks/taskManagement.ts +180 -0
  111. package/src/tools/tasks/taskOutput.js +64 -14
  112. package/src/tools/tasks/taskOutput.ts +102 -23
  113. package/src/tools/tasks/taskStop.js +48 -8
  114. package/src/tools/tasks/taskStop.ts +69 -9
  115. package/src/tools/tasks/taskStore.js +46 -0
  116. package/src/tools/tasks/taskStore.ts +77 -0
  117. package/src/tools/terminal/terminalCapture.js +167 -0
  118. package/src/tools/terminal/terminalCapture.ts +179 -0
  119. package/src/tools/trash/trash.js +233 -0
  120. package/src/tools/trash/trash.ts +283 -0
  121. package/src/tools/worktree/sessionWorktree.js +128 -0
  122. package/src/tools/worktree/sessionWorktree.ts +158 -0
  123. package/src/tools/write/write.js +6 -2
  124. package/src/tools/write/write.ts +6 -2
  125. package/src/types.ts +12 -0
  126. package/src/ui/hooks/useChat.js +31 -11
  127. package/src/ui/hooks/useChat.ts +31 -13
  128. package/src/web/protocol.ts +78 -3
@@ -35,6 +35,9 @@ export const EXPLORE_ALLOWED_TOOLS = [
35
35
  'Read',
36
36
  'Glob',
37
37
  'Grep',
38
+ 'Snip',
39
+ 'LSP',
40
+ 'Skill',
38
41
  'WebSearch',
39
42
  'WebFetch',
40
43
  'WebBrowser',
@@ -42,6 +45,8 @@ export const EXPLORE_ALLOWED_TOOLS = [
42
45
  'ListMemories',
43
46
  'ReviewMemories',
44
47
  'SearchConversations',
48
+ 'TaskGet',
49
+ 'TaskList',
45
50
  ];
46
51
  /** 借鉴官方 EXPLORE_AGENT「READ-ONLY MODE」文案中文化。 */
47
52
  const EXPLORE_APPENDIX = `# 本次子任务类型:只读调研(explore)
@@ -53,6 +53,9 @@ export const EXPLORE_ALLOWED_TOOLS: readonly string[] = [
53
53
  'Read',
54
54
  'Glob',
55
55
  'Grep',
56
+ 'Snip',
57
+ 'LSP',
58
+ 'Skill',
56
59
  'WebSearch',
57
60
  'WebFetch',
58
61
  'WebBrowser',
@@ -60,6 +63,8 @@ export const EXPLORE_ALLOWED_TOOLS: readonly string[] = [
60
63
  'ListMemories',
61
64
  'ReviewMemories',
62
65
  'SearchConversations',
66
+ 'TaskGet',
67
+ 'TaskList',
63
68
  ];
64
69
 
65
70
  /** 借鉴官方 EXPLORE_AGENT「READ-ONLY MODE」文案中文化。 */
@@ -56,6 +56,8 @@ import { requestUserChoice } from '../../askBridge.js';
56
56
  import { classifyCommand } from '../../irreversible/rules.js';
57
57
  import { startBackgroundTask } from '../tasks/backgroundTaskService.js';
58
58
  import { processExecutionMetadata } from '../tasks/processEvidence.js';
59
+ import { permanentDeleteReason } from '../shared/permanentDeleteGuard.js';
60
+ import { ToolOutputStore } from '../shared/toolOutputStore.js';
59
61
  // ---------------- 0. 常量 ----------------
60
62
  const DEFAULT_TIMEOUT_MS = 120_000; // 2 min
61
63
  const MAX_TIMEOUT_MS = 600_000; // 10 min
@@ -347,6 +349,9 @@ async function call(input, signal, context) {
347
349
  error: `安全检查拒绝执行:${forbiddenReason}\n命令:${command}`,
348
350
  };
349
351
  }
352
+ const permanentDelete = permanentDeleteReason(command, 'bash');
353
+ if (permanentDelete)
354
+ return { ok: false, error: `永久删除检查拒绝执行:${permanentDelete}\n命令:${command}` };
350
355
  // 4a-1. 不可逆动作闸门:毁掉「最后一份副本」的命令要**停下来问人**。
351
356
  //
352
357
  // 与上面黑名单的分工:黑名单是「灾难且无正当用途」→ 直接拒;这里是
@@ -389,18 +394,19 @@ async function call(input, signal, context) {
389
394
  try {
390
395
  const cwd = context?.cwd ?? getWorkingDir();
391
396
  const task = await startBackgroundTask(WIN_BASH
392
- ? { command, executable: WIN_BASH, args: ['-c', command], cwd, env: sanitizedChildEnv(), sessionId: context?.sessionId, shellKind: 'bash' }
397
+ ? { command, executable: WIN_BASH, args: ['-c', command], cwd, env: sanitizedChildEnv(), sessionId: context?.sessionId, workspaceId: context?.workspaceId, shellKind: 'bash' }
393
398
  : {
394
399
  command: process.platform === 'win32' ? `chcp 65001 >nul & ${command}` : command,
395
400
  cwd,
396
401
  env: sanitizedChildEnv(),
397
402
  shell: true,
398
403
  sessionId: context?.sessionId,
404
+ workspaceId: context?.workspaceId,
399
405
  shellKind: 'bash',
400
406
  }, context?.sessionState?.backgroundTasks);
401
407
  return {
402
408
  ok: true,
403
- content: `${destructiveWarning ? `⚠️ 警告: ${destructiveWarning}\n` : ''}后台任务已启动。task_id=${task.id}\nlog=${task.logPath}\n使用 TaskOutput 增量读取输出,TaskStop 停止进程。`,
409
+ content: `${destructiveWarning ? `⚠️ 警告: ${destructiveWarning}\n` : ''}后台任务已启动。task_id=${task.id.replace(/^proc_/, '')}\nprocess_id=${task.id}\nlog=${task.logPath}\n使用 TaskOutput 增量读取输出,TaskStop 停止进程。`,
404
410
  metadata: processExecutionMetadata({ shell: 'bash', mode: 'background', command, status: 'started', taskId: task.id }),
405
411
  };
406
412
  }
@@ -414,6 +420,10 @@ async function call(input, signal, context) {
414
420
  // Windows 无 bash: cmd.exe,命令前缀 chcp 65001 强制 UTF-8 输出
415
421
  let stdout = '';
416
422
  let stderr = '';
423
+ const outputStore = new ToolOutputStore({
424
+ workspaceId: context?.workspaceId,
425
+ sessionId: context?.sessionId,
426
+ });
417
427
  let exitCode = null;
418
428
  let timedOut = false;
419
429
  let killedBySignal = false;
@@ -456,14 +466,16 @@ async function call(input, signal, context) {
456
466
  child.stdout?.setEncoding('utf8');
457
467
  child.stderr?.setEncoding('utf8');
458
468
  child.stdout?.on('data', (chunk) => {
459
- // G1:即时封顶,防止狂输出命令在最终截断前撑爆内存(OOM)。不强行 kill,
460
- // 让命令自己跑完或被 timeout 收走;超出上限的部分直接丢弃,最终仍按上限截断展示。
461
- if (stdout.length < MAX_STREAM_BUFFER_CHARS)
462
- stdout += chunk;
469
+ if (!outputStore.append('stdout', chunk)) {
470
+ child.stdout?.pause();
471
+ outputStore.onDrain(() => child.stdout?.resume());
472
+ }
463
473
  });
464
474
  child.stderr?.on('data', (chunk) => {
465
- if (stderr.length < MAX_STREAM_BUFFER_CHARS)
466
- stderr += chunk;
475
+ if (!outputStore.append('stderr', chunk)) {
476
+ child.stderr?.pause();
477
+ outputStore.onDrain(() => child.stderr?.resume());
478
+ }
467
479
  });
468
480
  child.on('error', (e) => {
469
481
  ac.signal.removeEventListener('abort', onKill);
@@ -489,6 +501,9 @@ async function call(input, signal, context) {
489
501
  clearTimeout(timer);
490
502
  signal?.removeEventListener('abort', onAbort);
491
503
  }
504
+ const storedOutput = await outputStore.finish();
505
+ stdout = storedOutput.stdout;
506
+ stderr = storedOutput.stderr;
492
507
  // 4b-2. ATTACH_IMAGE sentinel:脚本可以打一行 `<<<ATTACH_IMAGE:path>>>` 让模型亲眼看图。
493
508
  // 协议行**从 content 剥掉**(那是给宿主看的,不是给模型读的噪声);被拒的条目
494
509
  // 如实写进输出(静默丢弃会让脚本作者以为图发出去了,无从 debug)。
@@ -518,13 +533,18 @@ async function call(input, signal, context) {
518
533
  parts.push(`\n[被信号中断]`);
519
534
  }
520
535
  parts.push(`\n[exit code: ${exitCode === null ? 'killed' : exitCode}]`);
536
+ const outputReference = storedOutput.path
537
+ ? `\n[完整输出: ${storedOutput.path}; bytes=${storedOutput.bytes}; sha256=${storedOutput.sha256}; complete=${storedOutput.complete}]`
538
+ : '';
521
539
  let combinedOutput = parts.join('\n');
522
540
  // 4d. 大小兜底
523
- if (combinedOutput.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
541
+ if (combinedOutput.length + outputReference.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
542
+ const suffix = `\n\n... (输出超过 ${DEFAULT_MAX_RESULT_SIZE_CHARS} 字符,预览已截断)${outputReference}`;
524
543
  combinedOutput =
525
- combinedOutput.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) +
526
- `\n\n... (输出超过 ${DEFAULT_MAX_RESULT_SIZE_CHARS} 字符,已截断)`;
544
+ combinedOutput.slice(0, Math.max(0, DEFAULT_MAX_RESULT_SIZE_CHARS - suffix.length)) + suffix;
527
545
  }
546
+ else
547
+ combinedOutput += outputReference;
528
548
  // 4e. 中断 / 超时仍然算硬失败
529
549
  // 失败也带上已解析的附图:脚本挂掉前截的那张诊断图正是最该看的。
530
550
  if (timedOut || killedBySignal) {
@@ -63,6 +63,8 @@ import { requestUserChoice } from '../../askBridge.js';
63
63
  import { classifyCommand } from '../../irreversible/rules.js';
64
64
  import { startBackgroundTask } from '../tasks/backgroundTaskService.js';
65
65
  import { processExecutionMetadata } from '../tasks/processEvidence.js';
66
+ import { permanentDeleteReason } from '../shared/permanentDeleteGuard.js';
67
+ import { ToolOutputStore } from '../shared/toolOutputStore.js';
66
68
 
67
69
  // ---------------- 0. 常量 ----------------
68
70
 
@@ -372,6 +374,8 @@ async function call(input: BashInput, signal?: AbortSignal, context?: ToolCallCo
372
374
  error: `安全检查拒绝执行:${forbiddenReason}\n命令:${command}`,
373
375
  };
374
376
  }
377
+ const permanentDelete = permanentDeleteReason(command, 'bash');
378
+ if (permanentDelete) return { ok:false, error:`永久删除检查拒绝执行:${permanentDelete}\n命令:${command}` };
375
379
 
376
380
  // 4a-1. 不可逆动作闸门:毁掉「最后一份副本」的命令要**停下来问人**。
377
381
  //
@@ -422,20 +426,21 @@ async function call(input: BashInput, signal?: AbortSignal, context?: ToolCallCo
422
426
  const cwd = context?.cwd ?? getWorkingDir();
423
427
  const task = await startBackgroundTask(
424
428
  WIN_BASH
425
- ? { command, executable: WIN_BASH, args: ['-c', command], cwd, env: sanitizedChildEnv(), sessionId: context?.sessionId, shellKind: 'bash' as const }
429
+ ? { command, executable: WIN_BASH, args: ['-c', command], cwd, env: sanitizedChildEnv(), sessionId: context?.sessionId, workspaceId: context?.workspaceId, shellKind: 'bash' as const }
426
430
  : {
427
431
  command: process.platform === 'win32' ? `chcp 65001 >nul & ${command}` : command,
428
432
  cwd,
429
433
  env: sanitizedChildEnv(),
430
434
  shell: true,
431
435
  sessionId: context?.sessionId,
436
+ workspaceId: context?.workspaceId,
432
437
  shellKind: 'bash' as const,
433
438
  },
434
439
  context?.sessionState?.backgroundTasks,
435
440
  );
436
441
  return {
437
442
  ok: true,
438
- content: `${destructiveWarning ? `⚠️ 警告: ${destructiveWarning}\n` : ''}后台任务已启动。task_id=${task.id}\nlog=${task.logPath}\n使用 TaskOutput 增量读取输出,TaskStop 停止进程。`,
443
+ content: `${destructiveWarning ? `⚠️ 警告: ${destructiveWarning}\n` : ''}后台任务已启动。task_id=${task.id.replace(/^proc_/,'')}\nprocess_id=${task.id}\nlog=${task.logPath}\n使用 TaskOutput 增量读取输出,TaskStop 停止进程。`,
439
444
  metadata: processExecutionMetadata({ shell: 'bash', mode: 'background', command, status: 'started', taskId: task.id }),
440
445
  };
441
446
  } catch (error) {
@@ -449,6 +454,10 @@ async function call(input: BashInput, signal?: AbortSignal, context?: ToolCallCo
449
454
  // Windows 无 bash: cmd.exe,命令前缀 chcp 65001 强制 UTF-8 输出
450
455
  let stdout = '';
451
456
  let stderr = '';
457
+ const outputStore = new ToolOutputStore({
458
+ workspaceId: context?.workspaceId,
459
+ sessionId: context?.sessionId,
460
+ });
452
461
  let exitCode: number | null = null;
453
462
  let timedOut = false;
454
463
  let killedBySignal = false;
@@ -496,12 +505,16 @@ async function call(input: BashInput, signal?: AbortSignal, context?: ToolCallCo
496
505
  child.stdout?.setEncoding('utf8');
497
506
  child.stderr?.setEncoding('utf8');
498
507
  child.stdout?.on('data', (chunk) => {
499
- // G1:即时封顶,防止狂输出命令在最终截断前撑爆内存(OOM)。不强行 kill,
500
- // 让命令自己跑完或被 timeout 收走;超出上限的部分直接丢弃,最终仍按上限截断展示。
501
- if (stdout.length < MAX_STREAM_BUFFER_CHARS) stdout += chunk;
508
+ if (!outputStore.append('stdout', chunk)) {
509
+ child.stdout?.pause();
510
+ outputStore.onDrain(() => child.stdout?.resume());
511
+ }
502
512
  });
503
513
  child.stderr?.on('data', (chunk) => {
504
- if (stderr.length < MAX_STREAM_BUFFER_CHARS) stderr += chunk;
514
+ if (!outputStore.append('stderr', chunk)) {
515
+ child.stderr?.pause();
516
+ outputStore.onDrain(() => child.stderr?.resume());
517
+ }
505
518
  });
506
519
  child.on('error', (e) => {
507
520
  ac.signal.removeEventListener('abort', onKill);
@@ -524,6 +537,9 @@ async function call(input: BashInput, signal?: AbortSignal, context?: ToolCallCo
524
537
  clearTimeout(timer);
525
538
  signal?.removeEventListener('abort', onAbort);
526
539
  }
540
+ const storedOutput = await outputStore.finish();
541
+ stdout = storedOutput.stdout;
542
+ stderr = storedOutput.stderr;
527
543
 
528
544
  // 4b-2. ATTACH_IMAGE sentinel:脚本可以打一行 `<<<ATTACH_IMAGE:path>>>` 让模型亲眼看图。
529
545
  // 协议行**从 content 剥掉**(那是给宿主看的,不是给模型读的噪声);被拒的条目
@@ -555,15 +571,18 @@ async function call(input: BashInput, signal?: AbortSignal, context?: ToolCallCo
555
571
  parts.push(`\n[被信号中断]`);
556
572
  }
557
573
  parts.push(`\n[exit code: ${exitCode === null ? 'killed' : exitCode}]`);
574
+ const outputReference = storedOutput.path
575
+ ? `\n[完整输出: ${storedOutput.path}; bytes=${storedOutput.bytes}; sha256=${storedOutput.sha256}; complete=${storedOutput.complete}]`
576
+ : '';
558
577
 
559
578
  let combinedOutput = parts.join('\n');
560
579
 
561
580
  // 4d. 大小兜底
562
- if (combinedOutput.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
581
+ if (combinedOutput.length + outputReference.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
582
+ const suffix = `\n\n... (输出超过 ${DEFAULT_MAX_RESULT_SIZE_CHARS} 字符,预览已截断)${outputReference}`;
563
583
  combinedOutput =
564
- combinedOutput.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) +
565
- `\n\n... (输出超过 ${DEFAULT_MAX_RESULT_SIZE_CHARS} 字符,已截断)`;
566
- }
584
+ combinedOutput.slice(0, Math.max(0, DEFAULT_MAX_RESULT_SIZE_CHARS - suffix.length)) + suffix;
585
+ } else combinedOutput += outputReference;
567
586
 
568
587
  // 4e. 中断 / 超时仍然算硬失败
569
588
  // 失败也带上已解析的附图:脚本挂掉前截的那张诊断图正是最该看的。
@@ -1,8 +1,13 @@
1
1
  const TOOL_CAPABILITIES = Object.freeze({
2
2
  Read: 'files.read', Glob: 'files.read', Grep: 'files.read',
3
+ Snip: 'files.read',
3
4
  Edit: 'files.write', MultiEdit: 'files.write', Write: 'files.write',
4
5
  Bash: 'shell',
5
6
  PowerShell: 'shell', TaskOutput: 'background.process', TaskStop: 'background.process',
7
+ Trash: 'files.trash',
8
+ TaskCreate: 'task.management', TaskGet: 'task.management', TaskUpdate: 'task.management', TaskList: 'task.management',
9
+ Skill: 'skill.invoke', EnterWorktree: 'worktree.session', ExitWorktree: 'worktree.session',
10
+ TerminalCapture: 'terminal.capture',
6
11
  LSP: 'code.intelligence', NotebookEdit: 'files.write',
7
12
  WebSearch: 'web.search', WebFetch: 'web.fetch', WebBrowser: 'web.browser',
8
13
  Download: 'network.download',
@@ -14,15 +14,25 @@ export type CapabilityId =
14
14
  | 'media'
15
15
  | 'memory'
16
16
  | 'code.intelligence'
17
- | 'background.process';
17
+ | 'background.process'
18
+ | 'files.trash'
19
+ | 'task.management'
20
+ | 'skill.invoke'
21
+ | 'worktree.session'
22
+ | 'terminal.capture';
18
23
 
19
24
  export type ToolBudgetClass = 'core' | 'task' | 'auxiliary';
20
25
 
21
26
  const TOOL_CAPABILITIES: Readonly<Record<string, CapabilityId>> = Object.freeze({
22
27
  Read: 'files.read', Glob: 'files.read', Grep: 'files.read',
28
+ Snip: 'files.read',
23
29
  Edit: 'files.write', MultiEdit: 'files.write', Write: 'files.write',
24
30
  Bash: 'shell',
25
31
  PowerShell: 'shell', TaskOutput: 'background.process', TaskStop: 'background.process',
32
+ Trash: 'files.trash',
33
+ TaskCreate: 'task.management', TaskGet: 'task.management', TaskUpdate: 'task.management', TaskList: 'task.management',
34
+ Skill: 'skill.invoke', EnterWorktree: 'worktree.session', ExitWorktree: 'worktree.session',
35
+ TerminalCapture: 'terminal.capture',
26
36
  LSP: 'code.intelligence', NotebookEdit: 'files.write',
27
37
  WebSearch: 'web.search', WebFetch: 'web.fetch', WebBrowser: 'web.browser',
28
38
  Download: 'network.download',
@@ -20,6 +20,7 @@ import { filePathField } from '../shared/schemas.js';
20
20
  import { assertFresh, hashFileBytes, recordRead, wasTruncated } from '../shared/fileState.js';
21
21
  import { fileActivityMetadata } from '../shared/fileActivity.js';
22
22
  import { emitFileMutation } from '../shared/fileMutationBus.js';
23
+ import { buildPatchSummary } from '../shared/diffSummary.js';
23
24
  /** 编辑文件大小上限:1GB。防止模型试图 Edit 超大文件撑爆内存。 */
24
25
  const MAX_EDIT_FILE_SIZE_BYTES = 1024 * 1024 * 1024;
25
26
  // ---------------- 1. Zod 输入 schema ----------------
@@ -96,10 +97,11 @@ async function call(input, state, cwd) {
96
97
  await atomicWriteFile(filePath, new_string);
97
98
  recordRead(filePath, { contentHash: hashFileBytes(encodeTextFile(new_string, 'utf8')) }, state);
98
99
  emitFileMutation(filePath);
100
+ const patch = buildPatchSummary('', new_string, input.file_path);
99
101
  return {
100
102
  ok: true,
101
- content: `已创建新文件:${filePath}(${new_string.length} 字符)`,
102
- metadata: await fileActivityMetadata(filePath, 'created', 'not_run', 'support', cwd),
103
+ content: `已创建新文件:${filePath}(${new_string.length} 字符)\n${patch.unifiedDiff ?? '[diff omitted: too large]'}`,
104
+ metadata: await fileActivityMetadata(filePath, 'created', 'not_run', 'support', cwd, patch),
103
105
  };
104
106
  }
105
107
  catch (e) {
@@ -186,12 +188,13 @@ async function call(input, state, cwd) {
186
188
  // 输出:报告改了几处 + 行变化
187
189
  const linesBefore = original.split('\n').length;
188
190
  const linesAfter = replaced.split('\n').length;
191
+ const patch = buildPatchSummary(original, normalizedReplaced, input.file_path);
189
192
  return {
190
193
  ok: true,
191
194
  content: `已编辑 ${filePath}\n` +
192
195
  `替换次数:${replaceAll ? occurrences : 1}\n` +
193
- `行数:${linesBefore} → ${linesAfter}`,
194
- metadata: await fileActivityMetadata(filePath, 'modified', 'not_run', 'support', cwd),
196
+ `行数:${linesBefore} → ${linesAfter}\n` + (patch.unifiedDiff ?? '[diff omitted: too large]'),
197
+ metadata: await fileActivityMetadata(filePath, 'modified', 'not_run', 'support', cwd, patch),
195
198
  };
196
199
  }
197
200
  // ---------------- 5. 模糊匹配提示 ----------------
@@ -35,6 +35,7 @@ import { filePathField } from '../shared/schemas.js';
35
35
  import { assertFresh, hashFileBytes, recordRead, wasTruncated, type FileReadState } from '../shared/fileState.js';
36
36
  import { fileActivityMetadata } from '../shared/fileActivity.js';
37
37
  import { emitFileMutation } from '../shared/fileMutationBus.js';
38
+ import { buildPatchSummary } from '../shared/diffSummary.js';
38
39
 
39
40
  /** 编辑文件大小上限:1GB。防止模型试图 Edit 超大文件撑爆内存。 */
40
41
  const MAX_EDIT_FILE_SIZE_BYTES = 1024 * 1024 * 1024;
@@ -124,10 +125,11 @@ async function call(input: EditInput, state?: FileReadState, cwd?: string): Prom
124
125
  await atomicWriteFile(filePath, new_string);
125
126
  recordRead(filePath, { contentHash: hashFileBytes(encodeTextFile(new_string, 'utf8')) }, state);
126
127
  emitFileMutation(filePath);
128
+ const patch = buildPatchSummary('', new_string, input.file_path);
127
129
  return {
128
130
  ok: true,
129
- content: `已创建新文件:${filePath}(${new_string.length} 字符)`,
130
- metadata: await fileActivityMetadata(filePath, 'created', 'not_run', 'support', cwd),
131
+ content: `已创建新文件:${filePath}(${new_string.length} 字符)\n${patch.unifiedDiff ?? '[diff omitted: too large]'}`,
132
+ metadata: await fileActivityMetadata(filePath, 'created', 'not_run', 'support', cwd, patch),
131
133
  };
132
134
  } catch (e) {
133
135
  return { ok: false, error: `创建文件失败:${(e as Error).message}` };
@@ -222,13 +224,14 @@ async function call(input: EditInput, state?: FileReadState, cwd?: string): Prom
222
224
  // 输出:报告改了几处 + 行变化
223
225
  const linesBefore = original.split('\n').length;
224
226
  const linesAfter = replaced.split('\n').length;
227
+ const patch = buildPatchSummary(original, normalizedReplaced, input.file_path);
225
228
  return {
226
229
  ok: true,
227
230
  content:
228
231
  `已编辑 ${filePath}\n` +
229
232
  `替换次数:${replaceAll ? occurrences : 1}\n` +
230
- `行数:${linesBefore} → ${linesAfter}`,
231
- metadata: await fileActivityMetadata(filePath, 'modified', 'not_run', 'support', cwd),
233
+ `行数:${linesBefore} → ${linesAfter}\n` + (patch.unifiedDiff ?? '[diff omitted: too large]'),
234
+ metadata: await fileActivityMetadata(filePath, 'modified', 'not_run', 'support', cwd, patch),
232
235
  };
233
236
  }
234
237
 
@@ -22,6 +22,7 @@ import { filePathField } from '../shared/schemas.js';
22
22
  import { assertFresh, hashFileBytes, recordRead, wasTruncated } from '../shared/fileState.js';
23
23
  import { fileActivityMetadata } from '../shared/fileActivity.js';
24
24
  import { emitFileMutation } from '../shared/fileMutationBus.js';
25
+ import { buildPatchSummary } from '../shared/diffSummary.js';
25
26
  // ---------------- 1. Zod 输入 schema ----------------
26
27
  const editItemSchema = z.object({
27
28
  old_string: z.string().min(1).describe('要替换的原文本(不允许为空 —— 创建新文件请用 Edit 工具)'),
@@ -168,10 +169,11 @@ async function call(input, state, cwd) {
168
169
  catch (e) {
169
170
  return { ok: false, error: `写入失败:${e.message}` };
170
171
  }
172
+ const patch = buildPatchSummary(originalContent, finalContent, input.file_path);
171
173
  return {
172
174
  ok: true,
173
- content: `已对 ${filePath} 应用 ${edits.length} 处修改`,
174
- metadata: await fileActivityMetadata(filePath, 'modified', 'not_run', 'support', cwd),
175
+ content: `已对 ${filePath} 应用 ${edits.length} 处修改\n${patch.unifiedDiff ?? '[diff omitted: too large]'}`,
176
+ metadata: await fileActivityMetadata(filePath, 'modified', 'not_run', 'support', cwd, patch),
175
177
  };
176
178
  }
177
179
  // ---------------- 5. 导出 ----------------
@@ -37,6 +37,7 @@ import { filePathField } from '../shared/schemas.js';
37
37
  import { assertFresh, hashFileBytes, recordRead, wasTruncated, type FileReadState } from '../shared/fileState.js';
38
38
  import { fileActivityMetadata } from '../shared/fileActivity.js';
39
39
  import { emitFileMutation } from '../shared/fileMutationBus.js';
40
+ import { buildPatchSummary } from '../shared/diffSummary.js';
40
41
 
41
42
  // ---------------- 1. Zod 输入 schema ----------------
42
43
  const editItemSchema = z.object({
@@ -199,10 +200,11 @@ async function call(input: MultiEditInput, state?: FileReadState, cwd?: string):
199
200
  return { ok: false, error: `写入失败:${(e as Error).message}` };
200
201
  }
201
202
 
203
+ const patch = buildPatchSummary(originalContent, finalContent, input.file_path);
202
204
  return {
203
205
  ok: true,
204
- content: `已对 ${filePath} 应用 ${edits.length} 处修改`,
205
- metadata: await fileActivityMetadata(filePath, 'modified', 'not_run', 'support', cwd),
206
+ content: `已对 ${filePath} 应用 ${edits.length} 处修改\n${patch.unifiedDiff ?? '[diff omitted: too large]'}`,
207
+ metadata: await fileActivityMetadata(filePath, 'modified', 'not_run', 'support', cwd, patch),
206
208
  };
207
209
  }
208
210