@caupulican/pi-adaptative 0.81.35 → 0.81.37
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 +24 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +10 -8
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +5 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +17 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
- package/dist/core/autonomy/approval-gate.js +2 -0
- package/dist/core/autonomy/approval-gate.js.map +1 -1
- package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -1
- package/dist/core/autonomy/foreground-envelope.js +2 -0
- package/dist/core/autonomy/foreground-envelope.js.map +1 -1
- package/dist/core/autonomy/gates.d.ts.map +1 -1
- package/dist/core/autonomy/gates.js +9 -2
- package/dist/core/autonomy/gates.js.map +1 -1
- package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
- package/dist/core/autonomy/risk-assessment.js +2 -0
- package/dist/core/autonomy/risk-assessment.js.map +1 -1
- package/dist/core/bash-execution-controller.d.ts +9 -4
- package/dist/core/bash-execution-controller.d.ts.map +1 -1
- package/dist/core/bash-execution-controller.js +7 -6
- package/dist/core/bash-execution-controller.js.map +1 -1
- package/dist/core/bash-executor.d.ts +2 -0
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +3 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/default-tool-surface.d.ts +7 -0
- package/dist/core/default-tool-surface.d.ts.map +1 -1
- package/dist/core/default-tool-surface.js +29 -10
- package/dist/core/default-tool-surface.js.map +1 -1
- package/dist/core/doctor.d.ts +10 -10
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +30 -20
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/exec.d.ts +2 -0
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +1 -0
- package/dist/core/exec.js.map +1 -1
- package/dist/core/extensions/types.d.ts +13 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +3 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/memory/memory-manager.d.ts.map +1 -1
- package/dist/core/memory/memory-manager.js +1 -0
- package/dist/core/memory/memory-manager.js.map +1 -1
- package/dist/core/model-capability.d.ts.map +1 -1
- package/dist/core/model-capability.js +2 -0
- package/dist/core/model-capability.js.map +1 -1
- package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
- package/dist/core/model-router/tool-escalation.js +17 -3
- package/dist/core/model-router/tool-escalation.js.map +1 -1
- package/dist/core/python-runtime.d.ts +46 -0
- package/dist/core/python-runtime.d.ts.map +1 -0
- package/dist/core/python-runtime.js +157 -0
- package/dist/core/python-runtime.js.map +1 -0
- package/dist/core/runtime-builder.d.ts +4 -0
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +31 -9
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/security/untrusted-boundary.d.ts +2 -2
- package/dist/core/security/untrusted-boundary.d.ts.map +1 -1
- package/dist/core/security/untrusted-boundary.js +4 -3
- package/dist/core/security/untrusted-boundary.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +2 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +8 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +13 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tasks/session-task-state.d.ts +10 -0
- package/dist/core/tasks/session-task-state.d.ts.map +1 -0
- package/dist/core/tasks/session-task-state.js +29 -0
- package/dist/core/tasks/session-task-state.js.map +1 -0
- package/dist/core/tasks/task-command.d.ts +30 -0
- package/dist/core/tasks/task-command.d.ts.map +1 -0
- package/dist/core/tasks/task-command.js +79 -0
- package/dist/core/tasks/task-command.js.map +1 -0
- package/dist/core/tasks/task-state.d.ts +71 -0
- package/dist/core/tasks/task-state.d.ts.map +1 -0
- package/dist/core/tasks/task-state.js +372 -0
- package/dist/core/tasks/task-state.js.map +1 -0
- package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
- package/dist/core/tool-selection/tool-selection-controller.js +1 -1
- package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
- package/dist/core/tools/bash.d.ts +20 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +122 -197
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +3 -0
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +3 -0
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +9 -6
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +24 -9
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/python.d.ts +64 -0
- package/dist/core/tools/python.d.ts.map +1 -0
- package/dist/core/tools/python.js +241 -0
- package/dist/core/tools/python.js.map +1 -0
- package/dist/core/tools/shell-contract-router.d.ts +13 -0
- package/dist/core/tools/shell-contract-router.d.ts.map +1 -0
- package/dist/core/tools/shell-contract-router.js +295 -0
- package/dist/core/tools/shell-contract-router.js.map +1 -0
- package/dist/core/tools/task-steps.d.ts +46 -0
- package/dist/core/tools/task-steps.d.ts.map +1 -0
- package/dist/core/tools/task-steps.js +160 -0
- package/dist/core/tools/task-steps.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +12 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/session-flow-commands.d.ts +10 -0
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-flow-commands.js +43 -0
- package/dist/modes/interactive/session-flow-commands.js.map +1 -1
- package/dist/utils/shell.d.ts +6 -8
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +61 -70
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +23 -3
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +94 -11
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/index.md +2 -0
- package/docs/python.md +43 -0
- package/docs/settings.md +7 -3
- package/docs/task-steps.md +90 -0
- package/docs/termux.md +7 -0
- package/docs/usage.md +5 -6
- package/docs/windows.md +15 -12
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +32 -37
- package/package.json +9 -7
- package/scripts/postinstall-python-runtime.mjs +20 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-gate.d.ts","sourceRoot":"","sources":["../../../src/core/autonomy/approval-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"approval-gate.d.ts","sourceRoot":"","sources":["../../../src/core/autonomy/approval-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA0BrD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,cAAc,EAAE,CAKvG","sourcesContent":["import type { CapabilityName } from \"./contracts.ts\";\n\nconst TOOL_CAPABILITY_REQUIREMENTS = new Map<string, readonly CapabilityName[]>([\n\t[\"read\", [\"read_files\"]],\n\t[\"ls\", [\"read_files\"]],\n\t[\"grep\", [\"read_files\"]],\n\t[\"find\", [\"read_files\"]],\n\t[\"write\", [\"write_files\"]],\n\t[\"edit\", [\"write_files\"]],\n\t[\"edit-diff\", [\"write_files\"]],\n\t[\"bash\", [\"run_shell\"]],\n\t[\"python\", [\"run_shell\"]],\n\t[\"powershell\", [\"run_shell\"]],\n\t[\"shell\", [\"run_shell\"]],\n\t[\"fetch\", [\"network\"]],\n\t[\"web_search\", [\"network\"]],\n\t[\"skill_audit\", [\"skill_read\"]],\n\t[\"skillify\", [\"skill_write\"]],\n\t[\"extensionify\", [\"source_write\"]],\n\t[\"goal\", [\"memory_write\"]],\n\t[\"memory\", [\"memory_write\"]],\n\t[\"delegate\", [\"delegate\"]],\n\t[\"model_fitness\", [\"research\"]],\n\t[\"run_toolkit_script\", [\"run_shell\"]],\n]);\n\nexport function hasCapabilityPolicyForTool(toolName: string): boolean {\n\treturn TOOL_CAPABILITY_REQUIREMENTS.has(toolName);\n}\n\nexport function requiredCapabilitiesForTool(toolName: string, args?: unknown): readonly CapabilityName[] {\n\tif (toolName === \"memory\" && args && typeof args === \"object\" && \"query\" in args) {\n\t\treturn [\"memory_read\"];\n\t}\n\treturn TOOL_CAPABILITY_REQUIREMENTS.get(toolName) ?? [];\n}\n"]}
|
|
@@ -7,6 +7,8 @@ const TOOL_CAPABILITY_REQUIREMENTS = new Map([
|
|
|
7
7
|
["edit", ["write_files"]],
|
|
8
8
|
["edit-diff", ["write_files"]],
|
|
9
9
|
["bash", ["run_shell"]],
|
|
10
|
+
["python", ["run_shell"]],
|
|
11
|
+
["powershell", ["run_shell"]],
|
|
10
12
|
["shell", ["run_shell"]],
|
|
11
13
|
["fetch", ["network"]],
|
|
12
14
|
["web_search", ["network"]],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-gate.js","sourceRoot":"","sources":["../../../src/core/autonomy/approval-gate.ts"],"names":[],"mappings":"AAEA,MAAM,4BAA4B,GAAG,IAAI,GAAG,CAAoC;IAC/E,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC;IACxB,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC;IACtB,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC;IACxB,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC;IACxB,CAAC,OAAO,EAAE,CAAC,aAAa,CAAC,CAAC;IAC1B,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,CAAC;IACzB,CAAC,WAAW,EAAE,CAAC,aAAa,CAAC,CAAC;IAC9B,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC;IACxB,CAAC,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IACtB,CAAC,YAAY,EAAE,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC,aAAa,EAAE,CAAC,YAAY,CAAC,CAAC;IAC/B,CAAC,UAAU,EAAE,CAAC,aAAa,CAAC,CAAC;IAC7B,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,CAAC;IAClC,CAAC,MAAM,EAAE,CAAC,cAAc,CAAC,CAAC;IAC1B,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC,CAAC;IAC5B,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC;IAC1B,CAAC,eAAe,EAAE,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC,oBAAoB,EAAE,CAAC,WAAW,CAAC,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,UAAU,0BAA0B,CAAC,QAAgB,EAAW;IACrE,OAAO,4BAA4B,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAAA,CAClD;AAED,MAAM,UAAU,2BAA2B,CAAC,QAAgB,EAAE,IAAc,EAA6B;IACxG,IAAI,QAAQ,KAAK,QAAQ,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QAClF,OAAO,CAAC,aAAa,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,4BAA4B,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;AAAA,CACxD","sourcesContent":["import type { CapabilityName } from \"./contracts.ts\";\n\nconst TOOL_CAPABILITY_REQUIREMENTS = new Map<string, readonly CapabilityName[]>([\n\t[\"read\", [\"read_files\"]],\n\t[\"ls\", [\"read_files\"]],\n\t[\"grep\", [\"read_files\"]],\n\t[\"find\", [\"read_files\"]],\n\t[\"write\", [\"write_files\"]],\n\t[\"edit\", [\"write_files\"]],\n\t[\"edit-diff\", [\"write_files\"]],\n\t[\"bash\", [\"run_shell\"]],\n\t[\"shell\", [\"run_shell\"]],\n\t[\"fetch\", [\"network\"]],\n\t[\"web_search\", [\"network\"]],\n\t[\"skill_audit\", [\"skill_read\"]],\n\t[\"skillify\", [\"skill_write\"]],\n\t[\"extensionify\", [\"source_write\"]],\n\t[\"goal\", [\"memory_write\"]],\n\t[\"memory\", [\"memory_write\"]],\n\t[\"delegate\", [\"delegate\"]],\n\t[\"model_fitness\", [\"research\"]],\n\t[\"run_toolkit_script\", [\"run_shell\"]],\n]);\n\nexport function hasCapabilityPolicyForTool(toolName: string): boolean {\n\treturn TOOL_CAPABILITY_REQUIREMENTS.has(toolName);\n}\n\nexport function requiredCapabilitiesForTool(toolName: string, args?: unknown): readonly CapabilityName[] {\n\tif (toolName === \"memory\" && args && typeof args === \"object\" && \"query\" in args) {\n\t\treturn [\"memory_read\"];\n\t}\n\treturn TOOL_CAPABILITY_REQUIREMENTS.get(toolName) ?? [];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"approval-gate.js","sourceRoot":"","sources":["../../../src/core/autonomy/approval-gate.ts"],"names":[],"mappings":"AAEA,MAAM,4BAA4B,GAAG,IAAI,GAAG,CAAoC;IAC/E,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC;IACxB,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC;IACtB,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC;IACxB,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC,CAAC;IACxB,CAAC,OAAO,EAAE,CAAC,aAAa,CAAC,CAAC;IAC1B,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,CAAC;IACzB,CAAC,WAAW,EAAE,CAAC,aAAa,CAAC,CAAC;IAC9B,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC,CAAC;IACzB,CAAC,YAAY,EAAE,CAAC,WAAW,CAAC,CAAC;IAC7B,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC;IACxB,CAAC,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IACtB,CAAC,YAAY,EAAE,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC,aAAa,EAAE,CAAC,YAAY,CAAC,CAAC;IAC/B,CAAC,UAAU,EAAE,CAAC,aAAa,CAAC,CAAC;IAC7B,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,CAAC;IAClC,CAAC,MAAM,EAAE,CAAC,cAAc,CAAC,CAAC;IAC1B,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC,CAAC;IAC5B,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC;IAC1B,CAAC,eAAe,EAAE,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC,oBAAoB,EAAE,CAAC,WAAW,CAAC,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,UAAU,0BAA0B,CAAC,QAAgB,EAAW;IACrE,OAAO,4BAA4B,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAAA,CAClD;AAED,MAAM,UAAU,2BAA2B,CAAC,QAAgB,EAAE,IAAc,EAA6B;IACxG,IAAI,QAAQ,KAAK,QAAQ,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QAClF,OAAO,CAAC,aAAa,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,4BAA4B,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;AAAA,CACxD","sourcesContent":["import type { CapabilityName } from \"./contracts.ts\";\n\nconst TOOL_CAPABILITY_REQUIREMENTS = new Map<string, readonly CapabilityName[]>([\n\t[\"read\", [\"read_files\"]],\n\t[\"ls\", [\"read_files\"]],\n\t[\"grep\", [\"read_files\"]],\n\t[\"find\", [\"read_files\"]],\n\t[\"write\", [\"write_files\"]],\n\t[\"edit\", [\"write_files\"]],\n\t[\"edit-diff\", [\"write_files\"]],\n\t[\"bash\", [\"run_shell\"]],\n\t[\"python\", [\"run_shell\"]],\n\t[\"powershell\", [\"run_shell\"]],\n\t[\"shell\", [\"run_shell\"]],\n\t[\"fetch\", [\"network\"]],\n\t[\"web_search\", [\"network\"]],\n\t[\"skill_audit\", [\"skill_read\"]],\n\t[\"skillify\", [\"skill_write\"]],\n\t[\"extensionify\", [\"source_write\"]],\n\t[\"goal\", [\"memory_write\"]],\n\t[\"memory\", [\"memory_write\"]],\n\t[\"delegate\", [\"delegate\"]],\n\t[\"model_fitness\", [\"research\"]],\n\t[\"run_toolkit_script\", [\"run_shell\"]],\n]);\n\nexport function hasCapabilityPolicyForTool(toolName: string): boolean {\n\treturn TOOL_CAPABILITY_REQUIREMENTS.has(toolName);\n}\n\nexport function requiredCapabilitiesForTool(toolName: string, args?: unknown): readonly CapabilityName[] {\n\tif (toolName === \"memory\" && args && typeof args === \"object\" && \"query\" in args) {\n\t\treturn [\"memory_read\"];\n\t}\n\treturn TOOL_CAPABILITY_REQUIREMENTS.get(toolName) ?? [];\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"foreground-envelope.d.ts","sourceRoot":"","sources":["../../../src/core/autonomy/foreground-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"foreground-envelope.d.ts","sourceRoot":"","sources":["../../../src/core/autonomy/foreground-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,gBAAgB,CAAC;AA4BzE;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,kBAAkB,CAuBrB;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAKxF","sourcesContent":["import type { CapabilityEnvelope, CapabilityName } from \"./contracts.ts\";\n\n/**\n * G7: explicit tool-name -> capability mapping for foreground turns.\n *\n * Background lanes carry hand-authored {@link CapabilityEnvelope}s; foreground turns have none, so\n * {@link buildForegroundEnvelope} derives one per turn purely for VISIBILITY (observe-only this\n * round -- the foreground envelope is NOT enforced). Any tool not in this table contributes NO\n * capability: unknown capabilities are omitted, never guessed. Keys are matched against the\n * lowercased active tool name. Every value below is a real member of the {@link CapabilityName}\n * union in contracts.ts.\n */\nconst TOOL_CAPABILITY_MAP: Readonly<Record<string, CapabilityName>> = {\n\tread: \"read_files\",\n\tgrep: \"read_files\",\n\tfind: \"read_files\",\n\tls: \"read_files\",\n\tedit: \"write_files\",\n\twrite: \"write_files\",\n\tbash: \"run_shell\",\n\tpython: \"run_shell\",\n\tpowershell: \"run_shell\",\n\trun_toolkit_script: \"run_shell\",\n\tdelegate: \"delegate\",\n\tgoal: \"memory_write\",\n\tmemory: \"memory_write\",\n};\n\n/**\n * Build the auto-constructed foreground {@link CapabilityEnvelope} for a single prompt turn.\n *\n * Pure and deterministic. `capabilities` are derived from the active tool names via the explicit\n * {@link TOOL_CAPABILITY_MAP} (deduplicated, first-seen order; unknown tools omitted).\n * `allowedTools` mirrors the active tool names, `allowedPaths` scopes to the working directory, and\n * `maxEstimatedUsd` is set only when a positive per-turn ceiling is supplied.\n */\nexport function buildForegroundEnvelope(args: {\n\tturnIndex: number;\n\tactiveToolNames: readonly string[];\n\tcwd: string;\n\tmaxTurnUsd?: number;\n}): CapabilityEnvelope {\n\tconst { turnIndex, activeToolNames, cwd, maxTurnUsd } = args;\n\n\tconst capabilities: CapabilityName[] = [];\n\tconst seen = new Set<CapabilityName>();\n\tfor (const toolName of activeToolNames) {\n\t\tconst capability = TOOL_CAPABILITY_MAP[toolName.toLowerCase()];\n\t\tif (capability !== undefined && !seen.has(capability)) {\n\t\t\tseen.add(capability);\n\t\t\tcapabilities.push(capability);\n\t\t}\n\t}\n\n\tconst envelope: CapabilityEnvelope = {\n\t\tid: `foreground-turn-${turnIndex}`,\n\t\tcapabilities,\n\t\tallowedTools: [...activeToolNames],\n\t\tallowedPaths: [cwd],\n\t};\n\tif (typeof maxTurnUsd === \"number\" && maxTurnUsd > 0) {\n\t\tenvelope.maxEstimatedUsd = maxTurnUsd;\n\t}\n\treturn envelope;\n}\n\n/**\n * One bounded plain-text line describing a foreground envelope, for the /context dashboard.\n * Lists capability names (bounded by the small {@link CapabilityName} union) and the tool COUNT\n * (never the full tool list) so the line stays short regardless of how many tools are active.\n */\nexport function formatForegroundEnvelopeObservation(envelope: CapabilityEnvelope): string {\n\tconst capabilityNames = envelope.capabilities.length > 0 ? envelope.capabilities.join(\", \") : \"none\";\n\tconst toolCount = envelope.allowedTools?.length ?? 0;\n\tconst pathScope = envelope.allowedPaths?.[0] ?? \"(unscoped)\";\n\treturn `foreground envelope: ${envelope.capabilities.length} capability(ies) [${capabilityNames}], ${toolCount} tool(s), path scope ${pathScope}`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"foreground-envelope.js","sourceRoot":"","sources":["../../../src/core/autonomy/foreground-envelope.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,mBAAmB,GAA6C;IACrE,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;IAClB,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,aAAa;IACpB,IAAI,EAAE,WAAW;IACjB,kBAAkB,EAAE,WAAW;IAC/B,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,cAAc;CACtB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAKvC,EAAsB;IACtB,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAE7D,MAAM,YAAY,GAAqB,EAAE,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,QAAQ,IAAI,eAAe,EAAE,CAAC;QACxC,MAAM,UAAU,GAAG,mBAAmB,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QAC/D,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACrB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,GAAuB;QACpC,EAAE,EAAE,mBAAmB,SAAS,EAAE;QAClC,YAAY;QACZ,YAAY,EAAE,CAAC,GAAG,eAAe,CAAC;QAClC,YAAY,EAAE,CAAC,GAAG,CAAC;KACnB,CAAC;IACF,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACtD,QAAQ,CAAC,eAAe,GAAG,UAAU,CAAC;IACvC,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED;;;;GAIG;AACH,MAAM,UAAU,mCAAmC,CAAC,QAA4B,EAAU;IACzF,MAAM,eAAe,GAAG,QAAQ,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACrG,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,EAAE,MAAM,IAAI,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC;IAC7D,OAAO,wBAAwB,QAAQ,CAAC,YAAY,CAAC,MAAM,qBAAqB,eAAe,MAAM,SAAS,wBAAwB,SAAS,EAAE,CAAC;AAAA,CAClJ","sourcesContent":["import type { CapabilityEnvelope, CapabilityName } from \"./contracts.ts\";\n\n/**\n * G7: explicit tool-name -> capability mapping for foreground turns.\n *\n * Background lanes carry hand-authored {@link CapabilityEnvelope}s; foreground turns have none, so\n * {@link buildForegroundEnvelope} derives one per turn purely for VISIBILITY (observe-only this\n * round -- the foreground envelope is NOT enforced). Any tool not in this table contributes NO\n * capability: unknown capabilities are omitted, never guessed. Keys are matched against the\n * lowercased active tool name. Every value below is a real member of the {@link CapabilityName}\n * union in contracts.ts.\n */\nconst TOOL_CAPABILITY_MAP: Readonly<Record<string, CapabilityName>> = {\n\tread: \"read_files\",\n\tgrep: \"read_files\",\n\tfind: \"read_files\",\n\tls: \"read_files\",\n\tedit: \"write_files\",\n\twrite: \"write_files\",\n\tbash: \"run_shell\",\n\trun_toolkit_script: \"run_shell\",\n\tdelegate: \"delegate\",\n\tgoal: \"memory_write\",\n\tmemory: \"memory_write\",\n};\n\n/**\n * Build the auto-constructed foreground {@link CapabilityEnvelope} for a single prompt turn.\n *\n * Pure and deterministic. `capabilities` are derived from the active tool names via the explicit\n * {@link TOOL_CAPABILITY_MAP} (deduplicated, first-seen order; unknown tools omitted).\n * `allowedTools` mirrors the active tool names, `allowedPaths` scopes to the working directory, and\n * `maxEstimatedUsd` is set only when a positive per-turn ceiling is supplied.\n */\nexport function buildForegroundEnvelope(args: {\n\tturnIndex: number;\n\tactiveToolNames: readonly string[];\n\tcwd: string;\n\tmaxTurnUsd?: number;\n}): CapabilityEnvelope {\n\tconst { turnIndex, activeToolNames, cwd, maxTurnUsd } = args;\n\n\tconst capabilities: CapabilityName[] = [];\n\tconst seen = new Set<CapabilityName>();\n\tfor (const toolName of activeToolNames) {\n\t\tconst capability = TOOL_CAPABILITY_MAP[toolName.toLowerCase()];\n\t\tif (capability !== undefined && !seen.has(capability)) {\n\t\t\tseen.add(capability);\n\t\t\tcapabilities.push(capability);\n\t\t}\n\t}\n\n\tconst envelope: CapabilityEnvelope = {\n\t\tid: `foreground-turn-${turnIndex}`,\n\t\tcapabilities,\n\t\tallowedTools: [...activeToolNames],\n\t\tallowedPaths: [cwd],\n\t};\n\tif (typeof maxTurnUsd === \"number\" && maxTurnUsd > 0) {\n\t\tenvelope.maxEstimatedUsd = maxTurnUsd;\n\t}\n\treturn envelope;\n}\n\n/**\n * One bounded plain-text line describing a foreground envelope, for the /context dashboard.\n * Lists capability names (bounded by the small {@link CapabilityName} union) and the tool COUNT\n * (never the full tool list) so the line stays short regardless of how many tools are active.\n */\nexport function formatForegroundEnvelopeObservation(envelope: CapabilityEnvelope): string {\n\tconst capabilityNames = envelope.capabilities.length > 0 ? envelope.capabilities.join(\", \") : \"none\";\n\tconst toolCount = envelope.allowedTools?.length ?? 0;\n\tconst pathScope = envelope.allowedPaths?.[0] ?? \"(unscoped)\";\n\treturn `foreground envelope: ${envelope.capabilities.length} capability(ies) [${capabilityNames}], ${toolCount} tool(s), path scope ${pathScope}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"foreground-envelope.js","sourceRoot":"","sources":["../../../src/core/autonomy/foreground-envelope.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,mBAAmB,GAA6C;IACrE,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,YAAY;IAClB,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,aAAa;IACpB,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,WAAW;IACnB,UAAU,EAAE,WAAW;IACvB,kBAAkB,EAAE,WAAW;IAC/B,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,cAAc;CACtB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAKvC,EAAsB;IACtB,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAE7D,MAAM,YAAY,GAAqB,EAAE,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,QAAQ,IAAI,eAAe,EAAE,CAAC;QACxC,MAAM,UAAU,GAAG,mBAAmB,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QAC/D,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACrB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,GAAuB;QACpC,EAAE,EAAE,mBAAmB,SAAS,EAAE;QAClC,YAAY;QACZ,YAAY,EAAE,CAAC,GAAG,eAAe,CAAC;QAClC,YAAY,EAAE,CAAC,GAAG,CAAC;KACnB,CAAC;IACF,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACtD,QAAQ,CAAC,eAAe,GAAG,UAAU,CAAC;IACvC,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED;;;;GAIG;AACH,MAAM,UAAU,mCAAmC,CAAC,QAA4B,EAAU;IACzF,MAAM,eAAe,GAAG,QAAQ,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACrG,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,EAAE,MAAM,IAAI,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC;IAC7D,OAAO,wBAAwB,QAAQ,CAAC,YAAY,CAAC,MAAM,qBAAqB,eAAe,MAAM,SAAS,wBAAwB,SAAS,EAAE,CAAC;AAAA,CAClJ","sourcesContent":["import type { CapabilityEnvelope, CapabilityName } from \"./contracts.ts\";\n\n/**\n * G7: explicit tool-name -> capability mapping for foreground turns.\n *\n * Background lanes carry hand-authored {@link CapabilityEnvelope}s; foreground turns have none, so\n * {@link buildForegroundEnvelope} derives one per turn purely for VISIBILITY (observe-only this\n * round -- the foreground envelope is NOT enforced). Any tool not in this table contributes NO\n * capability: unknown capabilities are omitted, never guessed. Keys are matched against the\n * lowercased active tool name. Every value below is a real member of the {@link CapabilityName}\n * union in contracts.ts.\n */\nconst TOOL_CAPABILITY_MAP: Readonly<Record<string, CapabilityName>> = {\n\tread: \"read_files\",\n\tgrep: \"read_files\",\n\tfind: \"read_files\",\n\tls: \"read_files\",\n\tedit: \"write_files\",\n\twrite: \"write_files\",\n\tbash: \"run_shell\",\n\tpython: \"run_shell\",\n\tpowershell: \"run_shell\",\n\trun_toolkit_script: \"run_shell\",\n\tdelegate: \"delegate\",\n\tgoal: \"memory_write\",\n\tmemory: \"memory_write\",\n};\n\n/**\n * Build the auto-constructed foreground {@link CapabilityEnvelope} for a single prompt turn.\n *\n * Pure and deterministic. `capabilities` are derived from the active tool names via the explicit\n * {@link TOOL_CAPABILITY_MAP} (deduplicated, first-seen order; unknown tools omitted).\n * `allowedTools` mirrors the active tool names, `allowedPaths` scopes to the working directory, and\n * `maxEstimatedUsd` is set only when a positive per-turn ceiling is supplied.\n */\nexport function buildForegroundEnvelope(args: {\n\tturnIndex: number;\n\tactiveToolNames: readonly string[];\n\tcwd: string;\n\tmaxTurnUsd?: number;\n}): CapabilityEnvelope {\n\tconst { turnIndex, activeToolNames, cwd, maxTurnUsd } = args;\n\n\tconst capabilities: CapabilityName[] = [];\n\tconst seen = new Set<CapabilityName>();\n\tfor (const toolName of activeToolNames) {\n\t\tconst capability = TOOL_CAPABILITY_MAP[toolName.toLowerCase()];\n\t\tif (capability !== undefined && !seen.has(capability)) {\n\t\t\tseen.add(capability);\n\t\t\tcapabilities.push(capability);\n\t\t}\n\t}\n\n\tconst envelope: CapabilityEnvelope = {\n\t\tid: `foreground-turn-${turnIndex}`,\n\t\tcapabilities,\n\t\tallowedTools: [...activeToolNames],\n\t\tallowedPaths: [cwd],\n\t};\n\tif (typeof maxTurnUsd === \"number\" && maxTurnUsd > 0) {\n\t\tenvelope.maxEstimatedUsd = maxTurnUsd;\n\t}\n\treturn envelope;\n}\n\n/**\n * One bounded plain-text line describing a foreground envelope, for the /context dashboard.\n * Lists capability names (bounded by the small {@link CapabilityName} union) and the tool COUNT\n * (never the full tool list) so the line stays short regardless of how many tools are active.\n */\nexport function formatForegroundEnvelopeObservation(envelope: CapabilityEnvelope): string {\n\tconst capabilityNames = envelope.capabilities.length > 0 ? envelope.capabilities.join(\", \") : \"none\";\n\tconst toolCount = envelope.allowedTools?.length ?? 0;\n\tconst pathScope = envelope.allowedPaths?.[0] ?? \"(unscoped)\";\n\treturn `foreground envelope: ${envelope.capabilities.length} capability(ies) [${capabilityNames}], ${toolCount} tool(s), path scope ${pathScope}`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../../src/core/autonomy/gates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAmB,MAAM,gBAAgB,CAAC;AAmBvF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,CA+BjF;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAWhH;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,CAmB/E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC9B,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../../src/core/autonomy/gates.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAmB,MAAM,gBAAgB,CAAC;AAmBvF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,CA+BjF;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAWhH;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,CAmB/E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC9B,GAAG,WAAW,CAuJd","sourcesContent":["import path from \"node:path\";\nimport { hasCapabilityPolicyForTool, requiredCapabilitiesForTool } from \"./approval-gate.ts\";\nimport type { CapabilityEnvelope, GateOutcome, GateOutcomeKind } from \"./contracts.ts\";\nimport { checkPathScope } from \"./path-scope.ts\";\nimport { assessOperationRisk } from \"./risk-assessment.ts\";\n\nfunction isGateOutcomeKind(value: unknown): value is GateOutcomeKind {\n\treturn (\n\t\tvalue === \"allow\" || value === \"downgrade\" || value === \"escalate\" || value === \"ask-user\" || value === \"block\"\n\t);\n}\n\nfunction getPrecedence(kind: unknown): number {\n\tif (kind === \"allow\") return 0;\n\tif (kind === \"downgrade\") return 1;\n\tif (kind === \"escalate\") return 2;\n\tif (kind === \"ask-user\") return 3;\n\tif (kind === \"block\") return 4;\n\treturn 4; // Malformed/unknown outcome kind defaults to most restrictive (block)\n}\n\nexport function combineGateOutcomes(outcomes: readonly GateOutcome[]): GateOutcome {\n\tif (outcomes.length === 0) {\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"gate-combiner\",\n\t\t\treasonCode: \"no_gate_outcomes\",\n\t\t\tmessage: \"No gate outcomes to combine\",\n\t\t};\n\t}\n\n\tlet winner = outcomes[0];\n\tlet maxPrecedence = getPrecedence(winner.outcome);\n\n\tfor (let i = 1; i < outcomes.length; i++) {\n\t\tconst current = outcomes[i];\n\t\tconst currentPrecedence = getPrecedence(current.outcome);\n\t\tif (currentPrecedence > maxPrecedence) {\n\t\t\twinner = current;\n\t\t\tmaxPrecedence = currentPrecedence;\n\t\t}\n\t}\n\n\tif (!isGateOutcomeKind(winner.outcome)) {\n\t\treturn {\n\t\t\t...winner,\n\t\t\toutcome: \"block\",\n\t\t\tmessage: winner.message || \"Malformed outcome kind coerced to block\",\n\t\t};\n\t}\n\n\treturn winner;\n}\n\nexport function fallbackGateOutcome(args: { gate: string; reversible: boolean; reasonCode: string }): GateOutcome {\n\tconst gate = (args.gate || \"\").trim() || \"unknown_gate\";\n\tconst reasonCode = (args.reasonCode || \"\").trim() || \"unknown_reason\";\n\tconst outcome: GateOutcomeKind = args.reversible ? \"ask-user\" : \"block\";\n\n\treturn {\n\t\toutcome,\n\t\tgate,\n\t\treasonCode,\n\t\tmessage: `Fallback gate outcome: ${outcome} for gate ${gate} (${reasonCode})`,\n\t};\n}\n\nexport function extractCandidatePaths(toolName: string, args: unknown): string[] {\n\tif (!args || typeof args !== \"object\") return [];\n\tconst obj = args as Record<string, unknown>;\n\tconst paths: string[] = [];\n\n\tif (\n\t\ttoolName === \"read\" ||\n\t\ttoolName === \"write\" ||\n\t\ttoolName === \"edit\" ||\n\t\ttoolName === \"ls\" ||\n\t\ttoolName === \"grep\" ||\n\t\ttoolName === \"find\"\n\t) {\n\t\tif (typeof obj.path === \"string\" && obj.path.trim()) {\n\t\t\tpaths.push(obj.path.trim());\n\t\t}\n\t}\n\n\treturn paths;\n}\n\nexport function evaluateToolGate(input: {\n\ttoolName: string;\n\targs?: unknown;\n\tcwd: string;\n\tenvelope?: CapabilityEnvelope;\n}): GateOutcome {\n\tif (!input.envelope) {\n\t\treturn {\n\t\t\toutcome: \"allow\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"no_envelope\",\n\t\t\tmessage: \"No envelope active, preserving existing session behavior.\",\n\t\t};\n\t}\n\n\tconst envelope = input.envelope;\n\n\t// 1. Tool allow/deny list overrides\n\tif (envelope.deniedTools?.includes(input.toolName)) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"tool_denied\",\n\t\t\tmessage: `Tool '${input.toolName}' is explicitly denied.`,\n\t\t};\n\t}\n\n\tif (envelope.allowedTools && !envelope.allowedTools.includes(input.toolName)) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"tool_not_allowed\",\n\t\t\tmessage: `Tool '${input.toolName}' is not in the allowed tools list.`,\n\t\t};\n\t}\n\n\t// 2. Path scope containment for file tools\n\tconst paths = extractCandidatePaths(input.toolName, input.args);\n\tif (paths.length > 0 && envelope.allowedPaths) {\n\t\t// If envelope has allowedPaths, we must check them\n\t\tfor (const targetPath of paths) {\n\t\t\tconst scopedTargetPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(input.cwd, targetPath);\n\t\t\tlet isInsideAny = false;\n\t\t\tlet isDenied = false;\n\t\t\tlet denyRule = \"\";\n\n\t\t\tfor (const allowedRoot of envelope.allowedPaths) {\n\t\t\t\tconst decision = checkPathScope(\n\t\t\t\t\t{\n\t\t\t\t\t\troot: allowedRoot,\n\t\t\t\t\t\tallowedPaths: envelope.allowedPaths,\n\t\t\t\t\t\tdeniedPaths: envelope.deniedPaths,\n\t\t\t\t\t},\n\t\t\t\t\tscopedTargetPath,\n\t\t\t\t);\n\n\t\t\t\tif (decision.kind === \"denied\") {\n\t\t\t\t\tisDenied = true;\n\t\t\t\t\tdenyRule = decision.matchedRule || \"\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (decision.kind === \"inside\") {\n\t\t\t\t\tisInsideAny = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isDenied) {\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"path_scope\",\n\t\t\t\t\treasonCode: \"path_denied\",\n\t\t\t\t\tmessage: `Path '${targetPath}' is explicitly denied by rule '${denyRule}'.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!isInsideAny) {\n\t\t\t\t// Block only if the tool is mutating. Wait, read path outside allowed root -> block.\n\t\t\t\t// \"read path inside allowed root -> allow. write/edit path outside allowed root -> block. denied path inside allowed root -> block.\"\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"path_scope\",\n\t\t\t\t\treasonCode: \"path_outside_allowed_roots\",\n\t\t\t\t\tmessage: `Path '${targetPath}' is outside all allowed roots.`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2.5. Capability checks\n\tif (!hasCapabilityPolicyForTool(input.toolName)) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"unknown_tool_capability\",\n\t\t\tmessage: `Tool '${input.toolName}' has no capability policy in the active envelope.`,\n\t\t};\n\t}\n\n\tconst requiredCaps = requiredCapabilitiesForTool(input.toolName, input.args);\n\tfor (const reqCap of requiredCaps) {\n\t\tif (!envelope.capabilities.includes(reqCap)) {\n\t\t\treturn {\n\t\t\t\toutcome: \"block\",\n\t\t\t\tgate: \"tool_gate\",\n\t\t\t\treasonCode: \"missing_capability\",\n\t\t\t\tmessage: `Tool '${input.toolName}' requires capability '${reqCap}', which is missing from the active envelope.`,\n\t\t\t};\n\t\t}\n\t}\n\tlet command = \"\";\n\tif (\n\t\tinput.toolName === \"bash\" ||\n\t\tinput.toolName === \"powershell\" ||\n\t\tinput.toolName === \"shell\" ||\n\t\tinput.toolName === \"python\"\n\t) {\n\t\tconst argsObj = input.args as Record<string, unknown>;\n\t\tif (argsObj && typeof argsObj.command === \"string\") command = argsObj.command;\n\t\telse if (input.toolName === \"python\" && argsObj && typeof argsObj.code === \"string\") command = argsObj.code;\n\t\telse if (input.toolName === \"python\" && argsObj && typeof argsObj.scriptPath === \"string\") {\n\t\t\tcommand = `python ${argsObj.scriptPath}`;\n\t\t}\n\t}\n\n\tconst riskResult = assessOperationRisk({\n\t\toperation: `Tool ${input.toolName}`,\n\t\ttoolName: input.toolName,\n\t\tcommand,\n\t\tpaths,\n\t\tcapabilities: envelope.capabilities,\n\t});\n\n\tif (riskResult.requiresApproval) {\n\t\treturn {\n\t\t\toutcome: \"ask-user\", // or block, prompt says: ask-user/block\n\t\t\tgate: \"risk_assessment\",\n\t\t\treasonCode: riskResult.reasonCode,\n\t\t\tmessage: `Operation requires approval: ${riskResult.reasons.join(\", \")}`,\n\t\t};\n\t}\n\n\tif (riskResult.risk === \"high-impact\") {\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"risk_assessment\",\n\t\t\treasonCode: riskResult.reasonCode,\n\t\t\tmessage: `High-impact operation requires review: ${riskResult.reasons.join(\", \")}`,\n\t\t};\n\t}\n\n\treturn {\n\t\toutcome: \"allow\",\n\t\tgate: \"tool_gate\",\n\t\treasonCode: \"allowed_by_envelope\",\n\t\tmessage: \"Operation allowed by current capability envelope.\",\n\t};\n}\n"]}
|
|
@@ -166,10 +166,17 @@ export function evaluateToolGate(input) {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
let command = "";
|
|
169
|
-
if (input.toolName === "bash" ||
|
|
169
|
+
if (input.toolName === "bash" ||
|
|
170
|
+
input.toolName === "powershell" ||
|
|
171
|
+
input.toolName === "shell" ||
|
|
172
|
+
input.toolName === "python") {
|
|
170
173
|
const argsObj = input.args;
|
|
171
|
-
if (argsObj && typeof argsObj.command === "string")
|
|
174
|
+
if (argsObj && typeof argsObj.command === "string")
|
|
172
175
|
command = argsObj.command;
|
|
176
|
+
else if (input.toolName === "python" && argsObj && typeof argsObj.code === "string")
|
|
177
|
+
command = argsObj.code;
|
|
178
|
+
else if (input.toolName === "python" && argsObj && typeof argsObj.scriptPath === "string") {
|
|
179
|
+
command = `python ${argsObj.scriptPath}`;
|
|
173
180
|
}
|
|
174
181
|
}
|
|
175
182
|
const riskResult = assessOperationRisk({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gates.js","sourceRoot":"","sources":["../../../src/core/autonomy/gates.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { hasCapabilityPolicyForTool, requiredCapabilitiesForTool } from \"./approval-gate.ts\";\nimport type { CapabilityEnvelope, GateOutcome, GateOutcomeKind } from \"./contracts.ts\";\nimport { checkPathScope } from \"./path-scope.ts\";\nimport { assessOperationRisk } from \"./risk-assessment.ts\";\n\nfunction isGateOutcomeKind(value: unknown): value is GateOutcomeKind {\n\treturn (\n\t\tvalue === \"allow\" || value === \"downgrade\" || value === \"escalate\" || value === \"ask-user\" || value === \"block\"\n\t);\n}\n\nfunction getPrecedence(kind: unknown): number {\n\tif (kind === \"allow\") return 0;\n\tif (kind === \"downgrade\") return 1;\n\tif (kind === \"escalate\") return 2;\n\tif (kind === \"ask-user\") return 3;\n\tif (kind === \"block\") return 4;\n\treturn 4; // Malformed/unknown outcome kind defaults to most restrictive (block)\n}\n\nexport function combineGateOutcomes(outcomes: readonly GateOutcome[]): GateOutcome {\n\tif (outcomes.length === 0) {\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"gate-combiner\",\n\t\t\treasonCode: \"no_gate_outcomes\",\n\t\t\tmessage: \"No gate outcomes to combine\",\n\t\t};\n\t}\n\n\tlet winner = outcomes[0];\n\tlet maxPrecedence = getPrecedence(winner.outcome);\n\n\tfor (let i = 1; i < outcomes.length; i++) {\n\t\tconst current = outcomes[i];\n\t\tconst currentPrecedence = getPrecedence(current.outcome);\n\t\tif (currentPrecedence > maxPrecedence) {\n\t\t\twinner = current;\n\t\t\tmaxPrecedence = currentPrecedence;\n\t\t}\n\t}\n\n\tif (!isGateOutcomeKind(winner.outcome)) {\n\t\treturn {\n\t\t\t...winner,\n\t\t\toutcome: \"block\",\n\t\t\tmessage: winner.message || \"Malformed outcome kind coerced to block\",\n\t\t};\n\t}\n\n\treturn winner;\n}\n\nexport function fallbackGateOutcome(args: { gate: string; reversible: boolean; reasonCode: string }): GateOutcome {\n\tconst gate = (args.gate || \"\").trim() || \"unknown_gate\";\n\tconst reasonCode = (args.reasonCode || \"\").trim() || \"unknown_reason\";\n\tconst outcome: GateOutcomeKind = args.reversible ? \"ask-user\" : \"block\";\n\n\treturn {\n\t\toutcome,\n\t\tgate,\n\t\treasonCode,\n\t\tmessage: `Fallback gate outcome: ${outcome} for gate ${gate} (${reasonCode})`,\n\t};\n}\n\nexport function extractCandidatePaths(toolName: string, args: unknown): string[] {\n\tif (!args || typeof args !== \"object\") return [];\n\tconst obj = args as Record<string, unknown>;\n\tconst paths: string[] = [];\n\n\tif (\n\t\ttoolName === \"read\" ||\n\t\ttoolName === \"write\" ||\n\t\ttoolName === \"edit\" ||\n\t\ttoolName === \"ls\" ||\n\t\ttoolName === \"grep\" ||\n\t\ttoolName === \"find\"\n\t) {\n\t\tif (typeof obj.path === \"string\" && obj.path.trim()) {\n\t\t\tpaths.push(obj.path.trim());\n\t\t}\n\t}\n\n\treturn paths;\n}\n\nexport function evaluateToolGate(input: {\n\ttoolName: string;\n\targs?: unknown;\n\tcwd: string;\n\tenvelope?: CapabilityEnvelope;\n}): GateOutcome {\n\tif (!input.envelope) {\n\t\treturn {\n\t\t\toutcome: \"allow\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"no_envelope\",\n\t\t\tmessage: \"No envelope active, preserving existing session behavior.\",\n\t\t};\n\t}\n\n\tconst envelope = input.envelope;\n\n\t// 1. Tool allow/deny list overrides\n\tif (envelope.deniedTools?.includes(input.toolName)) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"tool_denied\",\n\t\t\tmessage: `Tool '${input.toolName}' is explicitly denied.`,\n\t\t};\n\t}\n\n\tif (envelope.allowedTools && !envelope.allowedTools.includes(input.toolName)) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"tool_not_allowed\",\n\t\t\tmessage: `Tool '${input.toolName}' is not in the allowed tools list.`,\n\t\t};\n\t}\n\n\t// 2. Path scope containment for file tools\n\tconst paths = extractCandidatePaths(input.toolName, input.args);\n\tif (paths.length > 0 && envelope.allowedPaths) {\n\t\t// If envelope has allowedPaths, we must check them\n\t\tfor (const targetPath of paths) {\n\t\t\tconst scopedTargetPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(input.cwd, targetPath);\n\t\t\tlet isInsideAny = false;\n\t\t\tlet isDenied = false;\n\t\t\tlet denyRule = \"\";\n\n\t\t\tfor (const allowedRoot of envelope.allowedPaths) {\n\t\t\t\tconst decision = checkPathScope(\n\t\t\t\t\t{\n\t\t\t\t\t\troot: allowedRoot,\n\t\t\t\t\t\tallowedPaths: envelope.allowedPaths,\n\t\t\t\t\t\tdeniedPaths: envelope.deniedPaths,\n\t\t\t\t\t},\n\t\t\t\t\tscopedTargetPath,\n\t\t\t\t);\n\n\t\t\t\tif (decision.kind === \"denied\") {\n\t\t\t\t\tisDenied = true;\n\t\t\t\t\tdenyRule = decision.matchedRule || \"\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (decision.kind === \"inside\") {\n\t\t\t\t\tisInsideAny = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isDenied) {\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"path_scope\",\n\t\t\t\t\treasonCode: \"path_denied\",\n\t\t\t\t\tmessage: `Path '${targetPath}' is explicitly denied by rule '${denyRule}'.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!isInsideAny) {\n\t\t\t\t// Block only if the tool is mutating. Wait, read path outside allowed root -> block.\n\t\t\t\t// \"read path inside allowed root -> allow. write/edit path outside allowed root -> block. denied path inside allowed root -> block.\"\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"path_scope\",\n\t\t\t\t\treasonCode: \"path_outside_allowed_roots\",\n\t\t\t\t\tmessage: `Path '${targetPath}' is outside all allowed roots.`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2.5. Capability checks\n\tif (!hasCapabilityPolicyForTool(input.toolName)) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"unknown_tool_capability\",\n\t\t\tmessage: `Tool '${input.toolName}' has no capability policy in the active envelope.`,\n\t\t};\n\t}\n\n\tconst requiredCaps = requiredCapabilitiesForTool(input.toolName, input.args);\n\tfor (const reqCap of requiredCaps) {\n\t\tif (!envelope.capabilities.includes(reqCap)) {\n\t\t\treturn {\n\t\t\t\toutcome: \"block\",\n\t\t\t\tgate: \"tool_gate\",\n\t\t\t\treasonCode: \"missing_capability\",\n\t\t\t\tmessage: `Tool '${input.toolName}' requires capability '${reqCap}', which is missing from the active envelope.`,\n\t\t\t};\n\t\t}\n\t}\n\tlet command = \"\";\n\tif (input.toolName === \"bash\" || input.toolName === \"shell\") {\n\t\tconst argsObj = input.args as Record<string, unknown>;\n\t\tif (argsObj && typeof argsObj.command === \"string\") {\n\t\t\tcommand = argsObj.command;\n\t\t}\n\t}\n\n\tconst riskResult = assessOperationRisk({\n\t\toperation: `Tool ${input.toolName}`,\n\t\ttoolName: input.toolName,\n\t\tcommand,\n\t\tpaths,\n\t\tcapabilities: envelope.capabilities,\n\t});\n\n\tif (riskResult.requiresApproval) {\n\t\treturn {\n\t\t\toutcome: \"ask-user\", // or block, prompt says: ask-user/block\n\t\t\tgate: \"risk_assessment\",\n\t\t\treasonCode: riskResult.reasonCode,\n\t\t\tmessage: `Operation requires approval: ${riskResult.reasons.join(\", \")}`,\n\t\t};\n\t}\n\n\tif (riskResult.risk === \"high-impact\") {\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"risk_assessment\",\n\t\t\treasonCode: riskResult.reasonCode,\n\t\t\tmessage: `High-impact operation requires review: ${riskResult.reasons.join(\", \")}`,\n\t\t};\n\t}\n\n\treturn {\n\t\toutcome: \"allow\",\n\t\tgate: \"tool_gate\",\n\t\treasonCode: \"allowed_by_envelope\",\n\t\tmessage: \"Operation allowed by current capability envelope.\",\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"gates.js","sourceRoot":"","sources":["../../../src/core/autonomy/gates.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { hasCapabilityPolicyForTool, requiredCapabilitiesForTool } from \"./approval-gate.ts\";\nimport type { CapabilityEnvelope, GateOutcome, GateOutcomeKind } from \"./contracts.ts\";\nimport { checkPathScope } from \"./path-scope.ts\";\nimport { assessOperationRisk } from \"./risk-assessment.ts\";\n\nfunction isGateOutcomeKind(value: unknown): value is GateOutcomeKind {\n\treturn (\n\t\tvalue === \"allow\" || value === \"downgrade\" || value === \"escalate\" || value === \"ask-user\" || value === \"block\"\n\t);\n}\n\nfunction getPrecedence(kind: unknown): number {\n\tif (kind === \"allow\") return 0;\n\tif (kind === \"downgrade\") return 1;\n\tif (kind === \"escalate\") return 2;\n\tif (kind === \"ask-user\") return 3;\n\tif (kind === \"block\") return 4;\n\treturn 4; // Malformed/unknown outcome kind defaults to most restrictive (block)\n}\n\nexport function combineGateOutcomes(outcomes: readonly GateOutcome[]): GateOutcome {\n\tif (outcomes.length === 0) {\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"gate-combiner\",\n\t\t\treasonCode: \"no_gate_outcomes\",\n\t\t\tmessage: \"No gate outcomes to combine\",\n\t\t};\n\t}\n\n\tlet winner = outcomes[0];\n\tlet maxPrecedence = getPrecedence(winner.outcome);\n\n\tfor (let i = 1; i < outcomes.length; i++) {\n\t\tconst current = outcomes[i];\n\t\tconst currentPrecedence = getPrecedence(current.outcome);\n\t\tif (currentPrecedence > maxPrecedence) {\n\t\t\twinner = current;\n\t\t\tmaxPrecedence = currentPrecedence;\n\t\t}\n\t}\n\n\tif (!isGateOutcomeKind(winner.outcome)) {\n\t\treturn {\n\t\t\t...winner,\n\t\t\toutcome: \"block\",\n\t\t\tmessage: winner.message || \"Malformed outcome kind coerced to block\",\n\t\t};\n\t}\n\n\treturn winner;\n}\n\nexport function fallbackGateOutcome(args: { gate: string; reversible: boolean; reasonCode: string }): GateOutcome {\n\tconst gate = (args.gate || \"\").trim() || \"unknown_gate\";\n\tconst reasonCode = (args.reasonCode || \"\").trim() || \"unknown_reason\";\n\tconst outcome: GateOutcomeKind = args.reversible ? \"ask-user\" : \"block\";\n\n\treturn {\n\t\toutcome,\n\t\tgate,\n\t\treasonCode,\n\t\tmessage: `Fallback gate outcome: ${outcome} for gate ${gate} (${reasonCode})`,\n\t};\n}\n\nexport function extractCandidatePaths(toolName: string, args: unknown): string[] {\n\tif (!args || typeof args !== \"object\") return [];\n\tconst obj = args as Record<string, unknown>;\n\tconst paths: string[] = [];\n\n\tif (\n\t\ttoolName === \"read\" ||\n\t\ttoolName === \"write\" ||\n\t\ttoolName === \"edit\" ||\n\t\ttoolName === \"ls\" ||\n\t\ttoolName === \"grep\" ||\n\t\ttoolName === \"find\"\n\t) {\n\t\tif (typeof obj.path === \"string\" && obj.path.trim()) {\n\t\t\tpaths.push(obj.path.trim());\n\t\t}\n\t}\n\n\treturn paths;\n}\n\nexport function evaluateToolGate(input: {\n\ttoolName: string;\n\targs?: unknown;\n\tcwd: string;\n\tenvelope?: CapabilityEnvelope;\n}): GateOutcome {\n\tif (!input.envelope) {\n\t\treturn {\n\t\t\toutcome: \"allow\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"no_envelope\",\n\t\t\tmessage: \"No envelope active, preserving existing session behavior.\",\n\t\t};\n\t}\n\n\tconst envelope = input.envelope;\n\n\t// 1. Tool allow/deny list overrides\n\tif (envelope.deniedTools?.includes(input.toolName)) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"tool_denied\",\n\t\t\tmessage: `Tool '${input.toolName}' is explicitly denied.`,\n\t\t};\n\t}\n\n\tif (envelope.allowedTools && !envelope.allowedTools.includes(input.toolName)) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"tool_not_allowed\",\n\t\t\tmessage: `Tool '${input.toolName}' is not in the allowed tools list.`,\n\t\t};\n\t}\n\n\t// 2. Path scope containment for file tools\n\tconst paths = extractCandidatePaths(input.toolName, input.args);\n\tif (paths.length > 0 && envelope.allowedPaths) {\n\t\t// If envelope has allowedPaths, we must check them\n\t\tfor (const targetPath of paths) {\n\t\t\tconst scopedTargetPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(input.cwd, targetPath);\n\t\t\tlet isInsideAny = false;\n\t\t\tlet isDenied = false;\n\t\t\tlet denyRule = \"\";\n\n\t\t\tfor (const allowedRoot of envelope.allowedPaths) {\n\t\t\t\tconst decision = checkPathScope(\n\t\t\t\t\t{\n\t\t\t\t\t\troot: allowedRoot,\n\t\t\t\t\t\tallowedPaths: envelope.allowedPaths,\n\t\t\t\t\t\tdeniedPaths: envelope.deniedPaths,\n\t\t\t\t\t},\n\t\t\t\t\tscopedTargetPath,\n\t\t\t\t);\n\n\t\t\t\tif (decision.kind === \"denied\") {\n\t\t\t\t\tisDenied = true;\n\t\t\t\t\tdenyRule = decision.matchedRule || \"\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (decision.kind === \"inside\") {\n\t\t\t\t\tisInsideAny = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isDenied) {\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"path_scope\",\n\t\t\t\t\treasonCode: \"path_denied\",\n\t\t\t\t\tmessage: `Path '${targetPath}' is explicitly denied by rule '${denyRule}'.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!isInsideAny) {\n\t\t\t\t// Block only if the tool is mutating. Wait, read path outside allowed root -> block.\n\t\t\t\t// \"read path inside allowed root -> allow. write/edit path outside allowed root -> block. denied path inside allowed root -> block.\"\n\t\t\t\treturn {\n\t\t\t\t\toutcome: \"block\",\n\t\t\t\t\tgate: \"path_scope\",\n\t\t\t\t\treasonCode: \"path_outside_allowed_roots\",\n\t\t\t\t\tmessage: `Path '${targetPath}' is outside all allowed roots.`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2.5. Capability checks\n\tif (!hasCapabilityPolicyForTool(input.toolName)) {\n\t\treturn {\n\t\t\toutcome: \"block\",\n\t\t\tgate: \"tool_gate\",\n\t\t\treasonCode: \"unknown_tool_capability\",\n\t\t\tmessage: `Tool '${input.toolName}' has no capability policy in the active envelope.`,\n\t\t};\n\t}\n\n\tconst requiredCaps = requiredCapabilitiesForTool(input.toolName, input.args);\n\tfor (const reqCap of requiredCaps) {\n\t\tif (!envelope.capabilities.includes(reqCap)) {\n\t\t\treturn {\n\t\t\t\toutcome: \"block\",\n\t\t\t\tgate: \"tool_gate\",\n\t\t\t\treasonCode: \"missing_capability\",\n\t\t\t\tmessage: `Tool '${input.toolName}' requires capability '${reqCap}', which is missing from the active envelope.`,\n\t\t\t};\n\t\t}\n\t}\n\tlet command = \"\";\n\tif (\n\t\tinput.toolName === \"bash\" ||\n\t\tinput.toolName === \"powershell\" ||\n\t\tinput.toolName === \"shell\" ||\n\t\tinput.toolName === \"python\"\n\t) {\n\t\tconst argsObj = input.args as Record<string, unknown>;\n\t\tif (argsObj && typeof argsObj.command === \"string\") command = argsObj.command;\n\t\telse if (input.toolName === \"python\" && argsObj && typeof argsObj.code === \"string\") command = argsObj.code;\n\t\telse if (input.toolName === \"python\" && argsObj && typeof argsObj.scriptPath === \"string\") {\n\t\t\tcommand = `python ${argsObj.scriptPath}`;\n\t\t}\n\t}\n\n\tconst riskResult = assessOperationRisk({\n\t\toperation: `Tool ${input.toolName}`,\n\t\ttoolName: input.toolName,\n\t\tcommand,\n\t\tpaths,\n\t\tcapabilities: envelope.capabilities,\n\t});\n\n\tif (riskResult.requiresApproval) {\n\t\treturn {\n\t\t\toutcome: \"ask-user\", // or block, prompt says: ask-user/block\n\t\t\tgate: \"risk_assessment\",\n\t\t\treasonCode: riskResult.reasonCode,\n\t\t\tmessage: `Operation requires approval: ${riskResult.reasons.join(\", \")}`,\n\t\t};\n\t}\n\n\tif (riskResult.risk === \"high-impact\") {\n\t\treturn {\n\t\t\toutcome: \"ask-user\",\n\t\t\tgate: \"risk_assessment\",\n\t\t\treasonCode: riskResult.reasonCode,\n\t\t\tmessage: `High-impact operation requires review: ${riskResult.reasons.join(\", \")}`,\n\t\t};\n\t}\n\n\treturn {\n\t\toutcome: \"allow\",\n\t\tgate: \"tool_gate\",\n\t\treasonCode: \"allowed_by_envelope\",\n\t\tmessage: \"Operation allowed by current capability envelope.\",\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"risk-assessment.d.ts","sourceRoot":"","sources":["../../../src/core/autonomy/risk-assessment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"risk-assessment.d.ts","sourceRoot":"","sources":["../../../src/core/autonomy/risk-assessment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AA8B1E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,mBAAmB,GAAG,cAAc,CAoH9E","sourcesContent":["import type { RiskAssessment, RiskAssessmentInput } from \"./contracts.ts\";\n\nconst RELEASE_PUBLISH_RE = /\\b(publish|release|push|deploy|tag)\\b/i;\nconst SECURITY_AUTH_RE = /\\b(auth|token|credential|credentials|secret|api[-_]key)\\b/i;\nconst DESTRUCTIVE_RE = /\\b(delete|reset|rm\\s+-rf|clean)\\b/i;\n\nconst SELF_MOD_MUTATE_RE =\n\t/\\b(modify|change|write|update|edit|delete|add|remove|set)\\s+.*\\b(skills|prompts|settings|tools|behavior)\\b|self[-_]modification/i;\nconst ARCHITECTURE_MUTATE_RE = /\\b(rewrite|redesign|change|modify|rearchitect)\\s+.*\\b(architecture|architect)\\b/i;\n\nconst READ_ONLY_QUESTION_RE =\n\t/^(?:(?:can you|could you|please|pls|go ahead and|let'?s|i need you to|we need to|you should)\\s+)?(?:how|what|why|when|where|which|who|explain|summarize|compare|describe|list|show|search|find|view|read|locate)\\b/i;\n\nconst EXPLICIT_MODIFY_REQUEST_RE =\n\t/^(?:can you|could you|please|pls|go ahead and|let'?s|i need you to|we need to|you should)\\s+.*\\b(add|apply|build|change|commit|create|delete|edit|fix|generate|implement|install|modify|patch|refactor|remove|rename|replace|run|test|update|write|publish|release|push|deploy|tag|reset|clean|rewrite)\\b/i;\n\n// Shell parsing helpers\nconst DESTRUCTIVE_CMD_RE =\n\t/\\b(rm(\\s+-r|\\s+-f|\\s+-rf|\\s+-fr)?|mv|cp|chmod|chown|install)\\b|>\\s*\\/dev\\/(sda|hda|vda)|\\b(dd\\s+if=)/i;\nconst GIT_MUTATE_CMD_RE = /\\bgit\\s+(commit|push|reset|clean|stash|rebase)\\b/i;\nconst PKG_MUTATE_CMD_RE = /\\b(npm|pnpm|yarn|bun)\\s+(install|i|update|up|publish|run|remove|rm|uninstall)\\b/i;\nconst RELEASE_DEPLOY_CMD_RE = /\\b(release|deploy)\\b/i;\nconst PYTHON_HIGH_IMPACT_RE =\n\t/\\b(?:shutil\\.(?:rmtree|move|copy|copy2|copytree)|os\\.(?:remove|unlink|rmdir|removedirs|system)|subprocess\\.(?:run|call|check_call|check_output|Popen)|Path\\([^)]*\\)\\.(?:unlink|rmdir))\\b/i;\nconst REDIRECTION_RE = /[<>]/;\n\nfunction stripSingleQuotes(cmd: string): string {\n\treturn cmd.replace(/'[^']*'/g, \"''\");\n}\n\nexport function assessOperationRisk(input: RiskAssessmentInput): RiskAssessment {\n\tconst operation = input.operation.trim();\n\tconst command = input.command?.trim() ?? \"\";\n\tconst cleanCommand = command ? stripSingleQuotes(command) : \"\";\n\tconst fullText = `${operation} ${command}`.trim();\n\tconst cleanFullText = `${operation} ${cleanCommand}`.trim();\n\n\tif (fullText.length === 0) {\n\t\treturn {\n\t\t\trisk: \"read-only\",\n\t\t\treasonCode: \"empty_operation\",\n\t\t\treasons: [\"Empty operation\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\t// 1. Explicit read-only operations\n\tif (READ_ONLY_QUESTION_RE.test(operation) && !EXPLICIT_MODIFY_REQUEST_RE.test(operation)) {\n\t\treturn {\n\t\t\trisk: \"read-only\",\n\t\t\treasonCode: \"read_only_operation\",\n\t\t\treasons: [\"Operation is explicitly read-only (list/show/read/search)\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\t// 2. High-risk actions\n\tif (RELEASE_PUBLISH_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\",\n\t\t\treasonCode: \"release_publish_operation\",\n\t\t\treasons: [\"Operation mentions releasing, publishing, or deploying\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (SECURITY_AUTH_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\", // updated based on user instruction: \"high-impact or approval-required\"\n\t\t\treasonCode: \"security_auth_operation\",\n\t\t\treasons: [\"Operation mentions authentication or credentials\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (DESTRUCTIVE_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\",\n\t\t\treasonCode: \"destructive_operation\",\n\t\t\treasons: [\"Operation involves deleting, resetting, or cleaning\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (SELF_MOD_MUTATE_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\",\n\t\t\treasonCode: \"self_modification_operation\",\n\t\t\treasons: [\"Operation modifies settings, tools, skills, or prompts\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (ARCHITECTURE_MUTATE_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"high-impact\",\n\t\t\treasonCode: \"architecture_mutation_operation\",\n\t\t\treasons: [\"Operation mentions rewriting or rearchitecting core parts\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\t// 3. Command risks\n\tif (command) {\n\t\tconst cleanCmd = stripSingleQuotes(command);\n\t\tif (\n\t\t\tDESTRUCTIVE_CMD_RE.test(cleanCmd) ||\n\t\t\tGIT_MUTATE_CMD_RE.test(cleanCmd) ||\n\t\t\tPKG_MUTATE_CMD_RE.test(cleanCmd) ||\n\t\t\tRELEASE_DEPLOY_CMD_RE.test(cleanCmd) ||\n\t\t\tPYTHON_HIGH_IMPACT_RE.test(cleanCmd) ||\n\t\t\tREDIRECTION_RE.test(cleanCmd)\n\t\t) {\n\t\t\treturn {\n\t\t\t\trisk: \"approval-required\",\n\t\t\t\treasonCode: \"mutating_command\",\n\t\t\t\treasons: [\"Command executes a destructive, mutating, or publish operation\"],\n\t\t\t\trequiresApproval: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Default to scoped-write for any generic mutating tool action if it has a toolName or isn't read-only\n\t// or read-only if it's purely a non-mutating intent (but since it didn't match read-only above, we default to scoped-write)\n\tif (\n\t\tinput.toolName &&\n\t\t![\"read_file\", \"search_web\", \"list_dir\", \"grep_search\", \"view_file\", \"memory\"].includes(input.toolName)\n\t) {\n\t\treturn {\n\t\t\trisk: \"scoped-write\",\n\t\t\treasonCode: \"generic_mutation\",\n\t\t\treasons: [\"Generic mutating operation or command\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\tif (command) {\n\t\t// Even if not explicitly destructive, shell commands could be anything, but we assume read-only if it's just 'ls', 'git status' etc.\n\t\t// For now, if no mutating patterns matched, we assume read-only if there's no toolName, but wait, normal file edits are scoped-write.\n\t\t// If there is a command, and it doesn't match the mutating patterns, we fall through to read-only.\n\t\t// Wait, user says \"Risk shell read-only commands like git status, rg, ls, npm view do not require approval.\"\n\t\t// And \"ordinary file edits inside allowed scope are scoped-write.\" (Handled by tools)\n\t}\n\n\treturn {\n\t\trisk: \"read-only\",\n\t\treasonCode: \"default_read_only\",\n\t\treasons: [\"No mutating or high-risk patterns detected\"],\n\t\trequiresApproval: false,\n\t};\n}\n"]}
|
|
@@ -10,6 +10,7 @@ const DESTRUCTIVE_CMD_RE = /\b(rm(\s+-r|\s+-f|\s+-rf|\s+-fr)?|mv|cp|chmod|chown|
|
|
|
10
10
|
const GIT_MUTATE_CMD_RE = /\bgit\s+(commit|push|reset|clean|stash|rebase)\b/i;
|
|
11
11
|
const PKG_MUTATE_CMD_RE = /\b(npm|pnpm|yarn|bun)\s+(install|i|update|up|publish|run|remove|rm|uninstall)\b/i;
|
|
12
12
|
const RELEASE_DEPLOY_CMD_RE = /\b(release|deploy)\b/i;
|
|
13
|
+
const PYTHON_HIGH_IMPACT_RE = /\b(?:shutil\.(?:rmtree|move|copy|copy2|copytree)|os\.(?:remove|unlink|rmdir|removedirs|system)|subprocess\.(?:run|call|check_call|check_output|Popen)|Path\([^)]*\)\.(?:unlink|rmdir))\b/i;
|
|
13
14
|
const REDIRECTION_RE = /[<>]/;
|
|
14
15
|
function stripSingleQuotes(cmd) {
|
|
15
16
|
return cmd.replace(/'[^']*'/g, "''");
|
|
@@ -85,6 +86,7 @@ export function assessOperationRisk(input) {
|
|
|
85
86
|
GIT_MUTATE_CMD_RE.test(cleanCmd) ||
|
|
86
87
|
PKG_MUTATE_CMD_RE.test(cleanCmd) ||
|
|
87
88
|
RELEASE_DEPLOY_CMD_RE.test(cleanCmd) ||
|
|
89
|
+
PYTHON_HIGH_IMPACT_RE.test(cleanCmd) ||
|
|
88
90
|
REDIRECTION_RE.test(cleanCmd)) {
|
|
89
91
|
return {
|
|
90
92
|
risk: "approval-required",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"risk-assessment.js","sourceRoot":"","sources":["../../../src/core/autonomy/risk-assessment.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RiskAssessment, RiskAssessmentInput } from \"./contracts.ts\";\n\nconst RELEASE_PUBLISH_RE = /\\b(publish|release|push|deploy|tag)\\b/i;\nconst SECURITY_AUTH_RE = /\\b(auth|token|credential|credentials|secret|api[-_]key)\\b/i;\nconst DESTRUCTIVE_RE = /\\b(delete|reset|rm\\s+-rf|clean)\\b/i;\n\nconst SELF_MOD_MUTATE_RE =\n\t/\\b(modify|change|write|update|edit|delete|add|remove|set)\\s+.*\\b(skills|prompts|settings|tools|behavior)\\b|self[-_]modification/i;\nconst ARCHITECTURE_MUTATE_RE = /\\b(rewrite|redesign|change|modify|rearchitect)\\s+.*\\b(architecture|architect)\\b/i;\n\nconst READ_ONLY_QUESTION_RE =\n\t/^(?:(?:can you|could you|please|pls|go ahead and|let'?s|i need you to|we need to|you should)\\s+)?(?:how|what|why|when|where|which|who|explain|summarize|compare|describe|list|show|search|find|view|read|locate)\\b/i;\n\nconst EXPLICIT_MODIFY_REQUEST_RE =\n\t/^(?:can you|could you|please|pls|go ahead and|let'?s|i need you to|we need to|you should)\\s+.*\\b(add|apply|build|change|commit|create|delete|edit|fix|generate|implement|install|modify|patch|refactor|remove|rename|replace|run|test|update|write|publish|release|push|deploy|tag|reset|clean|rewrite)\\b/i;\n\n// Shell parsing helpers\nconst DESTRUCTIVE_CMD_RE =\n\t/\\b(rm(\\s+-r|\\s+-f|\\s+-rf|\\s+-fr)?|mv|cp|chmod|chown|install)\\b|>\\s*\\/dev\\/(sda|hda|vda)|\\b(dd\\s+if=)/i;\nconst GIT_MUTATE_CMD_RE = /\\bgit\\s+(commit|push|reset|clean|stash|rebase)\\b/i;\nconst PKG_MUTATE_CMD_RE = /\\b(npm|pnpm|yarn|bun)\\s+(install|i|update|up|publish|run|remove|rm|uninstall)\\b/i;\nconst RELEASE_DEPLOY_CMD_RE = /\\b(release|deploy)\\b/i;\nconst REDIRECTION_RE = /[<>]/;\n\nfunction stripSingleQuotes(cmd: string): string {\n\treturn cmd.replace(/'[^']*'/g, \"''\");\n}\n\nexport function assessOperationRisk(input: RiskAssessmentInput): RiskAssessment {\n\tconst operation = input.operation.trim();\n\tconst command = input.command?.trim() ?? \"\";\n\tconst cleanCommand = command ? stripSingleQuotes(command) : \"\";\n\tconst fullText = `${operation} ${command}`.trim();\n\tconst cleanFullText = `${operation} ${cleanCommand}`.trim();\n\n\tif (fullText.length === 0) {\n\t\treturn {\n\t\t\trisk: \"read-only\",\n\t\t\treasonCode: \"empty_operation\",\n\t\t\treasons: [\"Empty operation\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\t// 1. Explicit read-only operations\n\tif (READ_ONLY_QUESTION_RE.test(operation) && !EXPLICIT_MODIFY_REQUEST_RE.test(operation)) {\n\t\treturn {\n\t\t\trisk: \"read-only\",\n\t\t\treasonCode: \"read_only_operation\",\n\t\t\treasons: [\"Operation is explicitly read-only (list/show/read/search)\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\t// 2. High-risk actions\n\tif (RELEASE_PUBLISH_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\",\n\t\t\treasonCode: \"release_publish_operation\",\n\t\t\treasons: [\"Operation mentions releasing, publishing, or deploying\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (SECURITY_AUTH_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\", // updated based on user instruction: \"high-impact or approval-required\"\n\t\t\treasonCode: \"security_auth_operation\",\n\t\t\treasons: [\"Operation mentions authentication or credentials\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (DESTRUCTIVE_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\",\n\t\t\treasonCode: \"destructive_operation\",\n\t\t\treasons: [\"Operation involves deleting, resetting, or cleaning\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (SELF_MOD_MUTATE_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\",\n\t\t\treasonCode: \"self_modification_operation\",\n\t\t\treasons: [\"Operation modifies settings, tools, skills, or prompts\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (ARCHITECTURE_MUTATE_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"high-impact\",\n\t\t\treasonCode: \"architecture_mutation_operation\",\n\t\t\treasons: [\"Operation mentions rewriting or rearchitecting core parts\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\t// 3. Command risks\n\tif (command) {\n\t\tconst cleanCmd = stripSingleQuotes(command);\n\t\tif (\n\t\t\tDESTRUCTIVE_CMD_RE.test(cleanCmd) ||\n\t\t\tGIT_MUTATE_CMD_RE.test(cleanCmd) ||\n\t\t\tPKG_MUTATE_CMD_RE.test(cleanCmd) ||\n\t\t\tRELEASE_DEPLOY_CMD_RE.test(cleanCmd) ||\n\t\t\tREDIRECTION_RE.test(cleanCmd)\n\t\t) {\n\t\t\treturn {\n\t\t\t\trisk: \"approval-required\",\n\t\t\t\treasonCode: \"mutating_command\",\n\t\t\t\treasons: [\"Command executes a destructive, mutating, or publish operation\"],\n\t\t\t\trequiresApproval: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Default to scoped-write for any generic mutating tool action if it has a toolName or isn't read-only\n\t// or read-only if it's purely a non-mutating intent (but since it didn't match read-only above, we default to scoped-write)\n\tif (\n\t\tinput.toolName &&\n\t\t![\"read_file\", \"search_web\", \"list_dir\", \"grep_search\", \"view_file\", \"memory\"].includes(input.toolName)\n\t) {\n\t\treturn {\n\t\t\trisk: \"scoped-write\",\n\t\t\treasonCode: \"generic_mutation\",\n\t\t\treasons: [\"Generic mutating operation or command\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\tif (command) {\n\t\t// Even if not explicitly destructive, shell commands could be anything, but we assume read-only if it's just 'ls', 'git status' etc.\n\t\t// For now, if no mutating patterns matched, we assume read-only if there's no toolName, but wait, normal file edits are scoped-write.\n\t\t// If there is a command, and it doesn't match the mutating patterns, we fall through to read-only.\n\t\t// Wait, user says \"Risk shell read-only commands like git status, rg, ls, npm view do not require approval.\"\n\t\t// And \"ordinary file edits inside allowed scope are scoped-write.\" (Handled by tools)\n\t}\n\n\treturn {\n\t\trisk: \"read-only\",\n\t\treasonCode: \"default_read_only\",\n\t\treasons: [\"No mutating or high-risk patterns detected\"],\n\t\trequiresApproval: false,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"risk-assessment.js","sourceRoot":"","sources":["../../../src/core/autonomy/risk-assessment.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RiskAssessment, RiskAssessmentInput } from \"./contracts.ts\";\n\nconst RELEASE_PUBLISH_RE = /\\b(publish|release|push|deploy|tag)\\b/i;\nconst SECURITY_AUTH_RE = /\\b(auth|token|credential|credentials|secret|api[-_]key)\\b/i;\nconst DESTRUCTIVE_RE = /\\b(delete|reset|rm\\s+-rf|clean)\\b/i;\n\nconst SELF_MOD_MUTATE_RE =\n\t/\\b(modify|change|write|update|edit|delete|add|remove|set)\\s+.*\\b(skills|prompts|settings|tools|behavior)\\b|self[-_]modification/i;\nconst ARCHITECTURE_MUTATE_RE = /\\b(rewrite|redesign|change|modify|rearchitect)\\s+.*\\b(architecture|architect)\\b/i;\n\nconst READ_ONLY_QUESTION_RE =\n\t/^(?:(?:can you|could you|please|pls|go ahead and|let'?s|i need you to|we need to|you should)\\s+)?(?:how|what|why|when|where|which|who|explain|summarize|compare|describe|list|show|search|find|view|read|locate)\\b/i;\n\nconst EXPLICIT_MODIFY_REQUEST_RE =\n\t/^(?:can you|could you|please|pls|go ahead and|let'?s|i need you to|we need to|you should)\\s+.*\\b(add|apply|build|change|commit|create|delete|edit|fix|generate|implement|install|modify|patch|refactor|remove|rename|replace|run|test|update|write|publish|release|push|deploy|tag|reset|clean|rewrite)\\b/i;\n\n// Shell parsing helpers\nconst DESTRUCTIVE_CMD_RE =\n\t/\\b(rm(\\s+-r|\\s+-f|\\s+-rf|\\s+-fr)?|mv|cp|chmod|chown|install)\\b|>\\s*\\/dev\\/(sda|hda|vda)|\\b(dd\\s+if=)/i;\nconst GIT_MUTATE_CMD_RE = /\\bgit\\s+(commit|push|reset|clean|stash|rebase)\\b/i;\nconst PKG_MUTATE_CMD_RE = /\\b(npm|pnpm|yarn|bun)\\s+(install|i|update|up|publish|run|remove|rm|uninstall)\\b/i;\nconst RELEASE_DEPLOY_CMD_RE = /\\b(release|deploy)\\b/i;\nconst PYTHON_HIGH_IMPACT_RE =\n\t/\\b(?:shutil\\.(?:rmtree|move|copy|copy2|copytree)|os\\.(?:remove|unlink|rmdir|removedirs|system)|subprocess\\.(?:run|call|check_call|check_output|Popen)|Path\\([^)]*\\)\\.(?:unlink|rmdir))\\b/i;\nconst REDIRECTION_RE = /[<>]/;\n\nfunction stripSingleQuotes(cmd: string): string {\n\treturn cmd.replace(/'[^']*'/g, \"''\");\n}\n\nexport function assessOperationRisk(input: RiskAssessmentInput): RiskAssessment {\n\tconst operation = input.operation.trim();\n\tconst command = input.command?.trim() ?? \"\";\n\tconst cleanCommand = command ? stripSingleQuotes(command) : \"\";\n\tconst fullText = `${operation} ${command}`.trim();\n\tconst cleanFullText = `${operation} ${cleanCommand}`.trim();\n\n\tif (fullText.length === 0) {\n\t\treturn {\n\t\t\trisk: \"read-only\",\n\t\t\treasonCode: \"empty_operation\",\n\t\t\treasons: [\"Empty operation\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\t// 1. Explicit read-only operations\n\tif (READ_ONLY_QUESTION_RE.test(operation) && !EXPLICIT_MODIFY_REQUEST_RE.test(operation)) {\n\t\treturn {\n\t\t\trisk: \"read-only\",\n\t\t\treasonCode: \"read_only_operation\",\n\t\t\treasons: [\"Operation is explicitly read-only (list/show/read/search)\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\t// 2. High-risk actions\n\tif (RELEASE_PUBLISH_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\",\n\t\t\treasonCode: \"release_publish_operation\",\n\t\t\treasons: [\"Operation mentions releasing, publishing, or deploying\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (SECURITY_AUTH_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\", // updated based on user instruction: \"high-impact or approval-required\"\n\t\t\treasonCode: \"security_auth_operation\",\n\t\t\treasons: [\"Operation mentions authentication or credentials\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (DESTRUCTIVE_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\",\n\t\t\treasonCode: \"destructive_operation\",\n\t\t\treasons: [\"Operation involves deleting, resetting, or cleaning\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (SELF_MOD_MUTATE_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"approval-required\",\n\t\t\treasonCode: \"self_modification_operation\",\n\t\t\treasons: [\"Operation modifies settings, tools, skills, or prompts\"],\n\t\t\trequiresApproval: true,\n\t\t};\n\t}\n\tif (ARCHITECTURE_MUTATE_RE.test(cleanFullText)) {\n\t\treturn {\n\t\t\trisk: \"high-impact\",\n\t\t\treasonCode: \"architecture_mutation_operation\",\n\t\t\treasons: [\"Operation mentions rewriting or rearchitecting core parts\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\t// 3. Command risks\n\tif (command) {\n\t\tconst cleanCmd = stripSingleQuotes(command);\n\t\tif (\n\t\t\tDESTRUCTIVE_CMD_RE.test(cleanCmd) ||\n\t\t\tGIT_MUTATE_CMD_RE.test(cleanCmd) ||\n\t\t\tPKG_MUTATE_CMD_RE.test(cleanCmd) ||\n\t\t\tRELEASE_DEPLOY_CMD_RE.test(cleanCmd) ||\n\t\t\tPYTHON_HIGH_IMPACT_RE.test(cleanCmd) ||\n\t\t\tREDIRECTION_RE.test(cleanCmd)\n\t\t) {\n\t\t\treturn {\n\t\t\t\trisk: \"approval-required\",\n\t\t\t\treasonCode: \"mutating_command\",\n\t\t\t\treasons: [\"Command executes a destructive, mutating, or publish operation\"],\n\t\t\t\trequiresApproval: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Default to scoped-write for any generic mutating tool action if it has a toolName or isn't read-only\n\t// or read-only if it's purely a non-mutating intent (but since it didn't match read-only above, we default to scoped-write)\n\tif (\n\t\tinput.toolName &&\n\t\t![\"read_file\", \"search_web\", \"list_dir\", \"grep_search\", \"view_file\", \"memory\"].includes(input.toolName)\n\t) {\n\t\treturn {\n\t\t\trisk: \"scoped-write\",\n\t\t\treasonCode: \"generic_mutation\",\n\t\t\treasons: [\"Generic mutating operation or command\"],\n\t\t\trequiresApproval: false,\n\t\t};\n\t}\n\n\tif (command) {\n\t\t// Even if not explicitly destructive, shell commands could be anything, but we assume read-only if it's just 'ls', 'git status' etc.\n\t\t// For now, if no mutating patterns matched, we assume read-only if there's no toolName, but wait, normal file edits are scoped-write.\n\t\t// If there is a command, and it doesn't match the mutating patterns, we fall through to read-only.\n\t\t// Wait, user says \"Risk shell read-only commands like git status, rg, ls, npm view do not require approval.\"\n\t\t// And \"ordinary file edits inside allowed scope are scoped-write.\" (Handled by tools)\n\t}\n\n\treturn {\n\t\trisk: \"read-only\",\n\t\treasonCode: \"default_read_only\",\n\t\treasons: [\"No mutating or high-risk patterns detected\"],\n\t\trequiresApproval: false,\n\t};\n}\n"]}
|
|
@@ -18,15 +18,20 @@ export interface BashExecutionControllerDeps {
|
|
|
18
18
|
/** Whether the agent is currently streaming — defers appending a bash result if so. */
|
|
19
19
|
isStreaming(): boolean;
|
|
20
20
|
}
|
|
21
|
+
export interface BashExecutionOptions {
|
|
22
|
+
excludeFromContext?: boolean;
|
|
23
|
+
operations?: BashOperations;
|
|
24
|
+
/** Injectable target platform for tests and embedded runtimes. */
|
|
25
|
+
platform?: NodeJS.Platform;
|
|
26
|
+
/** Wall-clock timeout in seconds; non-positive values use the bounded default. */
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}
|
|
21
29
|
export declare class BashExecutionController {
|
|
22
30
|
private _bashAbortControllers;
|
|
23
31
|
private _pendingBashMessages;
|
|
24
32
|
private readonly deps;
|
|
25
33
|
constructor(deps: BashExecutionControllerDeps);
|
|
26
|
-
executeBash(command: string, onChunk?: (chunk: string) => void, options?:
|
|
27
|
-
excludeFromContext?: boolean;
|
|
28
|
-
operations?: BashOperations;
|
|
29
|
-
}): Promise<BashResult>;
|
|
34
|
+
executeBash(command: string, onChunk?: (chunk: string) => void, options?: BashExecutionOptions): Promise<BashResult>;
|
|
30
35
|
/**
|
|
31
36
|
* Record a bash execution result in session history.
|
|
32
37
|
* Used by executeBash and by extensions that handle bash execution themselves.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash-execution-controller.d.ts","sourceRoot":"","sources":["../../src/core/bash-execution-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAwB,MAAM,2BAA2B,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,KAAK,UAAU,EAA6B,MAAM,oBAAoB,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"bash-execution-controller.d.ts","sourceRoot":"","sources":["../../src/core/bash-execution-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAwB,MAAM,2BAA2B,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,KAAK,UAAU,EAA6B,MAAM,oBAAoB,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,KAAK,cAAc,EAAoE,MAAM,iBAAiB,CAAC;AAExH,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,IAAI,KAAK,CAAC;IAClB,iBAAiB,IAAI,cAAc,CAAC;IACpC,kBAAkB,IAAI,eAAe,CAAC;IACtC,yFAAuF;IACvF,WAAW,IAAI,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,uBAAuB;IACnC,OAAO,CAAC,qBAAqB,CAA8B;IAC3D,OAAO,CAAC,oBAAoB,CAA8B;IAE1D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8B;IAEnD,YAAY,IAAI,EAAE,2BAA2B,EAE5C;IAEK,WAAW,CAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,UAAU,CAAC,CA0BrB;IAED;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAwBtG;IAED;;OAEG;IACH,SAAS,IAAI,IAAI,CAIhB;IAED,kDAAkD;IAClD,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,oEAAoE;IACpE,IAAI,sBAAsB,IAAI,OAAO,CAEpC;IAED;;;OAGG;IACH,wBAAwB,IAAI,IAAI,CAY/B;CACD","sourcesContent":["/**\n * Standalone bash-command execution (the `/bash` path + extension-driven bash).\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the bash abort controller\n * and the pending-message queue that defers appending a bash result while the agent is streaming (so\n * tool_use/tool_result ordering is never broken); the deferred messages are flushed by the session on\n * agent turn completion. Persists results through the session log via deps.\n */\n\nimport type { Agent, BashExecutionMessage } from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { type BashOperations, createLocalPlatformShellOperations, resolveCommandTimeoutSeconds } from \"./tools/bash.ts\";\n\nexport interface BashExecutionControllerDeps {\n\tgetAgent(): Agent;\n\tgetSessionManager(): SessionManager;\n\tgetSettingsManager(): SettingsManager;\n\t/** Whether the agent is currently streaming — defers appending a bash result if so. */\n\tisStreaming(): boolean;\n}\n\nexport interface BashExecutionOptions {\n\texcludeFromContext?: boolean;\n\toperations?: BashOperations;\n\t/** Injectable target platform for tests and embedded runtimes. */\n\tplatform?: NodeJS.Platform;\n\t/** Wall-clock timeout in seconds; non-positive values use the bounded default. */\n\ttimeout?: number;\n}\n\nexport class BashExecutionController {\n\tprivate _bashAbortControllers = new Set<AbortController>();\n\tprivate _pendingBashMessages: BashExecutionMessage[] = [];\n\n\tprivate readonly deps: BashExecutionControllerDeps;\n\n\tconstructor(deps: BashExecutionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: BashExecutionOptions,\n\t): Promise<BashResult> {\n\t\tconst abortController = new AbortController();\n\t\tthis._bashAbortControllers.add(abortController);\n\n\t\tconst commandPrefix = this.deps.getSettingsManager().getShellCommandPrefix();\n\t\tconst shellPath = this.deps.getSettingsManager().getShellPath();\n\t\tconst platform = options?.platform ?? process.platform;\n\t\tconst enableGitFilter = !options?.operations && !commandPrefix && !shellPath;\n\t\tconst operations = createLocalPlatformShellOperations(\n\t\t\t{ shellPath, commandPrefix, operations: options?.operations },\n\t\t\tplatform,\n\t\t);\n\n\t\ttry {\n\t\t\tconst result = await executeBashWithOperations(command, this.deps.getSessionManager().getCwd(), operations, {\n\t\t\t\tonChunk,\n\t\t\t\tsignal: abortController.signal,\n\t\t\t\tenableGitFilter,\n\t\t\t\ttimeout: resolveCommandTimeoutSeconds(options?.timeout),\n\t\t\t});\n\n\t\t\tthis.recordBashResult(command, result, options);\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tthis._bashAbortControllers.delete(abortController);\n\t\t}\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this.deps.isStreaming()) {\n\t\t\t// Queue for later - will be flushed on agent_end\n\t\t\tthis._pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\t// Add to agent state immediately\n\t\t\tthis.deps.getAgent().state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.deps.getSessionManager().appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel running bash command.\n\t */\n\tabortBash(): void {\n\t\tfor (const controller of this._bashAbortControllers) {\n\t\t\tcontroller.abort();\n\t\t}\n\t}\n\n\t/** Whether a bash command is currently running */\n\tget isBashRunning(): boolean {\n\t\treturn this._bashAbortControllers.size > 0;\n\t}\n\n\t/** Whether there are pending bash messages waiting to be flushed */\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this._pendingBashMessages.length > 0;\n\t}\n\n\t/**\n\t * Flush pending bash messages to agent state and session.\n\t * Called after agent turn completes to maintain proper message ordering.\n\t */\n\tflushPendingBashMessages(): void {\n\t\tif (this._pendingBashMessages.length === 0) return;\n\n\t\tfor (const bashMessage of this._pendingBashMessages) {\n\t\t\t// Add to agent state\n\t\t\tthis.deps.getAgent().state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.deps.getSessionManager().appendMessage(bashMessage);\n\t\t}\n\n\t\tthis._pendingBashMessages = [];\n\t}\n}\n"]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* agent turn completion. Persists results through the session log via deps.
|
|
8
8
|
*/
|
|
9
9
|
import { executeBashWithOperations } from "./bash-executor.js";
|
|
10
|
-
import {
|
|
10
|
+
import { createLocalPlatformShellOperations, resolveCommandTimeoutSeconds } from "./tools/bash.js";
|
|
11
11
|
export class BashExecutionController {
|
|
12
12
|
_bashAbortControllers = new Set();
|
|
13
13
|
_pendingBashMessages = [];
|
|
@@ -18,16 +18,17 @@ export class BashExecutionController {
|
|
|
18
18
|
async executeBash(command, onChunk, options) {
|
|
19
19
|
const abortController = new AbortController();
|
|
20
20
|
this._bashAbortControllers.add(abortController);
|
|
21
|
-
|
|
22
|
-
const prefix = this.deps.getSettingsManager().getShellCommandPrefix();
|
|
21
|
+
const commandPrefix = this.deps.getSettingsManager().getShellCommandPrefix();
|
|
23
22
|
const shellPath = this.deps.getSettingsManager().getShellPath();
|
|
24
|
-
const
|
|
25
|
-
const enableGitFilter = !options?.operations && !
|
|
23
|
+
const platform = options?.platform ?? process.platform;
|
|
24
|
+
const enableGitFilter = !options?.operations && !commandPrefix && !shellPath;
|
|
25
|
+
const operations = createLocalPlatformShellOperations({ shellPath, commandPrefix, operations: options?.operations }, platform);
|
|
26
26
|
try {
|
|
27
|
-
const result = await executeBashWithOperations(
|
|
27
|
+
const result = await executeBashWithOperations(command, this.deps.getSessionManager().getCwd(), operations, {
|
|
28
28
|
onChunk,
|
|
29
29
|
signal: abortController.signal,
|
|
30
30
|
enableGitFilter,
|
|
31
|
+
timeout: resolveCommandTimeoutSeconds(options?.timeout),
|
|
31
32
|
});
|
|
32
33
|
this.recordBashResult(command, result, options);
|
|
33
34
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash-execution-controller.js","sourceRoot":"","sources":["../../src/core/bash-execution-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAmB,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EAAuB,
|
|
1
|
+
{"version":3,"file":"bash-execution-controller.js","sourceRoot":"","sources":["../../src/core/bash-execution-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Standalone bash-command execution (the `/bash` path + extension-driven bash).\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the bash abort controller\n * and the pending-message queue that defers appending a bash result while the agent is streaming (so\n * tool_use/tool_result ordering is never broken); the deferred messages are flushed by the session on\n * agent turn completion. Persists results through the session log via deps.\n */\n\nimport type { Agent, BashExecutionMessage } from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { type BashOperations, createLocalPlatformShellOperations, resolveCommandTimeoutSeconds } from \"./tools/bash.ts\";\n\nexport interface BashExecutionControllerDeps {\n\tgetAgent(): Agent;\n\tgetSessionManager(): SessionManager;\n\tgetSettingsManager(): SettingsManager;\n\t/** Whether the agent is currently streaming — defers appending a bash result if so. */\n\tisStreaming(): boolean;\n}\n\nexport interface BashExecutionOptions {\n\texcludeFromContext?: boolean;\n\toperations?: BashOperations;\n\t/** Injectable target platform for tests and embedded runtimes. */\n\tplatform?: NodeJS.Platform;\n\t/** Wall-clock timeout in seconds; non-positive values use the bounded default. */\n\ttimeout?: number;\n}\n\nexport class BashExecutionController {\n\tprivate _bashAbortControllers = new Set<AbortController>();\n\tprivate _pendingBashMessages: BashExecutionMessage[] = [];\n\n\tprivate readonly deps: BashExecutionControllerDeps;\n\n\tconstructor(deps: BashExecutionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: BashExecutionOptions,\n\t): Promise<BashResult> {\n\t\tconst abortController = new AbortController();\n\t\tthis._bashAbortControllers.add(abortController);\n\n\t\tconst commandPrefix = this.deps.getSettingsManager().getShellCommandPrefix();\n\t\tconst shellPath = this.deps.getSettingsManager().getShellPath();\n\t\tconst platform = options?.platform ?? process.platform;\n\t\tconst enableGitFilter = !options?.operations && !commandPrefix && !shellPath;\n\t\tconst operations = createLocalPlatformShellOperations(\n\t\t\t{ shellPath, commandPrefix, operations: options?.operations },\n\t\t\tplatform,\n\t\t);\n\n\t\ttry {\n\t\t\tconst result = await executeBashWithOperations(command, this.deps.getSessionManager().getCwd(), operations, {\n\t\t\t\tonChunk,\n\t\t\t\tsignal: abortController.signal,\n\t\t\t\tenableGitFilter,\n\t\t\t\ttimeout: resolveCommandTimeoutSeconds(options?.timeout),\n\t\t\t});\n\n\t\t\tthis.recordBashResult(command, result, options);\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tthis._bashAbortControllers.delete(abortController);\n\t\t}\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this.deps.isStreaming()) {\n\t\t\t// Queue for later - will be flushed on agent_end\n\t\t\tthis._pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\t// Add to agent state immediately\n\t\t\tthis.deps.getAgent().state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.deps.getSessionManager().appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel running bash command.\n\t */\n\tabortBash(): void {\n\t\tfor (const controller of this._bashAbortControllers) {\n\t\t\tcontroller.abort();\n\t\t}\n\t}\n\n\t/** Whether a bash command is currently running */\n\tget isBashRunning(): boolean {\n\t\treturn this._bashAbortControllers.size > 0;\n\t}\n\n\t/** Whether there are pending bash messages waiting to be flushed */\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this._pendingBashMessages.length > 0;\n\t}\n\n\t/**\n\t * Flush pending bash messages to agent state and session.\n\t * Called after agent turn completes to maintain proper message ordering.\n\t */\n\tflushPendingBashMessages(): void {\n\t\tif (this._pendingBashMessages.length === 0) return;\n\n\t\tfor (const bashMessage of this._pendingBashMessages) {\n\t\t\t// Add to agent state\n\t\t\tthis.deps.getAgent().state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.deps.getSessionManager().appendMessage(bashMessage);\n\t\t}\n\n\t\tthis._pendingBashMessages = [];\n\t}\n}\n"]}
|
|
@@ -13,6 +13,8 @@ export interface BashExecutorOptions {
|
|
|
13
13
|
signal?: AbortSignal;
|
|
14
14
|
/** Enable conservative pi-native git output filtering for local default execution paths */
|
|
15
15
|
enableGitFilter?: boolean;
|
|
16
|
+
/** Wall-clock timeout in seconds. Direct interactive/RPC callers provide the same bounded default as the agent tool. */
|
|
17
|
+
timeout?: number;
|
|
16
18
|
}
|
|
17
19
|
export interface BashResult {
|
|
18
20
|
/** Combined stdout + stderr output (sanitized, possibly truncated) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,2FAA2F;IAC3F,eAAe,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,2FAA2F;IAC3F,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wHAAwH;IACxH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAUD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAiKrB","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport type { WriteStream } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { sanitizeBinaryOutput } from \"@caupulican/pi-agent-core\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"@caupulican/pi-agent-core/node\";\nimport { getAgentDir } from \"../config.ts\";\nimport { stripAnsi } from \"../utils/ansi.ts\";\nimport { createSafeWriteStream, endWriteStream } from \"../utils/safe-write-stream.ts\";\nimport { getProcessWorkRun } from \"../utils/work-directory.ts\";\nimport type { BashOperations } from \"./tools/bash.ts\";\nimport { classifyGitCommand, executeFilteredGit } from \"./tools/git-filter.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n\t/** Enable conservative pi-native git output filtering for local default execution paths */\n\tenableGitFilter?: boolean;\n\t/** Wall-clock timeout in seconds. Direct interactive/RPC callers provide the same bounded default as the agent tool. */\n\ttimeout?: number;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nfunction getBashOutputPath(id: string): string {\n\treturn join(getProcessWorkRun(getAgentDir(), \"outputs\", \"bash\").path, `pi-bash-${id}.log`);\n}\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tif (options?.enableGitFilter) {\n\t\tconst classification = classifyGitCommand(command, process.env);\n\t\tif (classification.eligible && classification.subcommand) {\n\t\t\tconst res = await executeFilteredGit(\n\t\t\t\tcwd,\n\t\t\t\tclassification.subcommand,\n\t\t\t\tclassification.globalOptions || [],\n\t\t\t\tclassification.subcommandArgs || [],\n\t\t\t\t{ signal: options.signal, timeout: options.timeout },\n\t\t\t);\n\t\t\tif (res.exitCode !== -100) {\n\t\t\t\tconst rawBytes = res.rawBytes ?? Buffer.from(res.rawOut, \"utf-8\");\n\t\t\t\t// The filter already spills oversized output to a temp file; reuse it\n\t\t\t\t// instead of materializing another full copy here.\n\t\t\t\tlet fullOutputPath = res.fullOutputPath;\n\t\t\t\tif (fullOutputPath === undefined && rawBytes.length > DEFAULT_MAX_BYTES) {\n\t\t\t\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\t\t\t\tconst spillPath = getBashOutputPath(id);\n\t\t\t\t\tfullOutputPath = spillPath;\n\t\t\t\t\t// On stream failure (e.g. disk full), drop the advertised path instead of\n\t\t\t\t\t// pointing at a partial/missing file.\n\t\t\t\t\tconst tempFileStream = createSafeWriteStream(spillPath, () => {\n\t\t\t\t\t\tfullOutputPath = undefined;\n\t\t\t\t\t});\n\t\t\t\t\ttempFileStream.write(rawBytes);\n\t\t\t\t\t// Await the flush so the returned path points at a COMPLETE file, not one mid-write.\n\t\t\t\t\tawait endWriteStream(tempFileStream);\n\t\t\t\t}\n\t\t\t\toptions.onChunk?.(res.output);\n\t\t\t\treturn {\n\t\t\t\t\toutput: res.output,\n\t\t\t\t\texitCode: res.exitCode,\n\t\t\t\t\tcancelled: options.signal?.aborted ?? false,\n\t\t\t\t\ttruncated: res.fullOutputPath !== undefined || rawBytes.length > DEFAULT_MAX_BYTES,\n\t\t\t\t\tfullOutputPath,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst ensureTempFile = () => {\n\t\tif (tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\ttempFilePath = getBashOutputPath(id);\n\t\t// On stream failure (e.g. disk full), drop the artifact instead of\n\t\t// crashing the process; the rolling in-memory output is still returned.\n\t\ttempFileStream = createSafeWriteStream(tempFilePath, () => {\n\t\t\ttempFileStream = undefined;\n\t\t\ttempFilePath = undefined;\n\t\t});\n\t\tfor (const chunk of outputChunks) {\n\t\t\ttempFileStream.write(chunk);\n\t\t}\n\t};\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\tensureTempFile();\n\t\t}\n\n\t\t// Guard writableEnded: custom BashOperations may deliver late onData\n\t\t// callbacks after an abort path has already ended the stream.\n\t\tif (tempFileStream && !tempFileStream.writableEnded) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t\ttimeout: options?.timeout,\n\t\t});\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tif (truncationResult.truncated) {\n\t\t\tensureTempFile();\n\t\t}\n\t\tif (tempFileStream) {\n\t\t\t// Await the flush so fullOutputPath refers to a fully-written file on return, not one still\n\t\t\t// draining its buffer — otherwise a fast reader can see partial/empty content.\n\t\t\tawait endWriteStream(tempFileStream);\n\t\t}\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\tif (truncationResult.truncated) {\n\t\t\t\tensureTempFile();\n\t\t\t}\n\t\t\tif (tempFileStream) {\n\t\t\t\tawait endWriteStream(tempFileStream);\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\tawait endWriteStream(tempFileStream);\n\t\t}\n\n\t\t// The silence watchdog (see tools/bash.ts) throws a raw `silence:<secs>` sentinel.\n\t\t// Map it to the same user-facing message the interactive bash tool shows, instead of\n\t\t// leaking the sentinel to callers of this path (interactive !cmd, RPC).\n\t\tif (err instanceof Error && err.message.startsWith(\"silence:\")) {\n\t\t\tconst secs = err.message.split(\":\")[1];\n\t\t\tthrow new Error(\n\t\t\t\t`Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, re-run it with an explicit timeout.`,\n\t\t\t);\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}
|
|
@@ -28,7 +28,7 @@ export async function executeBashWithOperations(command, cwd, operations, option
|
|
|
28
28
|
if (options?.enableGitFilter) {
|
|
29
29
|
const classification = classifyGitCommand(command, process.env);
|
|
30
30
|
if (classification.eligible && classification.subcommand) {
|
|
31
|
-
const res = await executeFilteredGit(cwd, classification.subcommand, classification.globalOptions || [], classification.subcommandArgs || [], { signal: options.signal });
|
|
31
|
+
const res = await executeFilteredGit(cwd, classification.subcommand, classification.globalOptions || [], classification.subcommandArgs || [], { signal: options.signal, timeout: options.timeout });
|
|
32
32
|
if (res.exitCode !== -100) {
|
|
33
33
|
const rawBytes = res.rawBytes ?? Buffer.from(res.rawOut, "utf-8");
|
|
34
34
|
// The filter already spills oversized output to a temp file; reuse it
|
|
@@ -110,6 +110,7 @@ export async function executeBashWithOperations(command, cwd, operations, option
|
|
|
110
110
|
const result = await operations.exec(command, cwd, {
|
|
111
111
|
onData,
|
|
112
112
|
signal: options?.signal,
|
|
113
|
+
timeout: options?.timeout,
|
|
113
114
|
});
|
|
114
115
|
const fullOutput = outputChunks.join("");
|
|
115
116
|
const truncationResult = truncateTail(fullOutput);
|
|
@@ -157,7 +158,7 @@ export async function executeBashWithOperations(command, cwd, operations, option
|
|
|
157
158
|
// leaking the sentinel to callers of this path (interactive !cmd, RPC).
|
|
158
159
|
if (err instanceof Error && err.message.startsWith("silence:")) {
|
|
159
160
|
const secs = err.message.split(":")[1];
|
|
160
|
-
throw new Error(`Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, re-run it with an explicit timeout
|
|
161
|
+
throw new Error(`Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, re-run it with an explicit timeout.`);
|
|
161
162
|
}
|
|
162
163
|
throw err;
|
|
163
164
|
}
|