@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
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,94 @@
|
|
|
1
|
+
## [0.81.39] - 2026-07-20
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- New `processMatrix` system (on by default, `processMatrix.enabled: false` is the explicit opt-out): a durable, restart-surviving master/worker process matrix under `state/process-matrix/`. A worker launched with a known parent (`PI_PARENT_PID`/`--parent-pid`, threaded automatically through `tmux_agent_manager`'s `fire_task`) self-registers and watches its parent's liveness; on parent death it winds down gracefully (never silently), leaving a resumable payload, and either gets adopted by a new master or self-exits after a bounded grace window. A master's startup scan for orphaned workers ALWAYS asks the owner before touching anything (adopt, cooperative cleanup, or leave untouched) and is report-only with zero writes/kills when non-interactive — see `docs/process-matrix.md`.
|
|
6
|
+
- Closed the tool-selection observe/promote loop: durable per-(model, intent) agreement tracking, an evidence-gated compact prompt hint (activates only past utility/margin/min-evidence thresholds, deactivates when its tracked efficacy drops, never auto-executes), a `getReport()` surface joined into `/toolhealth`, and `PI_TOOL_SELECTION_OBSERVE` / `PI_TOOL_SELECTION_HINTS` kill switches.
|
|
7
|
+
- Goal evidence now carries a validated ref and a `verified` flag: `add_evidence` checks a "tool" ref against real session tool-call records or a "file" ref against the filesystem, and marking a goal `complete` requires at least one satisfied requirement backed by verified or user-confirmed evidence (configurable, default on).
|
|
8
|
+
- Delegated workers whose mutation needs a human look are now observable and sticky in `delegate_status` instead of purely advisory text: an unreviewed-mutation notice persists until the parent explicitly acknowledges it via a new `delegate_status` review action. Enforcement stays visibility-only, never write-blocking.
|
|
9
|
+
- Goals now carry a durable, per-goal cumulative continuation budget (turns, active wall-clock, and attributed USD spend) across every continuation invocation for the goal's lifetime, with a conservative default ceiling and a visible `goal_budget_exhausted` stop reason.
|
|
10
|
+
- A turn-boundary advisory nudge fires when the task_steps workflow contract (one in_progress step, no stale open steps) is violated for 3 consecutive turns — a one-line harness note, never a blocking gate.
|
|
11
|
+
- The goal continuation snapshot and prompt now include a read-only summary of open task_steps steps, and satisfying or completing a goal requirement nudges when an open task step appears to reference it — read-only cross-visibility between the goal and task-steps systems, with no shared state machine.
|
|
12
|
+
- A canonical, typed path module (`agent-paths.ts`) is now the single source of truth for everything machine-managed under the agent directory (`state/`, `cache/`, `work/`, runtimes, models, sessions, npm/git installs); an idempotent startup migration relocates the confirmed root-level straggler (`trust.json`) into `state/`.
|
|
13
|
+
- tmux workers launched via `tmux_agent_manager` are now PERSISTENT and talkable mid-run: a new `send_followup` action re-injects a fresh prompt into an already-live job's pane using a unique per-turn marker pair and re-arms the pane's completion watcher, reusing the existing event-driven per-turn handoff; session start now also reconciles tmux sessions against this session's own job records (an orphaned session is marked informational, never killed; a live session with a pending turn has its watcher re-armed); a new `dismiss` action stops tracking a job without killing its tmux session, and an idle worker (no turn currently dispatched) no longer holds a reload-quiesce registration.
|
|
14
|
+
- tmux worker dispatch is now approval-gated by a persisted, per-session/per-goal STANDING GRANT: `grant_dispatch` (interactively confirmed, or opt-in via `--allow-tmux-dispatch` with no UI) authorizes repeated unattended `fire_task`/`send_followup` launches within a budget (`maxLaunches`, optional expiry, goal/tool/path scope); `revoke_grant` ends it early; a launch with no covering grant and no interactive approval is refused, never silent. A grant-covered `pi` child is launched with a restricted profile (`--tools`/`--resource-profile` or `--no-extensions --no-skills`, plus a scoped `--append-system-prompt` naming the grant and its hard stops) pushed into the child's own launch configuration; non-`pi` agents are bounded at the launch layer only. Self-reported worker usage is ingested as an advisory, idempotent claim (`reportSpawnedUsage`), never a hard cross-process cap.
|
|
15
|
+
- A new host bridge (`pi.reportManagedLane`) makes out-of-process tmux workers first-class `tmux-worker` lanes: visible in `/autonomy` and `delegate_status` alongside in-process worker lanes, reload-quiesce-registered only while a turn is dispatching, and left as a bounded claim snapshot on terminal. A tmux worker's self-reported changed files are re-checked against the session's active write scope and flagged for parent review when out of scope (reusing the existing in-process worker review path verbatim); in-process worker lanes are now goalId-tagged, matching the existing research-lane tagging.
|
|
16
|
+
- Goals gain a `worker` evidence kind (verified against the worker's lane/result and its review flag, so an unreviewed worker completion can never ungate goal completion) and a structural `dispatch_worker` action that records a requirement↔lane binding without silently satisfying the requirement; the binding and worker-evidence verification are wired live (dispatches a real in-process worker lane today — tmux-worker selection from this tool is not yet available).
|
|
17
|
+
- The goal loop no longer stalls out or races a redundant re-dispatch when a worker is actively handling an open requirement: a new `waiting` continuation state (reason `worker_in_flight`) pauses the loop without submitting a hollow pass and stops the idle scheduler from re-dispatching, resuming automatically once the worker's lane goes terminal; per-goal worker/subagent spend is now tracked (`continuationWorkerSpendUsd`, summed from the goal's own lanes) against a new conservative default ceiling, accurate for in-process lanes today and advisory for tmux workers pending a documented follow-up.
|
|
18
|
+
- The goal tool's `dispatch_worker` action can now target a persistent tmux worker instead of the default in-process one (`dispatchTarget: "tmux"`, opt-in per call): core structurally invokes the tmux extension's `fire_task` itself — the same tool call the model would make — single-agent and 1:1 to the requirement's bound lane, still gated by the existing standing-grant authorization; an unattended dispatch with no covering grant is honestly refused (`dispatchSkipReason: "no_standing_grant"`), never silently launched and never a fabricated lane id. Correlating the extension's own dispatch report back to the internal lane id is a new deterministic keyed lookup (`BackgroundLaneController.resolveManagedLaneId`), not a racy lane-list diff.
|
|
19
|
+
- New `worktreeSync` system (on by default, `worktreeSync.enabled: false` is the explicit opt-out): a hard-gated, worktree-per-lane parallel-work workflow (`worktree_sync` tool: `status`/`create_lane`/`sync`/`continue`/`abort_sync`/`land`/`release_lane`/`reconcile`) where every writing agent works in its own git worktree/branch and integration is always rebase-onto-main + fast-forward, serialized under one integration lock with a configurable land-time gate command verified at the exact landing tip — see `docs/worktree-sync.md`. Wired into the goal loop and tmux dispatch: two new continuation reason codes (`lane_sync_required`/`lane_sync_conflict`) give the goal loop a deterministic directive when a bound worker's lane is stale or has a rebase stopped on conflicts; the goal runtime snapshot can surface per-requirement worktree lane state; a goal-bound tmux dispatch (`dispatchTarget: "tmux"`) creates the lane FIRST when worktree-sync is enabled, aborting cleanly before any tmux launch on a creation refusal (`dispatchSkipReason: "worktree_create_failed"`), and launches the worker with `--worktree-lane <key>` plus one extra lane-doctrine system-prompt clause; `LaneRecord` gains an optional `worktreeLaneKey` so `/autonomy` and the goal snapshot can render the pairing.
|
|
20
|
+
- A new session-role identity (`core/session-role.ts`) gives a worker session (lane-bound or `PI_SESSION_ROLE=worker`) a strict UAC ceiling: `goal`/`delegate`/`delegate_status`/`improvement_loop`/`extensionify`/`skillify`/`run_toolkit_script`/`model_fitness`/`tmux_agent_manager`/`context_scout`/`python` can never activate, wins over any allow-list or resource-profile grant; `worktree_sync`'s `create_lane`/`release_lane`/`reconcile` are refused and `land` is deny-by-default (new `worktreeSync.workerLand` setting) for a worker, which may only ever target its own bound lane; `land`/`release_lane` now refuse `lane_owner_conflict` when a different, still-alive session owns the target lane; a lane-bound session's `edit`/`write` targets are checked (symlink-safely) against the lane's own worktree root, refusing `path_outside_lane`; and every scattered on-disk store plus `SettingsManager`'s write path go read-only for a worker session, so no worker ever touches `~/.pi/agent/state` or `settings.json` — see `docs/worktree-sync.md`'s "Identity, UAC, and zero footprint" section.
|
|
21
|
+
- Lane-worker eligibility now rides the model-capability system end to end (`evaluateLaneWorkerRefusal` in `core/model-capability.ts`): a worktree-sync lane worker requires capability class `full`, a declared registry context window, an advertised native tool-call path (`Model.textToolCallProtocol` unset/false), and no graded `/toolprobe` demotion to text-protocol/none — a goal→tmux dispatch refuses early as a best-effort check (`dispatchSkipReason: "worker_capability_insufficient"`, zero lane/pane side effect) and the dispatched child session refuses authoritatively at its own startup with a deterministic, greppable log line, regardless of how it became lane-bound. The lean capability class also now blocks the full orchestration surface (`goal`, `worktree_sync`, `improvement_loop`, `extensionify`, `skillify`, `model_fitness`, `context_scout`, `tmux_agent_manager`) in addition to `delegate`/`context_audit` — see `docs/worktree-sync.md`'s "Capability adaptation" section.
|
|
22
|
+
|
|
23
|
+
- The Windows `bash` contract now routes through three tiers instead of one: the existing simple-command PowerShell floor (unchanged), a new bundled Python shell engine (uv-provisioned Python 3.13) for pipelines, redirection, chaining, quoting/expansion/globs, and the coreutils vocabulary, and named fail-closed refusals for constructs outside that grammar (job control, process substitution, arithmetic expansion, heredocs, nested shells, control-flow, `eval`/`source`/similar, and more) — see `docs/windows.md`. State-mutating commands (`cd`, `export`, `unset`) always route to the engine, the sole mutator; working directory and environment persist across calls and across tiers, with subshells (`( … )`) isolated and brace groups (`{ …; }`) sharing state like bash. A new `windowsShell.pythonEngine` setting (default `true`) is the hard kill switch: explicit `false` restores the prior PowerShell-only, simple-command-only contract verbatim. When the Python runtime cannot be resolved, the PowerShell floor keeps working and engine-only commands return a named, actionable degradation error instead of a silent wrong-approximation downgrade. Proven by a new engine conformance suite (Linux + Windows CI), a Linux-only differential oracle comparing engine output against real `bash -c` across the supported grammar, and Windows cross-tier integration tests exercising state handoff between the PowerShell and engine tiers.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Self-adaptation (native reflection + learning policy) is now on by default for a fresh session in every autonomy mode (off/safe/balanced/full) — evidence-gated (confidence threshold + `ObservationStore`). The preset lattice is monotonic: increasing autonomy no longer silently disables self-adaptation. All kill switches (`PI_NATIVE_REFLECTION=0`, `learningPolicy.enabled=false`, `AutoLearnSettings.enabled=false`) still disable it in every mode. The raw autonomy-prompt standing-grant authority block stays gated on `autonomy.mode` (`"off"` default unchanged).
|
|
28
|
+
- The `delegate` tool's description and prompt guidelines now vary by wiring mode: an async start/poll contract when worker delegation is asynchronous, the synchronous contract otherwise.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Bundled Python shell-engine source now keeps `BuiltinContext.stderr` optional for direct builtin callers, and its conformance/oracle tests parse the terminal control frame from stderr, matching the runtime transport.
|
|
33
|
+
- Tmux dispatch grants now authorize and charge every launched child process; persistent follow-ups reserve their turn before pane injection to prevent duplicate external prompts after a crash. Process-matrix workers verify a fresh parent-session heartbeat alongside PID liveness, and worktree landing advances the named main ref with compare-and-swap recovery stages rather than merging whichever branch is checked out.
|
|
34
|
+
- `/goal start` no longer double-fires its continuation loop: a single mutex now serializes the idle-driven and manual goal-continuation paths, eliminating both the "Agent is already processing" warning and an unhandled rejection that previously fired on effectively every `/goal start` (the idle auto-continue timer's default 0ms delay meant the manual and idle paths raced on nearly every invocation); the command layer gained try/catch parity and status-line handling for the new skip outcomes.
|
|
35
|
+
- Task/goal state resolution (and the whole goal runtime snapshot: goal state, evidence, worker results, learning decisions) now walks the active session branch's own ancestry instead of scanning every branch's flat entry list — fork/branch-switch no longer leaks another branch's goal or task-steps state.
|
|
36
|
+
- `task_steps`'s `add` action dedupes a retried add of an already-open duplicate step instead of creating a second copy (a legitimate re-add of previously completed work still creates a new step); a completed step with no evidence attached now surfaces a reminder in both the tool response and the injected per-turn context; and a `set`/`update` call that silently demotes another in_progress step to pending now names it in the response.
|
|
37
|
+
- Goal stall detection now keys on satisfied-requirement and verified-evidence counts instead of raw event count, so hollow goal-tool calls that append events without making real progress can no longer defeat the stall guard.
|
|
38
|
+
- Session disposal now durably persists canceled/in-flight worker lane records and a bounded worker result (including changed files) before the dispose cutoff, instead of losing them to an in-memory-only completion; queued (not-yet-started) workers are now visible to the reload quiesce gate from the moment they're enqueued, not just once running.
|
|
39
|
+
- All goal-state free text rendered in the continuation prompt (the user goal, requirement text, blocked reason, evidence summaries, and the open task-steps summary) is now wrapped as untrusted data, matching the existing worker-result boundary — closing a prompt-injection vector reachable via a model's own `add_requirement`/`block_goal` calls.
|
|
40
|
+
- The injected `task_steps_context` block is now GC-managed like `memory_context`, ending unbounded per-turn context growth for sessions with an open checklist; fixed a latent default drift between two independently hand-kept copies of the context-GC defaults (one was missing the `run_toolkit_script` tool, the other was missing `python`/`powershell`) by deriving both from one canonical source.
|
|
41
|
+
- Runaway-loop stops and repeated tool-validation-failure escalations are no longer silent: both log a session entry; runaway stops surface a user-visible warning; validation escalation feeds the model router's existing cheap-route escalation gate. Isolated/reflection child loops inherit the runaway handler when on the foreground model.
|
|
42
|
+
- Atomic, locked writes for model-adaptation, tool-recovery event-log rotation, the learning observation store, and skill-curator usage tracking (new shared `src/core/util/atomic-file.ts`); standing model-adaptation rules now also retire early once evidence shows they stopped reducing recurrence (in addition to the 30-day bound).
|
|
43
|
+
- Reflection's automatic skill promotion runs the same `skill_audit` overlap check the `skillify` tool enforces; a near-duplicate draft is held as a consolidation proposal (learning-audit trail) instead of written.
|
|
44
|
+
- Background/research/worker/reflection/fitness isolated-completion lanes carry a stable namespaced synthetic cache-affinity key (never the real session id), so provider session-affinity / `prompt_cache_key` caching actually hits on repeat lane calls.
|
|
45
|
+
- The session cost guard folds this turn's background/spawned spend (per-turn baseline delta) into the same ceiling as the foreground projection (default action stays "warn"; per-lane caps untouched).
|
|
46
|
+
- Spawned-usage accounting is idempotent: every `addSpawnedUsage` call site supplies a stable, content-derived `reportId`; the model-fitness probe threads its tool-call id as the idempotency token (separate deliberate runs count; retries dedupe).
|
|
47
|
+
- `IsolatedCompletionOptions.cacheRetention` is required at the type level (silent "none" default removed).
|
|
48
|
+
- Ollama/Transformers/prism readiness caching keyed per (server, model) instead of per-server — a second model on a confirmed server is never waved past the installed/residency checks. `LocalRuntimeController.reconcile()`/`dispose()` stops pi-spawned runtimes dropped from the live configuration on reload (detected-only servers never touched), wired into the reload path.
|
|
49
|
+
- Compaction's summarizer honors managed-local readiness/residency for auto and manual `/compact`; also fixed a latent manual-`/compact` bug that dropped the resolved fallback model/failure and could pair a model with the wrong credentials.
|
|
50
|
+
- Compaction pre-check accounts for this turn's own context-GC savings (read-only GC projection; suppression-only — near-full hard trigger unaffected).
|
|
51
|
+
- Reload gate honors all in-flight work via a unified in-process quiesce registry (`reload-blockers.ts`): foreground streaming/compaction, research/worker/model-fitness lanes, context-scout runs, and isolated completions (registered at their single choke point).
|
|
52
|
+
- Context-policy prompt-enforcement defaults now derive from the live context-GC settings (explicit values still win); corrected the stale GC-defaults doc comment (12/2500 → 8/1200).
|
|
53
|
+
- Documented and test-pinned the system-prompt cache-stability invariant (one provider-cached block, rebuilt only on tool-surface change, day-granularity date).
|
|
54
|
+
- A text-protocol ("phone") model that fails to parse the same tool-call signature 3 times in a row is now demoted to native fallback for real: the persisted tool-probe verdict itself is now cleared (was: only the cached protocol was cleared, leaving the phone flag on), which previously caused every subsequent turn to re-run up to 8 inline calibration completions before the user's prompt and, on failure, throw the user's turn away entirely. Ensuring the text protocol for the active model no longer performs blocking inline calibration or throws out of the prompt path under any circumstance; calibration now only ever runs off the hot path, via an explicit `/toolprobe` or the new evidence-gated auto-probe below.
|
|
55
|
+
- A model whose native tool calls keep failing argument validation is now automatically probed and, on graded evidence, escalated: a local/managed model gets an evidence-gated auto-probe (native is tried first; the model is only routed to the text protocol if native genuinely has no working path), and a cloud model escalates to a more capable router tier. This replaces a validation-failure escalation path that previously reused the mutation-blast-radius escape hatch and so treated a read-only tool's repeated validation failures as a no-op.
|
|
56
|
+
- The model router's tier resolution and configured-model lookup now honor a persisted "no working tool-call path" probe verdict for local/managed models: a medium-tier turn falls back to the expensive tier (or the model is skipped) instead of routing to a model already proven unable to call tools.
|
|
57
|
+
- A buffered cheap-tier routed turn that throws now rolls the live in-memory session state back to match what was actually persisted, instead of leaving never-flushed messages permanently stranded in memory.
|
|
58
|
+
- Native tool-call probe trials and text-protocol calibration completions now count toward the turn-scoped cost guard and daily usage totals (were previously untracked — up to ~10 free completions on a model's first probe).
|
|
59
|
+
- A text-protocol model that repeatedly emits unparseable prose instead of a tool call now receives an in-loop corrective reminder (throttled after the first firing), closing a gap where the existing runaway/stall backstop only trips on repeated *parsed* tool-call signatures.
|
|
60
|
+
- A dead tmux session whose job never reached a terminal state now releases its bound managed lane (one report per non-terminal agent, `reasonCode: "tmux_session_orphaned"`) during session-start reconcile, so a goal bound to it stops waiting on a worker that can no longer report back; nothing is killed, only the lane record is released.
|
|
61
|
+
- A goal waiting on a dispatched worker now escalates to the owner (`action: "ask-user"`, reason `worker_wait_timeout`) after a bounded max-wait (default 60 minutes) instead of waiting indefinitely, when the worker is alive but hung past its deadline; every pre-existing caller that omits the new optional wait-clock arguments behaves byte-identically (waits indefinitely, as before).
|
|
62
|
+
- A managed (tmux) worker's terminal usage claim now attributes to its lane's advisory `costUsd`, closing the previously-documented undercount in a goal's `continuationWorkerSpendUsd` for tmux-worker spend.
|
|
63
|
+
- A goal requirement bound to a worker lane that vanished after `/reload` (a genuinely-running managed lane is never snapshotted at dispatch time) is no longer at risk of a duplicate re-dispatch: `dispatch_worker` now refuses re-dispatch when the bound lane's liveness/outcome cannot be determined (`bound_lane_indeterminate`) or is still live (`requirement_already_bound`), preserving the existing binding unchanged; a bound lane with a confirmed terminal outcome still allows a legitimate retry.
|
|
64
|
+
- Hardened worktree synchronization with token-bound lock ownership, lifecycle serialization, exact gated-tip landing, crash-recovery reconciliation, cumulative overlap checks, and typed lane Git/check actions; hard lane workers no longer expose unrestricted `bash`.
|
|
65
|
+
- Fixed concurrent atomic writes by giving each invocation an exclusive same-directory temporary file and cleaning up only its own temporary path.
|
|
66
|
+
|
|
67
|
+
### Performance
|
|
68
|
+
|
|
69
|
+
- Memoized per-message context-audit and token-estimate work within a session (object-identity keyed), byte-identical vs full recompute across compaction and branch-switch boundaries — the full-history rescan no longer reruns on every tool-call round trip.
|
|
70
|
+
- Precompiled and cached minimatch patterns for resource-profile filtering (~8% of startup CPU; refired on every reload/profile switch before).
|
|
71
|
+
- Extension TS transforms cached in a durable `agentDir/cache/jiti-transforms` directory (jiti's content-hash fsCache made explicit; hot-reload module isolation preserved).
|
|
72
|
+
- Lazy highlight.js: `lib/core` + register-on-first-use (~190 eager grammar registrations removed from startup), identical output/alias resolution.
|
|
73
|
+
- Cached resolved `fd`/`rg` system-PATH locations across runs (no `spawnSync` probe per launch; staleness re-probes).
|
|
74
|
+
|
|
75
|
+
### Tests
|
|
76
|
+
|
|
77
|
+
- Pinned the transform-before-cost-guard pipeline-order invariant (regression test fails under a simulated inverted order).
|
|
78
|
+
- Added a permanent regression guard proving goal/task state resolution survives compaction whose cut point lands past the latest snapshot, in-memory and after a session-file reload (investigated as a suspected bug; the repro passed, so no source change was needed).
|
|
79
|
+
|
|
80
|
+
## [0.81.38] - 2026-07-16
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- Updated repository development and CI to Node.js 24 and moved GitHub workflows to Node 24-based action releases while retaining Node.js 22.19 as the published runtime minimum.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- Fixed Windows shell routing for native drive and UNC paths, extension live reloads, managed Ollama executable discovery, and canonical delegated-worker path reports.
|
|
89
|
+
- Fixed Windows full-path find/FFF search parity, cross-platform watcher/runtime test behavior, and SQLite handle cleanup when runtime-index schema validation fails.
|
|
90
|
+
- Fixed the Windows Bash-like router to prefer a usable PowerShell 7 executable, preserve `echo -n`, no-match `grep`, `rm -f`, `mkdir -p`, directory-copy, and hidden-listing semantics, and reject unquoted pathname, brace, and home expansion instead of passing it literally.
|
|
91
|
+
|
|
1
92
|
## [0.81.37] - 2026-07-16
|
|
2
93
|
|
|
3
94
|
### Fixed
|
package/README.md
CHANGED
|
@@ -438,7 +438,7 @@ pi update npm:@foo/pi-tools # update one package
|
|
|
438
438
|
pi config # enable/disable extensions, skills, prompts, themes
|
|
439
439
|
```
|
|
440
440
|
|
|
441
|
-
Packages install to `~/.pi/agent/git/` (git) or `~/.pi/agent/npm/` (npm). Use `-l` for project-local installs (`.pi/git/`, `.pi/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `pi update`, so use `pi install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@
|
|
441
|
+
Packages install to `~/.pi/agent/git/` (git) or `~/.pi/agent/npm/` (npm). Use `-l` for project-local installs (`.pi/git/`, `.pi/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `pi update`, so use `pi install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@24", "--", "npm"]`.
|
|
442
442
|
|
|
443
443
|
Create a package by adding a `pi` key to `package.json`:
|
|
444
444
|
|
|
@@ -20,9 +20,16 @@ Important actions:
|
|
|
20
20
|
- `workspace_plan` — dry-run a tmux session/pane layout.
|
|
21
21
|
- `launch_workspace` — launch panes immediately; pass `dryRun:true` only when a preview is useful.
|
|
22
22
|
- `fire_task` — open provider CLIs in tmux panes, inject prompts, stream pane output through event-driven DONE/BLOCKED watchers, write result files, and wake the parent with a bounded terminal handoff.
|
|
23
|
+
- `send_followup` — re-inject a fresh prompt into an already-live job's pane (default: primary agent; `agentId` targets another) without relaunching. Re-arms the completion watcher for a new turn with a unique per-turn marker pair and reuses the same event-driven handoff.
|
|
24
|
+
- `dismiss` — stop tracking a job (no more re-arming/handoffs) without killing its tmux session; the pane keeps running.
|
|
23
25
|
- `job_status`, `list_jobs`, `set_variable`, `list_variables` — inspect and steer managed jobs.
|
|
24
26
|
- `stop_job`, `stop_session` — dry-run/confirmed tmux cleanup.
|
|
25
27
|
- `notify`, `set_status`, `clear_status` — tmux UI/status metadata.
|
|
28
|
+
- `grant_dispatch`, `revoke_grant` — create/end a standing approval grant that lets `fire_task`/`send_followup` dispatch unattended within its bounds (agent, budget, optional goal/tool/path scope). See Safety below.
|
|
29
|
+
|
|
30
|
+
At session start, live tmux sessions are reconciled against this session's own job records: a session
|
|
31
|
+
that vanished while its job was not yet terminal is marked orphaned (informational only, nothing is
|
|
32
|
+
killed); a session that is still alive with a pending turn has its watcher re-armed.
|
|
26
33
|
|
|
27
34
|
## Built-in team templates
|
|
28
35
|
|
|
@@ -60,7 +67,17 @@ tmux_agent_manager({
|
|
|
60
67
|
|
|
61
68
|
## Safety
|
|
62
69
|
|
|
63
|
-
|
|
70
|
+
A real (non-`dryRun`) `fire_task`/`send_followup` launch is approval-gated: it requires either a
|
|
71
|
+
standing grant (`grant_dispatch` — interactively confirmed, or authorized via the `--allow-tmux-dispatch`
|
|
72
|
+
flag when no UI is attached) or a one-shot interactive approval. With neither available, the launch is
|
|
73
|
+
refused, never silent. A grant-covered `pi` child launches with a restricted profile (`--tools`/
|
|
74
|
+
`--resource-profile` or `--no-extensions --no-skills`, plus a scoped `--append-system-prompt` naming the
|
|
75
|
+
grant and its hard stops) pushed into the child's own launch configuration — this is not an in-process
|
|
76
|
+
sandbox, and non-`pi` agents are bounded only at the launch layer; their internal tool-loop behavior is
|
|
77
|
+
that CLI's own responsibility. Grant budget (`maxLaunches`, expiry) is enforced; any advisory USD figure
|
|
78
|
+
or self-reported worker usage is a claim to review, never a hard cross-process cap.
|
|
79
|
+
|
|
80
|
+
Use `dryRun:true` when the task or provider choice is still ambiguous. Stop actions are destructive to running pane work, so they remain previews by default and require `confirm:"yes-tmux-stop"` for real cleanup.
|
|
64
81
|
|
|
65
82
|
The tool refuses existing tmux sessions. Existing job directories are refused unless `force:true`, which archives the old job directory under `~/.pi/agent/work/background/tmux-agent-manager/state/archives` before launching.
|
|
66
83
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Usage } from "@caupulican/pi-ai";
|
|
2
|
+
/**
|
|
3
|
+
* The STANDING GRANT for approval-gated tmux dispatch (owner authorizes once via `grant_dispatch`;
|
|
4
|
+
* the extension then dispatches unattended WITHIN the grant's bounds). Pure data + pure decode/decision
|
|
5
|
+
* logic only — no session/tmux/filesystem access, so every function here is directly unit-testable.
|
|
6
|
+
* The extension (index.ts) owns reading/writing session custom entries and calls into this module for
|
|
7
|
+
* decoding and authorization decisions.
|
|
8
|
+
*
|
|
9
|
+
* HONEST TRUST BOUNDARY: this grant bounds WHO may be launched, HOW OFTEN, and WITH WHAT launch-time
|
|
10
|
+
* envelope (pushed into the child's own CLI flags) — it never claims to gate the child's in-process
|
|
11
|
+
* tool loop, which is a separate OS process outside this runtime's `beforeToolCall` machinery.
|
|
12
|
+
*/
|
|
13
|
+
export type Provider = "pi" | "codex" | "agy" | "claude" | "opencode" | "custom";
|
|
14
|
+
export declare const GRANT_CUSTOM_TYPE = "tmux-dispatch-grant";
|
|
15
|
+
export declare const GRANT_USAGE_CUSTOM_TYPE = "tmux-dispatch-grant-usage";
|
|
16
|
+
export interface TmuxDispatchGrantEnvelope {
|
|
17
|
+
allowedTools?: string[];
|
|
18
|
+
resourceProfile?: string;
|
|
19
|
+
writePaths?: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface TmuxDispatchGrantBudget {
|
|
22
|
+
maxLaunches: number;
|
|
23
|
+
maxWallClockMs?: number;
|
|
24
|
+
maxUsdAdvisory?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface TmuxDispatchGrant {
|
|
27
|
+
grantId: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
agent: Provider;
|
|
30
|
+
scope: {
|
|
31
|
+
goalId?: string;
|
|
32
|
+
};
|
|
33
|
+
envelope: TmuxDispatchGrantEnvelope;
|
|
34
|
+
budget: TmuxDispatchGrantBudget;
|
|
35
|
+
expiresAt?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Appended by `revoke_grant`. Shares `GRANT_CUSTOM_TYPE` so the SAME latest-on-branch read that
|
|
38
|
+
* resolves an active grant also sees a revocation and stops treating any older grant beneath it as
|
|
39
|
+
* active — a tombstone is a hard stop, never skipped over to resurrect an earlier grant. */
|
|
40
|
+
export interface TmuxDispatchGrantTombstone {
|
|
41
|
+
tombstone: true;
|
|
42
|
+
grantId: string;
|
|
43
|
+
revokedAt: string;
|
|
44
|
+
}
|
|
45
|
+
export interface TmuxDispatchGrantUsage {
|
|
46
|
+
grantId: string;
|
|
47
|
+
jobId: string;
|
|
48
|
+
at: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function isTmuxDispatchGrantTombstone(value: unknown): value is TmuxDispatchGrantTombstone;
|
|
51
|
+
export declare function isTmuxDispatchGrant(value: unknown): value is TmuxDispatchGrant;
|
|
52
|
+
export declare function isTmuxDispatchGrantUsage(value: unknown): value is TmuxDispatchGrantUsage;
|
|
53
|
+
export declare function makeGrantId(): string;
|
|
54
|
+
export interface GrantCoverageRequest {
|
|
55
|
+
agent: Provider;
|
|
56
|
+
goalId?: string;
|
|
57
|
+
nowIso?: string;
|
|
58
|
+
}
|
|
59
|
+
/** Whether `grant` authorizes a launch for `request`: same agent, goal-scope match (an UNSCOPED grant
|
|
60
|
+
* covers any goal including none; a goal-SCOPED grant covers ONLY that exact goalId), and the grant is
|
|
61
|
+
* not past `expiresAt`. Budget (maxLaunches) is checked separately via {@link isGrantBudgetExhausted}
|
|
62
|
+
* because counting usages requires the caller's session-entry access. */
|
|
63
|
+
export declare function grantCovers(grant: TmuxDispatchGrant, request: GrantCoverageRequest): boolean;
|
|
64
|
+
/** Count spend entries for `grantId` among already-decoded custom-entry payloads (the caller walks the
|
|
65
|
+
* session branch and passes the raw `data` values — this stays pure/session-agnostic). */
|
|
66
|
+
export declare function countGrantUsages(grantId: string, usageEntries: readonly unknown[]): number;
|
|
67
|
+
export declare function isGrantBudgetExhausted(grant: TmuxDispatchGrant, usedCount: number): boolean;
|
|
68
|
+
export declare function buildGrantUsageEntry(grantId: string, jobId: string, nowIso?: string): TmuxDispatchGrantUsage;
|
|
69
|
+
export declare function buildTombstone(grantId: string, nowIso?: string): TmuxDispatchGrantTombstone;
|
|
70
|
+
export interface GrantDispatchParams {
|
|
71
|
+
agent: Provider;
|
|
72
|
+
goalId?: string;
|
|
73
|
+
allowedTools?: string[];
|
|
74
|
+
resourceProfile?: string;
|
|
75
|
+
writePaths?: string[];
|
|
76
|
+
maxLaunches: number;
|
|
77
|
+
expiresInMinutes?: number;
|
|
78
|
+
maxUsdAdvisory?: number;
|
|
79
|
+
}
|
|
80
|
+
/** Build a new grant record from `grant_dispatch` params. Pure — the caller (index.ts) owns the
|
|
81
|
+
* approval gate (ui.confirm / opt-in flag) and the `pi.appendEntry` persistence; this only shapes the
|
|
82
|
+
* data, so it stays independently testable and never itself decides whether creation is authorized. */
|
|
83
|
+
export declare function buildGrant(params: GrantDispatchParams, nowIso?: string): TmuxDispatchGrant;
|
|
84
|
+
export declare function describeGrant(grant: TmuxDispatchGrant): string;
|
|
85
|
+
export declare const DEFAULT_READ_BIASED_TOOLS: readonly string[];
|
|
86
|
+
export interface LaunchProfileSource {
|
|
87
|
+
/** Human-readable identity for the scoped system prompt (e.g. "grant <id>" or a one-shot label). */
|
|
88
|
+
identity: string;
|
|
89
|
+
allowedTools?: string[];
|
|
90
|
+
resourceProfile?: string;
|
|
91
|
+
writePaths?: string[];
|
|
92
|
+
/**
|
|
93
|
+
* Worktree-sync lane key this launch is bound to (per-agent -- see `applyLaunchProfile`'s caller,
|
|
94
|
+
* which builds a per-agent `LaunchProfileSource` only when that agent carries `worktreeLane`).
|
|
95
|
+
* When present, `buildLaunchProfileFlags` appends `--worktree-lane <key>` (the same CLI flag
|
|
96
|
+
* `src/cli/args.ts` already exposes, sugar over the `PI_WORKTREE_LANE` env contract) and
|
|
97
|
+
* `buildScopedSystemPrompt` appends one lane-doctrine sentence.
|
|
98
|
+
*/
|
|
99
|
+
worktreeLane?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Process-matrix parent identity for this launch (see `core/process-matrix/runtime.ts`): the
|
|
102
|
+
* dispatching process's own pid/sessionId. When present, `buildLaunchProfileFlags` appends
|
|
103
|
+
* `--parent-pid`/`--parent-session` (sugar over `PI_PARENT_PID`/`PI_PARENT_SESSION`) so the
|
|
104
|
+
* child self-registers as a worker of this session and winds down gracefully if it disappears.
|
|
105
|
+
*/
|
|
106
|
+
parentPid?: number;
|
|
107
|
+
parentSession?: string;
|
|
108
|
+
}
|
|
109
|
+
/** A grant-covered launch derives its profile from the grant's envelope. */
|
|
110
|
+
export declare function launchProfileSourceFromGrant(grant: TmuxDispatchGrant): LaunchProfileSource;
|
|
111
|
+
/** A one-shot interactively-approved launch (no standing grant) still gets the conservative default
|
|
112
|
+
* profile — approving a single launch is not the same as defining an unrestricted envelope. */
|
|
113
|
+
export declare const ONE_SHOT_LAUNCH_PROFILE_SOURCE: LaunchProfileSource;
|
|
114
|
+
export interface LaunchProfileFlag {
|
|
115
|
+
flag: string;
|
|
116
|
+
value?: string;
|
|
117
|
+
}
|
|
118
|
+
/** Build the CLI flags to append to a child `pi` invocation so the grant's envelope lives in the
|
|
119
|
+
* child's OWN launch config (verified flag names: --tools, --resource-profile, --no-extensions,
|
|
120
|
+
* --no-skills, --append-system-prompt). Never includes --no-approve: the child must keep gating its
|
|
121
|
+
* own hard stops. */
|
|
122
|
+
export declare function buildLaunchProfileFlags(source: LaunchProfileSource): LaunchProfileFlag[];
|
|
123
|
+
export declare function buildScopedSystemPrompt(source: LaunchProfileSource): string;
|
|
124
|
+
/** Permissively decode a worker-reported usage CLAIM (a cooperative worker may write this to a
|
|
125
|
+
* well-known sibling file before printing its completion marker). Read-defensive: missing/malformed
|
|
126
|
+
* numeric fields default to 0 rather than throwing — this is untrusted, OPTIONAL, advisory input, never
|
|
127
|
+
* an authoritative billing record. Returns undefined only when `raw` isn't even an object, so the
|
|
128
|
+
* caller can distinguish "no claim offered" (skip reporting) from "a claim was offered" (report it,
|
|
129
|
+
* however partial). */
|
|
130
|
+
export declare function decodeTmuxWorkerUsageClaim(raw: unknown): Usage | undefined;
|
|
131
|
+
//# sourceMappingURL=dispatch-grant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch-grant.d.ts","sourceRoot":"","sources":["../../../../src/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts"],"names":[],"mappings":"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","sourcesContent":["import * as crypto from \"node:crypto\";\nimport type { Usage } from \"@caupulican/pi-ai\";\n\n/**\n * The STANDING GRANT for approval-gated tmux dispatch (owner authorizes once via `grant_dispatch`;\n * the extension then dispatches unattended WITHIN the grant's bounds). Pure data + pure decode/decision\n * logic only — no session/tmux/filesystem access, so every function here is directly unit-testable.\n * The extension (index.ts) owns reading/writing session custom entries and calls into this module for\n * decoding and authorization decisions.\n *\n * HONEST TRUST BOUNDARY: this grant bounds WHO may be launched, HOW OFTEN, and WITH WHAT launch-time\n * envelope (pushed into the child's own CLI flags) — it never claims to gate the child's in-process\n * tool loop, which is a separate OS process outside this runtime's `beforeToolCall` machinery.\n */\n\nexport type Provider = \"pi\" | \"codex\" | \"agy\" | \"claude\" | \"opencode\" | \"custom\";\n\nexport const GRANT_CUSTOM_TYPE = \"tmux-dispatch-grant\";\nexport const GRANT_USAGE_CUSTOM_TYPE = \"tmux-dispatch-grant-usage\";\n\nexport interface TmuxDispatchGrantEnvelope {\n\tallowedTools?: string[];\n\tresourceProfile?: string;\n\twritePaths?: string[];\n}\n\nexport interface TmuxDispatchGrantBudget {\n\tmaxLaunches: number;\n\tmaxWallClockMs?: number;\n\tmaxUsdAdvisory?: number;\n}\n\nexport interface TmuxDispatchGrant {\n\tgrantId: string;\n\tcreatedAt: string;\n\tagent: Provider;\n\tscope: { goalId?: string };\n\tenvelope: TmuxDispatchGrantEnvelope;\n\tbudget: TmuxDispatchGrantBudget;\n\texpiresAt?: string;\n}\n\n/** Appended by `revoke_grant`. Shares `GRANT_CUSTOM_TYPE` so the SAME latest-on-branch read that\n * resolves an active grant also sees a revocation and stops treating any older grant beneath it as\n * active — a tombstone is a hard stop, never skipped over to resurrect an earlier grant. */\nexport interface TmuxDispatchGrantTombstone {\n\ttombstone: true;\n\tgrantId: string;\n\trevokedAt: string;\n}\n\nexport interface TmuxDispatchGrantUsage {\n\tgrantId: string;\n\tjobId: string;\n\tat: string;\n}\n\nconst PROVIDERS: readonly Provider[] = [\"pi\", \"codex\", \"agy\", \"claude\", \"opencode\", \"custom\"];\n\nfunction isProvider(value: unknown): value is Provider {\n\treturn typeof value === \"string\" && (PROVIDERS as readonly string[]).includes(value);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function isTmuxDispatchGrantTombstone(value: unknown): value is TmuxDispatchGrantTombstone {\n\tif (!isPlainRecord(value)) return false;\n\treturn value.tombstone === true && typeof value.grantId === \"string\" && typeof value.revokedAt === \"string\";\n}\n\nexport function isTmuxDispatchGrant(value: unknown): value is TmuxDispatchGrant {\n\tif (!isPlainRecord(value) || value.tombstone === true) return false;\n\tif (typeof value.grantId !== \"string\" || typeof value.createdAt !== \"string\" || !isProvider(value.agent))\n\t\treturn false;\n\tif (!isPlainRecord(value.scope) || !isPlainRecord(value.envelope) || !isPlainRecord(value.budget)) return false;\n\treturn typeof value.budget.maxLaunches === \"number\" && Number.isFinite(value.budget.maxLaunches);\n}\n\nexport function isTmuxDispatchGrantUsage(value: unknown): value is TmuxDispatchGrantUsage {\n\tif (!isPlainRecord(value)) return false;\n\treturn typeof value.grantId === \"string\" && typeof value.jobId === \"string\" && typeof value.at === \"string\";\n}\n\nexport function makeGrantId(): string {\n\treturn `grant-${Date.now().toString(36)}-${crypto.randomBytes(4).toString(\"hex\")}`;\n}\n\nexport interface GrantCoverageRequest {\n\tagent: Provider;\n\tgoalId?: string;\n\tnowIso?: string;\n}\n\n/** Whether `grant` authorizes a launch for `request`: same agent, goal-scope match (an UNSCOPED grant\n * covers any goal including none; a goal-SCOPED grant covers ONLY that exact goalId), and the grant is\n * not past `expiresAt`. Budget (maxLaunches) is checked separately via {@link isGrantBudgetExhausted}\n * because counting usages requires the caller's session-entry access. */\nexport function grantCovers(grant: TmuxDispatchGrant, request: GrantCoverageRequest): boolean {\n\tif (grant.agent !== request.agent) return false;\n\tif (grant.scope.goalId && grant.scope.goalId !== request.goalId) return false;\n\tif (grant.expiresAt) {\n\t\tconst now = Date.parse(request.nowIso ?? new Date().toISOString());\n\t\tconst expiry = Date.parse(grant.expiresAt);\n\t\tif (Number.isFinite(expiry) && Number.isFinite(now) && now >= expiry) return false;\n\t}\n\treturn true;\n}\n\n/** Count spend entries for `grantId` among already-decoded custom-entry payloads (the caller walks the\n * session branch and passes the raw `data` values — this stays pure/session-agnostic). */\nexport function countGrantUsages(grantId: string, usageEntries: readonly unknown[]): number {\n\tlet count = 0;\n\tfor (const entry of usageEntries) if (isTmuxDispatchGrantUsage(entry) && entry.grantId === grantId) count++;\n\treturn count;\n}\n\nexport function isGrantBudgetExhausted(grant: TmuxDispatchGrant, usedCount: number): boolean {\n\treturn usedCount >= grant.budget.maxLaunches;\n}\n\nexport function buildGrantUsageEntry(\n\tgrantId: string,\n\tjobId: string,\n\tnowIso = new Date().toISOString(),\n): TmuxDispatchGrantUsage {\n\treturn { grantId, jobId, at: nowIso };\n}\n\nexport function buildTombstone(grantId: string, nowIso = new Date().toISOString()): TmuxDispatchGrantTombstone {\n\treturn { tombstone: true, grantId, revokedAt: nowIso };\n}\n\nexport interface GrantDispatchParams {\n\tagent: Provider;\n\tgoalId?: string;\n\tallowedTools?: string[];\n\tresourceProfile?: string;\n\twritePaths?: string[];\n\tmaxLaunches: number;\n\texpiresInMinutes?: number;\n\tmaxUsdAdvisory?: number;\n}\n\n/** Build a new grant record from `grant_dispatch` params. Pure — the caller (index.ts) owns the\n * approval gate (ui.confirm / opt-in flag) and the `pi.appendEntry` persistence; this only shapes the\n * data, so it stays independently testable and never itself decides whether creation is authorized. */\nexport function buildGrant(params: GrantDispatchParams, nowIso = new Date().toISOString()): TmuxDispatchGrant {\n\tif (!Number.isFinite(params.maxLaunches) || params.maxLaunches < 1)\n\t\tthrow new Error(\"grant_dispatch requires maxLaunches >= 1\");\n\tconst expiresAt =\n\t\tparams.expiresInMinutes !== undefined\n\t\t\t? new Date(Date.parse(nowIso) + Math.max(1, params.expiresInMinutes) * 60_000).toISOString()\n\t\t\t: undefined;\n\treturn {\n\t\tgrantId: makeGrantId(),\n\t\tcreatedAt: nowIso,\n\t\tagent: params.agent,\n\t\tscope: { goalId: params.goalId },\n\t\tenvelope: {\n\t\t\tallowedTools: params.allowedTools,\n\t\t\tresourceProfile: params.resourceProfile,\n\t\t\twritePaths: params.writePaths,\n\t\t},\n\t\tbudget: {\n\t\t\tmaxLaunches: Math.trunc(params.maxLaunches),\n\t\t\tmaxUsdAdvisory: params.maxUsdAdvisory,\n\t\t},\n\t\texpiresAt,\n\t};\n}\n\nexport function describeGrant(grant: TmuxDispatchGrant): string {\n\treturn [\n\t\t`Grant id: ${grant.grantId}`,\n\t\t`Agent: ${grant.agent}`,\n\t\tgrant.scope.goalId ? `Goal scope: ${grant.scope.goalId}` : \"Goal scope: none (covers any goal)\",\n\t\t`Max launches: ${grant.budget.maxLaunches}`,\n\t\tgrant.expiresAt ? `Expires: ${grant.expiresAt}` : \"Expires: never (until revoke_grant)\",\n\t\tgrant.envelope.allowedTools?.length\n\t\t\t? `Allowed tools: ${grant.envelope.allowedTools.join(\", \")}`\n\t\t\t: `Allowed tools: read-biased safe default (${DEFAULT_READ_BIASED_TOOLS.join(\", \")})`,\n\t\tgrant.envelope.resourceProfile\n\t\t\t? `Resource profile: ${grant.envelope.resourceProfile}`\n\t\t\t: \"Resource profile: none (child launches with --no-extensions --no-skills)\",\n\t\tgrant.envelope.writePaths?.length\n\t\t\t? `Write paths: ${grant.envelope.writePaths.join(\", \")}`\n\t\t\t: \"Write paths: none declared\",\n\t\tgrant.budget.maxUsdAdvisory !== undefined\n\t\t\t? `Advisory USD cap: ${grant.budget.maxUsdAdvisory} (not enforced across the process boundary — advisory only)`\n\t\t\t: undefined,\n\t]\n\t\t.filter((line): line is string => Boolean(line))\n\t\t.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Launch-profile flags: the REAL lever — pushed into the child `pi` CLI's own config.\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_READ_BIASED_TOOLS: readonly string[] = [\"read\", \"grep\", \"find\", \"ls\"];\n\nexport interface LaunchProfileSource {\n\t/** Human-readable identity for the scoped system prompt (e.g. \"grant <id>\" or a one-shot label). */\n\tidentity: string;\n\tallowedTools?: string[];\n\tresourceProfile?: string;\n\twritePaths?: string[];\n\t/**\n\t * Worktree-sync lane key this launch is bound to (per-agent -- see `applyLaunchProfile`'s caller,\n\t * which builds a per-agent `LaunchProfileSource` only when that agent carries `worktreeLane`).\n\t * When present, `buildLaunchProfileFlags` appends `--worktree-lane <key>` (the same CLI flag\n\t * `src/cli/args.ts` already exposes, sugar over the `PI_WORKTREE_LANE` env contract) and\n\t * `buildScopedSystemPrompt` appends one lane-doctrine sentence.\n\t */\n\tworktreeLane?: string;\n\t/**\n\t * Process-matrix parent identity for this launch (see `core/process-matrix/runtime.ts`): the\n\t * dispatching process's own pid/sessionId. When present, `buildLaunchProfileFlags` appends\n\t * `--parent-pid`/`--parent-session` (sugar over `PI_PARENT_PID`/`PI_PARENT_SESSION`) so the\n\t * child self-registers as a worker of this session and winds down gracefully if it disappears.\n\t */\n\tparentPid?: number;\n\tparentSession?: string;\n}\n\n/** A grant-covered launch derives its profile from the grant's envelope. */\nexport function launchProfileSourceFromGrant(grant: TmuxDispatchGrant): LaunchProfileSource {\n\treturn {\n\t\tidentity: `grant ${grant.grantId}`,\n\t\tallowedTools: grant.envelope.allowedTools,\n\t\tresourceProfile: grant.envelope.resourceProfile,\n\t\twritePaths: grant.envelope.writePaths,\n\t};\n}\n\n/** A one-shot interactively-approved launch (no standing grant) still gets the conservative default\n * profile — approving a single launch is not the same as defining an unrestricted envelope. */\nexport const ONE_SHOT_LAUNCH_PROFILE_SOURCE: LaunchProfileSource = {\n\tidentity: \"a one-shot owner-approved tmux dispatch (no standing grant)\",\n};\n\nexport interface LaunchProfileFlag {\n\tflag: string;\n\tvalue?: string;\n}\n\n/** Build the CLI flags to append to a child `pi` invocation so the grant's envelope lives in the\n * child's OWN launch config (verified flag names: --tools, --resource-profile, --no-extensions,\n * --no-skills, --append-system-prompt). Never includes --no-approve: the child must keep gating its\n * own hard stops. */\nexport function buildLaunchProfileFlags(source: LaunchProfileSource): LaunchProfileFlag[] {\n\tconst tools = source.allowedTools?.length ? source.allowedTools : DEFAULT_READ_BIASED_TOOLS;\n\tconst flags: LaunchProfileFlag[] = [{ flag: \"--tools\", value: tools.join(\",\") }];\n\tif (source.resourceProfile) flags.push({ flag: \"--resource-profile\", value: source.resourceProfile });\n\telse flags.push({ flag: \"--no-extensions\" }, { flag: \"--no-skills\" });\n\tif (source.worktreeLane) flags.push({ flag: \"--worktree-lane\", value: source.worktreeLane });\n\tif (source.parentPid !== undefined) flags.push({ flag: \"--parent-pid\", value: String(source.parentPid) });\n\tif (source.parentSession) flags.push({ flag: \"--parent-session\", value: source.parentSession });\n\tflags.push({ flag: \"--append-system-prompt\", value: buildScopedSystemPrompt(source) });\n\treturn flags;\n}\n\nexport function buildScopedSystemPrompt(source: LaunchProfileSource): string {\n\tconst paths = source.writePaths?.length ? source.writePaths.join(\", \") : \"no additional paths granted\";\n\tconst sentences = [\n\t\t`You are a tmux worker dispatched under ${source.identity}.`,\n\t\t`Stay within these write paths: ${paths}.`,\n\t\t\"Hard stops — publishing/npm, git push, tagging a release, changing credentials/auth, or destructive deletion — require returning BLOCKED for owner approval. Never self-approve a hard stop.\",\n\t];\n\tif (source.worktreeLane) {\n\t\tsentences.push(\n\t\t\t`You are bound to worktree-sync lane '${source.worktreeLane}': work only inside this lane's own worktree, integrate exclusively via worktree_sync land, and never touch main directly.`,\n\t\t);\n\t}\n\treturn sentences.join(\" \");\n}\n\n// ---------------------------------------------------------------------------\n// Advisory worker usage claim: budget stays advisory across the process boundary.\n// ---------------------------------------------------------------------------\n\n/** Permissively decode a worker-reported usage CLAIM (a cooperative worker may write this to a\n * well-known sibling file before printing its completion marker). Read-defensive: missing/malformed\n * numeric fields default to 0 rather than throwing — this is untrusted, OPTIONAL, advisory input, never\n * an authoritative billing record. Returns undefined only when `raw` isn't even an object, so the\n * caller can distinguish \"no claim offered\" (skip reporting) from \"a claim was offered\" (report it,\n * however partial). */\nexport function decodeTmuxWorkerUsageClaim(raw: unknown): Usage | undefined {\n\tif (!isPlainRecord(raw)) return undefined;\n\tconst num = (value: unknown): number => (typeof value === \"number\" && Number.isFinite(value) ? value : 0);\n\tconst cost = isPlainRecord(raw.cost) ? raw.cost : {};\n\treturn {\n\t\tinput: num(raw.input),\n\t\toutput: num(raw.output),\n\t\tcacheRead: num(raw.cacheRead),\n\t\tcacheWrite: num(raw.cacheWrite),\n\t\ttotalTokens: num(raw.totalTokens),\n\t\tcost: {\n\t\t\tinput: num(cost.input),\n\t\t\toutput: num(cost.output),\n\t\t\tcacheRead: num(cost.cacheRead),\n\t\t\tcacheWrite: num(cost.cacheWrite),\n\t\t\ttotal: num(cost.total),\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
export const GRANT_CUSTOM_TYPE = "tmux-dispatch-grant";
|
|
3
|
+
export const GRANT_USAGE_CUSTOM_TYPE = "tmux-dispatch-grant-usage";
|
|
4
|
+
const PROVIDERS = ["pi", "codex", "agy", "claude", "opencode", "custom"];
|
|
5
|
+
function isProvider(value) {
|
|
6
|
+
return typeof value === "string" && PROVIDERS.includes(value);
|
|
7
|
+
}
|
|
8
|
+
function isPlainRecord(value) {
|
|
9
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
export function isTmuxDispatchGrantTombstone(value) {
|
|
12
|
+
if (!isPlainRecord(value))
|
|
13
|
+
return false;
|
|
14
|
+
return value.tombstone === true && typeof value.grantId === "string" && typeof value.revokedAt === "string";
|
|
15
|
+
}
|
|
16
|
+
export function isTmuxDispatchGrant(value) {
|
|
17
|
+
if (!isPlainRecord(value) || value.tombstone === true)
|
|
18
|
+
return false;
|
|
19
|
+
if (typeof value.grantId !== "string" || typeof value.createdAt !== "string" || !isProvider(value.agent))
|
|
20
|
+
return false;
|
|
21
|
+
if (!isPlainRecord(value.scope) || !isPlainRecord(value.envelope) || !isPlainRecord(value.budget))
|
|
22
|
+
return false;
|
|
23
|
+
return typeof value.budget.maxLaunches === "number" && Number.isFinite(value.budget.maxLaunches);
|
|
24
|
+
}
|
|
25
|
+
export function isTmuxDispatchGrantUsage(value) {
|
|
26
|
+
if (!isPlainRecord(value))
|
|
27
|
+
return false;
|
|
28
|
+
return typeof value.grantId === "string" && typeof value.jobId === "string" && typeof value.at === "string";
|
|
29
|
+
}
|
|
30
|
+
export function makeGrantId() {
|
|
31
|
+
return `grant-${Date.now().toString(36)}-${crypto.randomBytes(4).toString("hex")}`;
|
|
32
|
+
}
|
|
33
|
+
/** Whether `grant` authorizes a launch for `request`: same agent, goal-scope match (an UNSCOPED grant
|
|
34
|
+
* covers any goal including none; a goal-SCOPED grant covers ONLY that exact goalId), and the grant is
|
|
35
|
+
* not past `expiresAt`. Budget (maxLaunches) is checked separately via {@link isGrantBudgetExhausted}
|
|
36
|
+
* because counting usages requires the caller's session-entry access. */
|
|
37
|
+
export function grantCovers(grant, request) {
|
|
38
|
+
if (grant.agent !== request.agent)
|
|
39
|
+
return false;
|
|
40
|
+
if (grant.scope.goalId && grant.scope.goalId !== request.goalId)
|
|
41
|
+
return false;
|
|
42
|
+
if (grant.expiresAt) {
|
|
43
|
+
const now = Date.parse(request.nowIso ?? new Date().toISOString());
|
|
44
|
+
const expiry = Date.parse(grant.expiresAt);
|
|
45
|
+
if (Number.isFinite(expiry) && Number.isFinite(now) && now >= expiry)
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
/** Count spend entries for `grantId` among already-decoded custom-entry payloads (the caller walks the
|
|
51
|
+
* session branch and passes the raw `data` values — this stays pure/session-agnostic). */
|
|
52
|
+
export function countGrantUsages(grantId, usageEntries) {
|
|
53
|
+
let count = 0;
|
|
54
|
+
for (const entry of usageEntries)
|
|
55
|
+
if (isTmuxDispatchGrantUsage(entry) && entry.grantId === grantId)
|
|
56
|
+
count++;
|
|
57
|
+
return count;
|
|
58
|
+
}
|
|
59
|
+
export function isGrantBudgetExhausted(grant, usedCount) {
|
|
60
|
+
return usedCount >= grant.budget.maxLaunches;
|
|
61
|
+
}
|
|
62
|
+
export function buildGrantUsageEntry(grantId, jobId, nowIso = new Date().toISOString()) {
|
|
63
|
+
return { grantId, jobId, at: nowIso };
|
|
64
|
+
}
|
|
65
|
+
export function buildTombstone(grantId, nowIso = new Date().toISOString()) {
|
|
66
|
+
return { tombstone: true, grantId, revokedAt: nowIso };
|
|
67
|
+
}
|
|
68
|
+
/** Build a new grant record from `grant_dispatch` params. Pure — the caller (index.ts) owns the
|
|
69
|
+
* approval gate (ui.confirm / opt-in flag) and the `pi.appendEntry` persistence; this only shapes the
|
|
70
|
+
* data, so it stays independently testable and never itself decides whether creation is authorized. */
|
|
71
|
+
export function buildGrant(params, nowIso = new Date().toISOString()) {
|
|
72
|
+
if (!Number.isFinite(params.maxLaunches) || params.maxLaunches < 1)
|
|
73
|
+
throw new Error("grant_dispatch requires maxLaunches >= 1");
|
|
74
|
+
const expiresAt = params.expiresInMinutes !== undefined
|
|
75
|
+
? new Date(Date.parse(nowIso) + Math.max(1, params.expiresInMinutes) * 60_000).toISOString()
|
|
76
|
+
: undefined;
|
|
77
|
+
return {
|
|
78
|
+
grantId: makeGrantId(),
|
|
79
|
+
createdAt: nowIso,
|
|
80
|
+
agent: params.agent,
|
|
81
|
+
scope: { goalId: params.goalId },
|
|
82
|
+
envelope: {
|
|
83
|
+
allowedTools: params.allowedTools,
|
|
84
|
+
resourceProfile: params.resourceProfile,
|
|
85
|
+
writePaths: params.writePaths,
|
|
86
|
+
},
|
|
87
|
+
budget: {
|
|
88
|
+
maxLaunches: Math.trunc(params.maxLaunches),
|
|
89
|
+
maxUsdAdvisory: params.maxUsdAdvisory,
|
|
90
|
+
},
|
|
91
|
+
expiresAt,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function describeGrant(grant) {
|
|
95
|
+
return [
|
|
96
|
+
`Grant id: ${grant.grantId}`,
|
|
97
|
+
`Agent: ${grant.agent}`,
|
|
98
|
+
grant.scope.goalId ? `Goal scope: ${grant.scope.goalId}` : "Goal scope: none (covers any goal)",
|
|
99
|
+
`Max launches: ${grant.budget.maxLaunches}`,
|
|
100
|
+
grant.expiresAt ? `Expires: ${grant.expiresAt}` : "Expires: never (until revoke_grant)",
|
|
101
|
+
grant.envelope.allowedTools?.length
|
|
102
|
+
? `Allowed tools: ${grant.envelope.allowedTools.join(", ")}`
|
|
103
|
+
: `Allowed tools: read-biased safe default (${DEFAULT_READ_BIASED_TOOLS.join(", ")})`,
|
|
104
|
+
grant.envelope.resourceProfile
|
|
105
|
+
? `Resource profile: ${grant.envelope.resourceProfile}`
|
|
106
|
+
: "Resource profile: none (child launches with --no-extensions --no-skills)",
|
|
107
|
+
grant.envelope.writePaths?.length
|
|
108
|
+
? `Write paths: ${grant.envelope.writePaths.join(", ")}`
|
|
109
|
+
: "Write paths: none declared",
|
|
110
|
+
grant.budget.maxUsdAdvisory !== undefined
|
|
111
|
+
? `Advisory USD cap: ${grant.budget.maxUsdAdvisory} (not enforced across the process boundary — advisory only)`
|
|
112
|
+
: undefined,
|
|
113
|
+
]
|
|
114
|
+
.filter((line) => Boolean(line))
|
|
115
|
+
.join("\n");
|
|
116
|
+
}
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Launch-profile flags: the REAL lever — pushed into the child `pi` CLI's own config.
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
export const DEFAULT_READ_BIASED_TOOLS = ["read", "grep", "find", "ls"];
|
|
121
|
+
/** A grant-covered launch derives its profile from the grant's envelope. */
|
|
122
|
+
export function launchProfileSourceFromGrant(grant) {
|
|
123
|
+
return {
|
|
124
|
+
identity: `grant ${grant.grantId}`,
|
|
125
|
+
allowedTools: grant.envelope.allowedTools,
|
|
126
|
+
resourceProfile: grant.envelope.resourceProfile,
|
|
127
|
+
writePaths: grant.envelope.writePaths,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** A one-shot interactively-approved launch (no standing grant) still gets the conservative default
|
|
131
|
+
* profile — approving a single launch is not the same as defining an unrestricted envelope. */
|
|
132
|
+
export const ONE_SHOT_LAUNCH_PROFILE_SOURCE = {
|
|
133
|
+
identity: "a one-shot owner-approved tmux dispatch (no standing grant)",
|
|
134
|
+
};
|
|
135
|
+
/** Build the CLI flags to append to a child `pi` invocation so the grant's envelope lives in the
|
|
136
|
+
* child's OWN launch config (verified flag names: --tools, --resource-profile, --no-extensions,
|
|
137
|
+
* --no-skills, --append-system-prompt). Never includes --no-approve: the child must keep gating its
|
|
138
|
+
* own hard stops. */
|
|
139
|
+
export function buildLaunchProfileFlags(source) {
|
|
140
|
+
const tools = source.allowedTools?.length ? source.allowedTools : DEFAULT_READ_BIASED_TOOLS;
|
|
141
|
+
const flags = [{ flag: "--tools", value: tools.join(",") }];
|
|
142
|
+
if (source.resourceProfile)
|
|
143
|
+
flags.push({ flag: "--resource-profile", value: source.resourceProfile });
|
|
144
|
+
else
|
|
145
|
+
flags.push({ flag: "--no-extensions" }, { flag: "--no-skills" });
|
|
146
|
+
if (source.worktreeLane)
|
|
147
|
+
flags.push({ flag: "--worktree-lane", value: source.worktreeLane });
|
|
148
|
+
if (source.parentPid !== undefined)
|
|
149
|
+
flags.push({ flag: "--parent-pid", value: String(source.parentPid) });
|
|
150
|
+
if (source.parentSession)
|
|
151
|
+
flags.push({ flag: "--parent-session", value: source.parentSession });
|
|
152
|
+
flags.push({ flag: "--append-system-prompt", value: buildScopedSystemPrompt(source) });
|
|
153
|
+
return flags;
|
|
154
|
+
}
|
|
155
|
+
export function buildScopedSystemPrompt(source) {
|
|
156
|
+
const paths = source.writePaths?.length ? source.writePaths.join(", ") : "no additional paths granted";
|
|
157
|
+
const sentences = [
|
|
158
|
+
`You are a tmux worker dispatched under ${source.identity}.`,
|
|
159
|
+
`Stay within these write paths: ${paths}.`,
|
|
160
|
+
"Hard stops — publishing/npm, git push, tagging a release, changing credentials/auth, or destructive deletion — require returning BLOCKED for owner approval. Never self-approve a hard stop.",
|
|
161
|
+
];
|
|
162
|
+
if (source.worktreeLane) {
|
|
163
|
+
sentences.push(`You are bound to worktree-sync lane '${source.worktreeLane}': work only inside this lane's own worktree, integrate exclusively via worktree_sync land, and never touch main directly.`);
|
|
164
|
+
}
|
|
165
|
+
return sentences.join(" ");
|
|
166
|
+
}
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Advisory worker usage claim: budget stays advisory across the process boundary.
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
/** Permissively decode a worker-reported usage CLAIM (a cooperative worker may write this to a
|
|
171
|
+
* well-known sibling file before printing its completion marker). Read-defensive: missing/malformed
|
|
172
|
+
* numeric fields default to 0 rather than throwing — this is untrusted, OPTIONAL, advisory input, never
|
|
173
|
+
* an authoritative billing record. Returns undefined only when `raw` isn't even an object, so the
|
|
174
|
+
* caller can distinguish "no claim offered" (skip reporting) from "a claim was offered" (report it,
|
|
175
|
+
* however partial). */
|
|
176
|
+
export function decodeTmuxWorkerUsageClaim(raw) {
|
|
177
|
+
if (!isPlainRecord(raw))
|
|
178
|
+
return undefined;
|
|
179
|
+
const num = (value) => (typeof value === "number" && Number.isFinite(value) ? value : 0);
|
|
180
|
+
const cost = isPlainRecord(raw.cost) ? raw.cost : {};
|
|
181
|
+
return {
|
|
182
|
+
input: num(raw.input),
|
|
183
|
+
output: num(raw.output),
|
|
184
|
+
cacheRead: num(raw.cacheRead),
|
|
185
|
+
cacheWrite: num(raw.cacheWrite),
|
|
186
|
+
totalTokens: num(raw.totalTokens),
|
|
187
|
+
cost: {
|
|
188
|
+
input: num(cost.input),
|
|
189
|
+
output: num(cost.output),
|
|
190
|
+
cacheRead: num(cost.cacheRead),
|
|
191
|
+
cacheWrite: num(cost.cacheWrite),
|
|
192
|
+
total: num(cost.total),
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=dispatch-grant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch-grant.js","sourceRoot":"","sources":["../../../../src/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts"],"names":[],"mappings":"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","sourcesContent":["import * as crypto from \"node:crypto\";\nimport type { Usage } from \"@caupulican/pi-ai\";\n\n/**\n * The STANDING GRANT for approval-gated tmux dispatch (owner authorizes once via `grant_dispatch`;\n * the extension then dispatches unattended WITHIN the grant's bounds). Pure data + pure decode/decision\n * logic only — no session/tmux/filesystem access, so every function here is directly unit-testable.\n * The extension (index.ts) owns reading/writing session custom entries and calls into this module for\n * decoding and authorization decisions.\n *\n * HONEST TRUST BOUNDARY: this grant bounds WHO may be launched, HOW OFTEN, and WITH WHAT launch-time\n * envelope (pushed into the child's own CLI flags) — it never claims to gate the child's in-process\n * tool loop, which is a separate OS process outside this runtime's `beforeToolCall` machinery.\n */\n\nexport type Provider = \"pi\" | \"codex\" | \"agy\" | \"claude\" | \"opencode\" | \"custom\";\n\nexport const GRANT_CUSTOM_TYPE = \"tmux-dispatch-grant\";\nexport const GRANT_USAGE_CUSTOM_TYPE = \"tmux-dispatch-grant-usage\";\n\nexport interface TmuxDispatchGrantEnvelope {\n\tallowedTools?: string[];\n\tresourceProfile?: string;\n\twritePaths?: string[];\n}\n\nexport interface TmuxDispatchGrantBudget {\n\tmaxLaunches: number;\n\tmaxWallClockMs?: number;\n\tmaxUsdAdvisory?: number;\n}\n\nexport interface TmuxDispatchGrant {\n\tgrantId: string;\n\tcreatedAt: string;\n\tagent: Provider;\n\tscope: { goalId?: string };\n\tenvelope: TmuxDispatchGrantEnvelope;\n\tbudget: TmuxDispatchGrantBudget;\n\texpiresAt?: string;\n}\n\n/** Appended by `revoke_grant`. Shares `GRANT_CUSTOM_TYPE` so the SAME latest-on-branch read that\n * resolves an active grant also sees a revocation and stops treating any older grant beneath it as\n * active — a tombstone is a hard stop, never skipped over to resurrect an earlier grant. */\nexport interface TmuxDispatchGrantTombstone {\n\ttombstone: true;\n\tgrantId: string;\n\trevokedAt: string;\n}\n\nexport interface TmuxDispatchGrantUsage {\n\tgrantId: string;\n\tjobId: string;\n\tat: string;\n}\n\nconst PROVIDERS: readonly Provider[] = [\"pi\", \"codex\", \"agy\", \"claude\", \"opencode\", \"custom\"];\n\nfunction isProvider(value: unknown): value is Provider {\n\treturn typeof value === \"string\" && (PROVIDERS as readonly string[]).includes(value);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function isTmuxDispatchGrantTombstone(value: unknown): value is TmuxDispatchGrantTombstone {\n\tif (!isPlainRecord(value)) return false;\n\treturn value.tombstone === true && typeof value.grantId === \"string\" && typeof value.revokedAt === \"string\";\n}\n\nexport function isTmuxDispatchGrant(value: unknown): value is TmuxDispatchGrant {\n\tif (!isPlainRecord(value) || value.tombstone === true) return false;\n\tif (typeof value.grantId !== \"string\" || typeof value.createdAt !== \"string\" || !isProvider(value.agent))\n\t\treturn false;\n\tif (!isPlainRecord(value.scope) || !isPlainRecord(value.envelope) || !isPlainRecord(value.budget)) return false;\n\treturn typeof value.budget.maxLaunches === \"number\" && Number.isFinite(value.budget.maxLaunches);\n}\n\nexport function isTmuxDispatchGrantUsage(value: unknown): value is TmuxDispatchGrantUsage {\n\tif (!isPlainRecord(value)) return false;\n\treturn typeof value.grantId === \"string\" && typeof value.jobId === \"string\" && typeof value.at === \"string\";\n}\n\nexport function makeGrantId(): string {\n\treturn `grant-${Date.now().toString(36)}-${crypto.randomBytes(4).toString(\"hex\")}`;\n}\n\nexport interface GrantCoverageRequest {\n\tagent: Provider;\n\tgoalId?: string;\n\tnowIso?: string;\n}\n\n/** Whether `grant` authorizes a launch for `request`: same agent, goal-scope match (an UNSCOPED grant\n * covers any goal including none; a goal-SCOPED grant covers ONLY that exact goalId), and the grant is\n * not past `expiresAt`. Budget (maxLaunches) is checked separately via {@link isGrantBudgetExhausted}\n * because counting usages requires the caller's session-entry access. */\nexport function grantCovers(grant: TmuxDispatchGrant, request: GrantCoverageRequest): boolean {\n\tif (grant.agent !== request.agent) return false;\n\tif (grant.scope.goalId && grant.scope.goalId !== request.goalId) return false;\n\tif (grant.expiresAt) {\n\t\tconst now = Date.parse(request.nowIso ?? new Date().toISOString());\n\t\tconst expiry = Date.parse(grant.expiresAt);\n\t\tif (Number.isFinite(expiry) && Number.isFinite(now) && now >= expiry) return false;\n\t}\n\treturn true;\n}\n\n/** Count spend entries for `grantId` among already-decoded custom-entry payloads (the caller walks the\n * session branch and passes the raw `data` values — this stays pure/session-agnostic). */\nexport function countGrantUsages(grantId: string, usageEntries: readonly unknown[]): number {\n\tlet count = 0;\n\tfor (const entry of usageEntries) if (isTmuxDispatchGrantUsage(entry) && entry.grantId === grantId) count++;\n\treturn count;\n}\n\nexport function isGrantBudgetExhausted(grant: TmuxDispatchGrant, usedCount: number): boolean {\n\treturn usedCount >= grant.budget.maxLaunches;\n}\n\nexport function buildGrantUsageEntry(\n\tgrantId: string,\n\tjobId: string,\n\tnowIso = new Date().toISOString(),\n): TmuxDispatchGrantUsage {\n\treturn { grantId, jobId, at: nowIso };\n}\n\nexport function buildTombstone(grantId: string, nowIso = new Date().toISOString()): TmuxDispatchGrantTombstone {\n\treturn { tombstone: true, grantId, revokedAt: nowIso };\n}\n\nexport interface GrantDispatchParams {\n\tagent: Provider;\n\tgoalId?: string;\n\tallowedTools?: string[];\n\tresourceProfile?: string;\n\twritePaths?: string[];\n\tmaxLaunches: number;\n\texpiresInMinutes?: number;\n\tmaxUsdAdvisory?: number;\n}\n\n/** Build a new grant record from `grant_dispatch` params. Pure — the caller (index.ts) owns the\n * approval gate (ui.confirm / opt-in flag) and the `pi.appendEntry` persistence; this only shapes the\n * data, so it stays independently testable and never itself decides whether creation is authorized. */\nexport function buildGrant(params: GrantDispatchParams, nowIso = new Date().toISOString()): TmuxDispatchGrant {\n\tif (!Number.isFinite(params.maxLaunches) || params.maxLaunches < 1)\n\t\tthrow new Error(\"grant_dispatch requires maxLaunches >= 1\");\n\tconst expiresAt =\n\t\tparams.expiresInMinutes !== undefined\n\t\t\t? new Date(Date.parse(nowIso) + Math.max(1, params.expiresInMinutes) * 60_000).toISOString()\n\t\t\t: undefined;\n\treturn {\n\t\tgrantId: makeGrantId(),\n\t\tcreatedAt: nowIso,\n\t\tagent: params.agent,\n\t\tscope: { goalId: params.goalId },\n\t\tenvelope: {\n\t\t\tallowedTools: params.allowedTools,\n\t\t\tresourceProfile: params.resourceProfile,\n\t\t\twritePaths: params.writePaths,\n\t\t},\n\t\tbudget: {\n\t\t\tmaxLaunches: Math.trunc(params.maxLaunches),\n\t\t\tmaxUsdAdvisory: params.maxUsdAdvisory,\n\t\t},\n\t\texpiresAt,\n\t};\n}\n\nexport function describeGrant(grant: TmuxDispatchGrant): string {\n\treturn [\n\t\t`Grant id: ${grant.grantId}`,\n\t\t`Agent: ${grant.agent}`,\n\t\tgrant.scope.goalId ? `Goal scope: ${grant.scope.goalId}` : \"Goal scope: none (covers any goal)\",\n\t\t`Max launches: ${grant.budget.maxLaunches}`,\n\t\tgrant.expiresAt ? `Expires: ${grant.expiresAt}` : \"Expires: never (until revoke_grant)\",\n\t\tgrant.envelope.allowedTools?.length\n\t\t\t? `Allowed tools: ${grant.envelope.allowedTools.join(\", \")}`\n\t\t\t: `Allowed tools: read-biased safe default (${DEFAULT_READ_BIASED_TOOLS.join(\", \")})`,\n\t\tgrant.envelope.resourceProfile\n\t\t\t? `Resource profile: ${grant.envelope.resourceProfile}`\n\t\t\t: \"Resource profile: none (child launches with --no-extensions --no-skills)\",\n\t\tgrant.envelope.writePaths?.length\n\t\t\t? `Write paths: ${grant.envelope.writePaths.join(\", \")}`\n\t\t\t: \"Write paths: none declared\",\n\t\tgrant.budget.maxUsdAdvisory !== undefined\n\t\t\t? `Advisory USD cap: ${grant.budget.maxUsdAdvisory} (not enforced across the process boundary — advisory only)`\n\t\t\t: undefined,\n\t]\n\t\t.filter((line): line is string => Boolean(line))\n\t\t.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Launch-profile flags: the REAL lever — pushed into the child `pi` CLI's own config.\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_READ_BIASED_TOOLS: readonly string[] = [\"read\", \"grep\", \"find\", \"ls\"];\n\nexport interface LaunchProfileSource {\n\t/** Human-readable identity for the scoped system prompt (e.g. \"grant <id>\" or a one-shot label). */\n\tidentity: string;\n\tallowedTools?: string[];\n\tresourceProfile?: string;\n\twritePaths?: string[];\n\t/**\n\t * Worktree-sync lane key this launch is bound to (per-agent -- see `applyLaunchProfile`'s caller,\n\t * which builds a per-agent `LaunchProfileSource` only when that agent carries `worktreeLane`).\n\t * When present, `buildLaunchProfileFlags` appends `--worktree-lane <key>` (the same CLI flag\n\t * `src/cli/args.ts` already exposes, sugar over the `PI_WORKTREE_LANE` env contract) and\n\t * `buildScopedSystemPrompt` appends one lane-doctrine sentence.\n\t */\n\tworktreeLane?: string;\n\t/**\n\t * Process-matrix parent identity for this launch (see `core/process-matrix/runtime.ts`): the\n\t * dispatching process's own pid/sessionId. When present, `buildLaunchProfileFlags` appends\n\t * `--parent-pid`/`--parent-session` (sugar over `PI_PARENT_PID`/`PI_PARENT_SESSION`) so the\n\t * child self-registers as a worker of this session and winds down gracefully if it disappears.\n\t */\n\tparentPid?: number;\n\tparentSession?: string;\n}\n\n/** A grant-covered launch derives its profile from the grant's envelope. */\nexport function launchProfileSourceFromGrant(grant: TmuxDispatchGrant): LaunchProfileSource {\n\treturn {\n\t\tidentity: `grant ${grant.grantId}`,\n\t\tallowedTools: grant.envelope.allowedTools,\n\t\tresourceProfile: grant.envelope.resourceProfile,\n\t\twritePaths: grant.envelope.writePaths,\n\t};\n}\n\n/** A one-shot interactively-approved launch (no standing grant) still gets the conservative default\n * profile — approving a single launch is not the same as defining an unrestricted envelope. */\nexport const ONE_SHOT_LAUNCH_PROFILE_SOURCE: LaunchProfileSource = {\n\tidentity: \"a one-shot owner-approved tmux dispatch (no standing grant)\",\n};\n\nexport interface LaunchProfileFlag {\n\tflag: string;\n\tvalue?: string;\n}\n\n/** Build the CLI flags to append to a child `pi` invocation so the grant's envelope lives in the\n * child's OWN launch config (verified flag names: --tools, --resource-profile, --no-extensions,\n * --no-skills, --append-system-prompt). Never includes --no-approve: the child must keep gating its\n * own hard stops. */\nexport function buildLaunchProfileFlags(source: LaunchProfileSource): LaunchProfileFlag[] {\n\tconst tools = source.allowedTools?.length ? source.allowedTools : DEFAULT_READ_BIASED_TOOLS;\n\tconst flags: LaunchProfileFlag[] = [{ flag: \"--tools\", value: tools.join(\",\") }];\n\tif (source.resourceProfile) flags.push({ flag: \"--resource-profile\", value: source.resourceProfile });\n\telse flags.push({ flag: \"--no-extensions\" }, { flag: \"--no-skills\" });\n\tif (source.worktreeLane) flags.push({ flag: \"--worktree-lane\", value: source.worktreeLane });\n\tif (source.parentPid !== undefined) flags.push({ flag: \"--parent-pid\", value: String(source.parentPid) });\n\tif (source.parentSession) flags.push({ flag: \"--parent-session\", value: source.parentSession });\n\tflags.push({ flag: \"--append-system-prompt\", value: buildScopedSystemPrompt(source) });\n\treturn flags;\n}\n\nexport function buildScopedSystemPrompt(source: LaunchProfileSource): string {\n\tconst paths = source.writePaths?.length ? source.writePaths.join(\", \") : \"no additional paths granted\";\n\tconst sentences = [\n\t\t`You are a tmux worker dispatched under ${source.identity}.`,\n\t\t`Stay within these write paths: ${paths}.`,\n\t\t\"Hard stops — publishing/npm, git push, tagging a release, changing credentials/auth, or destructive deletion — require returning BLOCKED for owner approval. Never self-approve a hard stop.\",\n\t];\n\tif (source.worktreeLane) {\n\t\tsentences.push(\n\t\t\t`You are bound to worktree-sync lane '${source.worktreeLane}': work only inside this lane's own worktree, integrate exclusively via worktree_sync land, and never touch main directly.`,\n\t\t);\n\t}\n\treturn sentences.join(\" \");\n}\n\n// ---------------------------------------------------------------------------\n// Advisory worker usage claim: budget stays advisory across the process boundary.\n// ---------------------------------------------------------------------------\n\n/** Permissively decode a worker-reported usage CLAIM (a cooperative worker may write this to a\n * well-known sibling file before printing its completion marker). Read-defensive: missing/malformed\n * numeric fields default to 0 rather than throwing — this is untrusted, OPTIONAL, advisory input, never\n * an authoritative billing record. Returns undefined only when `raw` isn't even an object, so the\n * caller can distinguish \"no claim offered\" (skip reporting) from \"a claim was offered\" (report it,\n * however partial). */\nexport function decodeTmuxWorkerUsageClaim(raw: unknown): Usage | undefined {\n\tif (!isPlainRecord(raw)) return undefined;\n\tconst num = (value: unknown): number => (typeof value === \"number\" && Number.isFinite(value) ? value : 0);\n\tconst cost = isPlainRecord(raw.cost) ? raw.cost : {};\n\treturn {\n\t\tinput: num(raw.input),\n\t\toutput: num(raw.output),\n\t\tcacheRead: num(raw.cacheRead),\n\t\tcacheWrite: num(raw.cacheWrite),\n\t\ttotalTokens: num(raw.totalTokens),\n\t\tcost: {\n\t\t\tinput: num(cost.input),\n\t\t\toutput: num(cost.output),\n\t\t\tcacheRead: num(cost.cacheRead),\n\t\t\tcacheWrite: num(cost.cacheWrite),\n\t\t\ttotal: num(cost.total),\n\t\t},\n\t};\n}\n"]}
|