@zooique/memora 3.0.1 → 5.0.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/CHANGELOG.md +161 -1033
- package/README.en.md +46 -39
- package/README.md +40 -35
- package/config.example.json +1 -8
- package/dist/agent/agent.d.ts +88 -80
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +272 -221
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/assembler.d.ts +22 -21
- package/dist/agent/assembler.d.ts.map +1 -1
- package/dist/agent/assembler.js +120 -46
- package/dist/agent/assembler.js.map +1 -1
- package/dist/agent/backgroundTasks.d.ts +194 -0
- package/dist/agent/backgroundTasks.d.ts.map +1 -0
- package/dist/agent/backgroundTasks.js +314 -0
- package/dist/agent/backgroundTasks.js.map +1 -0
- package/dist/agent/budget.d.ts +23 -5
- package/dist/agent/budget.d.ts.map +1 -1
- package/dist/agent/budget.js +8 -2
- package/dist/agent/budget.js.map +1 -1
- package/dist/agent/builtinToolHandlers.d.ts +29 -9
- package/dist/agent/builtinToolHandlers.d.ts.map +1 -1
- package/dist/agent/builtinToolHandlers.js +53 -13
- package/dist/agent/builtinToolHandlers.js.map +1 -1
- package/dist/agent/builtinTools.d.ts +15 -1
- package/dist/agent/builtinTools.d.ts.map +1 -1
- package/dist/agent/builtinTools.js +111 -30
- package/dist/agent/builtinTools.js.map +1 -1
- package/dist/agent/constants.d.ts +31 -6
- package/dist/agent/constants.d.ts.map +1 -1
- package/dist/agent/constants.js +38 -8
- package/dist/agent/constants.js.map +1 -1
- package/dist/agent/contextManager.d.ts +8 -0
- package/dist/agent/contextManager.d.ts.map +1 -1
- package/dist/agent/contextManager.js +45 -3
- package/dist/agent/contextManager.js.map +1 -1
- package/dist/agent/contextPreparer.d.ts +1 -1
- package/dist/agent/contextPreparer.d.ts.map +1 -1
- package/dist/agent/contextPreparer.js +15 -10
- package/dist/agent/contextPreparer.js.map +1 -1
- package/dist/agent/guardRail.d.ts +12 -1
- package/dist/agent/guardRail.d.ts.map +1 -1
- package/dist/agent/guardRail.js +37 -2
- package/dist/agent/guardRail.js.map +1 -1
- package/dist/agent/loop.d.ts +319 -34
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +713 -110
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/managers/archiveCoordinator.d.ts +19 -4
- package/dist/agent/managers/archiveCoordinator.d.ts.map +1 -1
- package/dist/agent/managers/archiveCoordinator.js +25 -4
- package/dist/agent/managers/archiveCoordinator.js.map +1 -1
- package/dist/agent/managers/chatLockManager.d.ts +3 -9
- package/dist/agent/managers/chatLockManager.d.ts.map +1 -1
- package/dist/agent/managers/chatLockManager.js +7 -25
- package/dist/agent/managers/chatLockManager.js.map +1 -1
- package/dist/agent/managers/llmCaller.d.ts.map +1 -1
- package/dist/agent/managers/llmCaller.js +27 -1
- package/dist/agent/managers/llmCaller.js.map +1 -1
- package/dist/agent/managers/loopMetrics.d.ts +14 -0
- package/dist/agent/managers/loopMetrics.d.ts.map +1 -1
- package/dist/agent/managers/loopMetrics.js +15 -0
- package/dist/agent/managers/loopMetrics.js.map +1 -1
- package/dist/agent/managers/memoryInspector.d.ts +13 -15
- package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
- package/dist/agent/managers/memoryInspector.js +38 -49
- package/dist/agent/managers/memoryInspector.js.map +1 -1
- package/dist/agent/managers/roundSummaryGenerator.d.ts +31 -1
- package/dist/agent/managers/roundSummaryGenerator.d.ts.map +1 -1
- package/dist/agent/managers/roundSummaryGenerator.js +81 -6
- package/dist/agent/managers/roundSummaryGenerator.js.map +1 -1
- package/dist/agent/managers/sessionArchiver.d.ts +35 -3
- package/dist/agent/managers/sessionArchiver.d.ts.map +1 -1
- package/dist/agent/managers/sessionArchiver.js +34 -3
- package/dist/agent/managers/sessionArchiver.js.map +1 -1
- package/dist/agent/managers/sessionManager.d.ts +21 -88
- package/dist/agent/managers/sessionManager.d.ts.map +1 -1
- package/dist/agent/managers/sessionManager.js +94 -278
- package/dist/agent/managers/sessionManager.js.map +1 -1
- package/dist/agent/managers/toolCallHelpers.d.ts +109 -28
- package/dist/agent/managers/toolCallHelpers.d.ts.map +1 -1
- package/dist/agent/managers/toolCallHelpers.js +93 -37
- package/dist/agent/managers/toolCallHelpers.js.map +1 -1
- package/dist/agent/messageHistory.d.ts.map +1 -1
- package/dist/agent/messageHistory.js +9 -5
- package/dist/agent/messageHistory.js.map +1 -1
- package/dist/agent/seed/orchestrator.d.ts +12 -1
- package/dist/agent/seed/orchestrator.d.ts.map +1 -1
- package/dist/agent/seed/orchestrator.js +15 -4
- package/dist/agent/seed/orchestrator.js.map +1 -1
- package/dist/agent/seed/prepare.d.ts.map +1 -1
- package/dist/agent/seed/prepare.js +9 -4
- package/dist/agent/seed/prepare.js.map +1 -1
- package/dist/agent/seed/types.d.ts +11 -4
- package/dist/agent/seed/types.d.ts.map +1 -1
- package/dist/agent/seed/types.js +2 -2
- package/dist/agent/textSanitize.d.ts +36 -0
- package/dist/agent/textSanitize.d.ts.map +1 -0
- package/dist/agent/textSanitize.js +55 -0
- package/dist/agent/textSanitize.js.map +1 -0
- package/dist/agent/toolExecutor.d.ts +97 -26
- package/dist/agent/toolExecutor.d.ts.map +1 -1
- package/dist/agent/toolExecutor.js +444 -78
- package/dist/agent/toolExecutor.js.map +1 -1
- package/dist/agent/toolLedger.d.ts +93 -41
- package/dist/agent/toolLedger.d.ts.map +1 -1
- package/dist/agent/toolLedger.js +114 -42
- package/dist/agent/toolLedger.js.map +1 -1
- package/dist/agent/toolResultCache.js +1 -1
- package/dist/agent/toolResultOffload.d.ts +8 -0
- package/dist/agent/toolResultOffload.d.ts.map +1 -1
- package/dist/agent/toolResultOffload.js +10 -3
- package/dist/agent/toolResultOffload.js.map +1 -1
- package/dist/agent/toolRunner.d.ts +16 -7
- package/dist/agent/toolRunner.d.ts.map +1 -1
- package/dist/agent/toolRunner.js +74 -29
- package/dist/agent/toolRunner.js.map +1 -1
- package/dist/agent/tracer.d.ts +13 -1
- package/dist/agent/tracer.d.ts.map +1 -1
- package/dist/agent/types.d.ts +74 -30
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/code-exec/types.d.ts +9 -0
- package/dist/code-exec/types.d.ts.map +1 -1
- package/dist/index.d.ts +28 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +65 -54
- package/dist/index.js.map +1 -1
- package/dist/logging/logger.d.ts +4 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +5 -2
- package/dist/logging/logger.js.map +1 -1
- package/dist/memory/gcService.d.ts.map +1 -1
- package/dist/memory/gcService.js +3 -5
- package/dist/memory/gcService.js.map +1 -1
- package/dist/memory/inMemorySessionStore.d.ts +2 -2
- package/dist/memory/inMemorySessionStore.js +2 -2
- package/dist/memory/inMemoryStorage.d.ts +4 -3
- package/dist/memory/inMemoryStorage.d.ts.map +1 -1
- package/dist/memory/inMemoryStorage.js +2 -2
- package/dist/memory/inMemoryStorage.js.map +1 -1
- package/dist/memory/nodeSqliteStorage.d.ts +17 -0
- package/dist/memory/nodeSqliteStorage.d.ts.map +1 -0
- package/dist/memory/nodeSqliteStorage.js +393 -0
- package/dist/memory/nodeSqliteStorage.js.map +1 -0
- package/dist/memory/projectManager.d.ts +10 -20
- package/dist/memory/projectManager.d.ts.map +1 -1
- package/dist/memory/projectManager.js +27 -41
- package/dist/memory/projectManager.js.map +1 -1
- package/dist/memory/roundStore.d.ts +143 -82
- package/dist/memory/roundStore.d.ts.map +1 -1
- package/dist/memory/roundStore.js +4 -15
- package/dist/memory/roundStore.js.map +1 -1
- package/dist/memory/roundSummaryLookup.d.ts +26 -0
- package/dist/memory/roundSummaryLookup.d.ts.map +1 -0
- package/dist/memory/roundSummaryLookup.js +19 -0
- package/dist/memory/roundSummaryLookup.js.map +1 -0
- package/dist/memory/sessionStore.d.ts +19 -2
- package/dist/memory/sessionStore.d.ts.map +1 -1
- package/dist/memory/sessionStore.js.map +1 -1
- package/dist/memory/sourceValidation.d.ts +2 -13
- package/dist/memory/sourceValidation.d.ts.map +1 -1
- package/dist/memory/sourceValidation.js +5 -16
- package/dist/memory/sourceValidation.js.map +1 -1
- package/dist/memory/storageInterface.d.ts +26 -16
- package/dist/memory/storageInterface.d.ts.map +1 -1
- package/dist/memory/supersedeResolve.d.ts +39 -0
- package/dist/memory/supersedeResolve.d.ts.map +1 -0
- package/dist/memory/supersedeResolve.js +16 -0
- package/dist/memory/supersedeResolve.js.map +1 -0
- package/dist/memory/types.d.ts +10 -2
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +6 -1
- package/dist/memory/types.js.map +1 -1
- package/dist/role-pack/rolePackManager.d.ts +74 -33
- package/dist/role-pack/rolePackManager.d.ts.map +1 -1
- package/dist/role-pack/rolePackManager.js +112 -53
- package/dist/role-pack/rolePackManager.js.map +1 -1
- package/dist/role-pack/strategyKeys.d.ts +44 -0
- package/dist/role-pack/strategyKeys.d.ts.map +1 -1
- package/dist/role-pack/strategyKeys.js +55 -0
- package/dist/role-pack/strategyKeys.js.map +1 -1
- package/dist/role-pack/strategyResolver.d.ts +6 -1
- package/dist/role-pack/strategyResolver.d.ts.map +1 -1
- package/dist/role-pack/strategyResolver.js +20 -2
- package/dist/role-pack/strategyResolver.js.map +1 -1
- package/dist/role-pack/types.d.ts +5 -1
- package/dist/role-pack/types.d.ts.map +1 -1
- package/dist/security/confirmEntries.d.ts +44 -0
- package/dist/security/confirmEntries.d.ts.map +1 -0
- package/dist/security/confirmEntries.js +59 -0
- package/dist/security/confirmEntries.js.map +1 -0
- package/dist/security/pathGuard.d.ts +42 -6
- package/dist/security/pathGuard.d.ts.map +1 -1
- package/dist/security/pathGuard.js +126 -5
- package/dist/security/pathGuard.js.map +1 -1
- package/dist/skill/skillScriptRunner.d.ts +147 -15
- package/dist/skill/skillScriptRunner.d.ts.map +1 -1
- package/dist/skill/skillScriptRunner.js +479 -68
- package/dist/skill/skillScriptRunner.js.map +1 -1
- package/dist/utils/backgroundTask.d.ts +56 -34
- package/dist/utils/backgroundTask.d.ts.map +1 -1
- package/dist/utils/backgroundTask.js +130 -99
- package/dist/utils/backgroundTask.js.map +1 -1
- package/dist/utils/configResourceManager.d.ts +6 -2
- package/dist/utils/configResourceManager.d.ts.map +1 -1
- package/dist/utils/configResourceManager.js +8 -2
- package/dist/utils/configResourceManager.js.map +1 -1
- package/dist/utils/errors.d.ts +12 -2
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +12 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/eventEmitter.d.ts +76 -23
- package/dist/utils/eventEmitter.d.ts.map +1 -1
- package/dist/utils/eventEmitter.js +23 -3
- package/dist/utils/eventEmitter.js.map +1 -1
- package/dist/utils/time.d.ts +8 -5
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +8 -5
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/toError.d.ts +1 -1
- package/dist/utils/toError.js +1 -1
- package/dist/web-fetch/loginWall.d.ts +39 -0
- package/dist/web-fetch/loginWall.d.ts.map +1 -0
- package/dist/web-fetch/loginWall.js +78 -0
- package/dist/web-fetch/loginWall.js.map +1 -0
- package/dist/web-fetch/types.d.ts +8 -0
- package/dist/web-fetch/types.d.ts.map +1 -1
- package/dist/web-fetch/webFetchProvider.d.ts.map +1 -1
- package/dist/web-fetch/webFetchProvider.js +3 -1
- package/dist/web-fetch/webFetchProvider.js.map +1 -1
- package/dist/web-search/fetchWebSearchProvider.js +28 -23
- package/dist/web-search/fetchWebSearchProvider.js.map +1 -1
- package/docs/architecture/role-pack-authoring-guide.md +3 -1
- package/docs/architecture/role-pack-spec.md +2 -1
- package/docs/memora-api-reference.md +1349 -1175
- package/docs/memora-/345/215/207/347/272/247/346/214/207/345/215/227-4.x-/345/210/260-5.0.0.md +385 -0
- package/docs/memora-/346/216/245/345/205/245/346/214/207/345/215/227.md +91 -52
- package/docs/role-pack-/345/274/200/346/224/276/351/224/256/346/214/207/345/215/227.md +148 -148
- package/package.json +9 -4
- package/role-packs/README.md +41 -40
- package/role-packs/memora/345/212/251/346/211/213/rules.md +4 -4
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/manifest.json +1 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/persona.md +1 -1
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/rules.md +1 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/emotion-wave.md +1 -1
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/foreshadow.md +1 -1
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/outline-expand.md +32 -5
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/project-init/scripts/init-novel-project.mjs +10 -6
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/story-bible.md +3 -1
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/manifest.json +1 -0
- package/dist/agent/managers/dedupManager.d.ts +0 -85
- package/dist/agent/managers/dedupManager.d.ts.map +0 -1
- package/dist/agent/managers/dedupManager.js +0 -248
- package/dist/agent/managers/dedupManager.js.map +0 -1
- package/dist/agent/managers/goalConsistencyChecker.d.ts +0 -42
- package/dist/agent/managers/goalConsistencyChecker.d.ts.map +0 -1
- package/dist/agent/managers/goalConsistencyChecker.js +0 -178
- package/dist/agent/managers/goalConsistencyChecker.js.map +0 -1
- package/dist/agent/managers/llmJudgeHelper.d.ts +0 -21
- package/dist/agent/managers/llmJudgeHelper.d.ts.map +0 -1
- package/dist/agent/managers/llmJudgeHelper.js +0 -32
- package/dist/agent/managers/llmJudgeHelper.js.map +0 -1
- package/dist/agent/managers/memoryAdvisor.d.ts +0 -105
- package/dist/agent/managers/memoryAdvisor.d.ts.map +0 -1
- package/dist/agent/managers/memoryAdvisor.js +0 -322
- package/dist/agent/managers/memoryAdvisor.js.map +0 -1
- package/dist/agent/managers/memoryGovernance.d.ts +0 -24
- package/dist/agent/managers/memoryGovernance.d.ts.map +0 -1
- package/dist/agent/managers/memoryGovernance.js +0 -46
- package/dist/agent/managers/memoryGovernance.js.map +0 -1
- package/dist/memory/governance.d.ts +0 -17
- package/dist/memory/governance.d.ts.map +0 -1
- package/dist/memory/governance.js +0 -17
- package/dist/memory/governance.js.map +0 -1
- package/dist/memory/projectRegistry.d.ts +0 -47
- package/dist/memory/projectRegistry.d.ts.map +0 -1
- package/dist/memory/projectRegistry.js +0 -151
- package/dist/memory/projectRegistry.js.map +0 -1
- package/dist/utils/recallDefaults.d.ts +0 -16
- package/dist/utils/recallDefaults.d.ts.map +0 -1
- package/dist/utils/recallDefaults.js +0 -16
- package/dist/utils/recallDefaults.js.map +0 -1
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import { requireConfirmingEntry } from '../security/confirmEntries.js';
|
|
1
2
|
import { toolError, configError, MemoraError, ToolErrorCode } from '../utils/errors.js';
|
|
2
3
|
import { toError } from '../utils/toError.js';
|
|
3
4
|
import { logger } from '../logging/logger.js';
|
|
4
5
|
import { truncate } from '../utils/strings.js';
|
|
5
|
-
import { ALL_BUILTIN_TOOL_DEFS, BUILTIN_TOOLS, WEB_SEARCH_TOOL, WEB_FETCH_TOOL, WEB_FETCH_CONTENT_MAX_LEN, WEB_FETCH_CONTENT_DEFAULT_LEN, RUN_CODE_TOOL, SEARCH_PROJECT_TOOL, } from '../agent/builtinTools.js';
|
|
6
|
+
import { ALL_BUILTIN_TOOL_DEFS, BUILTIN_TOOLS, WEB_SEARCH_TOOL, WEB_FETCH_TOOL, WEB_FETCH_CONTENT_MAX_LEN, WEB_FETCH_CONTENT_DEFAULT_LEN, RUN_CODE_TOOL, buildRunCodeTool, SEARCH_PROJECT_TOOL, } from '../agent/builtinTools.js';
|
|
7
|
+
import { sanitizeExternalText, stripControlChars } from '../agent/textSanitize.js';
|
|
8
|
+
import { okOutcome, failedOutcome, failedOutcomeWithCode, blockedOutcome, } from '../agent/managers/toolCallHelpers.js';
|
|
6
9
|
import { BuiltinToolHandlers } from '../agent/builtinToolHandlers.js';
|
|
7
10
|
import { safeSearch } from '../web-search/webSearchProvider.js';
|
|
8
11
|
import { safeFetch } from '../web-fetch/webFetchProvider.js';
|
|
12
|
+
import { isLoginWallPage } from '../web-fetch/loginWall.js';
|
|
9
13
|
import { safeExecuteCode } from '../code-exec/codeExecutionProvider.js';
|
|
10
|
-
import { formatExecutionResult, formatScriptResult, runSkillScript, } from '../skill/skillScriptRunner.js';
|
|
14
|
+
import { formatExecutionResult, formatScriptResult, formatCommandResult, formatKilledCommandOutput, resolveCommandTimeoutMs, runShellCommand, runSkillScript, } from '../skill/skillScriptRunner.js';
|
|
11
15
|
import { resolveSafePath, inferRuntimeFromExt } from '../utils/scanner.js';
|
|
12
16
|
import { safeSearchProjectFiles, safeSearchProjectText, } from '../project-search/projectSearchProvider.js';
|
|
13
17
|
import { buildSearchTerms } from '../project-search/terms.js';
|
|
@@ -17,6 +21,13 @@ export { BUILTIN_TOOLS, BUILTIN_TOOL_IDEMPOTENCY } from '../agent/builtinTools.j
|
|
|
17
21
|
const WEB_SEARCH_QUERY_MAX_LEN = 200;
|
|
18
22
|
/** web_search 单条结果字段最大长度(防长上下文注入) */
|
|
19
23
|
const WEB_SEARCH_RESULT_MAX_LEN = 500;
|
|
24
|
+
/**
|
|
25
|
+
* web_search 观测留痕的最大标题条数(证据面小帽)
|
|
26
|
+
*
|
|
27
|
+
* 留痕目的是归因取样(「搜到了什么」),不是全量镜像——全量计数由 count 承载,
|
|
28
|
+
* 标题只留前 N 条样本。与结果字段上限同族:证据落盘体积有界。
|
|
29
|
+
*/
|
|
30
|
+
const WEB_SEARCH_EVIDENCE_MAX_TITLES = 5;
|
|
20
31
|
// ─── web_fetch / run_code 注入防御常量 ─────────────────
|
|
21
32
|
/** web_fetch 的 url 最大长度(防超长/恶意 URL 滥用) */
|
|
22
33
|
const WEB_FETCH_URL_MAX_LEN = 2000;
|
|
@@ -44,9 +55,33 @@ function normalizeScriptRuntime(language) {
|
|
|
44
55
|
return 'shell';
|
|
45
56
|
return 'node';
|
|
46
57
|
}
|
|
47
|
-
// ─── run_skill_script 注入防御常量 ─────────────────
|
|
48
|
-
/**
|
|
58
|
+
// ─── run_skill_script / run_command 注入防御常量 ─────────────────
|
|
59
|
+
/**
|
|
60
|
+
* 脚本 / 命令单次结果最大长度(防刷屏撑爆上下文;对齐 run_code 的 RUN_CODE_RESULT_MAX_LEN)
|
|
61
|
+
*
|
|
62
|
+
* ⚠️ 这是**上下文层**的字符上限,与收集侧的**内存护栏**不是一回事:
|
|
63
|
+
* `skillScriptRunner.MAX_COLLECTED_OUTPUT_BYTES`(字节量纲,防 Node 进程内存膨胀)。
|
|
64
|
+
* 两者量纲不同(字符 / 字节 / token 三层各司其职),**禁止互相对齐或合并**——
|
|
65
|
+
* 合并会让「内存护栏」退化成「上下文截断」,或让后者在 CJK 下实际越界。
|
|
66
|
+
*/
|
|
49
67
|
const RUN_SCRIPT_RESULT_MAX_LEN = 20_000;
|
|
68
|
+
/**
|
|
69
|
+
* run_command 单条命令最大长度(防超长命令滥用)
|
|
70
|
+
*
|
|
71
|
+
* 对齐 `RUN_CODE_CODE_MAX_LEN`(50_000)的注入防御口径:LLM 可能吐出整份文件内容当命令传,
|
|
72
|
+
* 而命令原文会**四处扩散**——裁决链正则、确认卡 UI 展示、审计事件、turn 终态脱管报告
|
|
73
|
+
* (脱管报告把每条命令原文写进 system 消息)。不限长 = 一个超长命令污染整条链路。
|
|
74
|
+
*/
|
|
75
|
+
const RUN_COMMAND_MAX_LEN = 50_000;
|
|
76
|
+
/**
|
|
77
|
+
* 脚本 / 代码长输出的**尾部保留**字符数(缺口 D)
|
|
78
|
+
*
|
|
79
|
+
* 只服务「关键信息常在尾部」的长输出(构建失败原因、堆栈末尾、FAIL 汇总行)。
|
|
80
|
+
* 网页正文 / 技能文档 / 资源文件等**头部即要点**的内容**不传**此参数(默认 0,
|
|
81
|
+
* 保持纯头截断——尾部对它们是页脚噪声)。
|
|
82
|
+
* 是否保留尾部由**调用点显式声明**,不在函数内按内容猜测(猜测 = 隐式分支 + 不可预测)。
|
|
83
|
+
*/
|
|
84
|
+
const SCRIPT_RESULT_TAIL_CHARS = 4_000;
|
|
50
85
|
// ─── search_project 注入防御常量 ─────────────────
|
|
51
86
|
/** search_project 的 query 最大长度(防超长 glob/关键词滥用) */
|
|
52
87
|
const PROJECT_SEARCH_QUERY_MAX_LEN = 500;
|
|
@@ -188,36 +223,15 @@ const SKILL_CONTENT_MAX_LEN = 50_000;
|
|
|
188
223
|
/** read_resource 资源正文单次返回最大长度(防超长资源注入上下文;与 read_skill 同标准) */
|
|
189
224
|
const RESOURCE_CONTENT_MAX_LEN = 50_000;
|
|
190
225
|
/**
|
|
191
|
-
*
|
|
226
|
+
* 外部文本净化(转发 re-export,保持既有 import 面不破)
|
|
192
227
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
228
|
+
* 实现真源已迁至 `textSanitize.ts`:净化是**跨模块**的最后一道闸(工具返回 / 脚本与资源正文 /
|
|
229
|
+
* 命令结果三处消费),住在本文件有两个后果——① `builtinToolHandlers` 反向 import 本模块
|
|
230
|
+
* 形成循环依赖;② 命令结果的定长上限成了本模块的**私有常量**,第二个消费面
|
|
231
|
+
* (后台完成回流)看不到它 ⇒ 回流路径无长度上限。
|
|
232
|
+
* 本处只做转发,既有调用点无需改动;新代码直接 import `textSanitize.js`。
|
|
198
233
|
*/
|
|
199
|
-
export
|
|
200
|
-
// 去 ANSI 转义序列(CSI:ESC [ 参数 + 终结符;子进程经 env 继承可能拿到
|
|
201
|
-
// FORCE_COLOR 输出色码,单剥 ESC 会留 `[33m` 残渣——整个序列须剥净)
|
|
202
|
-
const withoutAnsi = text.replace(/\u001B\[[0-9;?]*[a-zA-Z]/g, '');
|
|
203
|
-
// 去控制字符:保留可打印字符(含 \t 制表符),其余控制字符移除
|
|
204
|
-
return withoutAnsi.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '');
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* 外部工具返回净化:去控制字符 + 长度上限
|
|
208
|
-
*
|
|
209
|
-
* 净化规则本身抽为 `stripControlChars`(**单一真理源**),供需要「净化但不限长」的调用方复用——
|
|
210
|
-
* 例如 `read_file`:它的长度由 **token 预算分段**(`sliceFileByLineBudget`)收口,不走字符上限,
|
|
211
|
-
* 但仍须剥掉 ANSI/控制字符。否则它只能传一个假的上限(如 MAX_SAFE_INTEGER)来迁就本函数签名。
|
|
212
|
-
*
|
|
213
|
-
* @param text 外部原始文本
|
|
214
|
-
* @param maxLen 最大长度
|
|
215
|
-
* @returns 净化后的文本
|
|
216
|
-
*/
|
|
217
|
-
export function sanitizeExternalText(text, maxLen) {
|
|
218
|
-
const cleaned = stripControlChars(text);
|
|
219
|
-
return cleaned.length > maxLen ? `${cleaned.slice(0, maxLen)}…` : cleaned;
|
|
220
|
-
}
|
|
234
|
+
export { stripControlChars, sanitizeExternalText } from '../agent/textSanitize.js';
|
|
221
235
|
/**
|
|
222
236
|
* 解析 task_table_update 的 plan_item_id 为真实任务项 uuid(唯一解析点)。
|
|
223
237
|
*
|
|
@@ -313,6 +327,12 @@ export const DEFAULT_EXPOSED_TOOLS = new Set([
|
|
|
313
327
|
'list_skills',
|
|
314
328
|
// 项目内既有脚本执行(判据 A+B:仓库已沉淀);search_project 走宿主注入例外,不在此集
|
|
315
329
|
'run_project_script',
|
|
330
|
+
// 命令执行(§11.5 拍板:常驻开放 + deny/ask/allow 三层护栏)——
|
|
331
|
+
// 不做角色包能力门:命令执行是通用脚手架(构建/测试/git 查询),按需声明会让
|
|
332
|
+
// 「宿主已注入确认闸、内核已恒拦黑名单」的双层防护退化为「角色包没声明就不能用」。
|
|
333
|
+
// 危险度由裁决链(deny 黑名单恒拦 / alwaysAsk 恒确认 / guest 恒确认)承担,不由暴露面承担。
|
|
334
|
+
'run_command',
|
|
335
|
+
'kill_command',
|
|
316
336
|
]);
|
|
317
337
|
/**
|
|
318
338
|
* 工具执行器
|
|
@@ -360,6 +380,14 @@ export class ToolExecutor {
|
|
|
360
380
|
projectSearchProvider;
|
|
361
381
|
/** node 可执行文件路径(可选):run_project_script 的 node runtime 分支注入真实 node 路径 */
|
|
362
382
|
scriptNodePath;
|
|
383
|
+
/**
|
|
384
|
+
* 后台任务注册表(可选,run_command background=true / kill_command 的唯一数据源)
|
|
385
|
+
*
|
|
386
|
+
* 由 agent 装配时注入(实例非单例,多会话互不可见——见 backgroundTasks 模块头注释)。
|
|
387
|
+
* 未注入时两个工具返回 NOT_AVAILABLE:工具面仍暴露(能力缺失要如实说,不静默假装能跑),
|
|
388
|
+
* 而非把工具从暴露面摘掉(那会让 LLM 以为自己看错了工具清单)。
|
|
389
|
+
*/
|
|
390
|
+
backgroundTasks;
|
|
363
391
|
/**
|
|
364
392
|
* 剩余对话预算提供者(可选,预算联动)
|
|
365
393
|
*
|
|
@@ -373,6 +401,29 @@ export class ToolExecutor {
|
|
|
373
401
|
setBudgetProvider(provider) {
|
|
374
402
|
this.budgetProvider = provider;
|
|
375
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* web_search 执行事实观察者(可选,观测留痕)
|
|
406
|
+
*
|
|
407
|
+
* 由 agent 装配时注入(loop.recordWebSearchEvidence,「搜不准」归因取证):
|
|
408
|
+
* 每次 web_search **实际执行后**(safeSearch 返回即发射,零结果/降级页同样发射——
|
|
409
|
+
* 「没搜到」与「搜到了但不对」同为归因现场,失败按原样记录 = 诚实镜像 LLM 实际所得)。
|
|
410
|
+
* 护栏硬拦的搜索不走此路(未执行即无结果面;拦截事实已在 tool 消息可见)。
|
|
411
|
+
* undefined = 未注入静默跳过(纯 ToolExecutor 单测场景)。
|
|
412
|
+
*/
|
|
413
|
+
searchResultObserver;
|
|
414
|
+
/** 注入 web_search 执行事实观察者(装配时由 loop 创建后注入,见 assembler) */
|
|
415
|
+
setSearchResultObserver(observer) {
|
|
416
|
+
this.searchResultObserver = observer;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* 注入后台任务注册表(装配层接线,run_command background / kill_command 数据源)
|
|
420
|
+
*
|
|
421
|
+
* 与 toolExec 构造解耦(注册表在 toolExec 之后创建,见 assembler),故用 setter。
|
|
422
|
+
* 注入的是**实例**——多会话各自一张任务表,kill_command 够不着别的会话的进程。
|
|
423
|
+
*/
|
|
424
|
+
setBackgroundTasks(registry) {
|
|
425
|
+
this.backgroundTasks = registry;
|
|
426
|
+
}
|
|
376
427
|
/** 任务表管理回调(由 agent 装配时注入,处理 task_table_write/update) */
|
|
377
428
|
planManager;
|
|
378
429
|
/**
|
|
@@ -397,6 +448,10 @@ export class ToolExecutor {
|
|
|
397
448
|
*
|
|
398
449
|
* 渐进披露 L3:执行技能的 scripts/ 目录下的可执行脚本。
|
|
399
450
|
* 脚本源码不进入 LLM 上下文,仅执行结果返回。
|
|
451
|
+
*
|
|
452
|
+
* B2-a 契约加宽:返回**结构化执行事实**而非已格式化字符串——格式化与 outcome
|
|
453
|
+
* 派生都在 ToolExecutor 单点完成(与 run_project_script 同源),
|
|
454
|
+
* 消除「exitCode/timedOut 在装配边界被压成文本」的事实丢失。
|
|
400
455
|
*/
|
|
401
456
|
runSkillScript;
|
|
402
457
|
/**
|
|
@@ -469,7 +524,8 @@ export class ToolExecutor {
|
|
|
469
524
|
* 转发给 builtinHandlers:search_memories 命中记忆时触发,为全库**唯一** memoryRecalled
|
|
470
525
|
* 发射位(无 warmRecall 等其他发射路径)。
|
|
471
526
|
*
|
|
472
|
-
* @param callback
|
|
527
|
+
* @param callback 命中回调(载荷类型 = AgentEventMap['memoryRecalled'] 单一真源:count 命中条数 /
|
|
528
|
+
* query 检索词 / hits 命中明细 / toolCallId 本次调用配对键),缺省注入则工具静默(无宿主 no-op)
|
|
473
529
|
*/
|
|
474
530
|
setOnMemoryRecalled(callback) {
|
|
475
531
|
this.builtinHandlers.setOnMemoryRecalled(callback);
|
|
@@ -484,6 +540,15 @@ export class ToolExecutor {
|
|
|
484
540
|
setExclusionRoundIdsProvider(provider) {
|
|
485
541
|
this.builtinHandlers.setExclusionRoundIdsProvider(provider);
|
|
486
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* 注入后台任务槽位调度器,转发给 builtinHandlers:search_memories 命中 touch 走实例级限流。
|
|
545
|
+
* 与 SeedDeps 的调度器同一实例(装配期 wiring),纳入 Agent.close() 单点等待。
|
|
546
|
+
*
|
|
547
|
+
* @param scheduler Agent 实例级后台任务调度器
|
|
548
|
+
*/
|
|
549
|
+
setBackgroundTaskScheduler(scheduler) {
|
|
550
|
+
this.builtinHandlers.setBackgroundTaskScheduler(scheduler);
|
|
551
|
+
}
|
|
487
552
|
/**
|
|
488
553
|
* 注册自定义工具
|
|
489
554
|
*
|
|
@@ -604,8 +669,10 @@ export class ToolExecutor {
|
|
|
604
669
|
baseTools = [...baseTools, WEB_SEARCH_TOOL];
|
|
605
670
|
if (this.fetchProvider)
|
|
606
671
|
baseTools = [...baseTools, WEB_FETCH_TOOL];
|
|
672
|
+
// run_code 定义按宿主执行器声明生成(命令执行能力方案 §10.4-①(b)):声明 supportedLanguages →
|
|
673
|
+
// 描述列举「支持:a、b、c」;未声明 → buildRunCodeTool 缺省形态(去承诺文案,不列举)
|
|
607
674
|
if (this.codeExecutionProvider)
|
|
608
|
-
baseTools = [...baseTools,
|
|
675
|
+
baseTools = [...baseTools, buildRunCodeTool(this.codeExecutionProvider.supportedLanguages)];
|
|
609
676
|
// 常驻豁免集无条件暴露(LLM 工具面不因角色包能力声明收窄本地能力)
|
|
610
677
|
const exposed = baseTools.filter((t) => DEFAULT_EXPOSED_TOOLS.has(t.name));
|
|
611
678
|
// 特权工具按白名单过滤(toolWhitelist=null 全暴露;否则只在名单内)
|
|
@@ -634,6 +701,24 @@ export class ToolExecutor {
|
|
|
634
701
|
get builtinDefinitions() {
|
|
635
702
|
return [...ALL_BUILTIN_TOOL_DEFS];
|
|
636
703
|
}
|
|
704
|
+
/**
|
|
705
|
+
* 按登记表把执行面路由到对应确认入口(CMD-1-BYPASS 2026-10-03)
|
|
706
|
+
*
|
|
707
|
+
* 入口由 `security/confirmEntries.ts` 的 `CONFIRM_ENTRY_BY_TOOL` 唯一指定,
|
|
708
|
+
* 不在各 case 分支里隐式选方法 ⇒ 新增执行面漏登记会在守卫测试处即红。
|
|
709
|
+
*
|
|
710
|
+
* @param entry 确认入口(来自登记表,类型已排除 `none`——非确认面不可能被路由进来)
|
|
711
|
+
* @param target 确认对象(命令原文 / 脚本路径 / 内联代码标识)
|
|
712
|
+
* @param tool 工具名
|
|
713
|
+
* @param description 面向用户的描述
|
|
714
|
+
* @returns 是否获确认放行
|
|
715
|
+
*/
|
|
716
|
+
async confirmByEntry(entry, target, tool, description) {
|
|
717
|
+
if (entry === 'command') {
|
|
718
|
+
return this.security.confirmCommandRun(target, tool, description);
|
|
719
|
+
}
|
|
720
|
+
return this.security.confirmScriptRun(target, tool, description);
|
|
721
|
+
}
|
|
637
722
|
/**
|
|
638
723
|
* 执行工具调用
|
|
639
724
|
*
|
|
@@ -645,9 +730,15 @@ export class ToolExecutor {
|
|
|
645
730
|
* @param name 工具名称
|
|
646
731
|
* @param argsJson 参数 JSON 字符串
|
|
647
732
|
* @param extensions 写入扩展(可选,用于 diff 确认等)
|
|
648
|
-
* @
|
|
733
|
+
* @param emitOutcome 原生结构化结果回调(可选 · SCRIPT-2 outcome 通道):
|
|
734
|
+
* 已切族工具在**返回字符串的同点**按执行事实产出 `ToolOutcome`(status 不读文本前缀);
|
|
735
|
+
* 未切族不调用 ⇒ ToolRunner 回落文本判据。每调用独立回调,无并发相关性问题。
|
|
736
|
+
* @param toolCallId 本次工具调用的唯一 ID(可选 · 证据配对通道):web_search 执行时随
|
|
737
|
+
* 观测留痕一并携带,使宿主渲染能把证据唯一配对回工具行(不再按 query 文本猜)。
|
|
738
|
+
* 非 web_search 工具忽略此参;缺省 = 证据不带 ID,宿主回退 query 启发式(保不带伤)。
|
|
739
|
+
* @returns 工具结果的字符串描述(渲染面,逐字保持)
|
|
649
740
|
*/
|
|
650
|
-
async execute(name, argsJson, extensions) {
|
|
741
|
+
async execute(name, argsJson, extensions, emitOutcome, toolCallId) {
|
|
651
742
|
let args;
|
|
652
743
|
try {
|
|
653
744
|
args = JSON.parse(argsJson);
|
|
@@ -681,7 +772,9 @@ export class ToolExecutor {
|
|
|
681
772
|
case 'list_dir':
|
|
682
773
|
return this.builtinHandlers.listDir(strArg('path', '.'), strArg('recursive', 'false'), strArg('maxDepth', '2'));
|
|
683
774
|
case 'search_memories':
|
|
684
|
-
|
|
775
|
+
// toolCallId 透传给处理器:命中明细随 memoryRecalled 携带配对键,宿主据此挂回工具行
|
|
776
|
+
// (与 web_search 证据配对同源通道;缺省 undefined 时处理器不带该字段,宿主走兜底落位)。
|
|
777
|
+
return this.builtinHandlers.searchMemories(strArg('query'), strArg('limit', '10'), strArg('mode', 'match'), toolCallId);
|
|
685
778
|
case 'trace_summary':
|
|
686
779
|
return this.builtinHandlers.traceSummary(strArg('sessionId'), strArg('roundId') || undefined, strArg('limit', '5'));
|
|
687
780
|
case 'list_sessions':
|
|
@@ -690,7 +783,11 @@ export class ToolExecutor {
|
|
|
690
783
|
// web_search 由 ToolExecutor 直接处理,不经过 BuiltinToolHandlers(文件系统导向)
|
|
691
784
|
// 使用注入的 webSearchProvider 执行网络搜索,带超时保护
|
|
692
785
|
if (!this.webSearchProvider) {
|
|
693
|
-
|
|
786
|
+
// 未注入提供方 = 工具能力缺失(执行失败事实,非主动拦截)→ 显式 emit failed
|
|
787
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
788
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', '错误:网络搜索功能未配置,请先注入 IWebSearchProvider');
|
|
789
|
+
emitOutcome?.(naOutcome);
|
|
790
|
+
return naOutcome.text;
|
|
694
791
|
}
|
|
695
792
|
const query = strArg('query');
|
|
696
793
|
if (!query) {
|
|
@@ -702,12 +799,27 @@ export class ToolExecutor {
|
|
|
702
799
|
}
|
|
703
800
|
const limit = Math.min(Number.parseInt(strArg('limit', '5'), 10) || 5, 20);
|
|
704
801
|
const results = await safeSearch(this.webSearchProvider, query, { limit });
|
|
802
|
+
// 搜索来源(降级透出 + 观测留痕同源):同批次结果来自同一后端,取首条 endpoint
|
|
803
|
+
const endpoint = results[0]?.endpoint;
|
|
804
|
+
// 观测留痕(SEARCH-QUALITY-1 P1-1):执行事实镜像给 loop 落裁决证据轨。
|
|
805
|
+
// 在空结果分支**之前**发射——零结果(count=0)是「没搜到」的归因现场,不静默跳过;
|
|
806
|
+
// safeSearch 降级页按原样记录(title 即「网络搜索暂不可用」)= 诚实镜像 LLM 实际所得。
|
|
807
|
+
// query 与 titles 同净化标准(stripControlChars 剥控制字符——落盘证据面不进原始
|
|
808
|
+
// 控制字符;长度已由 WEB_SEARCH_QUERY_MAX_LEN 校验管,不重复钳制)
|
|
809
|
+
this.searchResultObserver?.({
|
|
810
|
+
toolCallId,
|
|
811
|
+
query: stripControlChars(query),
|
|
812
|
+
endpoint,
|
|
813
|
+
count: results.length,
|
|
814
|
+
titles: results
|
|
815
|
+
.slice(0, WEB_SEARCH_EVIDENCE_MAX_TITLES)
|
|
816
|
+
.map((r) => sanitizeExternalText(r.title, WEB_SEARCH_RESULT_MAX_LEN)),
|
|
817
|
+
});
|
|
705
818
|
if (results.length === 0) {
|
|
706
819
|
return `(未找到与 "${query}" 相关的搜索结果)`;
|
|
707
820
|
}
|
|
708
821
|
// 搜索来源透出:同批次结果来自同一后端,取首条 endpoint 告知用户实际使用的搜索源
|
|
709
822
|
// (降级到 DuckDuckGo 时即透出「搜索来源:DuckDuckGo」),提升 in-flow 信任;宿主渲染工具结果文本即可见。
|
|
710
|
-
const endpoint = results[0]?.endpoint;
|
|
711
823
|
const sourceLine = endpoint ? `(搜索来源:${endpoint})` : '';
|
|
712
824
|
// 返回净化:外部内容去控制字符 + 长度上限,防长上下文注入
|
|
713
825
|
const body = results
|
|
@@ -724,7 +836,11 @@ export class ToolExecutor {
|
|
|
724
836
|
// web_fetch 由 ToolExecutor 直接处理(与 web_search 同侧,均为外部信息获取)
|
|
725
837
|
// 使用注入的 fetchProvider 抓取网页正文,带超时保护;失败由 safeFetch 降级
|
|
726
838
|
if (!this.fetchProvider) {
|
|
727
|
-
|
|
839
|
+
// 未注入提供方 = 工具能力缺失(执行失败事实,非主动拦截)→ 显式 emit failed
|
|
840
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
841
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', '错误:网页抓取功能未配置,请先注入 IFetchProvider');
|
|
842
|
+
emitOutcome?.(naOutcome);
|
|
843
|
+
return naOutcome.text;
|
|
728
844
|
}
|
|
729
845
|
const url = strArg('url');
|
|
730
846
|
if (!url) {
|
|
@@ -745,6 +861,18 @@ export class ToolExecutor {
|
|
|
745
861
|
if (!page.content) {
|
|
746
862
|
return `(未能从 "${url}" 提取到正文内容)`;
|
|
747
863
|
}
|
|
864
|
+
// 登录墙识别(SEARCH-QUALITY-1 P2-1):HTTP 200 的登录页洗成正文 = 「抓到了一堵墙」,
|
|
865
|
+
// 明示不可读并引导换源——不把表单碎片当页面内容交付(LLM 会当正文啃)。
|
|
866
|
+
// 判据是保守启发式(漏判=现状,误判=藏可读页,见 loginWall.ts 文件头);
|
|
867
|
+
// finalUrl 取重定向后实际地址 page.url(带登录态参数的原始 URL 常常正是要抓的页面)。
|
|
868
|
+
// 两条边界排除(落地自审 D-2/D-3):
|
|
869
|
+
// · fetchFailed 降级页不判墙——「抓取失败」不得谎报成「可能需登录」(失败语义不改写);
|
|
870
|
+
// · contentCap 传 maxChars——截断上限 < 弱信号阈值时「短正文」是截断假象,弱信号禁用。
|
|
871
|
+
if (!page.fetchFailed &&
|
|
872
|
+
isLoginWallPage(page.url || url, page.title, page.content, maxChars)) {
|
|
873
|
+
return (`(该页面(${page.url || url})可能需要登录才能查看,本次只获取到登录表单而非正文。` +
|
|
874
|
+
`请改用其他来源,或请用户提供可访问的链接。)`);
|
|
875
|
+
}
|
|
748
876
|
// 返回净化:外部内容去控制字符 + 长度上限,防长上下文注入
|
|
749
877
|
const title = sanitizeExternalText(page.title, 500);
|
|
750
878
|
const content = sanitizeExternalText(page.content, WEB_FETCH_CONTENT_MAX_LEN);
|
|
@@ -758,7 +886,11 @@ export class ToolExecutor {
|
|
|
758
886
|
// run_code 由 ToolExecutor 直接处理(通用计算/数据处理/验证底座)
|
|
759
887
|
// 使用注入的 codeExecutionProvider 执行代码,带超时保护;失败由 safeExecuteCode 降级
|
|
760
888
|
if (!this.codeExecutionProvider) {
|
|
761
|
-
|
|
889
|
+
// 未注入执行器 = 工具能力缺失(旧口径 [ERR 开头亦判失败),显式 emit failed
|
|
890
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
891
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', '错误:代码执行功能未配置,请先注入 ICodeExecutionProvider');
|
|
892
|
+
emitOutcome?.(naOutcome);
|
|
893
|
+
return naOutcome.text;
|
|
762
894
|
}
|
|
763
895
|
const language = strArg('language');
|
|
764
896
|
const code = strArg('code');
|
|
@@ -770,10 +902,11 @@ export class ToolExecutor {
|
|
|
770
902
|
// 统一执行入参:脚本源码 / 代码字符串 + 语言 + 工作目录
|
|
771
903
|
let execCode;
|
|
772
904
|
let execLanguage;
|
|
773
|
-
//
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
905
|
+
// cwd 恒=项目根(两模式同源供给,SKILL-CWD-1 审查实锤的 code 模式缺口,2026-10-14 补):
|
|
906
|
+
// 此前 code 模式不传 → 执行器 spawn 无 cwd → 子进程继承宿主进程 cwd(VS Code 启动目录,
|
|
907
|
+
// 不可控)→ 内联代码相对路径写文件可逃出项目根 / 读项目数据 ENOENT。
|
|
908
|
+
// 四执行面(run_command / run_project_script / run_skill_script / run_code)cwd 判据至此对称。
|
|
909
|
+
const execOptions = { cwd: this.builtinHandlers.projectPath };
|
|
777
910
|
if (scriptPath) {
|
|
778
911
|
// 读取脚本文件原样执行(路径白名单 + 长度上限由 readScriptFile 负责,截断会破坏语法)
|
|
779
912
|
execCode = await this.builtinHandlers.readScriptFile(scriptPath);
|
|
@@ -802,24 +935,35 @@ export class ToolExecutor {
|
|
|
802
935
|
}
|
|
803
936
|
// 执行确认:guest 模式或 confirmScripts 时询问(run_code 由宿主沙箱执行,此处为权限层确认;
|
|
804
937
|
// owner+confirmScripts=false 自动批准,走审计。code 无落盘路径,target 用描述型标识)
|
|
805
|
-
const execConfirmed = await this.
|
|
938
|
+
const execConfirmed = await this.confirmByEntry(requireConfirmingEntry('run_code'), scriptPath ? `run_code:script:${scriptPath}` : 'run_code:inline', 'run_code', scriptPath
|
|
806
939
|
? `运行脚本 ${scriptPath}(宿主沙箱)`
|
|
807
940
|
: `执行内联代码(${execLanguage},${execCode.length} 字符,宿主沙箱)`);
|
|
808
941
|
if (!execConfirmed) {
|
|
809
|
-
|
|
942
|
+
// 用户/系统拒绝执行 = 主动挡下(permission_denied),非执行失败
|
|
943
|
+
const declineText = '[ERR:SCRIPT_DECLINE] 代码执行未获确认(用户拒绝或未注入确认回调,fail-closed)';
|
|
944
|
+
emitOutcome?.(blockedOutcome('permission_denied', declineText));
|
|
945
|
+
return declineText;
|
|
810
946
|
}
|
|
811
947
|
const result = await safeExecuteCode(this.codeExecutionProvider, execCode, execLanguage, execOptions);
|
|
812
948
|
// 结果净化:stdout/stderr 当外部内容去控制字符 + 长度上限,防长上下文注入
|
|
813
|
-
const stdout = sanitizeExternalText(result.stdout, RUN_CODE_RESULT_MAX_LEN);
|
|
814
|
-
const stderr = sanitizeExternalText(result.stderr, RUN_CODE_RESULT_MAX_LEN);
|
|
949
|
+
const stdout = sanitizeExternalText(result.stdout, RUN_CODE_RESULT_MAX_LEN, SCRIPT_RESULT_TAIL_CHARS);
|
|
950
|
+
const stderr = sanitizeExternalText(result.stderr, RUN_CODE_RESULT_MAX_LEN, SCRIPT_RESULT_TAIL_CHARS);
|
|
815
951
|
// 格式化:与 run_skill_script 共用 formatExecutionResult(同一真理源,改格式契约须两链路同步)
|
|
816
|
-
|
|
952
|
+
const codeText = formatExecutionResult({ stdout, stderr, exitCode: result.exitCode, timedOut: result.timedOut }, { kind: 'CODE', timeoutDetail: '代码执行超时', errorDetail: '代码执行失败' });
|
|
953
|
+
// status 直接读执行事实(B2-a:不扫文本前缀):超时或退出码非 0 = failed,其余 = ok
|
|
954
|
+
const codeFailed = result.timedOut || result.exitCode !== 0;
|
|
955
|
+
emitOutcome?.(codeFailed ? failedOutcome(codeText) : okOutcome(codeText));
|
|
956
|
+
return codeText;
|
|
817
957
|
}
|
|
818
958
|
case 'search_project': {
|
|
819
959
|
// search_project 由 ToolExecutor 直接处理(与 web_search/run_code 同侧,均为宿主注入能力)
|
|
820
960
|
// 使用注入的 projectSearchProvider 执行项目内搜索,带超时保护;失败由 safe* 降级
|
|
821
961
|
if (!this.projectSearchProvider) {
|
|
822
|
-
|
|
962
|
+
// 未注入提供方 = 工具能力缺失(执行失败事实,非主动拦截)→ 显式 emit failed
|
|
963
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
964
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', '错误:项目搜索功能未配置,请先注入 IProjectSearchProvider');
|
|
965
|
+
emitOutcome?.(naOutcome);
|
|
966
|
+
return naOutcome.text;
|
|
823
967
|
}
|
|
824
968
|
const query = strArg('query');
|
|
825
969
|
const mode = strArg('mode', 'name');
|
|
@@ -834,11 +978,17 @@ export class ToolExecutor {
|
|
|
834
978
|
throw toolError('search_project exclude 参数过长', `exclude 超过 ${PROJECT_SEARCH_GLOB_MAX_LEN} 字符上限`, ['缩短 exclude 模式'], undefined, ToolErrorCode.ARGUMENT_ERROR);
|
|
835
979
|
}
|
|
836
980
|
if (mode !== 'name' && mode !== 'content') {
|
|
837
|
-
|
|
981
|
+
// 非法参数 = 执行失败事实(方案明文 INVALID_ARG=failed)→ 显式 emit failed
|
|
982
|
+
const invalidModeText = `[ERR:INVALID_ARG] 不支持的搜索模式 "${mode}",仅支持 name/content`;
|
|
983
|
+
emitOutcome?.(failedOutcome(invalidModeText));
|
|
984
|
+
return invalidModeText;
|
|
838
985
|
}
|
|
839
986
|
if (mode === 'content') {
|
|
840
987
|
if (!query) {
|
|
841
|
-
|
|
988
|
+
// 非法参数 = 执行失败事实(方案明文 INVALID_ARG=failed)→ 显式 emit failed
|
|
989
|
+
const missingQueryText = '[ERR:INVALID_ARG] content 模式需要 query 内容关键词';
|
|
990
|
+
emitOutcome?.(failedOutcome(missingQueryText));
|
|
991
|
+
return missingQueryText;
|
|
842
992
|
}
|
|
843
993
|
// 放宽词表(精确优先 + 零命中回退):剔除与整串等价的词——否则宿主会做一轮与
|
|
844
994
|
// 精确轮逐字相同的徒劳扫描,并回报一个名不副实的 relaxed
|
|
@@ -872,12 +1022,19 @@ export class ToolExecutor {
|
|
|
872
1022
|
case 'task_table_write': {
|
|
873
1023
|
// 写入任务表(overwrite / append / replace);items 每项可选 rolePack(会议表层装配角色)
|
|
874
1024
|
if (!this.planManager) {
|
|
875
|
-
|
|
1025
|
+
// 任务表能力未装配 = 执行失败事实(非主动拦截)→ 显式 emit failed
|
|
1026
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1027
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', '任务表功能未就绪');
|
|
1028
|
+
emitOutcome?.(naOutcome);
|
|
1029
|
+
return naOutcome.text;
|
|
876
1030
|
}
|
|
877
1031
|
// mode 为 required(validateAndCoerceArgs 强制校验),不设缺省——设缺省即与 required 契约矛盾
|
|
878
1032
|
const writeMode = strArg('mode');
|
|
879
1033
|
if (writeMode !== 'overwrite' && writeMode !== 'append' && writeMode !== 'replace') {
|
|
880
|
-
|
|
1034
|
+
// 非法参数 = 执行失败事实(方案明文 INVALID_ARG=failed)→ 显式 emit failed
|
|
1035
|
+
const invalidModeText = `[ERR:INVALID_ARG] 不支持的写入模式 "${writeMode}",仅支持 overwrite/append/replace`;
|
|
1036
|
+
emitOutcome?.(failedOutcome(invalidModeText));
|
|
1037
|
+
return invalidModeText;
|
|
881
1038
|
}
|
|
882
1039
|
const itemsRaw = args.items;
|
|
883
1040
|
const items = Array.isArray(itemsRaw)
|
|
@@ -888,18 +1045,28 @@ export class ToolExecutor {
|
|
|
888
1045
|
}))
|
|
889
1046
|
: [];
|
|
890
1047
|
if (items.length === 0 || items.some((s) => s.description.trim() === '')) {
|
|
891
|
-
|
|
1048
|
+
// 非法参数 = 执行失败事实(方案明文 INVALID_ARG=failed)→ 显式 emit failed
|
|
1049
|
+
const invalidItemsText = '[ERR:INVALID_ARG] items 参数不能为空,且每项须含非空 description';
|
|
1050
|
+
emitOutcome?.(failedOutcome(invalidItemsText));
|
|
1051
|
+
return invalidItemsText;
|
|
892
1052
|
}
|
|
893
1053
|
return this.planManager.writePlan(writeMode, items);
|
|
894
1054
|
}
|
|
895
1055
|
case 'task_table_update': {
|
|
896
1056
|
// 更新任务状态
|
|
897
1057
|
if (!this.planManager) {
|
|
898
|
-
|
|
1058
|
+
// 任务表能力未装配 = 执行失败事实(非主动拦截)→ 显式 emit failed
|
|
1059
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1060
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', '任务表功能未就绪');
|
|
1061
|
+
emitOutcome?.(naOutcome);
|
|
1062
|
+
return naOutcome.text;
|
|
899
1063
|
}
|
|
900
1064
|
const planItemId = strArg('plan_item_id');
|
|
901
1065
|
if (!planItemId) {
|
|
902
|
-
|
|
1066
|
+
// 非法参数 = 执行失败事实(方案明文 INVALID_ARG=failed)→ 显式 emit failed
|
|
1067
|
+
const missingIdText = '[ERR:INVALID_ARG] plan_item_id 不能为空';
|
|
1068
|
+
emitOutcome?.(failedOutcome(missingIdText));
|
|
1069
|
+
return missingIdText;
|
|
903
1070
|
}
|
|
904
1071
|
// 寻址统一解析(见 resolvePlanItemId):renderer 只向 LLM 展示行首序号(1-based),
|
|
905
1072
|
// task_table_write 返回 uuid 前 8 位短 id,planItemLog 展示完整 uuid——三种来源全部归一为真实 uuid 后
|
|
@@ -907,12 +1074,17 @@ export class ToolExecutor {
|
|
|
907
1074
|
const plan = this.planManager.getPlan?.() ?? [];
|
|
908
1075
|
const resolved = resolvePlanItemId(planItemId, plan);
|
|
909
1076
|
if (!resolved.ok) {
|
|
1077
|
+
// 寻址失败(PLAN_ITEM_NOT_FOUND)= 执行失败事实 → 显式 emit failed
|
|
1078
|
+
emitOutcome?.(failedOutcome(resolved.error));
|
|
910
1079
|
return resolved.error;
|
|
911
1080
|
}
|
|
912
1081
|
// status 为 required(validateAndCoerceArgs 强制校验),不设缺省
|
|
913
1082
|
const planItemStatus = strArg('status');
|
|
914
1083
|
if (planItemStatus !== 'done' && planItemStatus !== 'blocked') {
|
|
915
|
-
|
|
1084
|
+
// 非法参数 = 执行失败事实(方案明文 INVALID_ARG=failed)→ 显式 emit failed
|
|
1085
|
+
const invalidStatusText = `[ERR:INVALID_ARG] 不支持的状态 "${planItemStatus}",仅支持 done/blocked`;
|
|
1086
|
+
emitOutcome?.(failedOutcome(invalidStatusText));
|
|
1087
|
+
return invalidStatusText;
|
|
916
1088
|
}
|
|
917
1089
|
return this.planManager.updatePlanItem(resolved.id, planItemStatus);
|
|
918
1090
|
}
|
|
@@ -920,11 +1092,18 @@ export class ToolExecutor {
|
|
|
920
1092
|
// 渐进披露 L2:读取激活角色包内嵌技能正文(readSkill 回调由 agent 装配注入)
|
|
921
1093
|
// name 为必填参数,已由 validateAndCoerceArgs 校验,此处直接用
|
|
922
1094
|
if (!this.readSkill) {
|
|
923
|
-
|
|
1095
|
+
// 能力未装配 = 执行失败事实(非主动拦截)→ 显式 emit failed
|
|
1096
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1097
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', 'read_skill 不可用:未装配角色包技能读取回调');
|
|
1098
|
+
emitOutcome?.(naOutcome);
|
|
1099
|
+
return naOutcome.text;
|
|
924
1100
|
}
|
|
925
1101
|
const content = await this.readSkill(strArg('name'));
|
|
926
1102
|
if (content === null) {
|
|
927
|
-
|
|
1103
|
+
// 技能未找到 = 执行失败事实 → 显式 emit failed
|
|
1104
|
+
const notFoundText = `[ERR:SKILL_NOT_FOUND] 未找到技能 "${strArg('name')}"(角色包 skills/ 与全局技能池均无此名,或技能正文读取失败)`;
|
|
1105
|
+
emitOutcome?.(failedOutcome(notFoundText));
|
|
1106
|
+
return notFoundText;
|
|
928
1107
|
}
|
|
929
1108
|
// 返回净化:技能正文当外部内容去控制字符 + 长度上限(防超长技能正文撑爆上下文)
|
|
930
1109
|
return sanitizeExternalText(content, SKILL_CONTENT_MAX_LEN);
|
|
@@ -932,13 +1111,20 @@ export class ToolExecutor {
|
|
|
932
1111
|
case 'read_resource': {
|
|
933
1112
|
// 渐进披露 L3:读取技能的参考资源文件
|
|
934
1113
|
if (!this.readResource) {
|
|
935
|
-
|
|
1114
|
+
// 能力未装配 = 执行失败事实(非主动拦截)→ 显式 emit failed
|
|
1115
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1116
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', 'read_resource 不可用:未装配 L3 资源读取回调');
|
|
1117
|
+
emitOutcome?.(naOutcome);
|
|
1118
|
+
return naOutcome.text;
|
|
936
1119
|
}
|
|
937
1120
|
const skillName = strArg('skill_name');
|
|
938
1121
|
const resourcePath = strArg('resource_path');
|
|
939
1122
|
const resourceContent = await this.readResource(skillName, resourcePath);
|
|
940
1123
|
if (resourceContent === null) {
|
|
941
|
-
|
|
1124
|
+
// 资源未找到 = 执行失败事实 → 显式 emit failed
|
|
1125
|
+
const notFoundText = `[ERR:RESOURCE_NOT_FOUND] 未找到资源 "${resourcePath}"(技能 "${skillName}" 无此资源,或资源读取失败)`;
|
|
1126
|
+
emitOutcome?.(failedOutcome(notFoundText));
|
|
1127
|
+
return notFoundText;
|
|
942
1128
|
}
|
|
943
1129
|
// 返回净化:资源正文当外部内容去控制字符 + 长度上限(防超长资源撑爆上下文)
|
|
944
1130
|
return sanitizeExternalText(resourceContent, RESOURCE_CONTENT_MAX_LEN);
|
|
@@ -946,7 +1132,11 @@ export class ToolExecutor {
|
|
|
946
1132
|
case 'run_skill_script': {
|
|
947
1133
|
// 渐进披露 L3:执行技能的可执行脚本(脚本源码不进上下文,仅结果返回)
|
|
948
1134
|
if (!this.runSkillScript) {
|
|
949
|
-
|
|
1135
|
+
// 未装配回调 = 工具能力缺失(旧口径 [ERR 开头亦判失败),显式 emit failed
|
|
1136
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1137
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', 'run_skill_script 不可用:未装配 L3 脚本执行回调');
|
|
1138
|
+
emitOutcome?.(naOutcome);
|
|
1139
|
+
return naOutcome.text;
|
|
950
1140
|
}
|
|
951
1141
|
const skillName = strArg('skill_name');
|
|
952
1142
|
const scriptPath = strArg('script_path');
|
|
@@ -957,16 +1147,26 @@ export class ToolExecutor {
|
|
|
957
1147
|
// confirmScripts=true 时 owner 亦确认(开关语义 = "脚本执行二次确认",对全部脚本生效)。
|
|
958
1148
|
// 无 OS 级沙箱(子进程直跑 + env 继承用户环境),故不能以 Codex workspace-write 的
|
|
959
1149
|
// "边界内自动"前提豁免 guest。target 用技能名:脚本路径标识(无落盘绝对路径)。
|
|
960
|
-
const execConfirmed = await this.
|
|
1150
|
+
const execConfirmed = await this.confirmByEntry(requireConfirmingEntry('run_skill_script'), `skill:${skillName}:${scriptPath ?? ''}`, 'run_skill_script', `执行技能 ${skillName} 的脚本 ${scriptPath ?? ''}`);
|
|
961
1151
|
if (!execConfirmed) {
|
|
962
|
-
|
|
1152
|
+
// 用户/系统拒绝执行 = 主动挡下(permission_denied),非执行失败
|
|
1153
|
+
const declineText = '[ERR:SCRIPT_DECLINE] 技能脚本执行未获确认(用户拒绝或未注入确认回调,fail-closed)';
|
|
1154
|
+
emitOutcome?.(blockedOutcome('permission_denied', declineText));
|
|
1155
|
+
return declineText;
|
|
963
1156
|
}
|
|
964
1157
|
const result = await this.runSkillScript(skillName, scriptPath, scriptArgs);
|
|
965
1158
|
if (result === null) {
|
|
966
|
-
|
|
1159
|
+
// 脚本不存在/无法执行 = 执行事实失败(区别于权限拒绝)
|
|
1160
|
+
const notFoundText = `[ERR:SCRIPT_NOT_FOUND] 未找到脚本 "${scriptPath}"(技能 "${skillName}" 无此脚本,或执行失败)`;
|
|
1161
|
+
emitOutcome?.(failedOutcome(notFoundText));
|
|
1162
|
+
return notFoundText;
|
|
967
1163
|
}
|
|
968
|
-
//
|
|
969
|
-
|
|
1164
|
+
// 格式化在本点单点收口(B2-a:回调只返结构化事实);再经净化防刷屏撑爆上下文
|
|
1165
|
+
const formattedText = sanitizeExternalText(formatScriptResult(result), RUN_SCRIPT_RESULT_MAX_LEN, SCRIPT_RESULT_TAIL_CHARS);
|
|
1166
|
+
// status 直接读执行事实:超时或退出码非 0 = failed,其余 = ok(不扫文本前缀)
|
|
1167
|
+
const scriptFailed = result.timedOut || result.exitCode !== 0;
|
|
1168
|
+
emitOutcome?.(scriptFailed ? failedOutcome(formattedText) : okOutcome(formattedText));
|
|
1169
|
+
return formattedText;
|
|
970
1170
|
}
|
|
971
1171
|
case 'run_project_script': {
|
|
972
1172
|
// 项目内已有脚本执行(默认开放,判据 A+B):内核子进程执行,脚本源码不进上下文
|
|
@@ -979,12 +1179,18 @@ export class ToolExecutor {
|
|
|
979
1179
|
// ① 路径白名单:相对项目根解析,防穿越返回 null → 拒绝(越界不协商)
|
|
980
1180
|
const fullPath = resolveSafePath(this.builtinHandlers.projectPath, scriptPath);
|
|
981
1181
|
if (!fullPath) {
|
|
982
|
-
|
|
1182
|
+
// 安全白名单拦截 = 主动挡下(B2-a 拍板:归 permission_denied,不新增枚举值)
|
|
1183
|
+
const pathDeniedText = `[ERR:PATH_DENIED] 脚本路径越界(超出项目根):"${scriptPath}"`;
|
|
1184
|
+
emitOutcome?.(blockedOutcome('permission_denied', pathDeniedText));
|
|
1185
|
+
return pathDeniedText;
|
|
983
1186
|
}
|
|
984
1187
|
// ② 执行确认:guest 模式或 confirmScripts 时询问(owner 默认自动批准,走审计)
|
|
985
|
-
const confirmed = await this.
|
|
1188
|
+
const confirmed = await this.confirmByEntry(requireConfirmingEntry('run_project_script'), fullPath, 'run_project_script', `运行项目脚本 ${scriptPath}`);
|
|
986
1189
|
if (!confirmed) {
|
|
987
|
-
|
|
1190
|
+
// 用户/系统拒绝执行 = 主动挡下(permission_denied),非执行失败
|
|
1191
|
+
const declineText = '[ERR:SCRIPT_DECLINE] 脚本运行未获确认(用户拒绝或未注入确认回调,fail-closed)';
|
|
1192
|
+
emitOutcome?.(blockedOutcome('permission_denied', declineText));
|
|
1193
|
+
return declineText;
|
|
988
1194
|
}
|
|
989
1195
|
// ③ 运行时白名单:扩展名推断并收敛到 node/python/shell 三档(推断即可信,不规则兜底 node)
|
|
990
1196
|
const ext = scriptPath.slice(scriptPath.lastIndexOf('.')).toLowerCase();
|
|
@@ -1001,12 +1207,157 @@ export class ToolExecutor {
|
|
|
1001
1207
|
this.scriptNodePath);
|
|
1002
1208
|
// 返回净化:脚本输出当外部内容去控制字符 + 长度上限(防刷屏撑爆上下文,对齐 run_skill_script)
|
|
1003
1209
|
// 超时文案按本次实际超时生成(未传 timeout_ms → 内核默认 60s,见上),不恒报上限 600s
|
|
1004
|
-
|
|
1210
|
+
const projectScriptText = sanitizeExternalText(formatScriptResult(result, timeoutMs), RUN_SCRIPT_RESULT_MAX_LEN, SCRIPT_RESULT_TAIL_CHARS);
|
|
1211
|
+
// status 直接读执行事实(B2-a:不扫文本前缀):超时或退出码非 0 = failed,其余 = ok
|
|
1212
|
+
const projectScriptFailed = result.timedOut || result.exitCode !== 0;
|
|
1213
|
+
emitOutcome?.(projectScriptFailed ? failedOutcome(projectScriptText) : okOutcome(projectScriptText));
|
|
1214
|
+
return projectScriptText;
|
|
1215
|
+
}
|
|
1216
|
+
case 'run_command': {
|
|
1217
|
+
// 裸命令执行(§13.2 裁决链 → 执行):三道闸顺序不可颠倒——
|
|
1218
|
+
// ① 参数校验(空命令 / 越界 cwd)② 命令裁决(deny 恒拦 / alwaysAsk 恒确认)
|
|
1219
|
+
// ③ 执行(同步等退出码 / 后台注册表立即返回 taskId)
|
|
1220
|
+
// 裁决链判据在 SecurityGuard.confirmCommandRun(与 confirmScriptRun 共用 confirmGate 单点)。
|
|
1221
|
+
const command = strArg('command');
|
|
1222
|
+
if (!command.trim()) {
|
|
1223
|
+
throw toolError('run_command 缺少 command 参数', '命令为空', ['传要执行的 shell 命令,如 "npm test"'], undefined, ToolErrorCode.ARGUMENT_ERROR);
|
|
1224
|
+
}
|
|
1225
|
+
if (command.length > RUN_COMMAND_MAX_LEN) {
|
|
1226
|
+
// 截半条命令 = 语义破坏(执行的不是用户/模型写的那条),故整条拒收
|
|
1227
|
+
throw toolError('run_command 命令过长', `命令长度 ${command.length} 字符,超过上限 ${RUN_COMMAND_MAX_LEN}`, ['把命令拆成多条执行,或改用脚本文件 + run_project_script'], undefined, ToolErrorCode.ARGUMENT_ERROR);
|
|
1228
|
+
}
|
|
1229
|
+
// cwd 缺省 = 项目根;显式传入时按项目根解析,越界拒绝(与 run_project_script 同判据)
|
|
1230
|
+
let cwd = this.builtinHandlers.projectPath;
|
|
1231
|
+
const rawCwd = strArg('cwd');
|
|
1232
|
+
if (rawCwd) {
|
|
1233
|
+
const resolved = resolveSafePath(this.builtinHandlers.projectPath, rawCwd);
|
|
1234
|
+
if (!resolved) {
|
|
1235
|
+
const pathDeniedText = `[ERR:PATH_DENIED] 工作目录越界(超出项目根):"${rawCwd}"`;
|
|
1236
|
+
// 原生 outcome:越界拒绝 = 主动挡下 ⇒ blocked(对齐 run_project_script 同判据先例;
|
|
1237
|
+
// B4 口径:blocked 不再计入 toolFailureCount,见方案 §六决策二)
|
|
1238
|
+
emitOutcome?.(blockedOutcome('permission_denied', pathDeniedText));
|
|
1239
|
+
return pathDeniedText;
|
|
1240
|
+
}
|
|
1241
|
+
cwd = resolved;
|
|
1242
|
+
}
|
|
1243
|
+
// ② 命令裁决:deny 恒拦(不入确认流程)/ alwaysAsk 恒确认 / 其余同脚本判据
|
|
1244
|
+
const confirmed = await this.confirmByEntry(requireConfirmingEntry('run_command'), command, 'run_command', `在工作区执行命令:${command}`);
|
|
1245
|
+
if (!confirmed) {
|
|
1246
|
+
const declineText = '[ERR:COMMAND_DECLINE] 命令未获执行许可(命中恒拦黑名单 / 用户拒绝 / 未注入确认回调 fail-closed)。' +
|
|
1247
|
+
'未执行任何命令;如确需执行,请改用等效手段或请用户调整审批设置。';
|
|
1248
|
+
// 语义是「主动挡下」⇒ blocked(permission_denied),不是 failed。
|
|
1249
|
+
// ⚠️ 注释订正:原写「编排层仍按本前缀文本算 failed(已知口径分歧)」——**已不成立**,
|
|
1250
|
+
// 失败计数唯一判据面是 `outcome.status`(见 `loop._processToolResults`),内核无
|
|
1251
|
+
// 任何生产代码按文本算 failed,勿再去找那处并不存在的判据。
|
|
1252
|
+
// 本前缀只是**渲染面**(LLM 自愈看的证据类别),与判据面解耦,保留是有意的。
|
|
1253
|
+
emitOutcome?.(blockedOutcome('permission_denied', declineText));
|
|
1254
|
+
return declineText;
|
|
1255
|
+
}
|
|
1256
|
+
// ③ 执行:background=true 立即返回 taskId(不等进程结束)
|
|
1257
|
+
// `=== 1` 兜底:validateAndCoerceArgs 已把字符串 'true'/'1' 归一为布尔,
|
|
1258
|
+
// 裸数字 1 仍会漏过——若不认,模型要的后台会退化成同步阻塞(正是本能力要消除的形态)
|
|
1259
|
+
if (args['background'] === true || args['background'] === 1) {
|
|
1260
|
+
if (!this.backgroundTasks) {
|
|
1261
|
+
// 能力缺失如实说(B1 缺口补齐:补 emit,对齐 run_code/run_skill_script NOT_AVAILABLE 先例)
|
|
1262
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1263
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', 'run_command 后台模式不可用:未装配后台任务注册表');
|
|
1264
|
+
emitOutcome?.(naOutcome);
|
|
1265
|
+
return naOutcome.text;
|
|
1266
|
+
}
|
|
1267
|
+
const rawTimeout = args['timeoutMs'];
|
|
1268
|
+
const taskId = this.backgroundTasks.start(command, cwd, typeof rawTimeout === 'number' && Number.isFinite(rawTimeout) ? rawTimeout : undefined);
|
|
1269
|
+
return (`[BACKGROUND_STARTED] taskId=${taskId}(命令已在本轮之外继续运行,本轮对话结束不会终止它)。` +
|
|
1270
|
+
'**结果不会自动进入上下文**:它不会自己跳进来,你需要时用 kill_command 传入该 taskId,' +
|
|
1271
|
+
'取回截至当时的输出并终止它;若不取,它会一直跑到自己结束(未传 timeoutMs 时没有超时上限)。' +
|
|
1272
|
+
'寿命边界:正常退出 VS Code 时随扩展一并终止(扩展崩溃或被强制结束时不保证清理)。' +
|
|
1273
|
+
'命令:' +
|
|
1274
|
+
command);
|
|
1275
|
+
}
|
|
1276
|
+
// 同步路径:超时钳制与文案同源(resolveCommandTimeoutMs 单一真源,见 命令执行能力方案 §13.6-C)
|
|
1277
|
+
const effectiveTimeoutMs = resolveCommandTimeoutMs(typeof args['timeoutMs'] === 'number' ? args['timeoutMs'] : undefined);
|
|
1278
|
+
const result = await runShellCommand(command, cwd, effectiveTimeoutMs);
|
|
1279
|
+
// 定长与净化由 formatCommandResult 自带(真源在格式化层,回流面共用同一上限)
|
|
1280
|
+
const text = formatCommandResult(result, effectiveTimeoutMs);
|
|
1281
|
+
// 原生 status 直接读**执行事实**(不扫文本前缀):超时或退出码非 0 = failed,其余 = ok。
|
|
1282
|
+
// status 与 text 同源产出,编排层/计数层只读 status(B4 判据切换,无文本交叉核对)。
|
|
1283
|
+
const commandFailed = result.timedOut || result.exitCode !== 0;
|
|
1284
|
+
emitOutcome?.(commandFailed ? failedOutcome(text) : okOutcome(text));
|
|
1285
|
+
return text;
|
|
1286
|
+
}
|
|
1287
|
+
case 'kill_command': {
|
|
1288
|
+
// 后台任务终止(§14.1 免裁决链:只可杀本 agent 起的自家任务,taskId 寻址非任意 pid)
|
|
1289
|
+
if (!this.backgroundTasks) {
|
|
1290
|
+
// 能力缺失 = 执行失败事实(非主动拦截)→ 显式 emit failed
|
|
1291
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1292
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', 'kill_command 不可用:未装配后台任务注册表');
|
|
1293
|
+
emitOutcome?.(naOutcome);
|
|
1294
|
+
return naOutcome.text;
|
|
1295
|
+
}
|
|
1296
|
+
const taskId = strArg('taskId');
|
|
1297
|
+
if (!taskId) {
|
|
1298
|
+
throw toolError('kill_command 缺少 taskId 参数', '未传 taskId', ['传 run_command 后台执行返回的 taskId(如 "bg-1")'], undefined, ToolErrorCode.ARGUMENT_ERROR);
|
|
1299
|
+
}
|
|
1300
|
+
const outcome = this.backgroundTasks.kill(taskId);
|
|
1301
|
+
// 不静默成功:不存在与已终态是两种语义,必须让 LLM 知道(命令执行能力方案 §13.6 未决二定案)
|
|
1302
|
+
if (!outcome) {
|
|
1303
|
+
const notFoundText = `[ERR:TASK_NOT_FOUND] 未找到后台任务 "${taskId}"。` +
|
|
1304
|
+
'任务跨轮保留、可在后续轮次 kill;该 id 可能本就有误,或来自扩展重启前的会话' +
|
|
1305
|
+
'(注册表不跨重启保留)——本轮不会终止任何进程。';
|
|
1306
|
+
emitOutcome?.(failedOutcome(notFoundText));
|
|
1307
|
+
return notFoundText;
|
|
1308
|
+
}
|
|
1309
|
+
const task = this.backgroundTasks.get(taskId);
|
|
1310
|
+
if (!outcome.result) {
|
|
1311
|
+
const settledBareText = `[TASK_ALREADY_SETTLED] taskId=${taskId} 已是终态(status=${outcome.status},无需终止;该任务无已捕获输出)`;
|
|
1312
|
+
// 终止动作本身完成(无事可终止)→ ok;底层任务若有失败已由完成点单点结算(B3)
|
|
1313
|
+
emitOutcome?.(okOutcome(settledBareText));
|
|
1314
|
+
return settledBareText;
|
|
1315
|
+
}
|
|
1316
|
+
// 结果体的净化与定长由 formatCommandResult / formatKilledCommandOutput 自带
|
|
1317
|
+
// (定长真源在格式化层,两条消费面共用)——此处不再套一层,避免两个 policy。
|
|
1318
|
+
if (outcome.status === 'killed') {
|
|
1319
|
+
// 被主动终止 ≠ 执行失败:走专用格式化,不谎报退出码(见 formatKilledCommandOutput)
|
|
1320
|
+
const killedText = `[KILLED] taskId=${taskId}(已终止;以下为截至终止时的输出)\n命令:${task?.command ?? '(未知)'}\n${formatKilledCommandOutput(outcome.result)}`;
|
|
1321
|
+
// 终止动作成功 → ok(强杀无退出码,文本不谎报失败,status 同样不谎)
|
|
1322
|
+
emitOutcome?.(okOutcome(killedText));
|
|
1323
|
+
return killedText;
|
|
1324
|
+
}
|
|
1325
|
+
const settledNote = `[TASK_ALREADY_SETTLED] taskId=${taskId} 已是终态(status=${outcome.status},无需终止)`;
|
|
1326
|
+
// 行首归属判据改读**结构化执行事实**(B4 判据切换,不再扫文本前缀):
|
|
1327
|
+
// 「已是终态」是控制流事实、「退出码非零/超时」是执行结局事实,两者抢同一行首。
|
|
1328
|
+
// 原写法把 [TASK_ALREADY_SETTLED] 放行首 ⇒ 已终态的失败任务被判成功
|
|
1329
|
+
// (SCRIPT-1 同形状漏计,2026-10-06 实测)。
|
|
1330
|
+
// 修法:失败/超时终态把结果体提到行首(结局可判),「已终态」说明降为末尾附注。
|
|
1331
|
+
// ⚠️ 引导语必须**随结果体一起走**(不能钉在 settledNote 上):它在原文案里是
|
|
1332
|
+
// **前置引导**(结果在下方)。若失败路径把同一句放在结果体**之后**,「以下」就变成
|
|
1333
|
+
// 指向上方 —— 对 LLM 是指代错乱的谎。两条路径各自带对位置的引导语。
|
|
1334
|
+
const commandLine = `命令:${task?.command ?? '(未知)'}`;
|
|
1335
|
+
const body = formatCommandResult(outcome.result);
|
|
1336
|
+
// 失败终态判定(timedOut / exitCode 非零)仍写入文本(诚实呈现),
|
|
1337
|
+
// 但 **status 恒 ok**:底层任务的失败已在完成点单点结算(B3 setBackgroundTasks 回调),
|
|
1338
|
+
// kill 回传只是「取回输出快照」这一动作的成功 —— 若此处再判 failed,同一失败计 2 次
|
|
1339
|
+
// (双计伤:B3 完成点 × B4-c 行首 [COMMAND_ERROR] 桥判,2026-10-06 实测)。
|
|
1340
|
+
// 文本层的 [COMMAND_ERROR] 保留给 LLM 看结局,计数层只认 status(单点结算)。
|
|
1341
|
+
const settledWithFailure = outcome.result.timedOut || outcome.result.exitCode !== 0;
|
|
1342
|
+
if (settledWithFailure) {
|
|
1343
|
+
// 附注在结果**之后** ⇒ 用回指语(「其结果为」),不用「以下」。
|
|
1344
|
+
const settledFailedText = `${body}\n${commandLine}\n${settledNote};其结果为上述内容`;
|
|
1345
|
+
emitOutcome?.(okOutcome(settledFailedText));
|
|
1346
|
+
return settledFailedText;
|
|
1347
|
+
}
|
|
1348
|
+
// 成功路径:附注在前、结果在后 ⇒ 沿用原文案的前置引导语,逐字不变。
|
|
1349
|
+
const settledOkText = `${settledNote};以下为其结果\n${commandLine}\n${body}`;
|
|
1350
|
+
emitOutcome?.(okOutcome(settledOkText));
|
|
1351
|
+
return settledOkText;
|
|
1005
1352
|
}
|
|
1006
1353
|
case 'list_resources': {
|
|
1007
1354
|
// 渐进披露 L3:列出技能的资源清单
|
|
1008
1355
|
if (!this.listResources) {
|
|
1009
|
-
|
|
1356
|
+
// 能力未装配 = 执行失败事实(非主动拦截)→ 显式 emit failed
|
|
1357
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1358
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', 'list_resources 不可用');
|
|
1359
|
+
emitOutcome?.(naOutcome);
|
|
1360
|
+
return naOutcome.text;
|
|
1010
1361
|
}
|
|
1011
1362
|
const skillName = strArg('skill_name');
|
|
1012
1363
|
return await this.listResources(skillName);
|
|
@@ -1014,26 +1365,41 @@ export class ToolExecutor {
|
|
|
1014
1365
|
case 'list_skills': {
|
|
1015
1366
|
// 渐进披露 L1 补充:列出所有技能清单(>50 技能时使用)
|
|
1016
1367
|
if (!this.listSkills) {
|
|
1017
|
-
|
|
1368
|
+
// 能力未装配 = 执行失败事实(非主动拦截)→ 显式 emit failed
|
|
1369
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1370
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', 'list_skills 不可用');
|
|
1371
|
+
emitOutcome?.(naOutcome);
|
|
1372
|
+
return naOutcome.text;
|
|
1018
1373
|
}
|
|
1019
1374
|
return await this.listSkills();
|
|
1020
1375
|
}
|
|
1021
1376
|
case 'register_work': {
|
|
1022
1377
|
// 作品投影登记(registerWork 回调由 agent 装配注入;用户主动触发写索引卡片)
|
|
1023
1378
|
if (!this.registerWork) {
|
|
1024
|
-
|
|
1379
|
+
// 能力未装配 = 执行失败事实(非主动拦截)→ 显式 emit failed
|
|
1380
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1381
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', 'register_work 不可用:未装配作品投影登记回调');
|
|
1382
|
+
emitOutcome?.(naOutcome);
|
|
1383
|
+
return naOutcome.text;
|
|
1025
1384
|
}
|
|
1026
1385
|
const path = strArg('path');
|
|
1027
1386
|
const description = strArg('description');
|
|
1028
1387
|
if (!path || !description) {
|
|
1029
|
-
|
|
1388
|
+
// 非法参数 = 执行失败事实(方案明文 INVALID_ARG=failed)→ 显式 emit failed
|
|
1389
|
+
const invalidArgText = '[ERR:INVALID_ARG] register_work 需要 path(相对项目根)与 description(一句话说明)参数';
|
|
1390
|
+
emitOutcome?.(failedOutcome(invalidArgText));
|
|
1391
|
+
return invalidArgText;
|
|
1030
1392
|
}
|
|
1031
1393
|
return await this.registerWork(path, description);
|
|
1032
1394
|
}
|
|
1033
1395
|
case 'run_team_meeting': {
|
|
1034
1396
|
// 评估/评审型小组会议(runTeamMeeting 回调由 agent 装配注入;工具内嵌一次 LLM 调用)
|
|
1035
1397
|
if (!this.runTeamMeeting) {
|
|
1036
|
-
|
|
1398
|
+
// 能力未装配 = 执行失败事实(非主动拦截)→ 显式 emit failed
|
|
1399
|
+
// 错误码与文本前缀经 failedOutcomeWithCode 同源产出(NOT_AVAILABLE 不可重试)
|
|
1400
|
+
const naOutcome = failedOutcomeWithCode('NOT_AVAILABLE', 'run_team_meeting 不可用:未装配小组会议回调');
|
|
1401
|
+
emitOutcome?.(naOutcome);
|
|
1402
|
+
return naOutcome.text;
|
|
1037
1403
|
}
|
|
1038
1404
|
const group = strArg('group');
|
|
1039
1405
|
const topic = strArg('topic');
|