@caupulican/pi-adaptative 0.81.37 → 0.81.39
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 +91 -0
- package/README.md +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +18 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +131 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +196 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +308 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +22 -2
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +585 -24
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +749 -27
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/__init__.py +43 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/fs.py +270 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/search.py +252 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/strings.py +399 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +575 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +52 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +49 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +734 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +238 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +132 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +116 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +287 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/proc.py +137 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +100 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +579 -0
- package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +19 -9
- package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +22 -5
- package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +5 -5
- package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +15 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-paths.d.ts +49 -0
- package/dist/core/agent-paths.d.ts.map +1 -0
- package/dist/core/agent-paths.js +107 -0
- package/dist/core/agent-paths.js.map +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -3
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +234 -17
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +484 -62
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/contracts.d.ts +9 -0
- package/dist/core/autonomy/contracts.d.ts.map +1 -1
- package/dist/core/autonomy/contracts.js.map +1 -1
- package/dist/core/autonomy/lane-tracker.d.ts +10 -1
- package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
- package/dist/core/autonomy/lane-tracker.js +5 -1
- package/dist/core/autonomy/lane-tracker.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +122 -6
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +447 -90
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/bash-execution-controller.d.ts +4 -0
- package/dist/core/bash-execution-controller.d.ts.map +1 -1
- package/dist/core/bash-execution-controller.js +7 -1
- package/dist/core/bash-execution-controller.js.map +1 -1
- package/dist/core/compaction-support.d.ts +13 -3
- package/dist/core/compaction-support.d.ts.map +1 -1
- package/dist/core/compaction-support.js +43 -7
- package/dist/core/compaction-support.js.map +1 -1
- package/dist/core/context/context-audit.d.ts +33 -1
- package/dist/core/context/context-audit.d.ts.map +1 -1
- package/dist/core/context/context-audit.js +31 -5
- package/dist/core/context/context-audit.js.map +1 -1
- package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
- package/dist/core/context/sqlite-runtime-index.js +13 -7
- package/dist/core/context/sqlite-runtime-index.js.map +1 -1
- package/dist/core/context-gc.d.ts.map +1 -1
- package/dist/core/context-gc.js +16 -1
- package/dist/core/context-gc.js.map +1 -1
- package/dist/core/context-pipeline.d.ts +26 -4
- package/dist/core/context-pipeline.d.ts.map +1 -1
- package/dist/core/context-pipeline.js +137 -14
- package/dist/core/context-pipeline.js.map +1 -1
- package/dist/core/cost-guard.d.ts +19 -3
- package/dist/core/cost-guard.d.ts.map +1 -1
- package/dist/core/cost-guard.js +18 -3
- package/dist/core/cost-guard.js.map +1 -1
- package/dist/core/delegation/session-worker-result.d.ts +34 -4
- package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
- package/dist/core/delegation/session-worker-result.js +64 -4
- package/dist/core/delegation/session-worker-result.js.map +1 -1
- package/dist/core/delegation/worker-actions.d.ts.map +1 -1
- package/dist/core/delegation/worker-actions.js +1 -1
- package/dist/core/delegation/worker-actions.js.map +1 -1
- package/dist/core/delegation/worker-result.d.ts +42 -1
- package/dist/core/delegation/worker-result.d.ts.map +1 -1
- package/dist/core/delegation/worker-result.js +68 -0
- package/dist/core/delegation/worker-result.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +23 -7
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +1 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +51 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/goal-loop-controller.d.ts +17 -1
- package/dist/core/goal-loop-controller.d.ts.map +1 -1
- package/dist/core/goal-loop-controller.js +79 -11
- package/dist/core/goal-loop-controller.js.map +1 -1
- package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
- package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-controller.js +77 -0
- package/dist/core/goals/goal-continuation-controller.js.map +1 -1
- package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
- package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-defaults.js +42 -0
- package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.d.ts +4 -0
- package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.js +51 -10
- package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
- package/dist/core/goals/goal-runtime-snapshot.d.ts +101 -2
- package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
- package/dist/core/goals/goal-runtime-snapshot.js +87 -4
- package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
- package/dist/core/goals/goal-state.d.ts +89 -1
- package/dist/core/goals/goal-state.d.ts.map +1 -1
- package/dist/core/goals/goal-state.js +71 -5
- package/dist/core/goals/goal-state.js.map +1 -1
- package/dist/core/goals/goal-tool-core.d.ts +56 -2
- package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
- package/dist/core/goals/goal-tool-core.js +111 -5
- package/dist/core/goals/goal-tool-core.js.map +1 -1
- package/dist/core/goals/session-goal-state.d.ts +11 -2
- package/dist/core/goals/session-goal-state.d.ts.map +1 -1
- package/dist/core/goals/session-goal-state.js +32 -17
- package/dist/core/goals/session-goal-state.js.map +1 -1
- package/dist/core/keybindings.d.ts +10 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +10 -2
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/learning/observation-store.d.ts +12 -3
- package/dist/core/learning/observation-store.d.ts.map +1 -1
- package/dist/core/learning/observation-store.js +30 -15
- package/dist/core/learning/observation-store.js.map +1 -1
- package/dist/core/learning/skill-curator.d.ts +5 -1
- package/dist/core/learning/skill-curator.d.ts.map +1 -1
- package/dist/core/learning/skill-curator.js +21 -19
- package/dist/core/learning/skill-curator.js.map +1 -1
- package/dist/core/local-runtime-controller.d.ts +65 -3
- package/dist/core/local-runtime-controller.d.ts.map +1 -1
- package/dist/core/local-runtime-controller.js +186 -26
- package/dist/core/local-runtime-controller.js.map +1 -1
- package/dist/core/memory/providers/file-store.d.ts +1 -1
- package/dist/core/memory/providers/file-store.d.ts.map +1 -1
- package/dist/core/memory/providers/file-store.js +6 -6
- package/dist/core/memory/providers/file-store.js.map +1 -1
- package/dist/core/model-capability.d.ts +34 -0
- package/dist/core/model-capability.d.ts.map +1 -1
- package/dist/core/model-capability.js +42 -1
- package/dist/core/model-capability.js.map +1 -1
- package/dist/core/model-router/tool-escalation.d.ts +15 -0
- package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
- package/dist/core/model-router/tool-escalation.js +23 -1
- package/dist/core/model-router/tool-escalation.js.map +1 -1
- package/dist/core/model-router-controller.d.ts +34 -7
- package/dist/core/model-router-controller.d.ts.map +1 -1
- package/dist/core/model-router-controller.js +95 -16
- package/dist/core/model-router-controller.js.map +1 -1
- package/dist/core/models/adaptation-store.d.ts +7 -0
- package/dist/core/models/adaptation-store.d.ts.map +1 -1
- package/dist/core/models/adaptation-store.js +47 -11
- package/dist/core/models/adaptation-store.js.map +1 -1
- package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
- package/dist/core/models/default-model-suggestions.js +17 -0
- package/dist/core/models/default-model-suggestions.js.map +1 -1
- package/dist/core/models/fitness-store.d.ts +3 -0
- package/dist/core/models/fitness-store.d.ts.map +1 -1
- package/dist/core/models/fitness-store.js +11 -2
- package/dist/core/models/fitness-store.js.map +1 -1
- package/dist/core/models/llamacpp-runtime.d.ts +180 -0
- package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
- package/dist/core/models/llamacpp-runtime.js +475 -0
- package/dist/core/models/llamacpp-runtime.js.map +1 -0
- package/dist/core/models/local-registration.d.ts +40 -0
- package/dist/core/models/local-registration.d.ts.map +1 -1
- package/dist/core/models/local-registration.js +94 -5
- package/dist/core/models/local-registration.js.map +1 -1
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +9 -7
- package/dist/core/models/local-runtime.js.map +1 -1
- package/dist/core/models/model-ref.d.ts +7 -0
- package/dist/core/models/model-ref.d.ts.map +1 -1
- package/dist/core/models/model-ref.js +26 -0
- package/dist/core/models/model-ref.js.map +1 -1
- package/dist/core/models/needle-runtime.d.ts +257 -0
- package/dist/core/models/needle-runtime.d.ts.map +1 -0
- package/dist/core/models/needle-runtime.js +519 -0
- package/dist/core/models/needle-runtime.js.map +1 -0
- package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
- package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
- package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
- package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +11 -10
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/process-matrix/codes.d.ts +72 -0
- package/dist/core/process-matrix/codes.d.ts.map +1 -0
- package/dist/core/process-matrix/codes.js +15 -0
- package/dist/core/process-matrix/codes.js.map +1 -0
- package/dist/core/process-matrix/runtime.d.ts +63 -0
- package/dist/core/process-matrix/runtime.d.ts.map +1 -0
- package/dist/core/process-matrix/runtime.js +310 -0
- package/dist/core/process-matrix/runtime.js.map +1 -0
- package/dist/core/process-matrix/store.d.ts +22 -0
- package/dist/core/process-matrix/store.d.ts.map +1 -0
- package/dist/core/process-matrix/store.js +80 -0
- package/dist/core/process-matrix/store.js.map +1 -0
- package/dist/core/process-matrix/supervisor.d.ts +72 -0
- package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
- package/dist/core/process-matrix/supervisor.js +130 -0
- package/dist/core/process-matrix/supervisor.js.map +1 -0
- package/dist/core/profile-registry.d.ts +7 -0
- package/dist/core/profile-registry.d.ts.map +1 -1
- package/dist/core/profile-registry.js +20 -0
- package/dist/core/profile-registry.js.map +1 -1
- package/dist/core/python-runtime.d.ts.map +1 -1
- package/dist/core/python-runtime.js +3 -3
- package/dist/core/python-runtime.js.map +1 -1
- package/dist/core/reflection-controller.d.ts +29 -5
- package/dist/core/reflection-controller.d.ts.map +1 -1
- package/dist/core/reflection-controller.js +215 -126
- package/dist/core/reflection-controller.js.map +1 -1
- package/dist/core/reload-blockers.d.ts +36 -0
- package/dist/core/reload-blockers.d.ts.map +1 -1
- package/dist/core/reload-blockers.js +44 -0
- package/dist/core/reload-blockers.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +8 -7
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +58 -5
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +209 -25
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/scout-controller.d.ts +6 -0
- package/dist/core/scout-controller.d.ts.map +1 -1
- package/dist/core/scout-controller.js +66 -52
- package/dist/core/scout-controller.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-role.d.ts +31 -0
- package/dist/core/session-role.d.ts.map +1 -0
- package/dist/core/session-role.js +52 -0
- package/dist/core/session-role.js.map +1 -0
- package/dist/core/settings-manager.d.ts +42 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +94 -60
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +12 -0
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.js +6 -0
- package/dist/core/system-prompt-builder.js.map +1 -1
- package/dist/core/system-prompt.d.ts +14 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +20 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tasks/session-task-state.d.ts +11 -2
- package/dist/core/tasks/session-task-state.d.ts.map +1 -1
- package/dist/core/tasks/session-task-state.js +27 -11
- package/dist/core/tasks/session-task-state.js.map +1 -1
- package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
- package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
- package/dist/core/tasks/task-contract-monitor.js +56 -0
- package/dist/core/tasks/task-contract-monitor.js.map +1 -0
- package/dist/core/tasks/task-state.d.ts +8 -0
- package/dist/core/tasks/task-state.d.ts.map +1 -1
- package/dist/core/tasks/task-state.js +28 -1
- package/dist/core/tasks/task-state.js.map +1 -1
- package/dist/core/tool-gate-controller.d.ts.map +1 -1
- package/dist/core/tool-gate-controller.js +5 -0
- package/dist/core/tool-gate-controller.js.map +1 -1
- package/dist/core/tool-recovery-log-records.d.ts +7 -0
- package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
- package/dist/core/tool-recovery-log-records.js +14 -6
- package/dist/core/tool-recovery-log-records.js.map +1 -1
- package/dist/core/tool-selection/promotion.d.ts +54 -0
- package/dist/core/tool-selection/promotion.d.ts.map +1 -0
- package/dist/core/tool-selection/promotion.js +81 -0
- package/dist/core/tool-selection/promotion.js.map +1 -0
- package/dist/core/tool-selection/tool-performance-store.d.ts +37 -0
- package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
- package/dist/core/tool-selection/tool-performance-store.js +87 -3
- package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
- package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
- package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
- package/dist/core/tool-selection/tool-selection-controller.js +96 -0
- package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
- package/dist/core/tools/bash.d.ts +18 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +97 -18
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/delegate-status.d.ts +14 -0
- package/dist/core/tools/delegate-status.d.ts.map +1 -1
- package/dist/core/tools/delegate-status.js +82 -7
- package/dist/core/tools/delegate-status.js.map +1 -1
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +25 -8
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +52 -44
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/goal.d.ts +94 -3
- package/dist/core/tools/goal.d.ts.map +1 -1
- package/dist/core/tools/goal.js +165 -15
- package/dist/core/tools/goal.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +5 -4
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/model-fitness.d.ts +7 -0
- package/dist/core/tools/model-fitness.d.ts.map +1 -1
- package/dist/core/tools/model-fitness.js +2 -2
- package/dist/core/tools/model-fitness.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +1 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/shell-contract-router.d.ts +6 -1
- package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
- package/dist/core/tools/shell-contract-router.js +69 -13
- package/dist/core/tools/shell-contract-router.js.map +1 -1
- package/dist/core/tools/shell-session.d.ts +89 -0
- package/dist/core/tools/shell-session.d.ts.map +1 -0
- package/dist/core/tools/shell-session.js +432 -0
- package/dist/core/tools/shell-session.js.map +1 -0
- package/dist/core/tools/task-steps.d.ts +4 -0
- package/dist/core/tools/task-steps.d.ts.map +1 -1
- package/dist/core/tools/task-steps.js +63 -8
- package/dist/core/tools/task-steps.js.map +1 -1
- package/dist/core/tools/tmux-dispatch.d.ts +86 -0
- package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
- package/dist/core/tools/tmux-dispatch.js +91 -0
- package/dist/core/tools/tmux-dispatch.js.map +1 -0
- package/dist/core/tools/windows-shell-engine.d.ts +42 -0
- package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
- package/dist/core/tools/windows-shell-engine.js +153 -0
- package/dist/core/tools/windows-shell-engine.js.map +1 -0
- package/dist/core/tools/windows-shell-state.d.ts +40 -0
- package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
- package/dist/core/tools/windows-shell-state.js +59 -0
- package/dist/core/tools/windows-shell-state.js.map +1 -0
- package/dist/core/tools/worktree-sync.d.ts +24 -0
- package/dist/core/tools/worktree-sync.d.ts.map +1 -0
- package/dist/core/tools/worktree-sync.js +338 -0
- package/dist/core/tools/worktree-sync.js.map +1 -0
- package/dist/core/trust-manager.d.ts +4 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +20 -2
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/core/util/atomic-file.d.ts +55 -0
- package/dist/core/util/atomic-file.d.ts.map +1 -0
- package/dist/core/util/atomic-file.js +255 -0
- package/dist/core/util/atomic-file.js.map +1 -0
- package/dist/core/util/minimatch-cache.d.ts +33 -0
- package/dist/core/util/minimatch-cache.d.ts.map +1 -0
- package/dist/core/util/minimatch-cache.js +0 -0
- package/dist/core/util/minimatch-cache.js.map +1 -0
- package/dist/core/worktree-sync/codes.d.ts +227 -0
- package/dist/core/worktree-sync/codes.d.ts.map +1 -0
- package/dist/core/worktree-sync/codes.js +14 -0
- package/dist/core/worktree-sync/codes.js.map +1 -0
- package/dist/core/worktree-sync/git-engine.d.ts +156 -0
- package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
- package/dist/core/worktree-sync/git-engine.js +1191 -0
- package/dist/core/worktree-sync/git-engine.js.map +1 -0
- package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
- package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
- package/dist/core/worktree-sync/lane-gate.js +360 -0
- package/dist/core/worktree-sync/lane-gate.js.map +1 -0
- package/dist/core/worktree-sync/runtime.d.ts +47 -0
- package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
- package/dist/core/worktree-sync/runtime.js +96 -0
- package/dist/core/worktree-sync/runtime.js.map +1 -0
- package/dist/core/worktree-sync/store.d.ts +69 -0
- package/dist/core/worktree-sync/store.d.ts.map +1 -0
- package/dist/core/worktree-sync/store.js +247 -0
- package/dist/core/worktree-sync/store.js.map +1 -0
- package/dist/core/worktree-sync/watcher.d.ts +29 -0
- package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
- package/dist/core/worktree-sync/watcher.js +93 -0
- package/dist/core/worktree-sync/watcher.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +94 -0
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +9 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +38 -0
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
- package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.js +50 -8
- package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
- package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
- 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 +4 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/local-model-commands.d.ts +43 -0
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
- package/dist/modes/interactive/local-model-commands.js +290 -3
- package/dist/modes/interactive/local-model-commands.js.map +1 -1
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-flow-commands.js +24 -4
- package/dist/modes/interactive/session-flow-commands.js.map +1 -1
- package/dist/utils/fs-watch.d.ts +11 -0
- package/dist/utils/fs-watch.d.ts.map +1 -1
- package/dist/utils/fs-watch.js +20 -2
- package/dist/utils/fs-watch.js.map +1 -1
- package/dist/utils/highlight-js-languages.d.ts +4 -0
- package/dist/utils/highlight-js-languages.d.ts.map +1 -0
- package/dist/utils/highlight-js-languages.js +573 -0
- package/dist/utils/highlight-js-languages.js.map +1 -0
- package/dist/utils/shell.d.ts +7 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +39 -9
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +53 -5
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +112 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/development.md +2 -0
- package/docs/packages.md +1 -1
- package/docs/process-matrix.md +120 -0
- package/docs/settings.md +5 -2
- package/docs/tmux-agent-manager.md +85 -2
- package/docs/windows.md +52 -3
- package/docs/work-directory.md +29 -0
- package/docs/worktree-sync.md +250 -0
- 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 +12 -12
- package/package.json +10 -4
- package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
- package/docs/integration-sweep-resume-2026-07-09.md +0 -407
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-selection-controller.js","sourceRoot":"","sources":["../../../src/core/tool-selection/tool-selection-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tdecideExpectedUtility,\n\ttype ExpectedUtilityCandidate,\n\ttype ToolSelectionDecision,\n} from \"./expected-utility.ts\";\nimport type {\n\tToolExecutionObservation,\n\tToolPerformanceKey,\n\tToolPerformanceStore,\n\tToolSelectionIntentClass,\n\tToolSelectionObservation,\n} from \"./tool-performance-store.ts\";\n\nexport interface ToolSelectionTool {\n\tname: string;\n\tdescription?: string;\n\tparameters?: unknown;\n\tprofileAllowed?: boolean;\n\tcapabilityAllowed?: boolean;\n\tpathValidated?: boolean;\n\triskCost?: number;\n\tcontextCost?: number;\n}\n\nexport interface ToolSelectionControllerDeps {\n\tstore: ToolPerformanceStore;\n\tgetModelRef: () => string;\n\tgetActiveTools: () => readonly ToolSelectionTool[];\n\tisCandidateAllowed?: (toolName: string) => boolean;\n}\n\nexport interface ToolSelectionPendingObservation {\n\tid: string;\n\tkey: ToolPerformanceKey;\n\tfirstTool: boolean;\n\tstartedAt: number;\n\tinputTokenEstimate?: number;\n\tselection: Omit<ToolSelectionObservation, \"at\" | \"modelRef\" | \"intentClass\" | \"actualTool\" | \"succeeded\">;\n}\n\nconst INTENT_CLASSES: readonly ToolSelectionIntentClass[] = [\n\t\"read\",\n\t\"search\",\n\t\"execute\",\n\t\"write\",\n\t\"retrieve\",\n\t\"explain\",\n\t\"other\",\n];\n\nconst INTENT_WORDS: Record<ToolSelectionIntentClass, readonly string[]> = {\n\tread: [\"read\", \"cat\", \"ls\", \"list\", \"stat\", \"head\", \"tail\"],\n\tsearch: [\"search\", \"grep\", \"find\", \"glob\", \"ripgrep\", \"query\"],\n\texecute: [\"bash\", \"python\", \"powershell\", \"shell\", \"exec\", \"run\", \"process\", \"command\", \"script\"],\n\twrite: [\"write\", \"edit\", \"patch\", \"apply\", \"delete\", \"mkdir\", \"move\", \"rename\"],\n\tretrieve: [\"retrieve\", \"artifact\", \"recall\", \"memory\", \"fetch\", \"download\"],\n\texplain: [\"explain\", \"help\", \"describe\", \"inspect\", \"context\"],\n\tother: [],\n};\n\nfunction tokenize(value: string): string[] {\n\treturn value\n\t\t.toLowerCase()\n\t\t.split(/[^a-z0-9]+/u)\n\t\t.filter(Boolean);\n}\n\nfunction classifyToolIntent(tool: ToolSelectionTool): ToolSelectionIntentClass {\n\tconst tokens = tokenize(`${tool.name} ${tool.description ?? \"\"}`);\n\tlet best: ToolSelectionIntentClass = \"other\";\n\tlet bestScore = 0;\n\tfor (const intent of INTENT_CLASSES) {\n\t\tif (intent === \"other\") continue;\n\t\tconst score = INTENT_WORDS[intent].reduce((total, word) => total + (tokens.includes(word) ? 1 : 0), 0);\n\t\tif (score > bestScore) {\n\t\t\tbest = intent;\n\t\t\tbestScore = score;\n\t\t}\n\t}\n\treturn best;\n}\n\nfunction intentValue(intent: ToolSelectionIntentClass, toolIntent: ToolSelectionIntentClass): number {\n\tif (toolIntent === intent) return 0.85;\n\tif (intent === \"other\") return 0.35;\n\tif (toolIntent === \"other\") return 0.2;\n\treturn 0.12;\n}\n\nfunction noToolValue(intent: ToolSelectionIntentClass): number {\n\tswitch (intent) {\n\t\tcase \"explain\":\n\t\t\treturn 0.35;\n\t\tcase \"read\":\n\t\tcase \"search\":\n\t\tcase \"retrieve\":\n\t\t\treturn 0.12;\n\t\tcase \"write\":\n\t\tcase \"execute\":\n\t\t\treturn 0.05;\n\t\tdefault:\n\t\t\treturn 0.2;\n\t}\n}\n\nfunction estimateTokens(value: unknown): number | undefined {\n\ttry {\n\t\tconst serialized = typeof value === \"string\" ? value : JSON.stringify(value);\n\t\tif (!serialized) return undefined;\n\t\treturn Math.max(1, Math.ceil(serialized.length / 4));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction estimateContentTokens(content: readonly unknown[]): number | undefined {\n\tconst text = content\n\t\t.map((block) => {\n\t\t\tif (!block || typeof block !== \"object\") return \"\";\n\t\t\tconst candidate = block as { type?: unknown; text?: unknown };\n\t\t\treturn candidate.type === \"text\" && typeof candidate.text === \"string\" ? candidate.text : \"\";\n\t\t})\n\t\t.join(\"\\n\");\n\treturn estimateTokens(text);\n}\n\nfunction modelToolKey(modelRef: string, intentClass: ToolSelectionIntentClass, tool: string): ToolPerformanceKey {\n\treturn { modelRef, intentClass, tool };\n}\n\nfunction candidateFor(\n\tstore: ToolPerformanceStore,\n\tmodelRef: string,\n\tintentClass: ToolSelectionIntentClass,\n\ttool: ToolSelectionTool,\n\tactualTool: string,\n): ExpectedUtilityCandidate {\n\tconst key = modelToolKey(modelRef, intentClass, tool.name);\n\tconst stats = store.get(key);\n\treturn {\n\t\ttool: tool.name,\n\t\tvalue: intentValue(intentClass, classifyToolIntent(tool)),\n\t\talpha: stats.alpha,\n\t\tbeta: stats.beta,\n\t\tsampleCount: stats.sampleCount,\n\t\tlatencyMs: stats.latencyEwmaMs,\n\t\ttokenEstimate:\n\t\t\tstats.inputTokenEstimateEwma === undefined && stats.outputTokenEstimateEwma === undefined\n\t\t\t\t? undefined\n\t\t\t\t: (stats.inputTokenEstimateEwma ?? 0) + (stats.outputTokenEstimateEwma ?? 0),\n\t\triskCost: tool.riskCost,\n\t\tcontextCost: tool.contextCost,\n\t\tdeterministicMatch: tool.name === actualTool && tool.pathValidated !== false,\n\t\tautomaticEligible: tool.pathValidated !== false,\n\t};\n}\n\nexport class ToolSelectionController {\n\tprivate readonly deps: ToolSelectionControllerDeps;\n\tprivate readonly pending = new Map<string, ToolSelectionPendingObservation>();\n\tprivate firstToolInTurn = true;\n\n\tconstructor(deps: ToolSelectionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tstartTurn(): void {\n\t\tthis.firstToolInTurn = true;\n\t}\n\n\tbegin(toolCallId: string, toolName: string, args: unknown): ToolSelectionPendingObservation {\n\t\tconst modelRef = this.deps.getModelRef();\n\t\tconst activeTools = this.deps\n\t\t\t.getActiveTools()\n\t\t\t.filter(\n\t\t\t\t(tool) =>\n\t\t\t\t\ttool.profileAllowed !== false &&\n\t\t\t\t\ttool.capabilityAllowed !== false &&\n\t\t\t\t\t(this.deps.isCandidateAllowed?.(tool.name) ?? true),\n\t\t\t);\n\t\tconst actualTool = activeTools.find((tool) => tool.name === toolName) ?? {\n\t\t\tname: toolName,\n\t\t\tpathValidated: true,\n\t\t};\n\t\tconst intentClass = classifyToolIntent(actualTool);\n\t\tconst candidates = activeTools.map((tool) =>\n\t\t\tcandidateFor(\n\t\t\t\tthis.deps.store,\n\t\t\t\tmodelRef,\n\t\t\t\tintentClass,\n\t\t\t\ttool.name === toolName ? tool : { ...tool, pathValidated: false },\n\t\t\t\ttoolName,\n\t\t\t),\n\t\t);\n\t\tif (!candidates.some((candidate) => candidate.tool === toolName)) {\n\t\t\tcandidates.push(candidateFor(this.deps.store, modelRef, intentClass, actualTool, toolName));\n\t\t}\n\t\tcandidates.push({\n\t\t\ttool: \"no_tool\",\n\t\t\tvalue: noToolValue(intentClass),\n\t\t\talpha: 1,\n\t\t\tbeta: 1,\n\t\t\tsampleCount: 0,\n\t\t\tsuccessProbability: 1,\n\t\t\tlatencyMs: 0,\n\t\t\ttokenEstimate: 0,\n\t\t\triskCost: 0,\n\t\t\tcontextCost: 0,\n\t\t});\n\t\tconst decision = decideExpectedUtility(candidates);\n\t\tconst firstTool = this.firstToolInTurn;\n\t\tconst selection = this.selectionSnapshot(decision, firstTool);\n\t\tconst pending: ToolSelectionPendingObservation = {\n\t\t\tid: toolCallId,\n\t\t\tkey: modelToolKey(modelRef, intentClass, toolName),\n\t\t\tfirstTool: this.firstToolInTurn,\n\t\t\tstartedAt: Date.now(),\n\t\t\tinputTokenEstimate: estimateTokens(args),\n\t\t\tselection,\n\t\t};\n\t\tthis.firstToolInTurn = false;\n\t\tthis.pending.set(toolCallId, pending);\n\t\treturn pending;\n\t}\n\n\tcomplete(toolCallId: string, succeeded: boolean, content: readonly unknown[] = []): void {\n\t\tconst pending = this.pending.get(toolCallId);\n\t\tif (!pending) return;\n\t\tthis.pending.delete(toolCallId);\n\t\tconst execution: ToolExecutionObservation = {\n\t\t\tkey: pending.key,\n\t\t\tsuccess: succeeded,\n\t\t\tlatencyMs: Math.max(0, Date.now() - pending.startedAt),\n\t\t\tinputTokenEstimate: pending.inputTokenEstimate,\n\t\t\toutputTokenEstimate: estimateContentTokens(content),\n\t\t\tselection: pending.selection,\n\t\t};\n\t\tthis.deps.store.recordExecution(execution);\n\t}\n\n\trecordValidation(toolName: string, outcome: \"repaired\" | \"bounced\"): void {\n\t\tconst modelRef = this.deps.getModelRef();\n\t\tconst tool = this.deps.getActiveTools().find((candidate) => candidate.name === toolName) ?? { name: toolName };\n\t\tthis.deps.store.recordValidation(modelToolKey(modelRef, classifyToolIntent(tool), toolName), outcome);\n\t}\n\n\tprivate selectionSnapshot(\n\t\tdecision: ToolSelectionDecision,\n\t\tfirstTool: boolean,\n\t): ToolSelectionPendingObservation[\"selection\"] {\n\t\treturn {\n\t\t\tfirstTool,\n\t\t\tdisposition: decision.disposition,\n\t\t\trecommendation: decision.recommendation,\n\t\t\tshortlist: decision.shortlist,\n\t\t\tentropy: decision.entropy,\n\t\t\tmargin: decision.margin,\n\t\t\tranked: decision.ranked.map((candidate) => ({\n\t\t\t\ttool: candidate.tool,\n\t\t\t\tutility: candidate.utility,\n\t\t\t\tprobability: candidate.probability,\n\t\t\t})),\n\t\t};\n\t}\n}\n\nexport { classifyToolIntent, estimateContentTokens, estimateTokens };\n"]}
|
|
1
|
+
{"version":3,"file":"tool-selection-controller.js","sourceRoot":"","sources":["../../../src/core/tool-selection/tool-selection-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tdecideExpectedUtility,\n\ttype ExpectedUtilityCandidate,\n\ttype ToolSelectionDecision,\n} from \"./expected-utility.ts\";\nimport { evaluateToolPromotion, type ToolSelectionHint } from \"./promotion.ts\";\nimport type {\n\tToolExecutionObservation,\n\tToolPerformanceKey,\n\tToolPerformanceStore,\n\tToolSelectionIntentClass,\n\tToolSelectionObservation,\n} from \"./tool-performance-store.ts\";\n\nexport interface ToolSelectionTool {\n\tname: string;\n\tdescription?: string;\n\tparameters?: unknown;\n\tprofileAllowed?: boolean;\n\tcapabilityAllowed?: boolean;\n\tpathValidated?: boolean;\n\triskCost?: number;\n\tcontextCost?: number;\n}\n\nexport interface ToolSelectionControllerDeps {\n\tstore: ToolPerformanceStore;\n\tgetModelRef: () => string;\n\tgetActiveTools: () => readonly ToolSelectionTool[];\n\tisCandidateAllowed?: (toolName: string) => boolean;\n\t/**\n\t * Env-var source for the kill switches, injected for hermetic testing; defaults to\n\t * `process.env`. `PI_TOOL_SELECTION_OBSERVE=0` disables recording (the observe/stats layer,\n\t * default ON); `PI_TOOL_SELECTION_HINTS=0` disables surfacing the evidence-gated prompt hint\n\t * (default ON, but the hint itself only ever activates once evidence thresholds are met).\n\t */\n\tenv?: Record<string, string | undefined>;\n}\n\nexport interface ToolSelectionPendingObservation {\n\tid: string;\n\tkey: ToolPerformanceKey;\n\tfirstTool: boolean;\n\tstartedAt: number;\n\tinputTokenEstimate?: number;\n\tselection: Omit<ToolSelectionObservation, \"at\" | \"modelRef\" | \"intentClass\" | \"actualTool\" | \"succeeded\">;\n\t/** Was a promotion hint already active for this (model,intent) bucket before this call? */\n\thintActiveAtCallTime: boolean;\n}\n\n/** Report row for the observe/agreement/promotion loop — see {@link ToolSelectionController.getReport}. */\nexport interface ToolSelectionReportEntry {\n\tmodelRef: string;\n\tintentClass: ToolSelectionIntentClass;\n\tsampleCount: number;\n\tagreementRate?: number;\n\thintTool?: string;\n\thintSampleCount: number;\n\thintAgreementRate?: number;\n}\n\nconst INTENT_CLASSES: readonly ToolSelectionIntentClass[] = [\n\t\"read\",\n\t\"search\",\n\t\"execute\",\n\t\"write\",\n\t\"retrieve\",\n\t\"explain\",\n\t\"other\",\n];\n\nconst INTENT_WORDS: Record<ToolSelectionIntentClass, readonly string[]> = {\n\tread: [\"read\", \"cat\", \"ls\", \"list\", \"stat\", \"head\", \"tail\"],\n\tsearch: [\"search\", \"grep\", \"find\", \"glob\", \"ripgrep\", \"query\"],\n\texecute: [\"bash\", \"python\", \"powershell\", \"shell\", \"exec\", \"run\", \"process\", \"command\", \"script\"],\n\twrite: [\"write\", \"edit\", \"patch\", \"apply\", \"delete\", \"mkdir\", \"move\", \"rename\"],\n\tretrieve: [\"retrieve\", \"artifact\", \"recall\", \"memory\", \"fetch\", \"download\"],\n\texplain: [\"explain\", \"help\", \"describe\", \"inspect\", \"context\"],\n\tother: [],\n};\n\nfunction tokenize(value: string): string[] {\n\treturn value\n\t\t.toLowerCase()\n\t\t.split(/[^a-z0-9]+/u)\n\t\t.filter(Boolean);\n}\n\nfunction classifyToolIntent(tool: ToolSelectionTool): ToolSelectionIntentClass {\n\tconst tokens = tokenize(`${tool.name} ${tool.description ?? \"\"}`);\n\tlet best: ToolSelectionIntentClass = \"other\";\n\tlet bestScore = 0;\n\tfor (const intent of INTENT_CLASSES) {\n\t\tif (intent === \"other\") continue;\n\t\tconst score = INTENT_WORDS[intent].reduce((total, word) => total + (tokens.includes(word) ? 1 : 0), 0);\n\t\tif (score > bestScore) {\n\t\t\tbest = intent;\n\t\t\tbestScore = score;\n\t\t}\n\t}\n\treturn best;\n}\n\nfunction intentValue(intent: ToolSelectionIntentClass, toolIntent: ToolSelectionIntentClass): number {\n\tif (toolIntent === intent) return 0.85;\n\tif (intent === \"other\") return 0.35;\n\tif (toolIntent === \"other\") return 0.2;\n\treturn 0.12;\n}\n\nfunction noToolValue(intent: ToolSelectionIntentClass): number {\n\tswitch (intent) {\n\t\tcase \"explain\":\n\t\t\treturn 0.35;\n\t\tcase \"read\":\n\t\tcase \"search\":\n\t\tcase \"retrieve\":\n\t\t\treturn 0.12;\n\t\tcase \"write\":\n\t\tcase \"execute\":\n\t\t\treturn 0.05;\n\t\tdefault:\n\t\t\treturn 0.2;\n\t}\n}\n\nfunction estimateTokens(value: unknown): number | undefined {\n\ttry {\n\t\tconst serialized = typeof value === \"string\" ? value : JSON.stringify(value);\n\t\tif (!serialized) return undefined;\n\t\treturn Math.max(1, Math.ceil(serialized.length / 4));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction estimateContentTokens(content: readonly unknown[]): number | undefined {\n\tconst text = content\n\t\t.map((block) => {\n\t\t\tif (!block || typeof block !== \"object\") return \"\";\n\t\t\tconst candidate = block as { type?: unknown; text?: unknown };\n\t\t\treturn candidate.type === \"text\" && typeof candidate.text === \"string\" ? candidate.text : \"\";\n\t\t})\n\t\t.join(\"\\n\");\n\treturn estimateTokens(text);\n}\n\nfunction modelToolKey(modelRef: string, intentClass: ToolSelectionIntentClass, tool: string): ToolPerformanceKey {\n\treturn { modelRef, intentClass, tool };\n}\n\nfunction candidateFor(\n\tstore: ToolPerformanceStore,\n\tmodelRef: string,\n\tintentClass: ToolSelectionIntentClass,\n\ttool: ToolSelectionTool,\n\tactualTool: string,\n): ExpectedUtilityCandidate {\n\tconst key = modelToolKey(modelRef, intentClass, tool.name);\n\tconst stats = store.get(key);\n\treturn {\n\t\ttool: tool.name,\n\t\tvalue: intentValue(intentClass, classifyToolIntent(tool)),\n\t\talpha: stats.alpha,\n\t\tbeta: stats.beta,\n\t\tsampleCount: stats.sampleCount,\n\t\tlatencyMs: stats.latencyEwmaMs,\n\t\ttokenEstimate:\n\t\t\tstats.inputTokenEstimateEwma === undefined && stats.outputTokenEstimateEwma === undefined\n\t\t\t\t? undefined\n\t\t\t\t: (stats.inputTokenEstimateEwma ?? 0) + (stats.outputTokenEstimateEwma ?? 0),\n\t\triskCost: tool.riskCost,\n\t\tcontextCost: tool.contextCost,\n\t\tdeterministicMatch: tool.name === actualTool && tool.pathValidated !== false,\n\t\tautomaticEligible: tool.pathValidated !== false,\n\t};\n}\n\nexport class ToolSelectionController {\n\tprivate readonly deps: ToolSelectionControllerDeps;\n\tprivate readonly pending = new Map<string, ToolSelectionPendingObservation>();\n\tprivate firstToolInTurn = true;\n\t/** Kill switch: observe/stats recording, default ON. `PI_TOOL_SELECTION_OBSERVE=0` disables it. */\n\tprivate readonly observeEnabled: boolean;\n\n\tconstructor(deps: ToolSelectionControllerDeps) {\n\t\tthis.deps = deps;\n\t\tthis.observeEnabled = (deps.env ?? process.env).PI_TOOL_SELECTION_OBSERVE !== \"0\";\n\t}\n\n\tstartTurn(): void {\n\t\tthis.firstToolInTurn = true;\n\t}\n\n\tbegin(toolCallId: string, toolName: string, args: unknown): ToolSelectionPendingObservation {\n\t\tconst modelRef = this.deps.getModelRef();\n\t\tconst activeTools = this.deps\n\t\t\t.getActiveTools()\n\t\t\t.filter(\n\t\t\t\t(tool) =>\n\t\t\t\t\ttool.profileAllowed !== false &&\n\t\t\t\t\ttool.capabilityAllowed !== false &&\n\t\t\t\t\t(this.deps.isCandidateAllowed?.(tool.name) ?? true),\n\t\t\t);\n\t\tconst actualTool = activeTools.find((tool) => tool.name === toolName) ?? {\n\t\t\tname: toolName,\n\t\t\tpathValidated: true,\n\t\t};\n\t\tconst intentClass = classifyToolIntent(actualTool);\n\t\tconst candidates = activeTools.map((tool) =>\n\t\t\tcandidateFor(\n\t\t\t\tthis.deps.store,\n\t\t\t\tmodelRef,\n\t\t\t\tintentClass,\n\t\t\t\ttool.name === toolName ? tool : { ...tool, pathValidated: false },\n\t\t\t\ttoolName,\n\t\t\t),\n\t\t);\n\t\tif (!candidates.some((candidate) => candidate.tool === toolName)) {\n\t\t\tcandidates.push(candidateFor(this.deps.store, modelRef, intentClass, actualTool, toolName));\n\t\t}\n\t\tcandidates.push({\n\t\t\ttool: \"no_tool\",\n\t\t\tvalue: noToolValue(intentClass),\n\t\t\talpha: 1,\n\t\t\tbeta: 1,\n\t\t\tsampleCount: 0,\n\t\t\tsuccessProbability: 1,\n\t\t\tlatencyMs: 0,\n\t\t\ttokenEstimate: 0,\n\t\t\triskCost: 0,\n\t\t\tcontextCost: 0,\n\t\t});\n\t\tconst decision = decideExpectedUtility(candidates);\n\t\tconst firstTool = this.firstToolInTurn;\n\t\tconst selection = this.selectionSnapshot(decision, firstTool);\n\t\t// Evaluated BEFORE this call is recorded, so it reflects evidence up to (not including) this\n\t\t// observation — captured now because complete() (later, async) can no longer distinguish\n\t\t// \"before\" from \"after\" once the store has been written.\n\t\tconst hintActiveAtCallTime = this.observeEnabled\n\t\t\t? this.evaluatePromotion(modelRef, intentClass).tool !== undefined\n\t\t\t: false;\n\t\tconst pending: ToolSelectionPendingObservation = {\n\t\t\tid: toolCallId,\n\t\t\tkey: modelToolKey(modelRef, intentClass, toolName),\n\t\t\tfirstTool: this.firstToolInTurn,\n\t\t\tstartedAt: Date.now(),\n\t\t\tinputTokenEstimate: estimateTokens(args),\n\t\t\tselection,\n\t\t\thintActiveAtCallTime,\n\t\t};\n\t\tthis.firstToolInTurn = false;\n\t\tthis.pending.set(toolCallId, pending);\n\t\treturn pending;\n\t}\n\n\tcomplete(toolCallId: string, succeeded: boolean, content: readonly unknown[] = []): void {\n\t\tconst pending = this.pending.get(toolCallId);\n\t\tif (!pending) return;\n\t\tthis.pending.delete(toolCallId);\n\t\tif (!this.observeEnabled) return;\n\t\tconst execution: ToolExecutionObservation = {\n\t\t\tkey: pending.key,\n\t\t\tsuccess: succeeded,\n\t\t\tlatencyMs: Math.max(0, Date.now() - pending.startedAt),\n\t\t\tinputTokenEstimate: pending.inputTokenEstimate,\n\t\t\toutputTokenEstimate: estimateContentTokens(content),\n\t\t\tselection: pending.selection,\n\t\t\thintActiveAtCallTime: pending.hintActiveAtCallTime,\n\t\t};\n\t\tthis.deps.store.recordExecution(execution);\n\t}\n\n\trecordValidation(toolName: string, outcome: \"repaired\" | \"bounced\"): void {\n\t\tif (!this.observeEnabled) return;\n\t\tconst modelRef = this.deps.getModelRef();\n\t\tconst tool = this.deps.getActiveTools().find((candidate) => candidate.name === toolName) ?? { name: toolName };\n\t\tthis.deps.store.recordValidation(modelToolKey(modelRef, classifyToolIntent(tool), toolName), outcome);\n\t}\n\n\t/**\n\t * Evidence-gated promotion for one (model,intent) bucket — see promotion.ts. Pure read, no\n\t * mutation; used both to stamp `hintActiveAtCallTime` and to build the live hint list below.\n\t */\n\tprivate evaluatePromotion(modelRef: string, intentClass: ToolSelectionIntentClass) {\n\t\treturn evaluateToolPromotion(this.deps.store.getStatsForIntent(modelRef, intentClass));\n\t}\n\n\t/**\n\t * The currently active evidence-gated hints for the session's current model — one per\n\t * (intentClass) at most, only where accumulated evidence clears the promotion gate. Consumed by\n\t * `system-prompt-builder.ts` to render a compact, cache-stable prompt block. Empty when the\n\t * hint kill switch (`PI_TOOL_SELECTION_HINTS=0`) is set, observing is disabled, or no bucket has\n\t * cleared the gate yet.\n\t */\n\tgetActiveHints(modelRef: string = this.deps.getModelRef()): ToolSelectionHint[] {\n\t\tif ((this.deps.env ?? process.env).PI_TOOL_SELECTION_HINTS === \"0\") return [];\n\t\tif (!this.observeEnabled) return [];\n\t\tconst hints: ToolSelectionHint[] = [];\n\t\tfor (const intentClass of INTENT_CLASSES) {\n\t\t\tconst promotion = this.evaluatePromotion(modelRef, intentClass);\n\t\t\tif (!promotion.tool) continue;\n\t\t\thints.push({\n\t\t\t\tmodelRef,\n\t\t\t\tintentClass,\n\t\t\t\ttool: promotion.tool,\n\t\t\t\tsampleCount: promotion.sampleCount,\n\t\t\t\tmargin: promotion.margin,\n\t\t\t\tentropy: promotion.entropy,\n\t\t\t});\n\t\t}\n\t\treturn hints;\n\t}\n\n\t/**\n\t * Report surface for the observe/agreement/promotion loop: per (model,intent), the durable\n\t * agreement rate (did the raw ranking's top pick match what was actually called), plus the\n\t * currently active hint (if any) and its own efficacy (agreement rate while it has been active).\n\t * A read-only diagnostic — never used to gate behavior. Render with\n\t * {@link formatToolSelectionReport}.\n\t */\n\tgetReport(modelRef: string = this.deps.getModelRef()): ToolSelectionReportEntry[] {\n\t\tconst activeHints = new Map(this.getActiveHints(modelRef).map((hint) => [hint.intentClass, hint]));\n\t\treturn this.deps.store\n\t\t\t.getAllIntentAgreements(modelRef)\n\t\t\t.filter((agreement) => agreement.sampleCount > 0)\n\t\t\t.map((agreement) => ({\n\t\t\t\tmodelRef: agreement.modelRef,\n\t\t\t\tintentClass: agreement.intentClass,\n\t\t\t\tsampleCount: agreement.sampleCount,\n\t\t\t\tagreementRate: agreement.agreementCount / agreement.sampleCount,\n\t\t\t\thintTool: activeHints.get(agreement.intentClass)?.tool,\n\t\t\t\thintSampleCount: agreement.hintActiveSampleCount,\n\t\t\t\thintAgreementRate:\n\t\t\t\t\tagreement.hintActiveSampleCount > 0\n\t\t\t\t\t\t? agreement.hintActiveAgreementCount / agreement.hintActiveSampleCount\n\t\t\t\t\t\t: undefined,\n\t\t\t}));\n\t}\n\n\tprivate selectionSnapshot(\n\t\tdecision: ToolSelectionDecision,\n\t\tfirstTool: boolean,\n\t): ToolSelectionPendingObservation[\"selection\"] {\n\t\treturn {\n\t\t\tfirstTool,\n\t\t\tdisposition: decision.disposition,\n\t\t\trecommendation: decision.recommendation,\n\t\t\tshortlist: decision.shortlist,\n\t\t\tentropy: decision.entropy,\n\t\t\tmargin: decision.margin,\n\t\t\tranked: decision.ranked.map((candidate) => ({\n\t\t\t\ttool: candidate.tool,\n\t\t\t\tutility: candidate.utility,\n\t\t\t\tprobability: candidate.probability,\n\t\t\t})),\n\t\t};\n\t}\n}\n\n/** Renders {@link ToolSelectionController.getReport} rows into the /toolhealth-style diagnostic text. */\nexport function formatToolSelectionReport(entries: readonly ToolSelectionReportEntry[]): string {\n\tif (entries.length === 0) {\n\t\treturn \"Tool-selection loop: no observations recorded yet for this host.\";\n\t}\n\tconst sorted = [...entries].sort(\n\t\t(left, right) => left.modelRef.localeCompare(right.modelRef) || left.intentClass.localeCompare(right.intentClass),\n\t);\n\tconst lines = [\"Tool-selection loop (observe -> agreement -> evidence-gated hint)\"];\n\tfor (const entry of sorted) {\n\t\tconst agreementText =\n\t\t\tentry.agreementRate === undefined\n\t\t\t\t? \"n/a\"\n\t\t\t\t: `${Math.round(entry.agreementRate * 100)}% (n=${entry.sampleCount})`;\n\t\tlines.push(` ${entry.modelRef} / ${entry.intentClass}: agreement ${agreementText}`);\n\t\tif (entry.hintTool) {\n\t\t\tconst hintAgreementText =\n\t\t\t\tentry.hintAgreementRate === undefined\n\t\t\t\t\t? \"n/a\"\n\t\t\t\t\t: `${Math.round(entry.hintAgreementRate * 100)}% (n=${entry.hintSampleCount})`;\n\t\t\tlines.push(` hint active: prefer \\`${entry.hintTool}\\` — agreement while active ${hintAgreementText}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport { classifyToolIntent, estimateContentTokens, estimateTokens };\n"]}
|
|
@@ -2,6 +2,7 @@ import { type AgentTool } from "@caupulican/pi-agent-core";
|
|
|
2
2
|
import { type TruncationResult } from "@caupulican/pi-agent-core/node";
|
|
3
3
|
import { type Static, Type } from "typebox";
|
|
4
4
|
import type { ToolDefinition } from "../extensions/types.ts";
|
|
5
|
+
import { type WindowsShellEngineOptions } from "./windows-shell-engine.ts";
|
|
5
6
|
/** Agent-facing wall-clock bound: continuously producing output must not make a command immortal. */
|
|
6
7
|
export declare const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;
|
|
7
8
|
export declare const MAX_COMMAND_TIMEOUT_SECONDS = 3600;
|
|
@@ -53,16 +54,23 @@ export interface BashOperations {
|
|
|
53
54
|
*/
|
|
54
55
|
export declare function createLocalBashOperations(options?: {
|
|
55
56
|
shellPath?: string;
|
|
57
|
+
sessionKey?: string;
|
|
56
58
|
}): BashOperations;
|
|
57
59
|
/** Create PowerShell operations using pi's built-in local execution backend. */
|
|
58
60
|
export declare function createLocalPowerShellOperations(options?: {
|
|
59
61
|
shellPath?: string;
|
|
62
|
+
sessionKey?: string;
|
|
60
63
|
}): BashOperations;
|
|
61
64
|
/** Create the platform shell backend without requiring callers or the model to choose a shell. */
|
|
62
65
|
export declare function createLocalPlatformShellOperations(options?: {
|
|
63
66
|
shellPath?: string;
|
|
64
67
|
commandPrefix?: string;
|
|
65
68
|
operations?: BashOperations;
|
|
69
|
+
sessionKey?: string;
|
|
70
|
+
/** Route complex/state-mutating Bash constructs to the Python engine on Windows. Default: true. */
|
|
71
|
+
pythonEngine?: boolean;
|
|
72
|
+
/** Test/embedding hook: overrides the engine tier's runtime/spawn/state resolution. */
|
|
73
|
+
engineOptions?: WindowsShellEngineOptions;
|
|
66
74
|
}, platform?: NodeJS.Platform): BashOperations;
|
|
67
75
|
export interface BashSpawnContext {
|
|
68
76
|
command: string;
|
|
@@ -81,6 +89,16 @@ export interface BashToolOptions {
|
|
|
81
89
|
shellPath?: string;
|
|
82
90
|
/** Hook to adjust command, cwd, or env before execution */
|
|
83
91
|
spawnHook?: BashSpawnHook;
|
|
92
|
+
/**
|
|
93
|
+
* Stable key for this agent's persistent shell session. The host passes its per-agent key so
|
|
94
|
+
* the session survives runtime reloads and user `!` commands share it; separately created
|
|
95
|
+
* tool instances (subagents) auto-generate their own key and stay isolated.
|
|
96
|
+
*/
|
|
97
|
+
sessionKey?: string;
|
|
98
|
+
/** Route complex/state-mutating Bash constructs to the Python engine on Windows. Default: true. */
|
|
99
|
+
windowsShellPythonEngine?: boolean;
|
|
100
|
+
/** Test/embedding hook: overrides the engine tier's runtime/spawn/state resolution. */
|
|
101
|
+
windowsShellEngineOptions?: WindowsShellEngineOptions;
|
|
84
102
|
}
|
|
85
103
|
type BashRenderState = {
|
|
86
104
|
startedAt: number | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport { access as fsAccess } from \"node:fs/promises\";\nimport { type AgentTool, createSilenceWatchdog } from \"@caupulican/pi-agent-core\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n} from \"@caupulican/pi-agent-core/node\";\nimport { Container, Text, truncateToWidth } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { truncateToVisualLines } from \"../../modes/interactive/components/visual-truncate.ts\";\nimport { theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { waitForChildProcessWithTermination } from \"../../utils/child-process.ts\";\nimport {\n\tgetPlatformShellToolName,\n\tgetShellConfig,\n\tgetShellEnv,\n\ttype PlatformShellToolName,\n\tprefixPowerShellCommand,\n\ttrackDetachedChildPid,\n\tuntrackDetachedChildPid,\n} from \"../../utils/shell.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withExclusiveMutationBarrier } from \"./file-mutation-queue.ts\";\nimport { classifyGitCommand, executeFilteredGit } from \"./git-filter.ts\";\nimport { OutputAccumulator } from \"./output-accumulator.ts\";\nimport { getTextOutput, invalidArgText, str } from \"./render-utils.ts\";\nimport { routeShellContract } from \"./shell-contract-router.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\n/** Low-level silence bound retained for direct shell-operation consumers. Agent tool calls always pass a wall-clock bound. */\nconst DEFAULT_COMMAND_SILENCE_MS = 600_000;\n/** Agent-facing wall-clock bound: continuously producing output must not make a command immortal. */\nexport const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;\nexport const MAX_COMMAND_TIMEOUT_SECONDS = 3600;\nconst MIN_COMMAND_TIMEOUT_SECONDS = 0.1;\nlet commandSilenceMsOverride: number | undefined;\nlet commandTimeoutMsOverride: number | undefined;\n\n/** Test hook: override the low-level silence threshold. Pass undefined to restore the default. */\nexport function setCommandSilenceMsForTests(ms: number | undefined): void {\n\tcommandSilenceMsOverride = ms;\n}\n\n/** Test hook: override the agent tool's default wall-clock bound. Pass undefined to restore it. */\nexport function setCommandTimeoutMsForTests(ms: number | undefined): void {\n\tcommandTimeoutMsOverride = ms;\n}\n\nexport function resolveCommandTimeoutSeconds(timeout: number | undefined): number {\n\tif (typeof timeout !== \"number\" || !Number.isFinite(timeout) || timeout <= 0) {\n\t\treturn DEFAULT_COMMAND_TIMEOUT_SECONDS;\n\t}\n\treturn Math.max(MIN_COMMAND_TIMEOUT_SECONDS, Math.min(timeout, MAX_COMMAND_TIMEOUT_SECONDS));\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Shell command to execute\" }),\n\ttimeout: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription: `Wall-clock timeout in seconds. Defaults to ${DEFAULT_COMMAND_TIMEOUT_SECONDS}; positive overrides are capped at ${MAX_COMMAND_TIMEOUT_SECONDS}. Zero or negative values use the default.`,\n\t\t}),\n\t),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tfullOutputError?: string;\n\tpreview?: {\n\t\tcontent: string;\n\t\tskippedLines: number;\n\t};\n}\n\n/**\n * Pluggable operations for the bash tool.\n * Override these to delegate command execution to remote systems (for example SSH).\n */\nexport interface BashOperations {\n\t/**\n\t * Execute a command and stream output.\n\t * @param command The command to execute\n\t * @param cwd Working directory\n\t * @param options Execution options\n\t * @returns Promise resolving to exit code (null if killed)\n\t */\n\texec: (\n\t\tcommand: string,\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\ttimeout?: number;\n\t\t\tenv?: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nfunction createLocalShellOperations(\n\tshellName: PlatformShellToolName,\n\toptions?: { shellPath?: string },\n): BashOperations {\n\treturn {\n\t\texec: async (command, cwd, { onData, signal, timeout, env }) => {\n\t\t\tconst { shell, args } = getShellConfig(options?.shellPath, shellName);\n\t\t\ttry {\n\t\t\t\tawait fsAccess(cwd, constants.F_OK);\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`Working directory does not exist: ${cwd}\\nCannot execute ${shellName} commands.`);\n\t\t\t}\n\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tcwd,\n\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\tenv: env ?? getShellEnv(),\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (child.pid) trackDetachedChildPid(child.pid);\n\t\t\tconst terminationController = new AbortController();\n\t\t\tconst onAbort = () => terminationController.abort();\n\t\t\tlet silenceKilled = false;\n\t\t\tconst silenceMs = commandSilenceMsOverride ?? DEFAULT_COMMAND_SILENCE_MS;\n\t\t\tconst silenceWatchdog =\n\t\t\t\t(timeout === undefined || timeout <= 0) && silenceMs > 0\n\t\t\t\t\t? createSilenceWatchdog({\n\t\t\t\t\t\t\tsilenceMs,\n\t\t\t\t\t\t\tonSilence: () => {\n\t\t\t\t\t\t\t\tsilenceKilled = true;\n\t\t\t\t\t\t\t\tterminationController.abort();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t: undefined;\n\t\t\tconst onChunk = (data: Buffer) => {\n\t\t\t\tsilenceWatchdog?.touch();\n\t\t\t\tonData(data);\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tchild.stdout?.on(\"data\", onChunk);\n\t\t\t\tchild.stderr?.on(\"data\", onChunk);\n\t\t\t\tif (signal) {\n\t\t\t\t\tif (signal.aborted) onAbort();\n\t\t\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t\tconst terminal = await waitForChildProcessWithTermination(child, {\n\t\t\t\t\tsignal: terminationController.signal,\n\t\t\t\t\ttimeoutMs: timeout !== undefined && timeout > 0 ? timeout * 1000 : undefined,\n\t\t\t\t\tkillGraceMs: 2_000,\n\t\t\t\t});\n\t\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\t\t\t\tif (terminal.reason === \"timeout\") throw new Error(`timeout:${timeout}`);\n\t\t\t\tif (silenceKilled) throw new Error(`silence:${silenceMs / 1000}`);\n\t\t\t\treturn { exitCode: terminal.code };\n\t\t\t} finally {\n\t\t\t\tsilenceWatchdog?.disarm();\n\t\t\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Create bash operations using pi's built-in local shell execution backend.\n *\n * This is useful for extensions that intercept user_bash and still want pi's\n * standard local shell behavior while wrapping or rewriting commands.\n */\nexport function createLocalBashOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"bash\", options);\n}\n\n/** Create PowerShell operations using pi's built-in local execution backend. */\nexport function createLocalPowerShellOperations(options?: { shellPath?: string }): BashOperations {\n\treturn createLocalShellOperations(\"powershell\", options);\n}\n\n/** Create the platform shell backend without requiring callers or the model to choose a shell. */\nexport function createLocalPlatformShellOperations(\n\toptions: { shellPath?: string; commandPrefix?: string; operations?: BashOperations } = {},\n\tplatform: NodeJS.Platform = process.platform,\n): BashOperations {\n\tconst operations =\n\t\toptions.operations ??\n\t\tcreateLocalShellOperations(getPlatformShellToolName(platform), { shellPath: options.shellPath });\n\treturn {\n\t\tasync exec(command, cwd, execOptions) {\n\t\t\tlet resolvedCommand = command;\n\t\t\tif (platform === \"win32\") {\n\t\t\t\tconst route = routeShellContract(command, platform);\n\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\tif (route.kind === \"powershell\") resolvedCommand = route.command;\n\t\t\t}\n\t\t\tif (options.commandPrefix) resolvedCommand = `${options.commandPrefix}\\n${resolvedCommand}`;\n\t\t\tif (platform === \"win32\") resolvedCommand = prefixPowerShellCommand(resolvedCommand);\n\t\t\treturn operations.exec(resolvedCommand, cwd, execOptions);\n\t\t},\n\t};\n}\n\nexport interface BashSpawnContext {\n\tcommand: string;\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n}\n\nexport type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext;\n\nfunction resolveSpawnContext(command: string, cwd: string, spawnHook?: BashSpawnHook): BashSpawnContext {\n\tconst baseContext: BashSpawnContext = { command, cwd, env: { ...getShellEnv() } };\n\treturn spawnHook ? spawnHook(baseContext) : baseContext;\n}\n\nexport interface BashToolOptions {\n\t/** Platform used to choose the default backend and contract router. Defaults to process.platform. */\n\tplatform?: NodeJS.Platform;\n\t/** Custom operations for command execution. Default: local platform shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (for example shell setup commands) */\n\tcommandPrefix?: string;\n\t/** Optional explicit shell path from settings */\n\tshellPath?: string;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n}\n\nconst BASH_PREVIEW_LINES = 5;\nconst BASH_PREVIEW_BYTES = 8 * 1024;\nconst BASH_UPDATE_THROTTLE_MS = 100;\n\ntype BashRenderState = {\n\tstartedAt: number | undefined;\n\tendedAt: number | undefined;\n\tinterval: NodeJS.Timeout | undefined;\n};\n\ntype BashResultRenderState = {\n\tcachedWidth: number | undefined;\n\tcachedLines: string[] | undefined;\n\tcachedSkipped: number | undefined;\n};\n\nclass BashResultRenderComponent extends Container {\n\tstate: BashResultRenderState = {\n\t\tcachedWidth: undefined,\n\t\tcachedLines: undefined,\n\t\tcachedSkipped: undefined,\n\t};\n}\n\nfunction formatDuration(ms: number): string {\n\treturn `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatBashCall(\n\targs: { command?: string; timeout?: number } | undefined,\n\tshellName: PlatformShellToolName,\n): string {\n\tconst command = str(args?.command);\n\tconst timeout = args?.timeout as number | undefined;\n\tconst timeoutSuffix = timeout ? theme.fg(\"muted\", ` (timeout ${timeout}s)`) : \"\";\n\tconst commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg(\"toolOutput\", \"...\");\n\tconst prompt = shellName === \"powershell\" ? \"PS>\" : \"$\";\n\treturn theme.fg(\"toolTitle\", theme.bold(`${prompt} ${commandDisplay}`)) + timeoutSuffix;\n}\n\nfunction rebuildBashResultRenderComponent(\n\tcomponent: BashResultRenderComponent,\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: BashToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n\tstartedAt: number | undefined,\n\tendedAt: number | undefined,\n): void {\n\tconst state = component.state;\n\tcomponent.clear();\n\n\tconst renderPreview = !options.expanded ? result.details?.preview : undefined;\n\tlet output = (renderPreview ? renderPreview.content : getTextOutput(result as any, showImages)).trim();\n\tconst truncation = result.details?.truncation;\n\tconst fullOutputPath = result.details?.fullOutputPath;\n\tconst fullOutputError = result.details?.fullOutputError;\n\tif (!options.isPartial && truncation?.truncated && fullOutputPath && output.endsWith(\"]\")) {\n\t\tconst footerStart = output.lastIndexOf(\"\\n\\n[\");\n\t\tif (footerStart !== -1 && output.slice(footerStart).includes(fullOutputPath)) {\n\t\t\toutput = output.slice(0, footerStart).trimEnd();\n\t\t}\n\t}\n\n\tif (output) {\n\t\tif (options.expanded) {\n\t\t\tconst styledOutput = output\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => theme.fg(\"toolOutput\", line))\n\t\t\t\t.join(\"\\n\");\n\t\t\tcomponent.addChild(new Text(`\\n${styledOutput}`, 0, 0));\n\t\t} else {\n\t\t\tcomponent.addChild({\n\t\t\t\trender: (width: number) => {\n\t\t\t\t\tif (state.cachedLines === undefined || state.cachedWidth !== width) {\n\t\t\t\t\t\tconst preview = truncateToVisualLines(output, BASH_PREVIEW_LINES, width);\n\t\t\t\t\t\tstate.cachedLines = preview.visualLines.map((line) => theme.fg(\"toolOutput\", line));\n\t\t\t\t\t\tstate.cachedSkipped = (result.details?.preview?.skippedLines ?? 0) + preview.skippedCount;\n\t\t\t\t\t\tstate.cachedWidth = width;\n\t\t\t\t\t}\n\t\t\t\t\tif (state.cachedSkipped && state.cachedSkipped > 0) {\n\t\t\t\t\t\tconst hint =\n\t\t\t\t\t\t\ttheme.fg(\"muted\", `... (${state.cachedSkipped} earlier lines,`) +\n\t\t\t\t\t\t\t` ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t\t\t\t\treturn [\"\", truncateToWidth(hint, width, \"...\"), ...(state.cachedLines ?? [])];\n\t\t\t\t\t}\n\t\t\t\t\treturn [\"\", ...(state.cachedLines ?? [])];\n\t\t\t\t},\n\t\t\t\tinvalidate: () => {\n\t\t\t\t\tstate.cachedWidth = undefined;\n\t\t\t\t\tstate.cachedLines = undefined;\n\t\t\t\t\tstate.cachedSkipped = undefined;\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\tif (truncation?.truncated || fullOutputPath || fullOutputError) {\n\t\tconst warnings: string[] = [];\n\t\tif (fullOutputPath) {\n\t\t\twarnings.push(`Full output: ${fullOutputPath}`);\n\t\t} else if (fullOutputError) {\n\t\t\twarnings.push(`Full output unavailable: ${fullOutputError}`);\n\t\t}\n\t\tif (truncation?.truncated) {\n\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\twarnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);\n\t\t\t} else {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"warning\", `[${warnings.join(\". \")}]`)}`, 0, 0));\n\t}\n\n\tif (startedAt !== undefined) {\n\t\tconst label = options.isPartial ? \"Elapsed\" : \"Took\";\n\t\tconst endTime = endedAt ?? Date.now();\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"muted\", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));\n\t}\n}\n\nfunction createShellToolDefinition(\n\tcwd: string,\n\tbackendShell: PlatformShellToolName,\n\tcontractPlatform: NodeJS.Platform,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst toolName = \"bash\";\n\tconst ops =\n\t\toptions?.operations ??\n\t\t(backendShell === \"powershell\"\n\t\t\t? createLocalPowerShellOperations({ shellPath: options?.shellPath })\n\t\t\t: createLocalBashOperations({ shellPath: options?.shellPath }));\n\tconst commandPrefix = options?.commandPrefix;\n\tconst spawnHook = options?.spawnHook;\n\tconst hasExecutionOverrides = Boolean(options?.operations || options?.shellPath || commandPrefix || spawnHook);\n\tconst canFilterCommand = !hasExecutionOverrides;\n\tconst routesWindowsContract = contractPlatform === \"win32\";\n\tconst contractDescription = routesWindowsContract\n\t\t? \"Execute Pi's stable Bash-like command contract in the current working directory. On Windows, a finite deterministic router converts supported simple commands to PowerShell; unsupported Bash constructs fail closed instead of being guessed.\"\n\t\t: \"Execute a Bash command in the current working directory.\";\n\treturn {\n\t\tname: toolName,\n\t\tlabel: toolName,\n\t\tdescription: `${contractDescription} Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Commands have a ${DEFAULT_COMMAND_TIMEOUT_SECONDS}-second wall-clock default, including commands that keep producing output; use a positive timeout only when a scoped operation justifies a larger bound (maximum ${MAX_COMMAND_TIMEOUT_SECONDS} seconds).`,\n\t\tpromptSnippet: routesWindowsContract\n\t\t\t? \"Execute simple Bash-like commands; Pi routes supported forms deterministically to PowerShell on Windows\"\n\t\t\t: \"Execute Bash commands (ls, grep, find, etc.)\",\n\t\tpromptGuidelines: routesWindowsContract\n\t\t\t? [\n\t\t\t\t\t\"Use the bash tool's portable simple-command contract on Windows; do not write PowerShell or ask the user to choose a shell.\",\n\t\t\t\t\t\"Use one simple command per call. The deterministic router rejects pipelines, redirection, expansion, shell chaining, nested shells, and unsupported Bash forms; use dedicated read/edit/search tools or separate calls instead.\",\n\t\t\t\t\t\"Supported Bash-like file commands are converted with literal-path PowerShell operations; verify targets before recursive rm, cp, or mv calls.\",\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t],\n\t\tparameters: bashSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst output = new OutputAccumulator({ tempFilePrefix: `pi-${toolName}` });\n\t\t\tlet updateTimer: NodeJS.Timeout | undefined;\n\t\t\tlet updateDirty = false;\n\t\t\tlet lastUpdateAt = 0;\n\n\t\t\tconst emitOutputUpdate = () => {\n\t\t\t\tif (!onUpdate || !updateDirty) return;\n\t\t\t\tupdateDirty = false;\n\t\t\t\tlastUpdateAt = Date.now();\n\t\t\t\tconst snapshot = output.previewSnapshot(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES, {\n\t\t\t\t\tpersistIfFullTruncated: true,\n\t\t\t\t});\n\t\t\t\tconst preview = {\n\t\t\t\t\tcontent: snapshot.content,\n\t\t\t\t\tskippedLines: Math.max(0, snapshot.truncation.totalLines - snapshot.truncation.outputLines),\n\t\t\t\t};\n\t\t\t\tonUpdate({\n\t\t\t\t\tcontent: [{ type: \"text\", text: preview.content || \"\" }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\ttruncation: snapshot.truncation.truncated ? snapshot.truncation : undefined,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\tpreview,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst clearUpdateTimer = () => {\n\t\t\t\tif (updateTimer) {\n\t\t\t\t\tclearTimeout(updateTimer);\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst scheduleOutputUpdate = () => {\n\t\t\t\tif (!onUpdate) return;\n\t\t\t\tupdateDirty = true;\n\t\t\t\tconst delay = BASH_UPDATE_THROTTLE_MS - (Date.now() - lastUpdateAt);\n\t\t\t\tif (delay <= 0) {\n\t\t\t\t\tclearUpdateTimer();\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tupdateTimer ??= setTimeout(() => {\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t}, delay);\n\t\t\t};\n\n\t\t\tif (onUpdate) {\n\t\t\t\tonUpdate({ content: [], details: undefined });\n\t\t\t}\n\n\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\toutput.append(data);\n\t\t\t\tscheduleOutputUpdate();\n\t\t\t};\n\n\t\t\tconst finishOutput = async () => {\n\t\t\t\toutput.finish();\n\t\t\t\tclearUpdateTimer();\n\t\t\t\temitOutputUpdate();\n\t\t\t\treturn output.snapshot({ persistIfTruncated: true });\n\t\t\t};\n\n\t\t\tconst formatOutput = (snapshot: Awaited<ReturnType<typeof finishOutput>>, emptyText = \"(no output)\") => {\n\t\t\t\tconst truncation = snapshot.truncation;\n\t\t\t\tlet text = snapshot.content || emptyText;\n\t\t\t\tlet details: BashToolDetails | undefined;\n\t\t\t\tconst preview = output.preview(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES);\n\t\t\t\tconst fullOutputNotice = snapshot.fullOutputPath\n\t\t\t\t\t? `Full output: ${snapshot.fullOutputPath}`\n\t\t\t\t\t: snapshot.fullOutputError\n\t\t\t\t\t\t? `Full output unavailable: ${snapshot.fullOutputError}`\n\t\t\t\t\t\t: \"Full output unavailable\";\n\t\t\t\tif (truncation.truncated || preview.skippedLines > 0) {\n\t\t\t\t\tdetails = { preview };\n\t\t\t\t}\n\t\t\t\tif (snapshot.fullOutputPath || snapshot.fullOutputError) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\tconst endLine = truncation.totalLines;\n\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\tconst lastLineSize = formatSize(output.getLastLineBytes());\n\t\t\t\t\t\ttext += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). ${fullOutputNotice}]`;\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. ${fullOutputNotice}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). ${fullOutputNotice}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { text, details };\n\t\t\t};\n\n\t\t\tconst appendStatus = (text: string, status: string) => `${text ? `${text}\\n\\n` : \"\"}${status}`;\n\t\t\tconst effectiveTimeoutSeconds =\n\t\t\t\ttypeof timeout === \"number\" && Number.isFinite(timeout) && timeout > 0\n\t\t\t\t\t? resolveCommandTimeoutSeconds(timeout)\n\t\t\t\t\t: (commandTimeoutMsOverride ?? DEFAULT_COMMAND_TIMEOUT_SECONDS * 1000) / 1000;\n\n\t\t\ttry {\n\t\t\t\tif (canFilterCommand) {\n\t\t\t\t\tconst classification = classifyGitCommand(command, getShellEnv());\n\t\t\t\t\tif (classification.eligible && classification.subcommand) {\n\t\t\t\t\t\tconst res = await executeFilteredGit(\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\tclassification.subcommand,\n\t\t\t\t\t\t\tclassification.globalOptions || [],\n\t\t\t\t\t\t\tclassification.subcommandArgs || [],\n\t\t\t\t\t\t\t{ signal, timeout: effectiveTimeoutSeconds },\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (res.exitCode !== -100) {\n\t\t\t\t\t\t\toutput.append(res.rawBytes ?? Buffer.from(res.rawOut, \"utf-8\"));\n\t\t\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\t\t\tif (res.exitCode !== 0) {\n\t\t\t\t\t\t\t\tconst { text: rawOutputText } = formatOutput(snapshot);\n\t\t\t\t\t\t\t\tthrow new Error(appendStatus(rawOutputText, `Command exited with code ${res.exitCode}`));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst details = snapshot.truncation.truncated\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\ttruncation: snapshot.truncation,\n\t\t\t\t\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: snapshot.fullOutputPath || snapshot.fullOutputError\n\t\t\t\t\t\t\t\t\t? { fullOutputPath: snapshot.fullOutputPath, fullOutputError: snapshot.fullOutputError }\n\t\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\t\treturn { content: [{ type: \"text\", text: res.output }], details };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet backendCommand = command;\n\t\t\t\tif (routesWindowsContract) {\n\t\t\t\t\tconst route = routeShellContract(command, contractPlatform);\n\t\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\t\tbackendCommand = route.command;\n\t\t\t\t}\n\t\t\t\tconst commandWithPrefix = commandPrefix ? `${commandPrefix}\\n${backendCommand}` : backendCommand;\n\t\t\t\tconst resolvedCommand =\n\t\t\t\t\tbackendShell === \"powershell\" ? prefixPowerShellCommand(commandWithPrefix) : commandWithPrefix;\n\t\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);\n\n\t\t\t\tlet exitCode: number | null;\n\t\t\t\ttry {\n\t\t\t\t\t// Shell commands cannot statically declare which files they mutate, so the\n\t\t\t\t\t// actual execution takes the coarse exclusive barrier: it waits for\n\t\t\t\t\t// in-flight edit/write mutations to drain and blocks new ones meanwhile.\n\t\t\t\t\tconst result = await withExclusiveMutationBarrier(() =>\n\t\t\t\t\t\tops.exec(spawnContext.command, spawnContext.cwd, {\n\t\t\t\t\t\t\tonData: handleData,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\ttimeout: effectiveTimeoutSeconds,\n\t\t\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\texitCode = result.exitCode;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\tconst { text } = formatOutput(snapshot, \"\");\n\t\t\t\t\tif (err instanceof Error && err.message === \"aborted\") {\n\t\t\t\t\t\tthrow new Error(appendStatus(text, \"Command aborted\"));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\tthrow new Error(appendStatus(text, `Command timed out after ${timeoutSecs} seconds`));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"silence:\")) {\n\t\t\t\t\t\tconst secs = err.message.split(\":\")[1];\n\t\t\t\t\t\tconst recovery =\n\t\t\t\t\t\t\tbackendShell === \"bash\"\n\t\t\t\t\t\t\t\t? \"re-run it with an explicit timeout, or run it in the background with '&'.\"\n\t\t\t\t\t\t\t\t: \"re-run it with an explicit timeout.\";\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tappendStatus(\n\t\t\t\t\t\t\t\ttext,\n\t\t\t\t\t\t\t\t`Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, ${recovery}`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\n\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\tconst { text: outputText, details } = formatOutput(snapshot);\n\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\n\t\t\t\t\tthrow new Error(appendStatus(outputText, `Command exited with code ${exitCode}`));\n\t\t\t\t}\n\t\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t\t} finally {\n\t\t\t\tclearUpdateTimer();\n\t\t\t\tawait output.closeTempFile();\n\t\t\t}\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (context.executionStarted && state.startedAt === undefined) {\n\t\t\t\tstate.startedAt = Date.now();\n\t\t\t\tstate.endedAt = undefined;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatBashCall(args, toolName));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (state.startedAt !== undefined && options.isPartial && !state.interval) {\n\t\t\t\tstate.interval = setInterval(() => context.invalidate(), 1000);\n\t\t\t}\n\t\t\tif (!options.isPartial || context.isError) {\n\t\t\t\tstate.endedAt ??= Date.now();\n\t\t\t\tif (state.interval) {\n\t\t\t\t\tclearInterval(state.interval);\n\t\t\t\t\tstate.interval = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as BashResultRenderComponent | undefined) ?? new BashResultRenderComponent();\n\t\t\trebuildBashResultRenderComponent(\n\t\t\t\tcomponent,\n\t\t\t\tresult as any,\n\t\t\t\toptions,\n\t\t\t\tcontext.showImages,\n\t\t\t\tstate.startedAt,\n\t\t\t\tstate.endedAt,\n\t\t\t);\n\t\t\tcomponent.invalidate();\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createBashToolDefinition(\n\tcwd: string,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst platform = options?.platform ?? process.platform;\n\treturn createShellToolDefinition(cwd, getPlatformShellToolName(platform), platform, options);\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\treturn wrapToolDefinition(createBashToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { constants } from \"node:fs\";\nimport { access as fsAccess } from \"node:fs/promises\";\nimport { type AgentTool, createSilenceWatchdog } from \"@caupulican/pi-agent-core\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n} from \"@caupulican/pi-agent-core/node\";\nimport { Container, Text, truncateToWidth } from \"@caupulican/pi-tui\";\nimport { spawn } from \"child_process\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { truncateToVisualLines } from \"../../modes/interactive/components/visual-truncate.ts\";\nimport { theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { waitForChildProcessWithTermination } from \"../../utils/child-process.ts\";\nimport {\n\tgetPlatformShellToolName,\n\tgetShellConfig,\n\tgetShellEnv,\n\ttype PlatformShellToolName,\n\tprefixPowerShellCommand,\n\ttrackDetachedChildPid,\n\tuntrackDetachedChildPid,\n} from \"../../utils/shell.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withExclusiveMutationBarrier } from \"./file-mutation-queue.ts\";\nimport { classifyGitCommand, executeFilteredGit } from \"./git-filter.ts\";\nimport { OutputAccumulator } from \"./output-accumulator.ts\";\nimport { getTextOutput, invalidArgText, str } from \"./render-utils.ts\";\nimport { routeShellContract } from \"./shell-contract-router.ts\";\nimport { acquirePersistentShellSession } from \"./shell-session.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nimport { createWindowsShellEngineOperations, type WindowsShellEngineOptions } from \"./windows-shell-engine.ts\";\nimport { getOrCreateWindowsShellState, mergeEffectiveEnv, resolveEffectiveCwd } from \"./windows-shell-state.ts\";\n\n/** Default per-session key for the engine tier when a caller does not pass an explicit sessionKey\n * (e.g. the RPC `createLocalPlatformShellOperations` path); a stable key keeps `cd`/`export` state\n * persisting across calls even without an agent-scoped session key. */\nconst DEFAULT_WINDOWS_SHELL_ENGINE_SESSION_KEY = \"platform-shell-operations\";\n\n/** Low-level silence bound retained for direct shell-operation consumers. Agent tool calls always pass a wall-clock bound. */\nconst DEFAULT_COMMAND_SILENCE_MS = 600_000;\n/** Agent-facing wall-clock bound: continuously producing output must not make a command immortal. */\nexport const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;\nexport const MAX_COMMAND_TIMEOUT_SECONDS = 3600;\nconst MIN_COMMAND_TIMEOUT_SECONDS = 0.1;\nlet commandSilenceMsOverride: number | undefined;\nlet commandTimeoutMsOverride: number | undefined;\n\n/** Test hook: override the low-level silence threshold. Pass undefined to restore the default. */\nexport function setCommandSilenceMsForTests(ms: number | undefined): void {\n\tcommandSilenceMsOverride = ms;\n}\n\n/** Test hook: override the agent tool's default wall-clock bound. Pass undefined to restore it. */\nexport function setCommandTimeoutMsForTests(ms: number | undefined): void {\n\tcommandTimeoutMsOverride = ms;\n}\n\nexport function resolveCommandTimeoutSeconds(timeout: number | undefined): number {\n\tif (typeof timeout !== \"number\" || !Number.isFinite(timeout) || timeout <= 0) {\n\t\treturn DEFAULT_COMMAND_TIMEOUT_SECONDS;\n\t}\n\treturn Math.max(MIN_COMMAND_TIMEOUT_SECONDS, Math.min(timeout, MAX_COMMAND_TIMEOUT_SECONDS));\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Shell command to execute\" }),\n\ttimeout: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription: `Wall-clock timeout in seconds. Defaults to ${DEFAULT_COMMAND_TIMEOUT_SECONDS}; positive overrides are capped at ${MAX_COMMAND_TIMEOUT_SECONDS}. Zero or negative values use the default.`,\n\t\t}),\n\t),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tfullOutputError?: string;\n\tpreview?: {\n\t\tcontent: string;\n\t\tskippedLines: number;\n\t};\n}\n\n/**\n * Pluggable operations for the bash tool.\n * Override these to delegate command execution to remote systems (for example SSH).\n */\nexport interface BashOperations {\n\t/**\n\t * Execute a command and stream output.\n\t * @param command The command to execute\n\t * @param cwd Working directory\n\t * @param options Execution options\n\t * @returns Promise resolving to exit code (null if killed)\n\t */\n\texec: (\n\t\tcommand: string,\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\ttimeout?: number;\n\t\t\tenv?: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nfunction createLocalShellOperations(\n\tshellName: PlatformShellToolName,\n\toptions?: { shellPath?: string; sessionKey?: string },\n): BashOperations {\n\t// A session key selects the persistent per-agent backend. An explicit custom shell path keeps\n\t// per-command spawning: persistent sessions assume the resolved platform shell's flag set.\n\tconst sessionKey = options?.sessionKey;\n\tif (sessionKey !== undefined && !options?.shellPath) {\n\t\treturn {\n\t\t\texec: async (command, cwd, { onData, signal, timeout, env }) => {\n\t\t\t\ttry {\n\t\t\t\t\tawait fsAccess(cwd, constants.F_OK);\n\t\t\t\t} catch {\n\t\t\t\t\tthrow new Error(`Working directory does not exist: ${cwd}\\nCannot execute ${shellName} commands.`);\n\t\t\t\t}\n\t\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\t\t\t\tconst session = acquirePersistentShellSession(sessionKey, shellName);\n\t\t\t\tconst silenceMs = commandSilenceMsOverride ?? DEFAULT_COMMAND_SILENCE_MS;\n\t\t\t\tconst hasWallClock = timeout !== undefined && timeout > 0;\n\t\t\t\treturn session.exec(command, cwd, {\n\t\t\t\t\tonData,\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv,\n\t\t\t\t\ttimeoutSeconds: hasWallClock ? timeout : undefined,\n\t\t\t\t\tsilenceMs: !hasWallClock && silenceMs > 0 ? silenceMs : undefined,\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\t}\n\treturn {\n\t\texec: async (command, cwd, { onData, signal, timeout, env }) => {\n\t\t\tconst { shell, args } = getShellConfig(options?.shellPath, shellName);\n\t\t\ttry {\n\t\t\t\tawait fsAccess(cwd, constants.F_OK);\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`Working directory does not exist: ${cwd}\\nCannot execute ${shellName} commands.`);\n\t\t\t}\n\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tcwd,\n\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\tenv: env ?? getShellEnv(),\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t});\n\t\t\tif (child.pid) trackDetachedChildPid(child.pid);\n\t\t\tconst terminationController = new AbortController();\n\t\t\tconst onAbort = () => terminationController.abort();\n\t\t\tlet silenceKilled = false;\n\t\t\tconst silenceMs = commandSilenceMsOverride ?? DEFAULT_COMMAND_SILENCE_MS;\n\t\t\tconst silenceWatchdog =\n\t\t\t\t(timeout === undefined || timeout <= 0) && silenceMs > 0\n\t\t\t\t\t? createSilenceWatchdog({\n\t\t\t\t\t\t\tsilenceMs,\n\t\t\t\t\t\t\tonSilence: () => {\n\t\t\t\t\t\t\t\tsilenceKilled = true;\n\t\t\t\t\t\t\t\tterminationController.abort();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t: undefined;\n\t\t\tconst onChunk = (data: Buffer) => {\n\t\t\t\tsilenceWatchdog?.touch();\n\t\t\t\tonData(data);\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tchild.stdout?.on(\"data\", onChunk);\n\t\t\t\tchild.stderr?.on(\"data\", onChunk);\n\t\t\t\tif (signal) {\n\t\t\t\t\tif (signal.aborted) onAbort();\n\t\t\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t\tconst terminal = await waitForChildProcessWithTermination(child, {\n\t\t\t\t\tsignal: terminationController.signal,\n\t\t\t\t\ttimeoutMs: timeout !== undefined && timeout > 0 ? timeout * 1000 : undefined,\n\t\t\t\t\tkillGraceMs: 2_000,\n\t\t\t\t});\n\t\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\t\t\t\tif (terminal.reason === \"timeout\") throw new Error(`timeout:${timeout}`);\n\t\t\t\tif (silenceKilled) throw new Error(`silence:${silenceMs / 1000}`);\n\t\t\t\treturn { exitCode: terminal.code };\n\t\t\t} finally {\n\t\t\t\tsilenceWatchdog?.disarm();\n\t\t\t\tif (child.pid) untrackDetachedChildPid(child.pid);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Create bash operations using pi's built-in local shell execution backend.\n *\n * This is useful for extensions that intercept user_bash and still want pi's\n * standard local shell behavior while wrapping or rewriting commands.\n */\nexport function createLocalBashOperations(options?: { shellPath?: string; sessionKey?: string }): BashOperations {\n\treturn createLocalShellOperations(\"bash\", options);\n}\n\n/** Create PowerShell operations using pi's built-in local execution backend. */\nexport function createLocalPowerShellOperations(options?: { shellPath?: string; sessionKey?: string }): BashOperations {\n\treturn createLocalShellOperations(\"powershell\", options);\n}\n\n/** Create the platform shell backend without requiring callers or the model to choose a shell. */\nexport function createLocalPlatformShellOperations(\n\toptions: {\n\t\tshellPath?: string;\n\t\tcommandPrefix?: string;\n\t\toperations?: BashOperations;\n\t\tsessionKey?: string;\n\t\t/** Route complex/state-mutating Bash constructs to the Python engine on Windows. Default: true. */\n\t\tpythonEngine?: boolean;\n\t\t/** Test/embedding hook: overrides the engine tier's runtime/spawn/state resolution. */\n\t\tengineOptions?: WindowsShellEngineOptions;\n\t} = {},\n\tplatform: NodeJS.Platform = process.platform,\n): BashOperations {\n\tconst operations =\n\t\toptions.operations ??\n\t\tcreateLocalShellOperations(getPlatformShellToolName(platform), {\n\t\t\tshellPath: options.shellPath,\n\t\t\tsessionKey: options.sessionKey,\n\t\t});\n\tconst pythonEngineEnabled = options.pythonEngine !== false;\n\tconst engineSessionKey = options.sessionKey ?? DEFAULT_WINDOWS_SHELL_ENGINE_SESSION_KEY;\n\tconst engineOperations = createWindowsShellEngineOperations(engineSessionKey, options.engineOptions);\n\treturn {\n\t\tasync exec(command, cwd, execOptions) {\n\t\t\tlet resolvedCommand = command;\n\t\t\tlet resolvedCwd = cwd;\n\t\t\tlet resolvedExecOptions = execOptions;\n\t\t\tif (platform === \"win32\") {\n\t\t\t\tconst route = routeShellContract(command, platform, { pythonEngine: pythonEngineEnabled });\n\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\t// The engine is the sole state mutator (D4); every Windows call — engine or PS\n\t\t\t\t// tier — reads the SAME session state so a `cd`/`export` in one call is observed\n\t\t\t\t// by the very next call regardless of which tier runs it.\n\t\t\t\tconst state = getOrCreateWindowsShellState(engineSessionKey);\n\t\t\t\tresolvedCwd = resolveEffectiveCwd(state, cwd);\n\t\t\t\tresolvedExecOptions = { ...execOptions, env: mergeEffectiveEnv(state, execOptions.env ?? getShellEnv()) };\n\t\t\t\tif (route.kind === \"python-engine\") {\n\t\t\t\t\t// The engine owns the state transition and resolves the original host cwd\n\t\t\t\t\t// exactly once. Passing the already state-adjusted cwd here would make the\n\t\t\t\t\t// engine mistake its own `cd` result for a host cwd change on the next call.\n\t\t\t\t\treturn engineOperations.exec(route.command, cwd, execOptions);\n\t\t\t\t}\n\t\t\t\tif (route.kind === \"powershell\") resolvedCommand = route.command;\n\t\t\t}\n\t\t\tif (options.commandPrefix) resolvedCommand = `${options.commandPrefix}\\n${resolvedCommand}`;\n\t\t\tif (platform === \"win32\") resolvedCommand = prefixPowerShellCommand(resolvedCommand);\n\t\t\treturn operations.exec(resolvedCommand, resolvedCwd, resolvedExecOptions);\n\t\t},\n\t};\n}\n\nexport interface BashSpawnContext {\n\tcommand: string;\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n}\n\nexport type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext;\n\nfunction resolveSpawnContext(command: string, cwd: string, spawnHook?: BashSpawnHook): BashSpawnContext {\n\tconst baseContext: BashSpawnContext = { command, cwd, env: { ...getShellEnv() } };\n\treturn spawnHook ? spawnHook(baseContext) : baseContext;\n}\n\nexport interface BashToolOptions {\n\t/** Platform used to choose the default backend and contract router. Defaults to process.platform. */\n\tplatform?: NodeJS.Platform;\n\t/** Custom operations for command execution. Default: local platform shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (for example shell setup commands) */\n\tcommandPrefix?: string;\n\t/** Optional explicit shell path from settings */\n\tshellPath?: string;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n\t/**\n\t * Stable key for this agent's persistent shell session. The host passes its per-agent key so\n\t * the session survives runtime reloads and user `!` commands share it; separately created\n\t * tool instances (subagents) auto-generate their own key and stay isolated.\n\t */\n\tsessionKey?: string;\n\t/** Route complex/state-mutating Bash constructs to the Python engine on Windows. Default: true. */\n\twindowsShellPythonEngine?: boolean;\n\t/** Test/embedding hook: overrides the engine tier's runtime/spawn/state resolution. */\n\twindowsShellEngineOptions?: WindowsShellEngineOptions;\n}\n\nconst BASH_PREVIEW_LINES = 5;\nconst BASH_PREVIEW_BYTES = 8 * 1024;\nconst BASH_UPDATE_THROTTLE_MS = 100;\n\ntype BashRenderState = {\n\tstartedAt: number | undefined;\n\tendedAt: number | undefined;\n\tinterval: NodeJS.Timeout | undefined;\n};\n\ntype BashResultRenderState = {\n\tcachedWidth: number | undefined;\n\tcachedLines: string[] | undefined;\n\tcachedSkipped: number | undefined;\n};\n\nclass BashResultRenderComponent extends Container {\n\tstate: BashResultRenderState = {\n\t\tcachedWidth: undefined,\n\t\tcachedLines: undefined,\n\t\tcachedSkipped: undefined,\n\t};\n}\n\nfunction formatDuration(ms: number): string {\n\treturn `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatBashCall(\n\targs: { command?: string; timeout?: number } | undefined,\n\tshellName: PlatformShellToolName,\n): string {\n\tconst command = str(args?.command);\n\tconst timeout = args?.timeout as number | undefined;\n\tconst timeoutSuffix = timeout ? theme.fg(\"muted\", ` (timeout ${timeout}s)`) : \"\";\n\tconst commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg(\"toolOutput\", \"...\");\n\tconst prompt = shellName === \"powershell\" ? \"PS>\" : \"$\";\n\treturn theme.fg(\"toolTitle\", theme.bold(`${prompt} ${commandDisplay}`)) + timeoutSuffix;\n}\n\nfunction rebuildBashResultRenderComponent(\n\tcomponent: BashResultRenderComponent,\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: BashToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\tshowImages: boolean,\n\tstartedAt: number | undefined,\n\tendedAt: number | undefined,\n): void {\n\tconst state = component.state;\n\tcomponent.clear();\n\n\tconst renderPreview = !options.expanded ? result.details?.preview : undefined;\n\tlet output = (renderPreview ? renderPreview.content : getTextOutput(result as any, showImages)).trim();\n\tconst truncation = result.details?.truncation;\n\tconst fullOutputPath = result.details?.fullOutputPath;\n\tconst fullOutputError = result.details?.fullOutputError;\n\tif (!options.isPartial && truncation?.truncated && fullOutputPath && output.endsWith(\"]\")) {\n\t\tconst footerStart = output.lastIndexOf(\"\\n\\n[\");\n\t\tif (footerStart !== -1 && output.slice(footerStart).includes(fullOutputPath)) {\n\t\t\toutput = output.slice(0, footerStart).trimEnd();\n\t\t}\n\t}\n\n\tif (output) {\n\t\tif (options.expanded) {\n\t\t\tconst styledOutput = output\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.map((line) => theme.fg(\"toolOutput\", line))\n\t\t\t\t.join(\"\\n\");\n\t\t\tcomponent.addChild(new Text(`\\n${styledOutput}`, 0, 0));\n\t\t} else {\n\t\t\tcomponent.addChild({\n\t\t\t\trender: (width: number) => {\n\t\t\t\t\tif (state.cachedLines === undefined || state.cachedWidth !== width) {\n\t\t\t\t\t\tconst preview = truncateToVisualLines(output, BASH_PREVIEW_LINES, width);\n\t\t\t\t\t\tstate.cachedLines = preview.visualLines.map((line) => theme.fg(\"toolOutput\", line));\n\t\t\t\t\t\tstate.cachedSkipped = (result.details?.preview?.skippedLines ?? 0) + preview.skippedCount;\n\t\t\t\t\t\tstate.cachedWidth = width;\n\t\t\t\t\t}\n\t\t\t\t\tif (state.cachedSkipped && state.cachedSkipped > 0) {\n\t\t\t\t\t\tconst hint =\n\t\t\t\t\t\t\ttheme.fg(\"muted\", `... (${state.cachedSkipped} earlier lines,`) +\n\t\t\t\t\t\t\t` ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t\t\t\t\treturn [\"\", truncateToWidth(hint, width, \"...\"), ...(state.cachedLines ?? [])];\n\t\t\t\t\t}\n\t\t\t\t\treturn [\"\", ...(state.cachedLines ?? [])];\n\t\t\t\t},\n\t\t\t\tinvalidate: () => {\n\t\t\t\t\tstate.cachedWidth = undefined;\n\t\t\t\t\tstate.cachedLines = undefined;\n\t\t\t\t\tstate.cachedSkipped = undefined;\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\tif (truncation?.truncated || fullOutputPath || fullOutputError) {\n\t\tconst warnings: string[] = [];\n\t\tif (fullOutputPath) {\n\t\t\twarnings.push(`Full output: ${fullOutputPath}`);\n\t\t} else if (fullOutputError) {\n\t\t\twarnings.push(`Full output unavailable: ${fullOutputError}`);\n\t\t}\n\t\tif (truncation?.truncated) {\n\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\twarnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);\n\t\t\t} else {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"warning\", `[${warnings.join(\". \")}]`)}`, 0, 0));\n\t}\n\n\tif (startedAt !== undefined) {\n\t\tconst label = options.isPartial ? \"Elapsed\" : \"Took\";\n\t\tconst endTime = endedAt ?? Date.now();\n\t\tcomponent.addChild(new Text(`\\n${theme.fg(\"muted\", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));\n\t}\n}\n\nfunction createShellToolDefinition(\n\tcwd: string,\n\tbackendShell: PlatformShellToolName,\n\tcontractPlatform: NodeJS.Platform,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst toolName = \"bash\";\n\tconst sessionKey = options?.sessionKey ?? `bash-tool:${randomUUID()}`;\n\tconst ops =\n\t\toptions?.operations ??\n\t\t(backendShell === \"powershell\"\n\t\t\t? createLocalPowerShellOperations({ shellPath: options?.shellPath, sessionKey })\n\t\t\t: createLocalBashOperations({ shellPath: options?.shellPath, sessionKey }));\n\tconst commandPrefix = options?.commandPrefix;\n\tconst spawnHook = options?.spawnHook;\n\tconst hasExecutionOverrides = Boolean(options?.operations || options?.shellPath || commandPrefix || spawnHook);\n\tconst canFilterCommand = !hasExecutionOverrides;\n\tconst routesWindowsContract = contractPlatform === \"win32\";\n\tconst pythonEngineEnabled = options?.windowsShellPythonEngine !== false;\n\tconst engineOperations = routesWindowsContract\n\t\t? createWindowsShellEngineOperations(sessionKey, options?.windowsShellEngineOptions)\n\t\t: undefined;\n\tconst contractDescription = routesWindowsContract\n\t\t? \"Execute Pi's stable Bash-like command contract in a persistent per-agent shell session (current directory and environment variables persist across calls, including across the PowerShell and Python engine tiers). On Windows, a deterministic router converts simple commands directly to PowerShell and routes pipelines, redirection, expansion, chaining, and state-mutating commands (cd/export/unset) through a bundled Python engine that implements the supported Bash grammar; named unsupported constructs (job control, process substitution, heredocs, nested shells, and similar) fail closed instead of being guessed.\"\n\t\t: \"Execute a Bash command in a persistent per-agent shell session: the current directory and environment variables persist across calls, and a timed-out or aborted command resets the session.\";\n\treturn {\n\t\tname: toolName,\n\t\tlabel: toolName,\n\t\tdescription: `${contractDescription} Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Commands have a ${DEFAULT_COMMAND_TIMEOUT_SECONDS}-second wall-clock default, including commands that keep producing output; use a positive timeout only when a scoped operation justifies a larger bound (maximum ${MAX_COMMAND_TIMEOUT_SECONDS} seconds).`,\n\t\tpromptSnippet: routesWindowsContract\n\t\t\t? \"Execute Bash-like commands; Pi routes them deterministically to PowerShell or a bundled Python engine on Windows\"\n\t\t\t: \"Execute Bash commands (ls, grep, find, etc.)\",\n\t\tpromptGuidelines: routesWindowsContract\n\t\t\t? [\n\t\t\t\t\t\"Use ordinary Bash-like commands on Windows; do not write PowerShell or ask the user to choose a shell.\",\n\t\t\t\t\t\"Pipelines, redirection, expansion (variables, command substitution, globs), chaining (&&/||/;), and cd/export/unset are supported and routed to a bundled Python engine; a fixed set of named constructs (job control, process substitution, heredocs, arithmetic expansion, nested shells, and similar) fail closed with an actionable message instead of being guessed.\",\n\t\t\t\t\t\"Working directory and environment changes from cd/export/unset persist across subsequent bash calls, including calls that route to the PowerShell tier.\",\n\t\t\t\t\t\"Supported Bash-like file commands are converted with literal-path operations; verify targets before recursive rm, cp, or mv calls.\",\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t\"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.\",\n\t\t\t\t],\n\t\tparameters: bashSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst output = new OutputAccumulator({ tempFilePrefix: `pi-${toolName}` });\n\t\t\tlet updateTimer: NodeJS.Timeout | undefined;\n\t\t\tlet updateDirty = false;\n\t\t\tlet lastUpdateAt = 0;\n\n\t\t\tconst emitOutputUpdate = () => {\n\t\t\t\tif (!onUpdate || !updateDirty) return;\n\t\t\t\tupdateDirty = false;\n\t\t\t\tlastUpdateAt = Date.now();\n\t\t\t\tconst snapshot = output.previewSnapshot(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES, {\n\t\t\t\t\tpersistIfFullTruncated: true,\n\t\t\t\t});\n\t\t\t\tconst preview = {\n\t\t\t\t\tcontent: snapshot.content.replace(/\\r/g, \"\"),\n\t\t\t\t\tskippedLines: Math.max(0, snapshot.truncation.totalLines - snapshot.truncation.outputLines),\n\t\t\t\t};\n\t\t\t\tonUpdate({\n\t\t\t\t\tcontent: [{ type: \"text\", text: preview.content || \"\" }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\ttruncation: snapshot.truncation.truncated ? snapshot.truncation : undefined,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\tpreview,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst clearUpdateTimer = () => {\n\t\t\t\tif (updateTimer) {\n\t\t\t\t\tclearTimeout(updateTimer);\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst scheduleOutputUpdate = () => {\n\t\t\t\tif (!onUpdate) return;\n\t\t\t\tupdateDirty = true;\n\t\t\t\tconst delay = BASH_UPDATE_THROTTLE_MS - (Date.now() - lastUpdateAt);\n\t\t\t\tif (delay <= 0) {\n\t\t\t\t\tclearUpdateTimer();\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tupdateTimer ??= setTimeout(() => {\n\t\t\t\t\tupdateTimer = undefined;\n\t\t\t\t\temitOutputUpdate();\n\t\t\t\t}, delay);\n\t\t\t};\n\n\t\t\tif (onUpdate) {\n\t\t\t\tonUpdate({ content: [], details: undefined });\n\t\t\t}\n\n\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\toutput.append(data);\n\t\t\t\tscheduleOutputUpdate();\n\t\t\t};\n\n\t\t\tconst finishOutput = async () => {\n\t\t\t\toutput.finish();\n\t\t\t\tclearUpdateTimer();\n\t\t\t\temitOutputUpdate();\n\t\t\t\treturn output.snapshot({ persistIfTruncated: true });\n\t\t\t};\n\n\t\t\tconst formatOutput = (snapshot: Awaited<ReturnType<typeof finishOutput>>, emptyText = \"(no output)\") => {\n\t\t\t\tconst truncation = snapshot.truncation;\n\t\t\t\tlet text = snapshot.content.replace(/\\r/g, \"\") || emptyText;\n\t\t\t\tlet details: BashToolDetails | undefined;\n\t\t\t\tconst preview = output.preview(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES);\n\t\t\t\tconst fullOutputNotice = snapshot.fullOutputPath\n\t\t\t\t\t? `Full output: ${snapshot.fullOutputPath}`\n\t\t\t\t\t: snapshot.fullOutputError\n\t\t\t\t\t\t? `Full output unavailable: ${snapshot.fullOutputError}`\n\t\t\t\t\t\t: \"Full output unavailable\";\n\t\t\t\tif (truncation.truncated || preview.skippedLines > 0) {\n\t\t\t\t\tdetails = { preview };\n\t\t\t\t}\n\t\t\t\tif (snapshot.fullOutputPath || snapshot.fullOutputError) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\t...(details ?? {}),\n\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t};\n\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\tconst endLine = truncation.totalLines;\n\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\tconst lastLineSize = formatSize(output.getLastLineBytes());\n\t\t\t\t\t\ttext += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). ${fullOutputNotice}]`;\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. ${fullOutputNotice}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). ${fullOutputNotice}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { text, details };\n\t\t\t};\n\n\t\t\tconst appendStatus = (text: string, status: string) => `${text ? `${text}\\n\\n` : \"\"}${status}`;\n\t\t\tconst effectiveTimeoutSeconds =\n\t\t\t\ttypeof timeout === \"number\" && Number.isFinite(timeout) && timeout > 0\n\t\t\t\t\t? resolveCommandTimeoutSeconds(timeout)\n\t\t\t\t\t: (commandTimeoutMsOverride ?? DEFAULT_COMMAND_TIMEOUT_SECONDS * 1000) / 1000;\n\n\t\t\ttry {\n\t\t\t\tif (canFilterCommand) {\n\t\t\t\t\tconst classification = classifyGitCommand(command, getShellEnv());\n\t\t\t\t\tif (classification.eligible && classification.subcommand) {\n\t\t\t\t\t\tconst res = await executeFilteredGit(\n\t\t\t\t\t\t\tcwd,\n\t\t\t\t\t\t\tclassification.subcommand,\n\t\t\t\t\t\t\tclassification.globalOptions || [],\n\t\t\t\t\t\t\tclassification.subcommandArgs || [],\n\t\t\t\t\t\t\t{ signal, timeout: effectiveTimeoutSeconds },\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (res.exitCode !== -100) {\n\t\t\t\t\t\t\toutput.append(res.rawBytes ?? Buffer.from(res.rawOut, \"utf-8\"));\n\t\t\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\t\t\tif (res.exitCode !== 0) {\n\t\t\t\t\t\t\t\tconst { text: rawOutputText } = formatOutput(snapshot);\n\t\t\t\t\t\t\t\tthrow new Error(appendStatus(rawOutputText, `Command exited with code ${res.exitCode}`));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst details = snapshot.truncation.truncated\n\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\ttruncation: snapshot.truncation,\n\t\t\t\t\t\t\t\t\t\tfullOutputPath: snapshot.fullOutputPath,\n\t\t\t\t\t\t\t\t\t\tfullOutputError: snapshot.fullOutputError,\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: snapshot.fullOutputPath || snapshot.fullOutputError\n\t\t\t\t\t\t\t\t\t? { fullOutputPath: snapshot.fullOutputPath, fullOutputError: snapshot.fullOutputError }\n\t\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\t\treturn { content: [{ type: \"text\", text: res.output }], details };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet backendCommand = command;\n\t\t\t\tlet engineRoute = false;\n\t\t\t\tlet effectiveCwd = cwd;\n\t\t\t\tif (routesWindowsContract) {\n\t\t\t\t\tconst route = routeShellContract(command, contractPlatform, { pythonEngine: pythonEngineEnabled });\n\t\t\t\t\tif (route.kind === \"unsupported\") throw new Error(route.error);\n\t\t\t\t\tif (route.kind === \"python-engine\") engineRoute = true;\n\t\t\t\t\tbackendCommand = route.command;\n\t\t\t\t\t// The engine is the sole state mutator (D4); every Windows call — engine or PS\n\t\t\t\t\t// tier — reads the SAME session state so a `cd`/`export` in one call is observed\n\t\t\t\t\t// by the very next call regardless of which tier runs it.\n\t\t\t\t\teffectiveCwd = resolveEffectiveCwd(getOrCreateWindowsShellState(sessionKey), cwd);\n\t\t\t\t}\n\t\t\t\t// The engine executes the RAW Bash source unchanged: no commandPrefix (arbitrary\n\t\t\t\t// PowerShell setup would not parse as Bash grammar) and no PowerShell UTF-8 prefix.\n\t\t\t\tconst resolvedCommand = engineRoute\n\t\t\t\t\t? backendCommand\n\t\t\t\t\t: backendShell === \"powershell\"\n\t\t\t\t\t\t? prefixPowerShellCommand(commandPrefix ? `${commandPrefix}\\n${backendCommand}` : backendCommand)\n\t\t\t\t\t\t: commandPrefix\n\t\t\t\t\t\t\t? `${commandPrefix}\\n${backendCommand}`\n\t\t\t\t\t\t\t: backendCommand;\n\t\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, effectiveCwd, spawnHook);\n\t\t\t\tif (routesWindowsContract) {\n\t\t\t\t\tspawnContext.env = mergeEffectiveEnv(getOrCreateWindowsShellState(sessionKey), spawnContext.env);\n\t\t\t\t}\n\n\t\t\t\tlet exitCode: number | null;\n\t\t\t\ttry {\n\t\t\t\t\t// Shell commands cannot statically declare which files they mutate, so the\n\t\t\t\t\t// actual execution takes the coarse exclusive barrier: it waits for\n\t\t\t\t\t// in-flight edit/write mutations to drain and blocks new ones meanwhile.\n\t\t\t\t\tconst result = await withExclusiveMutationBarrier(() =>\n\t\t\t\t\t\t(engineRoute && engineOperations ? engineOperations : ops).exec(\n\t\t\t\t\t\t\tspawnContext.command,\n\t\t\t\t\t\t\tspawnContext.cwd,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tonData: handleData,\n\t\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\t\ttimeout: effectiveTimeoutSeconds,\n\t\t\t\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\texitCode = result.exitCode;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\t\tconst { text } = formatOutput(snapshot, \"\");\n\t\t\t\t\tif (err instanceof Error && err.message === \"aborted\") {\n\t\t\t\t\t\tthrow new Error(appendStatus(text, \"Command aborted\"));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\tthrow new Error(appendStatus(text, `Command timed out after ${timeoutSecs} seconds`));\n\t\t\t\t\t}\n\t\t\t\t\tif (err instanceof Error && err.message.startsWith(\"silence:\")) {\n\t\t\t\t\t\tconst secs = err.message.split(\":\")[1];\n\t\t\t\t\t\tconst recovery =\n\t\t\t\t\t\t\tbackendShell === \"bash\"\n\t\t\t\t\t\t\t\t? \"re-run it with an explicit timeout, or run it in the background with '&'.\"\n\t\t\t\t\t\t\t\t: \"re-run it with an explicit timeout.\";\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tappendStatus(\n\t\t\t\t\t\t\t\ttext,\n\t\t\t\t\t\t\t\t`Command killed after ${secs}s of silence (no output). If the command is legitimately quiet for long stretches, ${recovery}`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\n\t\t\t\tconst snapshot = await finishOutput();\n\t\t\t\tconst { text: outputText, details } = formatOutput(snapshot);\n\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\n\t\t\t\t\tthrow new Error(appendStatus(outputText, `Command exited with code ${exitCode}`));\n\t\t\t\t}\n\t\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t\t} finally {\n\t\t\t\tclearUpdateTimer();\n\t\t\t\tawait output.closeTempFile();\n\t\t\t}\n\t\t},\n\t\trenderCall(args, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (context.executionStarted && state.startedAt === undefined) {\n\t\t\t\tstate.startedAt = Date.now();\n\t\t\t\tstate.endedAt = undefined;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatBashCall(args, toolName));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, _theme, context) {\n\t\t\tconst state = context.state;\n\t\t\tif (state.startedAt !== undefined && options.isPartial && !state.interval) {\n\t\t\t\tstate.interval = setInterval(() => context.invalidate(), 1000);\n\t\t\t}\n\t\t\tif (!options.isPartial || context.isError) {\n\t\t\t\tstate.endedAt ??= Date.now();\n\t\t\t\tif (state.interval) {\n\t\t\t\t\tclearInterval(state.interval);\n\t\t\t\t\tstate.interval = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as BashResultRenderComponent | undefined) ?? new BashResultRenderComponent();\n\t\t\trebuildBashResultRenderComponent(\n\t\t\t\tcomponent,\n\t\t\t\tresult as any,\n\t\t\t\toptions,\n\t\t\t\tcontext.showImages,\n\t\t\t\tstate.startedAt,\n\t\t\t\tstate.endedAt,\n\t\t\t);\n\t\t\tcomponent.invalidate();\n\t\t\treturn component;\n\t\t},\n\t};\n}\n\nexport function createBashToolDefinition(\n\tcwd: string,\n\toptions?: BashToolOptions,\n): ToolDefinition<typeof bashSchema, BashToolDetails | undefined, BashRenderState> {\n\tconst platform = options?.platform ?? process.platform;\n\treturn createShellToolDefinition(cwd, getPlatformShellToolName(platform), platform, options);\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\treturn wrapToolDefinition(createBashToolDefinition(cwd, options));\n}\n"]}
|
package/dist/core/tools/bash.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { constants } from "node:fs";
|
|
2
3
|
import { access as fsAccess } from "node:fs/promises";
|
|
3
4
|
import { createSilenceWatchdog } from "@caupulican/pi-agent-core";
|
|
@@ -15,7 +16,14 @@ import { classifyGitCommand, executeFilteredGit } from "./git-filter.js";
|
|
|
15
16
|
import { OutputAccumulator } from "./output-accumulator.js";
|
|
16
17
|
import { getTextOutput, invalidArgText, str } from "./render-utils.js";
|
|
17
18
|
import { routeShellContract } from "./shell-contract-router.js";
|
|
19
|
+
import { acquirePersistentShellSession } from "./shell-session.js";
|
|
18
20
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
21
|
+
import { createWindowsShellEngineOperations } from "./windows-shell-engine.js";
|
|
22
|
+
import { getOrCreateWindowsShellState, mergeEffectiveEnv, resolveEffectiveCwd } from "./windows-shell-state.js";
|
|
23
|
+
/** Default per-session key for the engine tier when a caller does not pass an explicit sessionKey
|
|
24
|
+
* (e.g. the RPC `createLocalPlatformShellOperations` path); a stable key keeps `cd`/`export` state
|
|
25
|
+
* persisting across calls even without an agent-scoped session key. */
|
|
26
|
+
const DEFAULT_WINDOWS_SHELL_ENGINE_SESSION_KEY = "platform-shell-operations";
|
|
19
27
|
/** Low-level silence bound retained for direct shell-operation consumers. Agent tool calls always pass a wall-clock bound. */
|
|
20
28
|
const DEFAULT_COMMAND_SILENCE_MS = 600_000;
|
|
21
29
|
/** Agent-facing wall-clock bound: continuously producing output must not make a command immortal. */
|
|
@@ -45,6 +53,33 @@ const bashSchema = Type.Object({
|
|
|
45
53
|
})),
|
|
46
54
|
});
|
|
47
55
|
function createLocalShellOperations(shellName, options) {
|
|
56
|
+
// A session key selects the persistent per-agent backend. An explicit custom shell path keeps
|
|
57
|
+
// per-command spawning: persistent sessions assume the resolved platform shell's flag set.
|
|
58
|
+
const sessionKey = options?.sessionKey;
|
|
59
|
+
if (sessionKey !== undefined && !options?.shellPath) {
|
|
60
|
+
return {
|
|
61
|
+
exec: async (command, cwd, { onData, signal, timeout, env }) => {
|
|
62
|
+
try {
|
|
63
|
+
await fsAccess(cwd, constants.F_OK);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new Error(`Working directory does not exist: ${cwd}\nCannot execute ${shellName} commands.`);
|
|
67
|
+
}
|
|
68
|
+
if (signal?.aborted)
|
|
69
|
+
throw new Error("aborted");
|
|
70
|
+
const session = acquirePersistentShellSession(sessionKey, shellName);
|
|
71
|
+
const silenceMs = commandSilenceMsOverride ?? DEFAULT_COMMAND_SILENCE_MS;
|
|
72
|
+
const hasWallClock = timeout !== undefined && timeout > 0;
|
|
73
|
+
return session.exec(command, cwd, {
|
|
74
|
+
onData,
|
|
75
|
+
signal,
|
|
76
|
+
env,
|
|
77
|
+
timeoutSeconds: hasWallClock ? timeout : undefined,
|
|
78
|
+
silenceMs: !hasWallClock && silenceMs > 0 ? silenceMs : undefined,
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
48
83
|
return {
|
|
49
84
|
exec: async (command, cwd, { onData, signal, timeout, env }) => {
|
|
50
85
|
const { shell, args } = getShellConfig(options?.shellPath, shellName);
|
|
@@ -130,14 +165,34 @@ export function createLocalPowerShellOperations(options) {
|
|
|
130
165
|
/** Create the platform shell backend without requiring callers or the model to choose a shell. */
|
|
131
166
|
export function createLocalPlatformShellOperations(options = {}, platform = process.platform) {
|
|
132
167
|
const operations = options.operations ??
|
|
133
|
-
createLocalShellOperations(getPlatformShellToolName(platform), {
|
|
168
|
+
createLocalShellOperations(getPlatformShellToolName(platform), {
|
|
169
|
+
shellPath: options.shellPath,
|
|
170
|
+
sessionKey: options.sessionKey,
|
|
171
|
+
});
|
|
172
|
+
const pythonEngineEnabled = options.pythonEngine !== false;
|
|
173
|
+
const engineSessionKey = options.sessionKey ?? DEFAULT_WINDOWS_SHELL_ENGINE_SESSION_KEY;
|
|
174
|
+
const engineOperations = createWindowsShellEngineOperations(engineSessionKey, options.engineOptions);
|
|
134
175
|
return {
|
|
135
176
|
async exec(command, cwd, execOptions) {
|
|
136
177
|
let resolvedCommand = command;
|
|
178
|
+
let resolvedCwd = cwd;
|
|
179
|
+
let resolvedExecOptions = execOptions;
|
|
137
180
|
if (platform === "win32") {
|
|
138
|
-
const route = routeShellContract(command, platform);
|
|
181
|
+
const route = routeShellContract(command, platform, { pythonEngine: pythonEngineEnabled });
|
|
139
182
|
if (route.kind === "unsupported")
|
|
140
183
|
throw new Error(route.error);
|
|
184
|
+
// The engine is the sole state mutator (D4); every Windows call — engine or PS
|
|
185
|
+
// tier — reads the SAME session state so a `cd`/`export` in one call is observed
|
|
186
|
+
// by the very next call regardless of which tier runs it.
|
|
187
|
+
const state = getOrCreateWindowsShellState(engineSessionKey);
|
|
188
|
+
resolvedCwd = resolveEffectiveCwd(state, cwd);
|
|
189
|
+
resolvedExecOptions = { ...execOptions, env: mergeEffectiveEnv(state, execOptions.env ?? getShellEnv()) };
|
|
190
|
+
if (route.kind === "python-engine") {
|
|
191
|
+
// The engine owns the state transition and resolves the original host cwd
|
|
192
|
+
// exactly once. Passing the already state-adjusted cwd here would make the
|
|
193
|
+
// engine mistake its own `cd` result for a host cwd change on the next call.
|
|
194
|
+
return engineOperations.exec(route.command, cwd, execOptions);
|
|
195
|
+
}
|
|
141
196
|
if (route.kind === "powershell")
|
|
142
197
|
resolvedCommand = route.command;
|
|
143
198
|
}
|
|
@@ -145,7 +200,7 @@ export function createLocalPlatformShellOperations(options = {}, platform = proc
|
|
|
145
200
|
resolvedCommand = `${options.commandPrefix}\n${resolvedCommand}`;
|
|
146
201
|
if (platform === "win32")
|
|
147
202
|
resolvedCommand = prefixPowerShellCommand(resolvedCommand);
|
|
148
|
-
return operations.exec(resolvedCommand,
|
|
203
|
+
return operations.exec(resolvedCommand, resolvedCwd, resolvedExecOptions);
|
|
149
204
|
},
|
|
150
205
|
};
|
|
151
206
|
}
|
|
@@ -246,30 +301,36 @@ function rebuildBashResultRenderComponent(component, result, options, showImages
|
|
|
246
301
|
}
|
|
247
302
|
function createShellToolDefinition(cwd, backendShell, contractPlatform, options) {
|
|
248
303
|
const toolName = "bash";
|
|
304
|
+
const sessionKey = options?.sessionKey ?? `bash-tool:${randomUUID()}`;
|
|
249
305
|
const ops = options?.operations ??
|
|
250
306
|
(backendShell === "powershell"
|
|
251
|
-
? createLocalPowerShellOperations({ shellPath: options?.shellPath })
|
|
252
|
-
: createLocalBashOperations({ shellPath: options?.shellPath }));
|
|
307
|
+
? createLocalPowerShellOperations({ shellPath: options?.shellPath, sessionKey })
|
|
308
|
+
: createLocalBashOperations({ shellPath: options?.shellPath, sessionKey }));
|
|
253
309
|
const commandPrefix = options?.commandPrefix;
|
|
254
310
|
const spawnHook = options?.spawnHook;
|
|
255
311
|
const hasExecutionOverrides = Boolean(options?.operations || options?.shellPath || commandPrefix || spawnHook);
|
|
256
312
|
const canFilterCommand = !hasExecutionOverrides;
|
|
257
313
|
const routesWindowsContract = contractPlatform === "win32";
|
|
314
|
+
const pythonEngineEnabled = options?.windowsShellPythonEngine !== false;
|
|
315
|
+
const engineOperations = routesWindowsContract
|
|
316
|
+
? createWindowsShellEngineOperations(sessionKey, options?.windowsShellEngineOptions)
|
|
317
|
+
: undefined;
|
|
258
318
|
const contractDescription = routesWindowsContract
|
|
259
|
-
? "Execute Pi's stable Bash-like command contract in
|
|
260
|
-
: "Execute a Bash command in the current
|
|
319
|
+
? "Execute Pi's stable Bash-like command contract in a persistent per-agent shell session (current directory and environment variables persist across calls, including across the PowerShell and Python engine tiers). On Windows, a deterministic router converts simple commands directly to PowerShell and routes pipelines, redirection, expansion, chaining, and state-mutating commands (cd/export/unset) through a bundled Python engine that implements the supported Bash grammar; named unsupported constructs (job control, process substitution, heredocs, nested shells, and similar) fail closed instead of being guessed."
|
|
320
|
+
: "Execute a Bash command in a persistent per-agent shell session: the current directory and environment variables persist across calls, and a timed-out or aborted command resets the session.";
|
|
261
321
|
return {
|
|
262
322
|
name: toolName,
|
|
263
323
|
label: toolName,
|
|
264
324
|
description: `${contractDescription} Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Commands have a ${DEFAULT_COMMAND_TIMEOUT_SECONDS}-second wall-clock default, including commands that keep producing output; use a positive timeout only when a scoped operation justifies a larger bound (maximum ${MAX_COMMAND_TIMEOUT_SECONDS} seconds).`,
|
|
265
325
|
promptSnippet: routesWindowsContract
|
|
266
|
-
? "Execute
|
|
326
|
+
? "Execute Bash-like commands; Pi routes them deterministically to PowerShell or a bundled Python engine on Windows"
|
|
267
327
|
: "Execute Bash commands (ls, grep, find, etc.)",
|
|
268
328
|
promptGuidelines: routesWindowsContract
|
|
269
329
|
? [
|
|
270
|
-
"Use
|
|
271
|
-
"
|
|
272
|
-
"
|
|
330
|
+
"Use ordinary Bash-like commands on Windows; do not write PowerShell or ask the user to choose a shell.",
|
|
331
|
+
"Pipelines, redirection, expansion (variables, command substitution, globs), chaining (&&/||/;), and cd/export/unset are supported and routed to a bundled Python engine; a fixed set of named constructs (job control, process substitution, heredocs, arithmetic expansion, nested shells, and similar) fail closed with an actionable message instead of being guessed.",
|
|
332
|
+
"Working directory and environment changes from cd/export/unset persist across subsequent bash calls, including calls that route to the PowerShell tier.",
|
|
333
|
+
"Supported Bash-like file commands are converted with literal-path operations; verify targets before recursive rm, cp, or mv calls.",
|
|
273
334
|
"Keep searches scoped and purpose-driven: discover paths first, pass an explicit root and filters, prefer rg over broad find, and increase the timeout only for a justified bounded search.",
|
|
274
335
|
]
|
|
275
336
|
: [
|
|
@@ -290,7 +351,7 @@ function createShellToolDefinition(cwd, backendShell, contractPlatform, options)
|
|
|
290
351
|
persistIfFullTruncated: true,
|
|
291
352
|
});
|
|
292
353
|
const preview = {
|
|
293
|
-
content: snapshot.content,
|
|
354
|
+
content: snapshot.content.replace(/\r/g, ""),
|
|
294
355
|
skippedLines: Math.max(0, snapshot.truncation.totalLines - snapshot.truncation.outputLines),
|
|
295
356
|
};
|
|
296
357
|
onUpdate({
|
|
@@ -339,7 +400,7 @@ function createShellToolDefinition(cwd, backendShell, contractPlatform, options)
|
|
|
339
400
|
};
|
|
340
401
|
const formatOutput = (snapshot, emptyText = "(no output)") => {
|
|
341
402
|
const truncation = snapshot.truncation;
|
|
342
|
-
let text = snapshot.content || emptyText;
|
|
403
|
+
let text = snapshot.content.replace(/\r/g, "") || emptyText;
|
|
343
404
|
let details;
|
|
344
405
|
const preview = output.preview(BASH_PREVIEW_LINES, BASH_PREVIEW_BYTES);
|
|
345
406
|
const fullOutputNotice = snapshot.fullOutputPath
|
|
@@ -409,21 +470,39 @@ function createShellToolDefinition(cwd, backendShell, contractPlatform, options)
|
|
|
409
470
|
}
|
|
410
471
|
}
|
|
411
472
|
let backendCommand = command;
|
|
473
|
+
let engineRoute = false;
|
|
474
|
+
let effectiveCwd = cwd;
|
|
412
475
|
if (routesWindowsContract) {
|
|
413
|
-
const route = routeShellContract(command, contractPlatform);
|
|
476
|
+
const route = routeShellContract(command, contractPlatform, { pythonEngine: pythonEngineEnabled });
|
|
414
477
|
if (route.kind === "unsupported")
|
|
415
478
|
throw new Error(route.error);
|
|
479
|
+
if (route.kind === "python-engine")
|
|
480
|
+
engineRoute = true;
|
|
416
481
|
backendCommand = route.command;
|
|
482
|
+
// The engine is the sole state mutator (D4); every Windows call — engine or PS
|
|
483
|
+
// tier — reads the SAME session state so a `cd`/`export` in one call is observed
|
|
484
|
+
// by the very next call regardless of which tier runs it.
|
|
485
|
+
effectiveCwd = resolveEffectiveCwd(getOrCreateWindowsShellState(sessionKey), cwd);
|
|
486
|
+
}
|
|
487
|
+
// The engine executes the RAW Bash source unchanged: no commandPrefix (arbitrary
|
|
488
|
+
// PowerShell setup would not parse as Bash grammar) and no PowerShell UTF-8 prefix.
|
|
489
|
+
const resolvedCommand = engineRoute
|
|
490
|
+
? backendCommand
|
|
491
|
+
: backendShell === "powershell"
|
|
492
|
+
? prefixPowerShellCommand(commandPrefix ? `${commandPrefix}\n${backendCommand}` : backendCommand)
|
|
493
|
+
: commandPrefix
|
|
494
|
+
? `${commandPrefix}\n${backendCommand}`
|
|
495
|
+
: backendCommand;
|
|
496
|
+
const spawnContext = resolveSpawnContext(resolvedCommand, effectiveCwd, spawnHook);
|
|
497
|
+
if (routesWindowsContract) {
|
|
498
|
+
spawnContext.env = mergeEffectiveEnv(getOrCreateWindowsShellState(sessionKey), spawnContext.env);
|
|
417
499
|
}
|
|
418
|
-
const commandWithPrefix = commandPrefix ? `${commandPrefix}\n${backendCommand}` : backendCommand;
|
|
419
|
-
const resolvedCommand = backendShell === "powershell" ? prefixPowerShellCommand(commandWithPrefix) : commandWithPrefix;
|
|
420
|
-
const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);
|
|
421
500
|
let exitCode;
|
|
422
501
|
try {
|
|
423
502
|
// Shell commands cannot statically declare which files they mutate, so the
|
|
424
503
|
// actual execution takes the coarse exclusive barrier: it waits for
|
|
425
504
|
// in-flight edit/write mutations to drain and blocks new ones meanwhile.
|
|
426
|
-
const result = await withExclusiveMutationBarrier(() => ops.exec(spawnContext.command, spawnContext.cwd, {
|
|
505
|
+
const result = await withExclusiveMutationBarrier(() => (engineRoute && engineOperations ? engineOperations : ops).exec(spawnContext.command, spawnContext.cwd, {
|
|
427
506
|
onData: handleData,
|
|
428
507
|
signal,
|
|
429
508
|
timeout: effectiveTimeoutSeconds,
|