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.
- package/package.json +4 -1
- package/plugins/workflow-runner/plugin.js +27 -15
- package/plugins/workflow-runner/plugin.ts +34 -20
- package/plugins/workflow-runner/src/agentCommon.js +13 -12
- package/plugins/workflow-runner/src/agentCommon.ts +22 -13
- package/plugins/workflow-runner/src/index.js +5 -3
- package/plugins/workflow-runner/src/index.ts +31 -13
- package/plugins/workflow-runner/src/multimodal.js +2 -1
- package/plugins/workflow-runner/src/multimodal.ts +2 -1
- package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +12 -7
- package/plugins/workflow-runner/src/stepExecutors/tool.js +68 -57
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +88 -66
- package/plugins/workflow-runner/src/toolRuntime.js +48 -0
- package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
- package/plugins/workflow-runner/src/types.ts +26 -7
- package/plugins/workflow-runner/test/context-files.test.ts +47 -16
- package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
- package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
- package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
- package/src/cli/print.js +19 -4
- package/src/cli/print.ts +17 -4
- package/src/cli/streamJson.js +19 -4
- package/src/cli/streamJson.ts +17 -4
- package/src/config/configDefaults.js +25 -0
- package/src/config/configDefaults.ts +33 -0
- package/src/config/configFile.js +2 -21
- package/src/config/configFile.ts +11 -24
- package/src/config/configManagement.js +1 -1
- package/src/config/configManagement.ts +2 -2
- package/src/context/compact.js +4 -0
- package/src/context/compact.ts +4 -0
- package/src/context/contextBundle.js +38 -13
- package/src/context/contextBundle.ts +47 -13
- package/src/harness/stallGuard.js +19 -4
- package/src/harness/stallGuard.ts +30 -7
- package/src/loop.js +95 -11
- package/src/loop.ts +125 -16
- package/src/main.js +8 -1
- package/src/main.tsx +7 -1
- package/src/memory/constants.js +10 -0
- package/src/memory/constants.ts +13 -0
- package/src/memory/episodicMemory.js +274 -2
- package/src/memory/episodicMemory.ts +306 -1
- package/src/memory/fileStore.js +24 -4
- package/src/memory/fileStore.ts +22 -2
- package/src/memory/memoryService.js +79 -12
- package/src/memory/memoryService.ts +108 -9
- package/src/memory/repository.js +415 -85
- package/src/memory/repository.ts +437 -86
- package/src/memory/turnJournal.js +95 -0
- package/src/memory/turnJournal.ts +120 -0
- package/src/memory/types.ts +16 -0
- package/src/plugins/pluginApi.ts +30 -6
- package/src/plugins/pluginRunner.js +6 -2
- package/src/plugins/pluginRunner.ts +12 -7
- package/src/prompts/policyRegistry.js +27 -0
- package/src/prompts/policyRegistry.ts +27 -0
- package/src/prompts/promptAssembly.js +39 -7
- package/src/prompts/promptAssembly.ts +145 -59
- package/src/session/runAgentTurn.js +43 -21
- package/src/session/runAgentTurn.ts +68 -26
- package/src/session/runTurn.js +11 -46
- package/src/session/runTurn.ts +12 -46
- package/src/session/runtimeStore.js +149 -0
- package/src/session/runtimeStore.ts +197 -0
- package/src/session/sessionState.js +5 -0
- package/src/session/sessionState.ts +14 -0
- package/src/tools/agent/agent.js +143 -14
- package/src/tools/agent/agent.ts +149 -14
- package/src/tools/agent/agentTaskService.js +60 -0
- package/src/tools/agent/agentTaskService.ts +113 -0
- package/src/tools/agent/agentTypes.js +5 -0
- package/src/tools/agent/agentTypes.ts +5 -0
- package/src/tools/bash/bash.js +31 -11
- package/src/tools/bash/bash.ts +29 -10
- package/src/tools/capabilities.js +5 -0
- package/src/tools/capabilities.ts +11 -1
- package/src/tools/edit/edit.js +7 -4
- package/src/tools/edit/edit.ts +7 -4
- package/src/tools/edit/multi-edit.js +4 -2
- package/src/tools/edit/multi-edit.ts +4 -2
- package/src/tools/glob/glob.js +156 -40
- package/src/tools/glob/glob.ts +185 -46
- package/src/tools/index.js +35 -17
- package/src/tools/index.ts +81 -20
- package/src/tools/lsp/lsp.js +165 -22
- package/src/tools/lsp/lsp.ts +206 -36
- package/src/tools/powershell/powershell.js +116 -26
- package/src/tools/powershell/powershell.ts +155 -30
- package/src/tools/presentresult/presentresult.js +10 -0
- package/src/tools/presentresult/presentresult.ts +10 -0
- package/src/tools/read/read.js +244 -29
- package/src/tools/read/read.ts +391 -59
- package/src/tools/shared/diffSummary.js +44 -0
- package/src/tools/shared/diffSummary.ts +63 -0
- package/src/tools/shared/fileActivity.js +2 -1
- package/src/tools/shared/fileActivity.ts +3 -0
- package/src/tools/shared/permanentDeleteGuard.js +26 -0
- package/src/tools/shared/permanentDeleteGuard.ts +34 -0
- package/src/tools/shared/toolOutputStore.js +97 -0
- package/src/tools/shared/toolOutputStore.ts +110 -0
- package/src/tools/skill/skill.js +86 -0
- package/src/tools/skill/skill.ts +94 -0
- package/src/tools/snip/snip.js +67 -0
- package/src/tools/snip/snip.ts +80 -0
- package/src/tools/tasks/backgroundTaskService.js +79 -28
- package/src/tools/tasks/backgroundTaskService.ts +165 -49
- package/src/tools/tasks/taskManagement.js +163 -0
- package/src/tools/tasks/taskManagement.ts +180 -0
- package/src/tools/tasks/taskOutput.js +64 -14
- package/src/tools/tasks/taskOutput.ts +102 -23
- package/src/tools/tasks/taskStop.js +48 -8
- package/src/tools/tasks/taskStop.ts +69 -9
- package/src/tools/tasks/taskStore.js +46 -0
- package/src/tools/tasks/taskStore.ts +77 -0
- package/src/tools/terminal/terminalCapture.js +167 -0
- package/src/tools/terminal/terminalCapture.ts +179 -0
- package/src/tools/trash/trash.js +233 -0
- package/src/tools/trash/trash.ts +283 -0
- package/src/tools/worktree/sessionWorktree.js +128 -0
- package/src/tools/worktree/sessionWorktree.ts +158 -0
- package/src/tools/write/write.js +6 -2
- package/src/tools/write/write.ts +6 -2
- package/src/types.ts +12 -0
- package/src/ui/hooks/useChat.js +31 -11
- package/src/ui/hooks/useChat.ts +31 -13
- 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」文案中文化。 */
|
package/src/tools/bash/bash.js
CHANGED
|
@@ -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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
|
466
|
-
stderr
|
|
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) {
|
package/src/tools/bash/bash.ts
CHANGED
|
@@ -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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
|
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
|
-
|
|
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',
|
package/src/tools/edit/edit.js
CHANGED
|
@@ -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. 模糊匹配提示 ----------------
|
package/src/tools/edit/edit.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|