@caupulican/pi-adaptative 0.81.37 → 0.81.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/README.md +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +18 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +131 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +196 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +308 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +22 -2
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +585 -24
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +749 -27
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/__init__.py +43 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/fs.py +270 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/search.py +252 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/strings.py +399 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +575 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +52 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +49 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +734 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +238 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +132 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +116 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +287 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/proc.py +137 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +100 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +579 -0
- package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +19 -9
- package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +22 -5
- package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +5 -5
- package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +15 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-paths.d.ts +49 -0
- package/dist/core/agent-paths.d.ts.map +1 -0
- package/dist/core/agent-paths.js +107 -0
- package/dist/core/agent-paths.js.map +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -3
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +234 -17
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +484 -62
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/contracts.d.ts +9 -0
- package/dist/core/autonomy/contracts.d.ts.map +1 -1
- package/dist/core/autonomy/contracts.js.map +1 -1
- package/dist/core/autonomy/lane-tracker.d.ts +10 -1
- package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
- package/dist/core/autonomy/lane-tracker.js +5 -1
- package/dist/core/autonomy/lane-tracker.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +122 -6
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +447 -90
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/bash-execution-controller.d.ts +4 -0
- package/dist/core/bash-execution-controller.d.ts.map +1 -1
- package/dist/core/bash-execution-controller.js +7 -1
- package/dist/core/bash-execution-controller.js.map +1 -1
- package/dist/core/compaction-support.d.ts +13 -3
- package/dist/core/compaction-support.d.ts.map +1 -1
- package/dist/core/compaction-support.js +43 -7
- package/dist/core/compaction-support.js.map +1 -1
- package/dist/core/context/context-audit.d.ts +33 -1
- package/dist/core/context/context-audit.d.ts.map +1 -1
- package/dist/core/context/context-audit.js +31 -5
- package/dist/core/context/context-audit.js.map +1 -1
- package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
- package/dist/core/context/sqlite-runtime-index.js +13 -7
- package/dist/core/context/sqlite-runtime-index.js.map +1 -1
- package/dist/core/context-gc.d.ts.map +1 -1
- package/dist/core/context-gc.js +16 -1
- package/dist/core/context-gc.js.map +1 -1
- package/dist/core/context-pipeline.d.ts +26 -4
- package/dist/core/context-pipeline.d.ts.map +1 -1
- package/dist/core/context-pipeline.js +137 -14
- package/dist/core/context-pipeline.js.map +1 -1
- package/dist/core/cost-guard.d.ts +19 -3
- package/dist/core/cost-guard.d.ts.map +1 -1
- package/dist/core/cost-guard.js +18 -3
- package/dist/core/cost-guard.js.map +1 -1
- package/dist/core/delegation/session-worker-result.d.ts +34 -4
- package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
- package/dist/core/delegation/session-worker-result.js +64 -4
- package/dist/core/delegation/session-worker-result.js.map +1 -1
- package/dist/core/delegation/worker-actions.d.ts.map +1 -1
- package/dist/core/delegation/worker-actions.js +1 -1
- package/dist/core/delegation/worker-actions.js.map +1 -1
- package/dist/core/delegation/worker-result.d.ts +42 -1
- package/dist/core/delegation/worker-result.d.ts.map +1 -1
- package/dist/core/delegation/worker-result.js +68 -0
- package/dist/core/delegation/worker-result.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +23 -7
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +1 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +51 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/goal-loop-controller.d.ts +17 -1
- package/dist/core/goal-loop-controller.d.ts.map +1 -1
- package/dist/core/goal-loop-controller.js +79 -11
- package/dist/core/goal-loop-controller.js.map +1 -1
- package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
- package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-controller.js +77 -0
- package/dist/core/goals/goal-continuation-controller.js.map +1 -1
- package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
- package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-defaults.js +42 -0
- package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.d.ts +4 -0
- package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.js +51 -10
- package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
- package/dist/core/goals/goal-runtime-snapshot.d.ts +101 -2
- package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
- package/dist/core/goals/goal-runtime-snapshot.js +87 -4
- package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
- package/dist/core/goals/goal-state.d.ts +89 -1
- package/dist/core/goals/goal-state.d.ts.map +1 -1
- package/dist/core/goals/goal-state.js +71 -5
- package/dist/core/goals/goal-state.js.map +1 -1
- package/dist/core/goals/goal-tool-core.d.ts +56 -2
- package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
- package/dist/core/goals/goal-tool-core.js +111 -5
- package/dist/core/goals/goal-tool-core.js.map +1 -1
- package/dist/core/goals/session-goal-state.d.ts +11 -2
- package/dist/core/goals/session-goal-state.d.ts.map +1 -1
- package/dist/core/goals/session-goal-state.js +32 -17
- package/dist/core/goals/session-goal-state.js.map +1 -1
- package/dist/core/keybindings.d.ts +10 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +10 -2
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/learning/observation-store.d.ts +12 -3
- package/dist/core/learning/observation-store.d.ts.map +1 -1
- package/dist/core/learning/observation-store.js +30 -15
- package/dist/core/learning/observation-store.js.map +1 -1
- package/dist/core/learning/skill-curator.d.ts +5 -1
- package/dist/core/learning/skill-curator.d.ts.map +1 -1
- package/dist/core/learning/skill-curator.js +21 -19
- package/dist/core/learning/skill-curator.js.map +1 -1
- package/dist/core/local-runtime-controller.d.ts +65 -3
- package/dist/core/local-runtime-controller.d.ts.map +1 -1
- package/dist/core/local-runtime-controller.js +186 -26
- package/dist/core/local-runtime-controller.js.map +1 -1
- package/dist/core/memory/providers/file-store.d.ts +1 -1
- package/dist/core/memory/providers/file-store.d.ts.map +1 -1
- package/dist/core/memory/providers/file-store.js +6 -6
- package/dist/core/memory/providers/file-store.js.map +1 -1
- package/dist/core/model-capability.d.ts +34 -0
- package/dist/core/model-capability.d.ts.map +1 -1
- package/dist/core/model-capability.js +42 -1
- package/dist/core/model-capability.js.map +1 -1
- package/dist/core/model-router/tool-escalation.d.ts +15 -0
- package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
- package/dist/core/model-router/tool-escalation.js +23 -1
- package/dist/core/model-router/tool-escalation.js.map +1 -1
- package/dist/core/model-router-controller.d.ts +34 -7
- package/dist/core/model-router-controller.d.ts.map +1 -1
- package/dist/core/model-router-controller.js +95 -16
- package/dist/core/model-router-controller.js.map +1 -1
- package/dist/core/models/adaptation-store.d.ts +7 -0
- package/dist/core/models/adaptation-store.d.ts.map +1 -1
- package/dist/core/models/adaptation-store.js +47 -11
- package/dist/core/models/adaptation-store.js.map +1 -1
- package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
- package/dist/core/models/default-model-suggestions.js +17 -0
- package/dist/core/models/default-model-suggestions.js.map +1 -1
- package/dist/core/models/fitness-store.d.ts +3 -0
- package/dist/core/models/fitness-store.d.ts.map +1 -1
- package/dist/core/models/fitness-store.js +11 -2
- package/dist/core/models/fitness-store.js.map +1 -1
- package/dist/core/models/llamacpp-runtime.d.ts +180 -0
- package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
- package/dist/core/models/llamacpp-runtime.js +475 -0
- package/dist/core/models/llamacpp-runtime.js.map +1 -0
- package/dist/core/models/local-registration.d.ts +40 -0
- package/dist/core/models/local-registration.d.ts.map +1 -1
- package/dist/core/models/local-registration.js +94 -5
- package/dist/core/models/local-registration.js.map +1 -1
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +9 -7
- package/dist/core/models/local-runtime.js.map +1 -1
- package/dist/core/models/model-ref.d.ts +7 -0
- package/dist/core/models/model-ref.d.ts.map +1 -1
- package/dist/core/models/model-ref.js +26 -0
- package/dist/core/models/model-ref.js.map +1 -1
- package/dist/core/models/needle-runtime.d.ts +257 -0
- package/dist/core/models/needle-runtime.d.ts.map +1 -0
- package/dist/core/models/needle-runtime.js +519 -0
- package/dist/core/models/needle-runtime.js.map +1 -0
- package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
- package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
- package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
- package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +11 -10
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/process-matrix/codes.d.ts +72 -0
- package/dist/core/process-matrix/codes.d.ts.map +1 -0
- package/dist/core/process-matrix/codes.js +15 -0
- package/dist/core/process-matrix/codes.js.map +1 -0
- package/dist/core/process-matrix/runtime.d.ts +63 -0
- package/dist/core/process-matrix/runtime.d.ts.map +1 -0
- package/dist/core/process-matrix/runtime.js +310 -0
- package/dist/core/process-matrix/runtime.js.map +1 -0
- package/dist/core/process-matrix/store.d.ts +22 -0
- package/dist/core/process-matrix/store.d.ts.map +1 -0
- package/dist/core/process-matrix/store.js +80 -0
- package/dist/core/process-matrix/store.js.map +1 -0
- package/dist/core/process-matrix/supervisor.d.ts +72 -0
- package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
- package/dist/core/process-matrix/supervisor.js +130 -0
- package/dist/core/process-matrix/supervisor.js.map +1 -0
- package/dist/core/profile-registry.d.ts +7 -0
- package/dist/core/profile-registry.d.ts.map +1 -1
- package/dist/core/profile-registry.js +20 -0
- package/dist/core/profile-registry.js.map +1 -1
- package/dist/core/python-runtime.d.ts.map +1 -1
- package/dist/core/python-runtime.js +3 -3
- package/dist/core/python-runtime.js.map +1 -1
- package/dist/core/reflection-controller.d.ts +29 -5
- package/dist/core/reflection-controller.d.ts.map +1 -1
- package/dist/core/reflection-controller.js +215 -126
- package/dist/core/reflection-controller.js.map +1 -1
- package/dist/core/reload-blockers.d.ts +36 -0
- package/dist/core/reload-blockers.d.ts.map +1 -1
- package/dist/core/reload-blockers.js +44 -0
- package/dist/core/reload-blockers.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +8 -7
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +58 -5
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +209 -25
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/scout-controller.d.ts +6 -0
- package/dist/core/scout-controller.d.ts.map +1 -1
- package/dist/core/scout-controller.js +66 -52
- package/dist/core/scout-controller.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-role.d.ts +31 -0
- package/dist/core/session-role.d.ts.map +1 -0
- package/dist/core/session-role.js +52 -0
- package/dist/core/session-role.js.map +1 -0
- package/dist/core/settings-manager.d.ts +42 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +94 -60
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +12 -0
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.js +6 -0
- package/dist/core/system-prompt-builder.js.map +1 -1
- package/dist/core/system-prompt.d.ts +14 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +20 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tasks/session-task-state.d.ts +11 -2
- package/dist/core/tasks/session-task-state.d.ts.map +1 -1
- package/dist/core/tasks/session-task-state.js +27 -11
- package/dist/core/tasks/session-task-state.js.map +1 -1
- package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
- package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
- package/dist/core/tasks/task-contract-monitor.js +56 -0
- package/dist/core/tasks/task-contract-monitor.js.map +1 -0
- package/dist/core/tasks/task-state.d.ts +8 -0
- package/dist/core/tasks/task-state.d.ts.map +1 -1
- package/dist/core/tasks/task-state.js +28 -1
- package/dist/core/tasks/task-state.js.map +1 -1
- package/dist/core/tool-gate-controller.d.ts.map +1 -1
- package/dist/core/tool-gate-controller.js +5 -0
- package/dist/core/tool-gate-controller.js.map +1 -1
- package/dist/core/tool-recovery-log-records.d.ts +7 -0
- package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
- package/dist/core/tool-recovery-log-records.js +14 -6
- package/dist/core/tool-recovery-log-records.js.map +1 -1
- package/dist/core/tool-selection/promotion.d.ts +54 -0
- package/dist/core/tool-selection/promotion.d.ts.map +1 -0
- package/dist/core/tool-selection/promotion.js +81 -0
- package/dist/core/tool-selection/promotion.js.map +1 -0
- package/dist/core/tool-selection/tool-performance-store.d.ts +37 -0
- package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
- package/dist/core/tool-selection/tool-performance-store.js +87 -3
- package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
- package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
- package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
- package/dist/core/tool-selection/tool-selection-controller.js +96 -0
- package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
- package/dist/core/tools/bash.d.ts +18 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +97 -18
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/delegate-status.d.ts +14 -0
- package/dist/core/tools/delegate-status.d.ts.map +1 -1
- package/dist/core/tools/delegate-status.js +82 -7
- package/dist/core/tools/delegate-status.js.map +1 -1
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +25 -8
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +52 -44
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/goal.d.ts +94 -3
- package/dist/core/tools/goal.d.ts.map +1 -1
- package/dist/core/tools/goal.js +165 -15
- package/dist/core/tools/goal.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +5 -4
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/model-fitness.d.ts +7 -0
- package/dist/core/tools/model-fitness.d.ts.map +1 -1
- package/dist/core/tools/model-fitness.js +2 -2
- package/dist/core/tools/model-fitness.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +1 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/shell-contract-router.d.ts +6 -1
- package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
- package/dist/core/tools/shell-contract-router.js +69 -13
- package/dist/core/tools/shell-contract-router.js.map +1 -1
- package/dist/core/tools/shell-session.d.ts +89 -0
- package/dist/core/tools/shell-session.d.ts.map +1 -0
- package/dist/core/tools/shell-session.js +432 -0
- package/dist/core/tools/shell-session.js.map +1 -0
- package/dist/core/tools/task-steps.d.ts +4 -0
- package/dist/core/tools/task-steps.d.ts.map +1 -1
- package/dist/core/tools/task-steps.js +63 -8
- package/dist/core/tools/task-steps.js.map +1 -1
- package/dist/core/tools/tmux-dispatch.d.ts +86 -0
- package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
- package/dist/core/tools/tmux-dispatch.js +91 -0
- package/dist/core/tools/tmux-dispatch.js.map +1 -0
- package/dist/core/tools/windows-shell-engine.d.ts +42 -0
- package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
- package/dist/core/tools/windows-shell-engine.js +153 -0
- package/dist/core/tools/windows-shell-engine.js.map +1 -0
- package/dist/core/tools/windows-shell-state.d.ts +40 -0
- package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
- package/dist/core/tools/windows-shell-state.js +59 -0
- package/dist/core/tools/windows-shell-state.js.map +1 -0
- package/dist/core/tools/worktree-sync.d.ts +24 -0
- package/dist/core/tools/worktree-sync.d.ts.map +1 -0
- package/dist/core/tools/worktree-sync.js +338 -0
- package/dist/core/tools/worktree-sync.js.map +1 -0
- package/dist/core/trust-manager.d.ts +4 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +20 -2
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/core/util/atomic-file.d.ts +55 -0
- package/dist/core/util/atomic-file.d.ts.map +1 -0
- package/dist/core/util/atomic-file.js +255 -0
- package/dist/core/util/atomic-file.js.map +1 -0
- package/dist/core/util/minimatch-cache.d.ts +33 -0
- package/dist/core/util/minimatch-cache.d.ts.map +1 -0
- package/dist/core/util/minimatch-cache.js +0 -0
- package/dist/core/util/minimatch-cache.js.map +1 -0
- package/dist/core/worktree-sync/codes.d.ts +227 -0
- package/dist/core/worktree-sync/codes.d.ts.map +1 -0
- package/dist/core/worktree-sync/codes.js +14 -0
- package/dist/core/worktree-sync/codes.js.map +1 -0
- package/dist/core/worktree-sync/git-engine.d.ts +156 -0
- package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
- package/dist/core/worktree-sync/git-engine.js +1191 -0
- package/dist/core/worktree-sync/git-engine.js.map +1 -0
- package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
- package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
- package/dist/core/worktree-sync/lane-gate.js +360 -0
- package/dist/core/worktree-sync/lane-gate.js.map +1 -0
- package/dist/core/worktree-sync/runtime.d.ts +47 -0
- package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
- package/dist/core/worktree-sync/runtime.js +96 -0
- package/dist/core/worktree-sync/runtime.js.map +1 -0
- package/dist/core/worktree-sync/store.d.ts +69 -0
- package/dist/core/worktree-sync/store.d.ts.map +1 -0
- package/dist/core/worktree-sync/store.js +247 -0
- package/dist/core/worktree-sync/store.js.map +1 -0
- package/dist/core/worktree-sync/watcher.d.ts +29 -0
- package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
- package/dist/core/worktree-sync/watcher.js +93 -0
- package/dist/core/worktree-sync/watcher.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +94 -0
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +9 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +38 -0
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
- package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.js +50 -8
- package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
- package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +4 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/local-model-commands.d.ts +43 -0
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
- package/dist/modes/interactive/local-model-commands.js +290 -3
- package/dist/modes/interactive/local-model-commands.js.map +1 -1
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-flow-commands.js +24 -4
- package/dist/modes/interactive/session-flow-commands.js.map +1 -1
- package/dist/utils/fs-watch.d.ts +11 -0
- package/dist/utils/fs-watch.d.ts.map +1 -1
- package/dist/utils/fs-watch.js +20 -2
- package/dist/utils/fs-watch.js.map +1 -1
- package/dist/utils/highlight-js-languages.d.ts +4 -0
- package/dist/utils/highlight-js-languages.d.ts.map +1 -0
- package/dist/utils/highlight-js-languages.js +573 -0
- package/dist/utils/highlight-js-languages.js.map +1 -0
- package/dist/utils/shell.d.ts +7 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +39 -9
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +53 -5
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +112 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/development.md +2 -0
- package/docs/packages.md +1 -1
- package/docs/process-matrix.md +120 -0
- package/docs/settings.md +5 -2
- package/docs/tmux-agent-manager.md +85 -2
- package/docs/windows.md +52 -3
- package/docs/work-directory.md +29 -0
- package/docs/worktree-sync.md +250 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +10 -4
- package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
- package/docs/integration-sweep-resume-2026-07-09.md +0 -407
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-runtime-controller.js","sourceRoot":"","sources":["../../src/core/local-runtime-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local-runtime (Ollama) lifecycle controller.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the cached, per-server\n * {@link OllamaRuntime} instances, the \"confirmed up this session\" flag, and the router's readiness\n * gate for a turn routed to a local (`ollama`) model — including the #31 install-on-consent flow and\n * the #27 graceful tier-escalation fallback. Takes narrow deps (agent dir, a last-assistant-message\n * accessor, the session's UI context/event emitter, and the router's own tier resolver) rather than\n * the whole AgentSession.\n */\n\nimport { totalmem } from \"node:os\";\nimport type { Api, AssistantMessage, Model } from \"@caupulican/pi-ai\";\nimport type { AgentSessionEvent } from \"./agent-session.ts\";\nimport type { RouteDecision } from \"./autonomy/contracts.ts\";\nimport type { ExtensionUIContext } from \"./extensions/index.ts\";\nimport { HF_TRANSFORMERS_PROVIDER, OLLAMA_PROVIDER } from \"./models/local-registration.ts\";\nimport {\n\ttype LocalRuntimeDeps,\n\tOllamaRuntime,\n\tresolveTransformersBaseUrl,\n\tTransformersRuntime,\n} from \"./models/local-runtime.ts\";\nimport { matchesInstalledLocalModel } from \"./models/model-ref.ts\";\nimport {\n\tOllamaRuntimeResidencyAdapter,\n\ttype RuntimeEvictionRecord,\n\ttype RuntimeResidencyAdapter,\n\tRuntimeResidencyArbiter,\n\tTransformersRuntimeResidencyAdapter,\n} from \"./models/runtime-arbiter.ts\";\n\n/** User-facing router tiers in ascending order — \"learning\" is never selected for a user turn, so\n * it has no place in the escalation ladder (#27's ensureRouteModelReady walks this forward only). */\nconst MODEL_ROUTER_TIER_ORDER: readonly (\"cheap\" | \"medium\" | \"expensive\")[] = [\"cheap\", \"medium\", \"expensive\"];\n\n/** How long the #31 \"install ollama now?\" confirm waits before auto-dismissing (same as a \"No\") —\n * long enough to read and decide, short enough that an unattended session doesn't hang a turn on it. */\nconst OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS = 30_000;\n\nfunction parseWrongOllamaStoreReason(\n\treason: string,\n): { activePath: string; modelCount: number; ownedPath: string } | undefined {\n\tif (!reason.startsWith(\"wrong_store:\")) return undefined;\n\tconst parts = reason.slice(\"wrong_store:\".length).split(\":\");\n\tif (parts.length < 3) return undefined;\n\tconst [activePath, countText, ...ownedPathParts] = parts;\n\tconst modelCount = Number(countText);\n\treturn {\n\t\tactivePath,\n\t\tmodelCount: Number.isFinite(modelCount) ? modelCount : 0,\n\t\townedPath: ownedPathParts.join(\":\"),\n\t};\n}\n\nexport interface LocalRuntimeControllerDeps {\n\t/** Root directory OllamaRuntime instances are scoped under — fixed for the session's lifetime. */\n\tagentDir: string;\n\t/** Test-injectable seams for OllamaRuntime's own fetch/spawn/exists calls; unset in production. */\n\tlocalRuntimeDeps?: LocalRuntimeDeps;\n\t/** The session's last assistant message, to detect a just-failed local call and drop a stale\n\t * \"confirmed up\" flag. */\n\tgetLastAssistantMessage(): AssistantMessage | undefined;\n\t/** The session's live interactive UI context, if any — undefined in headless/RPC/print modes. */\n\tgetUIContext(): ExtensionUIContext | undefined;\n\t/** Emits a session event (only ever `warning` / `routing_start` / `routing_end` from this controller). */\n\temit(event: AgentSessionEvent): void;\n\t/** Resolves the model configured for a router tier, respecting configured auth — owned by the\n\t * router itself, not this controller. */\n\tresolveConfiguredTierModel(tier: \"medium\" | \"expensive\"): Model<Api> | undefined;\n\t/** `${provider}/${id}` label for a model, for warning/confirm text. */\n\tformatModel(model: Model<Api>): string;\n}\n\nexport class LocalRuntimeController {\n\t/** Lazy, cached by baseUrl so the router path and any other caller share one instance per server. */\n\tprivate readonly _runtimes = new Map<string, OllamaRuntime>();\n\t/** Lazy, cached by model+baseUrl so the router and `/models` share one sidecar handle per HF model. */\n\tprivate readonly _transformersRuntimes = new Map<string, TransformersRuntime>();\n\t/** Server URLs confirmed reachable THIS session — skips the health-check round trip on every\n\t * local-routed turn once warm. Keyed the same way as _runtimes. */\n\tprivate readonly _confirmedUp = new Set<string>();\n\t/** All live runtime adapters participate in one session-wide residency view. */\n\tprivate readonly _residencyAdapters = new Map<string, RuntimeResidencyAdapter>();\n\tprivate readonly _recentEvictions: RuntimeEvictionRecord[] = [];\n\n\tprivate readonly deps: LocalRuntimeControllerDeps;\n\n\tconstructor(deps: LocalRuntimeControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/**\n\t * Shared {@link OllamaRuntime} for a given server, lazily created and cached by baseUrl so every\n\t * caller — the router's readiness gate below and any host UI's own model-lifecycle commands\n\t * (e.g. `/models`) — sees and can stop the SAME pi-managed process instead of each tracking its\n\t * own untracked child.\n\t */\n\tgetLocalRuntime(baseUrl?: string): OllamaRuntime {\n\t\tconst key = baseUrl ?? \"default\";\n\t\tlet runtime = this._runtimes.get(key);\n\t\tif (!runtime) {\n\t\t\truntime = new OllamaRuntime({ agentDir: this.deps.agentDir, baseUrl, deps: this.deps.localRuntimeDeps });\n\t\t\tthis._runtimes.set(key, runtime);\n\t\t}\n\t\treturn runtime;\n\t}\n\n\tgetTransformersRuntime(modelId: string, baseUrl?: string): TransformersRuntime {\n\t\tconst resolvedBaseUrl = baseUrl?.replace(/\\/$/, \"\") ?? resolveTransformersBaseUrl(modelId);\n\t\tconst key = `${modelId}\\0${resolvedBaseUrl}`;\n\t\tlet runtime = this._transformersRuntimes.get(key);\n\t\tif (!runtime) {\n\t\t\truntime = new TransformersRuntime({\n\t\t\t\tagentDir: this.deps.agentDir,\n\t\t\t\tmodelId,\n\t\t\t\tbaseUrl: resolvedBaseUrl,\n\t\t\t\tdeps: this.deps.localRuntimeDeps,\n\t\t\t});\n\t\t\tthis._transformersRuntimes.set(key, runtime);\n\t\t}\n\t\treturn runtime;\n\t}\n\n\t/** models.json registers a local model's baseUrl as `<server>/v1` (OpenAI-compat); the runtime's\n\t * own health/boot endpoints are on the Ollama-native server root. */\n\tderiveOllamaServerUrl(modelBaseUrl: string): string {\n\t\treturn modelBaseUrl.replace(/\\/v1\\/?$/, \"\");\n\t}\n\n\tprivate deriveOpenAICompatServerUrl(modelBaseUrl: string): string {\n\t\treturn modelBaseUrl.replace(/\\/v1\\/?$/, \"\");\n\t}\n\n\tprivate isManagedLocalProvider(provider: string): boolean {\n\t\treturn provider === OLLAMA_PROVIDER || provider === HF_TRANSFORMERS_PROVIDER;\n\t}\n\n\t/**\n\t * If the last assistant message in this session was an error from THIS exact local server, a\n\t * cached \"confirmed up\" flag would be stale (the server may have died mid-session) — drop it so\n\t * the next ensure-check is a real one instead of trusting stale state.\n\t */\n\tprivate confirmationKey(model: Model<Api>, serverUrl: string): string {\n\t\treturn model.provider === HF_TRANSFORMERS_PROVIDER ? `${serverUrl}\\0${model.id}` : serverUrl;\n\t}\n\n\tprivate invalidateIfLastCallFailed(model: Model<Api>, serverUrl: string): void {\n\t\tconst lastAssistant = this.deps.getLastAssistantMessage();\n\t\tif (\n\t\t\tlastAssistant?.stopReason === \"error\" &&\n\t\t\tlastAssistant.provider === model.provider &&\n\t\t\tlastAssistant.model === model.id\n\t\t) {\n\t\t\tthis._confirmedUp.delete(this.confirmationKey(model, serverUrl));\n\t\t}\n\t}\n\n\tprivate async ensureOllamaResident(\n\t\tmodel: Model<Api>,\n\t\truntime: OllamaRuntime,\n\t\tknownSizeBytes?: number,\n\t): Promise<{ ok: boolean; reason?: string }> {\n\t\tlet bytes = knownSizeBytes ?? 0;\n\t\tif (knownSizeBytes === undefined) {\n\t\t\ttry {\n\t\t\t\tconst installed = await runtime.list();\n\t\t\t\tbytes = installed.find((entry) => matchesInstalledLocalModel(model.id, entry.name))?.sizeBytes ?? 0;\n\t\t\t} catch {\n\t\t\t\t// Residency is best-effort; a list failure falls back to a zero-byte admission request.\n\t\t\t}\n\t\t}\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tconst adapterId = `ollama:${serverUrl}`;\n\t\tthis._residencyAdapters.set(adapterId, new OllamaRuntimeResidencyAdapter(adapterId, runtime));\n\t\tconst arbiter = this.createResidencyArbiter();\n\t\ttry {\n\t\t\tconst nowMs = Date.now();\n\t\t\tconst plan = await arbiter.ensureResident(adapterId, {\n\t\t\t\tmodel: model.id,\n\t\t\t\tbytes,\n\t\t\t\trole: \"active\",\n\t\t\t\tpriority: 100,\n\t\t\t\tnowMs,\n\t\t\t\tpinActiveModel: model.id,\n\t\t\t\trecentEvictions: this._recentEvictions,\n\t\t\t\t// Cold Ollama loads can legitimately take several minutes. The actual adaptive stream\n\t\t\t\t// owns that wait; readiness must not front-run it with a 60-second empty generation.\n\t\t\t\tloadModel: false,\n\t\t\t});\n\t\t\tthis.recordEvictions(plan.evict, model.id, nowMs, adapterId);\n\t\t\treturn plan.status === \"fits\" ? { ok: true } : { ok: false, reason: `residency_refused:${plan.reason}` };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, reason: `residency_error:${error instanceof Error ? error.message : String(error)}` };\n\t\t}\n\t}\n\n\tprivate async ensureTransformersResident(\n\t\tmodel: Model<Api>,\n\t\truntime: TransformersRuntime,\n\t): Promise<{ ok: boolean; reason?: string }> {\n\t\tconst serverUrl = this.deriveOpenAICompatServerUrl(model.baseUrl);\n\t\tconst adapterId = `transformers:${model.id}:${serverUrl}`;\n\t\tthis._residencyAdapters.set(adapterId, new TransformersRuntimeResidencyAdapter(adapterId, runtime, model.id, 0));\n\t\tconst arbiter = this.createResidencyArbiter();\n\t\ttry {\n\t\t\tconst nowMs = Date.now();\n\t\t\tconst plan = await arbiter.ensureResident(adapterId, {\n\t\t\t\tmodel: model.id,\n\t\t\t\tbytes: 0,\n\t\t\t\trole: \"active\",\n\t\t\t\tpriority: 100,\n\t\t\t\tnowMs,\n\t\t\t\tpinActiveModel: model.id,\n\t\t\t\trecentEvictions: this._recentEvictions,\n\t\t\t\tloadModel: false,\n\t\t\t});\n\t\t\tthis.recordEvictions(plan.evict, model.id, nowMs, adapterId);\n\t\t\treturn plan.status === \"fits\" ? { ok: true } : { ok: false, reason: `residency_refused:${plan.reason}` };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, reason: `residency_error:${error instanceof Error ? error.message : String(error)}` };\n\t\t}\n\t}\n\n\tprivate createResidencyArbiter(): RuntimeResidencyArbiter {\n\t\treturn new RuntimeResidencyArbiter({\n\t\t\tbudgetBytes: Math.floor(totalmem() * 0.85),\n\t\t\tadapters: [...this._residencyAdapters.values()],\n\t\t});\n\t}\n\n\tprivate recordEvictions(\n\t\tevicted: readonly { adapterId: string; model: string }[],\n\t\tloaded: string,\n\t\tatMs: number,\n\t\tloadedAdapterId: string,\n\t): void {\n\t\tfor (const resident of evicted) {\n\t\t\tthis._recentEvictions.push({\n\t\t\t\tevicted: resident.model,\n\t\t\t\tloaded,\n\t\t\t\tatMs,\n\t\t\t\tevictedAdapterId: resident.adapterId,\n\t\t\t\tloadedAdapterId,\n\t\t\t});\n\t\t}\n\t\tconst cutoff = atMs - 5 * 60_000;\n\t\twhile (this._recentEvictions[0]?.atMs < cutoff) this._recentEvictions.shift();\n\t}\n\n\t/**\n\t * Readiness gate for every isolated/background completion. Unlike the foreground route gate it\n\t * never prompts or changes tiers: lanes either use the configured model or fail visibly.\n\t */\n\tasync ensureIsolatedModelReady(model: Model<Api>): Promise<void> {\n\t\tif (!this.isManagedLocalProvider(model.provider)) return;\n\t\tconst readiness =\n\t\t\tmodel.provider === OLLAMA_PROVIDER\n\t\t\t\t? await this.ensureLocalModelReady(model)\n\t\t\t\t: await this.ensureTransformersModelReady(model);\n\t\tif (readiness.ready) return;\n\t\tconst guide = readiness.installGuide?.length ? `\\n${readiness.installGuide.join(\"\\n\")}` : \"\";\n\t\tthrow new Error(\n\t\t\t`Managed local model ${this.deps.formatModel(model)} is unavailable for isolated execution (${readiness.reason}).${guide}`,\n\t\t);\n\t}\n\n\t/**\n\t * Readiness gate for a manually selected/default foreground model when no router route owns the\n\t * turn. Interactive sessions may offer the same managed-install consent as routed turns; the\n\t * selected model is never silently replaced.\n\t */\n\tasync ensureForegroundModelReady(model: Model<Api>): Promise<void> {\n\t\tif (!this.isManagedLocalProvider(model.provider)) return;\n\t\tlet readiness: {\n\t\t\tready: boolean;\n\t\t\treason: string;\n\t\t\tinstallGuide?: string[];\n\t\t\tinstallAttemptError?: string;\n\t\t} =\n\t\t\tmodel.provider === OLLAMA_PROVIDER\n\t\t\t\t? await this.ensureLocalModelReady(model)\n\t\t\t\t: await this.ensureTransformersModelReady(model);\n\t\tif (!readiness.ready) {\n\t\t\treadiness =\n\t\t\t\tmodel.provider === OLLAMA_PROVIDER\n\t\t\t\t\t? await this.maybeInstallOllamaOnConsent(model, readiness)\n\t\t\t\t\t: await this.maybeInstallTransformersOnConsent(model, readiness);\n\t\t}\n\t\tif (readiness.ready) return;\n\t\tconst detail = readiness.installAttemptError ?? readiness.installGuide?.join(\"\\n\") ?? readiness.reason;\n\t\tthrow new Error(`Managed local model ${this.deps.formatModel(model)} is unavailable: ${detail}`);\n\t}\n\n\t/**\n\t * Ensure a routed managed-local model is actually reachable before the turn calls it. No-op (and\n\t * free) for non-local/API models. Caches a \"confirmed up this session\" flag per server (and per\n\t * Transformers model) so steady-state routing pays the health-check round trip once; invalidated\n\t * above when a prior local call failed so a dead sidecar gets re-detected instead of trusted.\n\t */\n\tasync ensureLocalModelReady(\n\t\tmodel: Model<Api>,\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[] }> {\n\t\tif (model.provider !== OLLAMA_PROVIDER) {\n\t\t\treturn { ready: true, reason: \"not_local\" };\n\t\t}\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tconst confirmedKey = this.confirmationKey(model, serverUrl);\n\t\tthis.invalidateIfLastCallFailed(model, serverUrl);\n\t\tif (this._confirmedUp.has(confirmedKey)) {\n\t\t\treturn { ready: true, reason: \"confirmed_up_cached\" };\n\t\t}\n\t\tconst runtime = this.getLocalRuntime(serverUrl);\n\t\tconst status = await runtime.detect();\n\t\tif (status.serverUp) {\n\t\t\t// Server ownership is not a capability boundary. Reuse a configured user/system server\n\t\t\t// when it exposes the requested model; this preserves its accelerator settings and warm\n\t\t\t// cache instead of forcing a slower duplicate Pi-owned process/store.\n\t\t\tconst installedEntry = status.serverModels.find((entry) => matchesInstalledLocalModel(model.id, entry.name));\n\t\t\tif (!installedEntry) {\n\t\t\t\treturn {\n\t\t\t\t\tready: false,\n\t\t\t\t\treason: `model_missing_on_server:${model.id}:${status.activeStore?.path ?? \"external/unknown\"}`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst resident = await this.ensureOllamaResident(model, runtime, installedEntry.sizeBytes);\n\t\t\tif (!resident.ok) return { ready: false, reason: resident.reason ?? \"residency_refused\" };\n\t\t\tthis._confirmedUp.add(confirmedKey);\n\t\t\treturn {\n\t\t\t\tready: true,\n\t\t\t\treason: status.managedByPi ? \"already_running_managed\" : \"already_running_configured_server\",\n\t\t\t};\n\t\t}\n\t\tif (!status.binaryPath) {\n\t\t\treturn { ready: false, reason: \"binary_missing\", installGuide: runtime.installGuide() };\n\t\t}\n\t\tconst started = await runtime.start();\n\t\tif (started.started) {\n\t\t\tlet installedEntry: { name: string; sizeBytes: number } | undefined;\n\t\t\ttry {\n\t\t\t\tconst installed = await runtime.list();\n\t\t\t\tinstalledEntry = installed.find((entry) => matchesInstalledLocalModel(model.id, entry.name));\n\t\t\t} catch (error) {\n\t\t\t\truntime.stop();\n\t\t\t\treturn {\n\t\t\t\t\tready: false,\n\t\t\t\t\treason: `model_list_failed_after_start:${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (!installedEntry) {\n\t\t\t\truntime.stop();\n\t\t\t\treturn { ready: false, reason: `model_missing_on_started_server:${model.id}` };\n\t\t\t}\n\t\t\tconst resident = await this.ensureOllamaResident(model, runtime, installedEntry.sizeBytes);\n\t\t\tif (!resident.ok) {\n\t\t\t\truntime.stop();\n\t\t\t\treturn { ready: false, reason: resident.reason ?? \"residency_refused\" };\n\t\t\t}\n\t\t\tthis._confirmedUp.add(confirmedKey);\n\t\t}\n\t\treturn { ready: started.started, reason: started.reason };\n\t}\n\n\tasync ensureTransformersModelReady(\n\t\tmodel: Model<Api>,\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[] }> {\n\t\tif (model.provider !== HF_TRANSFORMERS_PROVIDER) {\n\t\t\treturn { ready: true, reason: \"not_transformers\" };\n\t\t}\n\t\tconst serverUrl = this.deriveOpenAICompatServerUrl(model.baseUrl);\n\t\tconst confirmedKey = this.confirmationKey(model, serverUrl);\n\t\tthis.invalidateIfLastCallFailed(model, serverUrl);\n\t\tif (this._confirmedUp.has(confirmedKey)) {\n\t\t\treturn { ready: true, reason: \"confirmed_up_cached\" };\n\t\t}\n\t\tconst runtime = this.getTransformersRuntime(model.id, serverUrl);\n\t\tconst status = await runtime.detect();\n\t\tif (status.serverUp) {\n\t\t\tconst resident = await this.ensureTransformersResident(model, runtime);\n\t\t\tif (!resident.ok) return { ready: false, reason: resident.reason ?? \"residency_refused\" };\n\t\t\tthis._confirmedUp.add(confirmedKey);\n\t\t\treturn { ready: true, reason: \"already_running\" };\n\t\t}\n\t\tif (!status.runtimeInstalled) {\n\t\t\treturn { ready: false, reason: \"runtime_missing\", installGuide: runtime.installGuide() };\n\t\t}\n\t\tconst started = await runtime.start();\n\t\tif (started.started || started.reason === \"already_running\") {\n\t\t\tconst resident = await this.ensureTransformersResident(model, runtime);\n\t\t\tif (!resident.ok) return { ready: false, reason: resident.reason ?? \"residency_refused\" };\n\t\t\tthis._confirmedUp.add(confirmedKey);\n\t\t\treturn { ready: true, reason: started.reason };\n\t\t}\n\t\treturn { ready: false, reason: started.reason };\n\t}\n\n\t/**\n\t * #31: the ONE case a routed local model's unreadiness can be fixed automatically is a missing\n\t * ollama binary — an unreachable server can't be helped by installing, so that reason is left to\n\t * the graceful-fallback warning below unchanged. Only offered when there's an interactive UI to\n\t * ask through: headless/RPC/print sessions have no UI context and fall straight through, same as\n\t * declining or timing out (both resolve confirm() to false). Reverses \"pi never runs installers\n\t * itself\" specifically for this one path — the user is asked first, the download is pi's own\n\t * (never curl|sh), and it lands in pi's own runtimes dir (see OllamaRuntime.installManaged).\n\t *\n\t * Pauses/resumes the routing working-indicator around the confirm dialog itself (re-emitting\n\t * routing_end/routing_start — both already idempotent, see interactive-mode.ts's handlers) so an\n\t * animated spinner doesn't fight a dialog the user is trying to read and answer; the indicator\n\t * comes back for the download/extract that follows a \"yes\", which is genuine processing feedback.\n\t */\n\tprivate async maybeInstallOllamaOnConsent(\n\t\tmodel: Model<Api>,\n\t\treadiness: { ready: boolean; reason: string; installGuide?: string[] },\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string }> {\n\t\tconst ui = this.deps.getUIContext();\n\t\tif (!ui || readiness.ready || readiness.reason !== \"binary_missing\") return readiness;\n\n\t\tconst modelLabel = this.deps.formatModel(model);\n\t\tthis.deps.emit({ type: \"routing_end\" });\n\t\tlet confirmed: boolean;\n\t\ttry {\n\t\t\tconfirmed = await ui.confirm(\n\t\t\t\t\"Install Ollama?\",\n\t\t\t\t`Ollama isn't installed, so the local model \"${modelLabel}\" can't run. Pi can download and ` +\n\t\t\t\t\t\"install it now (a large one-time download, possibly over 1 GB depending on your platform) \" +\n\t\t\t\t\t\"into its own runtimes folder — never curl|sh, never touching anything outside pi's own \" +\n\t\t\t\t\t\"directory. Install it now?\",\n\t\t\t\t{ timeout: OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS },\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.deps.emit({ type: \"routing_start\" });\n\t\t}\n\t\tif (!confirmed) return readiness;\n\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tconst runtime = this.getLocalRuntime(serverUrl);\n\t\tlet installResult: { ok: boolean; error?: string };\n\t\ttry {\n\t\t\tinstallResult = await runtime.installManaged((status) => ui.setStatus(\"ollama-install\", status));\n\t\t} finally {\n\t\t\tui.setStatus(\"ollama-install\", undefined);\n\t\t}\n\t\tif (!installResult.ok) {\n\t\t\treturn { ready: false, reason: \"install_failed\", installAttemptError: installResult.error };\n\t\t}\n\t\treturn this.ensureLocalModelReady(model);\n\t}\n\n\tprivate async maybeInstallTransformersOnConsent(\n\t\tmodel: Model<Api>,\n\t\treadiness: { ready: boolean; reason: string; installGuide?: string[] },\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string }> {\n\t\tconst ui = this.deps.getUIContext();\n\t\tif (!ui || readiness.ready || readiness.reason !== \"runtime_missing\") return readiness;\n\n\t\tconst modelLabel = this.deps.formatModel(model);\n\t\tthis.deps.emit({ type: \"routing_end\" });\n\t\tlet confirmed: boolean;\n\t\ttry {\n\t\t\tconfirmed = await ui.confirm(\n\t\t\t\t\"Install Transformers runtime?\",\n\t\t\t\t`The Hugging Face model \"${modelLabel}\" needs a pi-managed Python venv with Transformers ` +\n\t\t\t\t\t\"and CPU PyTorch before it can run. Pi will install those packages into its own runtimes \" +\n\t\t\t\t\t\"folder, download the model into a pi-owned Hugging Face cache, and leave system Python, \" +\n\t\t\t\t\t\"your Ollama models, and your user HF cache untouched. Install it now?\",\n\t\t\t\t{ timeout: OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS },\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.deps.emit({ type: \"routing_start\" });\n\t\t}\n\t\tif (!confirmed) return readiness;\n\n\t\tconst serverUrl = this.deriveOpenAICompatServerUrl(model.baseUrl);\n\t\tconst runtime = this.getTransformersRuntime(model.id, serverUrl);\n\t\tlet installResult: { ok: boolean; error?: string };\n\t\ttry {\n\t\t\tinstallResult = await runtime.installManaged((status) => ui.setStatus(\"transformers-install\", status));\n\t\t} finally {\n\t\t\tui.setStatus(\"transformers-install\", undefined);\n\t\t}\n\t\tif (!installResult.ok) {\n\t\t\treturn { ready: false, reason: \"install_failed\", installAttemptError: installResult.error };\n\t\t}\n\t\tlet downloadResult: { ok: boolean; error?: string };\n\t\ttry {\n\t\t\tdownloadResult = await runtime.downloadModel((status) => ui.setStatus(\"transformers-download\", status));\n\t\t} finally {\n\t\t\tui.setStatus(\"transformers-download\", undefined);\n\t\t}\n\t\tif (!downloadResult.ok) {\n\t\t\treturn { ready: false, reason: \"download_failed\", installAttemptError: downloadResult.error };\n\t\t}\n\t\treturn this.ensureTransformersModelReady(model);\n\t}\n\n\t/**\n\t * Router-swap gate (#27): a turn routed to a local model (any tier, including an executor-direct\n\t * route — both carry tier \"cheap\") must not dead-end the turn just because ollama isn't up.\n\t * Never a SILENT swap: every fallback is announced in a warning that states (i) the local model\n\t * was unavailable and WHY — binary missing surfaces the install guide inline; any other reason\n\t * gets a \"check that ollama is running\" hint — and (ii) which tier is now handling the turn, so\n\t * the cost shift is never a surprise. Escalates cheap -> medium -> expensive, skipping any\n\t * unconfigured intermediate tier, reusing the router's own existing \"model unavailable\"\n\t * resolution (resolveConfiguredTierModel) rather than inventing a new fallback mechanism.\n\t * Escalation is bounded: tier strictly increases each hop, so it terminates within two hops.\n\t *\n\t * Before the warning/escalation below: #31's consent gate gets one shot at fixing a missing\n\t * binary interactively (see maybeInstallOllamaOnConsent) — declining, timing out, running\n\t * headless, or the install attempt itself failing all fall through here unchanged, just with an\n\t * honest reason (an install that failed is worded as a failed install, not re-labeled as if\n\t * nothing was ever tried).\n\t */\n\tasync ensureRouteModelReady(\n\t\tresolved: { decision: RouteDecision; model: Model<Api> } | undefined,\n\t): Promise<{ decision: RouteDecision; model: Model<Api> } | undefined> {\n\t\tlet current = resolved;\n\t\twhile (current && this.isManagedLocalProvider(current.model.provider)) {\n\t\t\tlet readiness: { ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string } =\n\t\t\t\tcurrent.model.provider === OLLAMA_PROVIDER\n\t\t\t\t\t? await this.ensureLocalModelReady(current.model)\n\t\t\t\t\t: await this.ensureTransformersModelReady(current.model);\n\t\t\tif (!readiness.ready) {\n\t\t\t\treadiness =\n\t\t\t\t\tcurrent.model.provider === OLLAMA_PROVIDER\n\t\t\t\t\t\t? await this.maybeInstallOllamaOnConsent(current.model, readiness)\n\t\t\t\t\t\t: await this.maybeInstallTransformersOnConsent(current.model, readiness);\n\t\t\t}\n\t\t\tif (readiness.ready) return current;\n\n\t\t\t// Walk the remaining tiers in order (never back down to cheap) and take the first one that\n\t\t\t// actually resolves — an unconfigured intermediate tier (e.g. no mediumModel set) must be\n\t\t\t// skipped, not treated as \"no fallback available\".\n\t\t\tconst startIndex = MODEL_ROUTER_TIER_ORDER.indexOf(current.decision.tier as \"cheap\" | \"medium\" | \"expensive\");\n\t\t\tlet escalated: { tier: \"medium\" | \"expensive\"; model: Model<Api> } | undefined;\n\t\t\tfor (let i = startIndex + 1; startIndex !== -1 && i < MODEL_ROUTER_TIER_ORDER.length; i++) {\n\t\t\t\tconst tier = MODEL_ROUTER_TIER_ORDER[i] as \"medium\" | \"expensive\";\n\t\t\t\tconst model = this.deps.resolveConfiguredTierModel(tier);\n\t\t\t\tif (model) {\n\t\t\t\t\tescalated = { tier, model };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst modelLabel = this.deps.formatModel(current.model);\n\t\t\tconst localRuntimeName = current.model.provider === OLLAMA_PROVIDER ? \"ollama\" : \"Transformers\";\n\t\t\tconst wrongStore = parseWrongOllamaStoreReason(readiness.reason);\n\t\t\tconst whyText = readiness.installAttemptError\n\t\t\t\t? `pi tried to install it just now, but the install attempt failed: ${readiness.installAttemptError}`\n\t\t\t\t: readiness.installGuide\n\t\t\t\t\t? [\n\t\t\t\t\t\t\tcurrent.model.provider === OLLAMA_PROVIDER\n\t\t\t\t\t\t\t\t? \"the ollama binary is not installed.\"\n\t\t\t\t\t\t\t\t: \"the pi-managed Transformers runtime is not installed.\",\n\t\t\t\t\t\t\t...readiness.installGuide,\n\t\t\t\t\t\t].join(\"\\n\")\n\t\t\t\t\t: wrongStore\n\t\t\t\t\t\t? `an Ollama server is already running with store ${wrongStore.activePath} (${wrongStore.modelCount} model(s)); pi's owned store is ${wrongStore.ownedPath}. Stop the other server or run /models import, then retry.`\n\t\t\t\t\t\t: current.model.provider === OLLAMA_PROVIDER\n\t\t\t\t\t\t\t? `its ${localRuntimeName} server is not reachable (${readiness.reason}) — check that ollama is running.`\n\t\t\t\t\t\t\t: `its ${localRuntimeName} server is not reachable (${readiness.reason}) — check that the runtime is running.`;\n\t\t\tconst fallbackText = escalated\n\t\t\t\t? `Falling back to the ${escalated.tier} tier for this turn.`\n\t\t\t\t: \"No other tier is configured — falling back to the session's default model.\";\n\t\t\tthis.deps.emit({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tmessage: `Local model \"${modelLabel}\" is unavailable: ${whyText}\\n${fallbackText}`,\n\t\t\t});\n\n\t\t\tif (!escalated) return undefined; // no higher tier resolves — caller falls back to the session default\n\t\t\tcurrent = {\n\t\t\t\tmodel: escalated.model,\n\t\t\t\tdecision: {\n\t\t\t\t\t...current.decision,\n\t\t\t\t\ttier: escalated.tier,\n\t\t\t\t\tfallbackFrom: current.decision.tier,\n\t\t\t\t\treasonCode: \"local_model_not_ready_fallback\",\n\t\t\t\t\treasons: [\n\t\t\t\t\t\t...current.decision.reasons,\n\t\t\t\t\t\t`Local model not ready (${readiness.reason}); escalated to ${escalated.tier}`,\n\t\t\t\t\t],\n\t\t\t\t\tmodel: this.deps.formatModel(escalated.model),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn current;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"local-runtime-controller.js","sourceRoot":"","sources":["../../src/core/local-runtime-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local-runtime (Ollama) lifecycle controller.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the cached, per-server\n * {@link OllamaRuntime} instances, the \"confirmed up this session\" flag, and the router's readiness\n * gate for a turn routed to a local (`ollama`) model — including the #31 install-on-consent flow and\n * the #27 graceful tier-escalation fallback. Takes narrow deps (agent dir, a last-assistant-message\n * accessor, the session's UI context/event emitter, and the router's own tier resolver) rather than\n * the whole AgentSession.\n */\n\nimport { totalmem } from \"node:os\";\nimport type { Api, AssistantMessage, Model } from \"@caupulican/pi-ai\";\nimport type { AgentSessionEvent } from \"./agent-session.ts\";\nimport type { RouteDecision } from \"./autonomy/contracts.ts\";\nimport type { ExtensionUIContext } from \"./extensions/index.ts\";\nimport { type PrismLlamaCppDeps, PrismLlamaCppRuntime } from \"./models/llamacpp-runtime.ts\";\nimport { HF_TRANSFORMERS_PROVIDER, OLLAMA_PROVIDER } from \"./models/local-registration.ts\";\nimport {\n\ttype LocalRuntimeDeps,\n\tOllamaRuntime,\n\tresolveTransformersBaseUrl,\n\tTransformersRuntime,\n} from \"./models/local-runtime.ts\";\nimport { matchesInstalledLocalModel } from \"./models/model-ref.ts\";\nimport {\n\tensurePrismModelFilesThenServe,\n\tisPiManagedPrismLlamaCppModel,\n\tisPrismLlamaCppServerHealthy,\n\tPRISM_LLAMACPP_DESCRIPTORS,\n\tPRISM_LLAMACPP_SERVE_PORT,\n} from \"./models/prism-llamacpp-lifecycle.ts\";\nimport {\n\tOllamaRuntimeResidencyAdapter,\n\ttype RuntimeEvictionRecord,\n\ttype RuntimeResidencyAdapter,\n\tRuntimeResidencyArbiter,\n\tTransformersRuntimeResidencyAdapter,\n} from \"./models/runtime-arbiter.ts\";\n\n/** User-facing router tiers in ascending order — \"learning\" is never selected for a user turn, so\n * it has no place in the escalation ladder (#27's ensureRouteModelReady walks this forward only). */\nconst MODEL_ROUTER_TIER_ORDER: readonly (\"cheap\" | \"medium\" | \"expensive\")[] = [\"cheap\", \"medium\", \"expensive\"];\n\n/** How long the #31 \"install ollama now?\" confirm waits before auto-dismissing (same as a \"No\") —\n * long enough to read and decide, short enough that an unattended session doesn't hang a turn on it. */\nconst OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS = 30_000;\n\nfunction parseWrongOllamaStoreReason(\n\treason: string,\n): { activePath: string; modelCount: number; ownedPath: string } | undefined {\n\tif (!reason.startsWith(\"wrong_store:\")) return undefined;\n\tconst parts = reason.slice(\"wrong_store:\".length).split(\":\");\n\tif (parts.length < 3) return undefined;\n\tconst [activePath, countText, ...ownedPathParts] = parts;\n\tconst modelCount = Number(countText);\n\treturn {\n\t\tactivePath,\n\t\tmodelCount: Number.isFinite(modelCount) ? modelCount : 0,\n\t\townedPath: ownedPathParts.join(\":\"),\n\t};\n}\n\nexport interface LocalRuntimeControllerDeps {\n\t/** Root directory OllamaRuntime instances are scoped under — fixed for the session's lifetime. */\n\tagentDir: string;\n\t/** Test-injectable seams for OllamaRuntime's own fetch/spawn/exists calls; unset in production. */\n\tlocalRuntimeDeps?: LocalRuntimeDeps;\n\t/** Test-injectable seams for PrismLlamaCppRuntime's own fetch/spawn/exists calls; unset in\n\t * production (see {@link getPrismLlamaCppRuntime}). */\n\tprismLlamaCppDeps?: PrismLlamaCppDeps;\n\t/** The session's last assistant message, to detect a just-failed local call and drop a stale\n\t * \"confirmed up\" flag. */\n\tgetLastAssistantMessage(): AssistantMessage | undefined;\n\t/** The session's live interactive UI context, if any — undefined in headless/RPC/print modes. */\n\tgetUIContext(): ExtensionUIContext | undefined;\n\t/** Emits a session event (only ever `warning` / `routing_start` / `routing_end` from this controller). */\n\temit(event: AgentSessionEvent): void;\n\t/** Resolves the model configured for a router tier, respecting configured auth — owned by the\n\t * router itself, not this controller. */\n\tresolveConfiguredTierModel(tier: \"medium\" | \"expensive\"): Model<Api> | undefined;\n\t/** `${provider}/${id}` label for a model, for warning/confirm text. */\n\tformatModel(model: Model<Api>): string;\n}\n\nexport class LocalRuntimeController {\n\t/** Lazy, cached by baseUrl so the router path and any other caller share one instance per server. */\n\tprivate readonly _runtimes = new Map<string, OllamaRuntime>();\n\t/** Lazy, cached by model+baseUrl so the router and `/models` share one sidecar handle per HF model. */\n\tprivate readonly _transformersRuntimes = new Map<string, TransformersRuntime>();\n\t/** Lazy, cached (not baseUrl-keyed like the two above — pi's prism install is a single managed\n\t * instance under agentDir, never an externally-configured server) so `/models add`, `/models\n\t * stop`, `/models remove`, and the readiness gate below all share the SAME instance and can\n\t * reliably reattach to a process this session started (`stop()`/`isRunning()` rely on in-memory\n\t * child-process state). */\n\tprivate _prismLlamaCppRuntime: PrismLlamaCppRuntime | undefined;\n\t/** Server URLs confirmed reachable THIS session — skips the health-check round trip on every\n\t * local-routed turn once warm. Keyed the same way as _runtimes. */\n\tprivate readonly _confirmedUp = new Set<string>();\n\t/** All live runtime adapters participate in one session-wide residency view. */\n\tprivate readonly _residencyAdapters = new Map<string, RuntimeResidencyAdapter>();\n\tprivate readonly _recentEvictions: RuntimeEvictionRecord[] = [];\n\n\tprivate readonly deps: LocalRuntimeControllerDeps;\n\n\tconstructor(deps: LocalRuntimeControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/**\n\t * Shared {@link OllamaRuntime} for a given server, lazily created and cached by baseUrl so every\n\t * caller — the router's readiness gate below and any host UI's own model-lifecycle commands\n\t * (e.g. `/models`) — sees and can stop the SAME pi-managed process instead of each tracking its\n\t * own untracked child.\n\t */\n\tgetLocalRuntime(baseUrl?: string): OllamaRuntime {\n\t\tconst key = baseUrl ?? \"default\";\n\t\tlet runtime = this._runtimes.get(key);\n\t\tif (!runtime) {\n\t\t\truntime = new OllamaRuntime({ agentDir: this.deps.agentDir, baseUrl, deps: this.deps.localRuntimeDeps });\n\t\t\tthis._runtimes.set(key, runtime);\n\t\t}\n\t\treturn runtime;\n\t}\n\n\tgetTransformersRuntime(modelId: string, baseUrl?: string): TransformersRuntime {\n\t\tconst resolvedBaseUrl = baseUrl?.replace(/\\/$/, \"\") ?? resolveTransformersBaseUrl(modelId);\n\t\tconst key = this._transformersRuntimeKey(modelId, resolvedBaseUrl);\n\t\tlet runtime = this._transformersRuntimes.get(key);\n\t\tif (!runtime) {\n\t\t\truntime = new TransformersRuntime({\n\t\t\t\tagentDir: this.deps.agentDir,\n\t\t\t\tmodelId,\n\t\t\t\tbaseUrl: resolvedBaseUrl,\n\t\t\t\tdeps: this.deps.localRuntimeDeps,\n\t\t\t});\n\t\t\tthis._transformersRuntimes.set(key, runtime);\n\t\t}\n\t\treturn runtime;\n\t}\n\n\t/**\n\t * Shared {@link PrismLlamaCppRuntime} for pi's own managed prism install — lazily created, cached\n\t * for the controller's lifetime so `/models` (add/stop/remove) and the readiness gate below\n\t * always see and can stop the SAME pi-managed process.\n\t */\n\tgetPrismLlamaCppRuntime(): PrismLlamaCppRuntime {\n\t\tif (!this._prismLlamaCppRuntime) {\n\t\t\tthis._prismLlamaCppRuntime = new PrismLlamaCppRuntime({\n\t\t\t\tagentDir: this.deps.agentDir,\n\t\t\t\tdeps: this.deps.prismLlamaCppDeps,\n\t\t\t});\n\t\t}\n\t\treturn this._prismLlamaCppRuntime;\n\t}\n\n\t/** models.json registers a local model's baseUrl as `<server>/v1` (OpenAI-compat); the runtime's\n\t * own health/boot endpoints are on the Ollama-native server root. */\n\tderiveOllamaServerUrl(modelBaseUrl: string): string {\n\t\treturn modelBaseUrl.replace(/\\/v1\\/?$/, \"\");\n\t}\n\n\tprivate deriveOpenAICompatServerUrl(modelBaseUrl: string): string {\n\t\treturn modelBaseUrl.replace(/\\/v1\\/?$/, \"\");\n\t}\n\n\tprivate isManagedLocalProvider(provider: string): boolean {\n\t\treturn provider === OLLAMA_PROVIDER || provider === HF_TRANSFORMERS_PROVIDER;\n\t}\n\n\t/** Ollama/Transformers (provider-scoped) OR a pi-registered prism llama.cpp model (id-scoped —\n\t * see {@link isPiManagedPrismLlamaCppModel}, which never matches a user's own hand-configured\n\t * `llama-cpp` entry such as the built-in `llama-cpp/local` catalog model). */\n\tprivate isManagedLocalModel(model: Model<Api>): boolean {\n\t\treturn this.isManagedLocalProvider(model.provider) || isPiManagedPrismLlamaCppModel(model);\n\t}\n\n\t/** Three-way readiness dispatch shared by ensureIsolatedModelReady/ensureForegroundModelReady/\n\t * ensureRouteModelReady — one place to add a new managed-local kind instead of tripling a ternary. */\n\tprivate async ensureManagedLocalReadiness(\n\t\tmodel: Model<Api>,\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[] }> {\n\t\tif (model.provider === OLLAMA_PROVIDER) return this.ensureLocalModelReady(model);\n\t\tif (model.provider === HF_TRANSFORMERS_PROVIDER) return this.ensureTransformersModelReady(model);\n\t\treturn this.ensurePrismLlamaCppModelReady(model);\n\t}\n\n\t/** Consent-gated install dispatch, paired with {@link ensureManagedLocalReadiness}. Prism\n\t * llama.cpp has no consent step here: unlike Ollama's #31 first-run \"install the binary?\" and\n\t * Transformers' \"install the runtime?\" prompts (which handle a runtime that was NEVER installed),\n\t * a prism model only reaches this gate after a prior `/models add` already installed the runtime\n\t * and the curated-menu pick already WAS the consent (same doctrine as the Transformers\n\t * precedent) — a later self-heal (re-download a missing file, restart a dead server) is\n\t * maintaining an install the user already approved, not a fresh one needing to be asked again. */\n\tprivate async maybeInstallManagedLocalOnConsent(\n\t\tmodel: Model<Api>,\n\t\treadiness: { ready: boolean; reason: string; installGuide?: string[] },\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string }> {\n\t\tif (model.provider === OLLAMA_PROVIDER) return this.maybeInstallOllamaOnConsent(model, readiness);\n\t\tif (model.provider === HF_TRANSFORMERS_PROVIDER) return this.maybeInstallTransformersOnConsent(model, readiness);\n\t\treturn readiness;\n\t}\n\n\t/**\n\t * ALWAYS scoped to both the server AND the exact model — never bare `serverUrl`. A single Ollama\n\t * (or prism) server can host several models, and a serverUrl-only key let the FIRST model\n\t * confirmed on a server silently wave through every other model requested on that same server:\n\t * the cache hit at {@link ensureLocalModelReady}/{@link ensurePrismLlamaCppModelReady} short-\n\t * circuits BEFORE the installed-model check and the residency arbiter run, so a genuinely missing\n\t * model surfaced as a raw runtime error instead of `model_missing_on_server`, and residency\n\t * bookkeeping silently skipped a model it never actually admitted. Transformers already had this\n\t * right (one server per model, by construction); this makes every provider consistent.\n\t */\n\tprivate confirmationKey(model: Model<Api>, serverUrl: string): string {\n\t\treturn `${serverUrl}\\0${model.id}`;\n\t}\n\n\tprivate invalidateIfLastCallFailed(model: Model<Api>, serverUrl: string): void {\n\t\tconst lastAssistant = this.deps.getLastAssistantMessage();\n\t\tif (\n\t\t\tlastAssistant?.stopReason === \"error\" &&\n\t\t\tlastAssistant.provider === model.provider &&\n\t\t\tlastAssistant.model === model.id\n\t\t) {\n\t\t\tthis._confirmedUp.delete(this.confirmationKey(model, serverUrl));\n\t\t}\n\t}\n\n\tprivate async ensureOllamaResident(\n\t\tmodel: Model<Api>,\n\t\truntime: OllamaRuntime,\n\t\tknownSizeBytes?: number,\n\t): Promise<{ ok: boolean; reason?: string }> {\n\t\tlet bytes = knownSizeBytes ?? 0;\n\t\tif (knownSizeBytes === undefined) {\n\t\t\ttry {\n\t\t\t\tconst installed = await runtime.list();\n\t\t\t\tbytes = installed.find((entry) => matchesInstalledLocalModel(model.id, entry.name))?.sizeBytes ?? 0;\n\t\t\t} catch {\n\t\t\t\t// Residency is best-effort; a list failure falls back to a zero-byte admission request.\n\t\t\t}\n\t\t}\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tconst adapterId = `ollama:${serverUrl}`;\n\t\tthis._residencyAdapters.set(adapterId, new OllamaRuntimeResidencyAdapter(adapterId, runtime));\n\t\tconst arbiter = this.createResidencyArbiter();\n\t\ttry {\n\t\t\tconst nowMs = Date.now();\n\t\t\tconst plan = await arbiter.ensureResident(adapterId, {\n\t\t\t\tmodel: model.id,\n\t\t\t\tbytes,\n\t\t\t\trole: \"active\",\n\t\t\t\tpriority: 100,\n\t\t\t\tnowMs,\n\t\t\t\tpinActiveModel: model.id,\n\t\t\t\trecentEvictions: this._recentEvictions,\n\t\t\t\t// Cold Ollama loads can legitimately take several minutes. The actual adaptive stream\n\t\t\t\t// owns that wait; readiness must not front-run it with a 60-second empty generation.\n\t\t\t\tloadModel: false,\n\t\t\t});\n\t\t\tthis.recordEvictions(plan.evict, model.id, nowMs, adapterId);\n\t\t\treturn plan.status === \"fits\" ? { ok: true } : { ok: false, reason: `residency_refused:${plan.reason}` };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, reason: `residency_error:${error instanceof Error ? error.message : String(error)}` };\n\t\t}\n\t}\n\n\tprivate async ensureTransformersResident(\n\t\tmodel: Model<Api>,\n\t\truntime: TransformersRuntime,\n\t): Promise<{ ok: boolean; reason?: string }> {\n\t\tconst serverUrl = this.deriveOpenAICompatServerUrl(model.baseUrl);\n\t\tconst adapterId = `transformers:${model.id}:${serverUrl}`;\n\t\tthis._residencyAdapters.set(adapterId, new TransformersRuntimeResidencyAdapter(adapterId, runtime, model.id, 0));\n\t\tconst arbiter = this.createResidencyArbiter();\n\t\ttry {\n\t\t\tconst nowMs = Date.now();\n\t\t\tconst plan = await arbiter.ensureResident(adapterId, {\n\t\t\t\tmodel: model.id,\n\t\t\t\tbytes: 0,\n\t\t\t\trole: \"active\",\n\t\t\t\tpriority: 100,\n\t\t\t\tnowMs,\n\t\t\t\tpinActiveModel: model.id,\n\t\t\t\trecentEvictions: this._recentEvictions,\n\t\t\t\tloadModel: false,\n\t\t\t});\n\t\t\tthis.recordEvictions(plan.evict, model.id, nowMs, adapterId);\n\t\t\treturn plan.status === \"fits\" ? { ok: true } : { ok: false, reason: `residency_refused:${plan.reason}` };\n\t\t} catch (error) {\n\t\t\treturn { ok: false, reason: `residency_error:${error instanceof Error ? error.message : String(error)}` };\n\t\t}\n\t}\n\n\tprivate createResidencyArbiter(): RuntimeResidencyArbiter {\n\t\treturn new RuntimeResidencyArbiter({\n\t\t\tbudgetBytes: Math.floor(totalmem() * 0.85),\n\t\t\tadapters: [...this._residencyAdapters.values()],\n\t\t});\n\t}\n\n\tprivate recordEvictions(\n\t\tevicted: readonly { adapterId: string; model: string }[],\n\t\tloaded: string,\n\t\tatMs: number,\n\t\tloadedAdapterId: string,\n\t): void {\n\t\tfor (const resident of evicted) {\n\t\t\tthis._recentEvictions.push({\n\t\t\t\tevicted: resident.model,\n\t\t\t\tloaded,\n\t\t\t\tatMs,\n\t\t\t\tevictedAdapterId: resident.adapterId,\n\t\t\t\tloadedAdapterId,\n\t\t\t});\n\t\t}\n\t\tconst cutoff = atMs - 5 * 60_000;\n\t\twhile (this._recentEvictions[0]?.atMs < cutoff) this._recentEvictions.shift();\n\t}\n\n\t/**\n\t * Readiness gate for every isolated/background completion. Unlike the foreground route gate it\n\t * never prompts or changes tiers: lanes either use the configured model or fail visibly.\n\t */\n\tasync ensureIsolatedModelReady(model: Model<Api>): Promise<void> {\n\t\tif (!this.isManagedLocalModel(model)) return;\n\t\tconst readiness = await this.ensureManagedLocalReadiness(model);\n\t\tif (readiness.ready) return;\n\t\tconst guide = readiness.installGuide?.length ? `\\n${readiness.installGuide.join(\"\\n\")}` : \"\";\n\t\tthrow new Error(\n\t\t\t`Managed local model ${this.deps.formatModel(model)} is unavailable for isolated execution (${readiness.reason}).${guide}`,\n\t\t);\n\t}\n\n\t/**\n\t * Readiness gate for a manually selected/default foreground model when no router route owns the\n\t * turn. Interactive sessions may offer the same managed-install consent as routed turns; the\n\t * selected model is never silently replaced.\n\t */\n\tasync ensureForegroundModelReady(model: Model<Api>): Promise<void> {\n\t\tif (!this.isManagedLocalModel(model)) return;\n\t\tlet readiness: {\n\t\t\tready: boolean;\n\t\t\treason: string;\n\t\t\tinstallGuide?: string[];\n\t\t\tinstallAttemptError?: string;\n\t\t} = await this.ensureManagedLocalReadiness(model);\n\t\tif (!readiness.ready) {\n\t\t\treadiness = await this.maybeInstallManagedLocalOnConsent(model, readiness);\n\t\t}\n\t\tif (readiness.ready) return;\n\t\tconst detail = readiness.installAttemptError ?? readiness.installGuide?.join(\"\\n\") ?? readiness.reason;\n\t\tthrow new Error(`Managed local model ${this.deps.formatModel(model)} is unavailable: ${detail}`);\n\t}\n\n\t/**\n\t * Ensure a routed managed-local model is actually reachable before the turn calls it. No-op (and\n\t * free) for non-local/API models. Caches a \"confirmed up this session\" flag per server (and per\n\t * Transformers model) so steady-state routing pays the health-check round trip once; invalidated\n\t * above when a prior local call failed so a dead sidecar gets re-detected instead of trusted.\n\t */\n\tasync ensureLocalModelReady(\n\t\tmodel: Model<Api>,\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[] }> {\n\t\tif (model.provider !== OLLAMA_PROVIDER) {\n\t\t\treturn { ready: true, reason: \"not_local\" };\n\t\t}\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tconst confirmedKey = this.confirmationKey(model, serverUrl);\n\t\tthis.invalidateIfLastCallFailed(model, serverUrl);\n\t\tif (this._confirmedUp.has(confirmedKey)) {\n\t\t\treturn { ready: true, reason: \"confirmed_up_cached\" };\n\t\t}\n\t\tconst runtime = this.getLocalRuntime(serverUrl);\n\t\tconst status = await runtime.detect();\n\t\tif (status.serverUp) {\n\t\t\t// Server ownership is not a capability boundary. Reuse a configured user/system server\n\t\t\t// when it exposes the requested model; this preserves its accelerator settings and warm\n\t\t\t// cache instead of forcing a slower duplicate Pi-owned process/store.\n\t\t\tconst installedEntry = status.serverModels.find((entry) => matchesInstalledLocalModel(model.id, entry.name));\n\t\t\tif (!installedEntry) {\n\t\t\t\treturn {\n\t\t\t\t\tready: false,\n\t\t\t\t\treason: `model_missing_on_server:${model.id}:${status.activeStore?.path ?? \"external/unknown\"}`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst resident = await this.ensureOllamaResident(model, runtime, installedEntry.sizeBytes);\n\t\t\tif (!resident.ok) return { ready: false, reason: resident.reason ?? \"residency_refused\" };\n\t\t\tthis._confirmedUp.add(confirmedKey);\n\t\t\treturn {\n\t\t\t\tready: true,\n\t\t\t\treason: status.managedByPi ? \"already_running_managed\" : \"already_running_configured_server\",\n\t\t\t};\n\t\t}\n\t\tif (!status.binaryPath) {\n\t\t\treturn { ready: false, reason: \"binary_missing\", installGuide: runtime.installGuide() };\n\t\t}\n\t\tconst started = await runtime.start();\n\t\tif (started.started) {\n\t\t\tlet installedEntry: { name: string; sizeBytes: number } | undefined;\n\t\t\ttry {\n\t\t\t\tconst installed = await runtime.list();\n\t\t\t\tinstalledEntry = installed.find((entry) => matchesInstalledLocalModel(model.id, entry.name));\n\t\t\t} catch (error) {\n\t\t\t\truntime.stop();\n\t\t\t\treturn {\n\t\t\t\t\tready: false,\n\t\t\t\t\treason: `model_list_failed_after_start:${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (!installedEntry) {\n\t\t\t\truntime.stop();\n\t\t\t\treturn { ready: false, reason: `model_missing_on_started_server:${model.id}` };\n\t\t\t}\n\t\t\tconst resident = await this.ensureOllamaResident(model, runtime, installedEntry.sizeBytes);\n\t\t\tif (!resident.ok) {\n\t\t\t\truntime.stop();\n\t\t\t\treturn { ready: false, reason: resident.reason ?? \"residency_refused\" };\n\t\t\t}\n\t\t\tthis._confirmedUp.add(confirmedKey);\n\t\t}\n\t\treturn { ready: started.started, reason: started.reason };\n\t}\n\n\tasync ensureTransformersModelReady(\n\t\tmodel: Model<Api>,\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[] }> {\n\t\tif (model.provider !== HF_TRANSFORMERS_PROVIDER) {\n\t\t\treturn { ready: true, reason: \"not_transformers\" };\n\t\t}\n\t\tconst serverUrl = this.deriveOpenAICompatServerUrl(model.baseUrl);\n\t\tconst confirmedKey = this.confirmationKey(model, serverUrl);\n\t\tthis.invalidateIfLastCallFailed(model, serverUrl);\n\t\tif (this._confirmedUp.has(confirmedKey)) {\n\t\t\treturn { ready: true, reason: \"confirmed_up_cached\" };\n\t\t}\n\t\tconst runtime = this.getTransformersRuntime(model.id, serverUrl);\n\t\tconst status = await runtime.detect();\n\t\tif (status.serverUp) {\n\t\t\tconst resident = await this.ensureTransformersResident(model, runtime);\n\t\t\tif (!resident.ok) return { ready: false, reason: resident.reason ?? \"residency_refused\" };\n\t\t\tthis._confirmedUp.add(confirmedKey);\n\t\t\treturn { ready: true, reason: \"already_running\" };\n\t\t}\n\t\tif (!status.runtimeInstalled) {\n\t\t\treturn { ready: false, reason: \"runtime_missing\", installGuide: runtime.installGuide() };\n\t\t}\n\t\tconst started = await runtime.start();\n\t\tif (started.started || started.reason === \"already_running\") {\n\t\t\tconst resident = await this.ensureTransformersResident(model, runtime);\n\t\t\tif (!resident.ok) return { ready: false, reason: resident.reason ?? \"residency_refused\" };\n\t\t\tthis._confirmedUp.add(confirmedKey);\n\t\t\treturn { ready: true, reason: started.reason };\n\t\t}\n\t\treturn { ready: false, reason: started.reason };\n\t}\n\n\t/**\n\t * Readiness gate for a pi-registered prism llama.cpp model (Bonsai-27B) — \"usable when needed\",\n\t * not \"served once at install time\": healthy at its registered baseUrl -> proceed (no downloads,\n\t * no spawn); not healthy -> re-verify BOTH GGUF files exist on disk, re-downloading any missing\n\t * one, then serve with the vision projector ALWAYS attached (see\n\t * {@link ensurePrismModelFilesThenServe} — the sole path allowed to call `runtime.serve()` for\n\t * this model) -> proceed; any stage fails -> the turn fails with that stage's error and\n\t * llama-server is never spawned half-configured. No consent dialog here (contrast\n\t * maybeInstallOllamaOnConsent/maybeInstallTransformersOnConsent) — see\n\t * maybeInstallManagedLocalOnConsent's doc comment for why. Reuses the model's already-registered\n\t * `contextWindow` for a re-serve rather than re-deriving from current host RAM, so a restarted\n\t * server can never end up with a different served context than the rest of the session (e.g.\n\t * compaction) already assumes.\n\t */\n\tprivate async ensurePrismLlamaCppModelReady(\n\t\tmodel: Model<Api>,\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[] }> {\n\t\tif (!isPiManagedPrismLlamaCppModel(model)) {\n\t\t\treturn { ready: true, reason: \"not_pi_managed_llama_cpp\" };\n\t\t}\n\t\tconst serverUrl = this.deriveOpenAICompatServerUrl(model.baseUrl);\n\t\tconst confirmedKey = this.confirmationKey(model, serverUrl);\n\t\tthis.invalidateIfLastCallFailed(model, serverUrl);\n\t\tif (this._confirmedUp.has(confirmedKey)) {\n\t\t\treturn { ready: true, reason: \"confirmed_up_cached\" };\n\t\t}\n\t\tif (await isPrismLlamaCppServerHealthy(serverUrl, this.deps.prismLlamaCppDeps?.fetchFn)) {\n\t\t\tthis._confirmedUp.add(confirmedKey);\n\t\t\treturn { ready: true, reason: \"already_running\" };\n\t\t}\n\t\tconst descriptor = PRISM_LLAMACPP_DESCRIPTORS[model.id];\n\t\tif (!descriptor) {\n\t\t\t// Unreachable given the isPiManagedPrismLlamaCppModel check above (same lookup table), but\n\t\t\t// an honest failure beats a silent bad-cast if the two ever drift apart.\n\t\t\treturn { ready: false, reason: `no_curated_descriptor:${model.id}` };\n\t\t}\n\t\tconst served = await ensurePrismModelFilesThenServe(\n\t\t\tthis.getPrismLlamaCppRuntime(),\n\t\t\tdescriptor,\n\t\t\t{ port: PRISM_LLAMACPP_SERVE_PORT, numCtx: model.contextWindow },\n\t\t\t() => {},\n\t\t);\n\t\tif (!served.ok) {\n\t\t\treturn { ready: false, reason: `${served.stage}:${served.error}` };\n\t\t}\n\t\tthis._confirmedUp.add(confirmedKey);\n\t\treturn { ready: true, reason: \"started\" };\n\t}\n\n\t/**\n\t * #31: the ONE case a routed local model's unreadiness can be fixed automatically is a missing\n\t * ollama binary — an unreachable server can't be helped by installing, so that reason is left to\n\t * the graceful-fallback warning below unchanged. Only offered when there's an interactive UI to\n\t * ask through: headless/RPC/print sessions have no UI context and fall straight through, same as\n\t * declining or timing out (both resolve confirm() to false). Reverses \"pi never runs installers\n\t * itself\" specifically for this one path — the user is asked first, the download is pi's own\n\t * (never curl|sh), and it lands in pi's own runtimes dir (see OllamaRuntime.installManaged).\n\t *\n\t * Pauses/resumes the routing working-indicator around the confirm dialog itself (re-emitting\n\t * routing_end/routing_start — both already idempotent, see interactive-mode.ts's handlers) so an\n\t * animated spinner doesn't fight a dialog the user is trying to read and answer; the indicator\n\t * comes back for the download/extract that follows a \"yes\", which is genuine processing feedback.\n\t */\n\tprivate async maybeInstallOllamaOnConsent(\n\t\tmodel: Model<Api>,\n\t\treadiness: { ready: boolean; reason: string; installGuide?: string[] },\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string }> {\n\t\tconst ui = this.deps.getUIContext();\n\t\tif (!ui || readiness.ready || readiness.reason !== \"binary_missing\") return readiness;\n\n\t\tconst modelLabel = this.deps.formatModel(model);\n\t\tthis.deps.emit({ type: \"routing_end\" });\n\t\tlet confirmed: boolean;\n\t\ttry {\n\t\t\tconfirmed = await ui.confirm(\n\t\t\t\t\"Install Ollama?\",\n\t\t\t\t`Ollama isn't installed, so the local model \"${modelLabel}\" can't run. Pi can download and ` +\n\t\t\t\t\t\"install it now (a large one-time download, possibly over 1 GB depending on your platform) \" +\n\t\t\t\t\t\"into its own runtimes folder — never curl|sh, never touching anything outside pi's own \" +\n\t\t\t\t\t\"directory. Install it now?\",\n\t\t\t\t{ timeout: OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS },\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.deps.emit({ type: \"routing_start\" });\n\t\t}\n\t\tif (!confirmed) return readiness;\n\n\t\tconst serverUrl = this.deriveOllamaServerUrl(model.baseUrl);\n\t\tconst runtime = this.getLocalRuntime(serverUrl);\n\t\tlet installResult: { ok: boolean; error?: string };\n\t\ttry {\n\t\t\tinstallResult = await runtime.installManaged((status) => ui.setStatus(\"ollama-install\", status));\n\t\t} finally {\n\t\t\tui.setStatus(\"ollama-install\", undefined);\n\t\t}\n\t\tif (!installResult.ok) {\n\t\t\treturn { ready: false, reason: \"install_failed\", installAttemptError: installResult.error };\n\t\t}\n\t\treturn this.ensureLocalModelReady(model);\n\t}\n\n\tprivate async maybeInstallTransformersOnConsent(\n\t\tmodel: Model<Api>,\n\t\treadiness: { ready: boolean; reason: string; installGuide?: string[] },\n\t): Promise<{ ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string }> {\n\t\tconst ui = this.deps.getUIContext();\n\t\tif (!ui || readiness.ready || readiness.reason !== \"runtime_missing\") return readiness;\n\n\t\tconst modelLabel = this.deps.formatModel(model);\n\t\tthis.deps.emit({ type: \"routing_end\" });\n\t\tlet confirmed: boolean;\n\t\ttry {\n\t\t\tconfirmed = await ui.confirm(\n\t\t\t\t\"Install Transformers runtime?\",\n\t\t\t\t`The Hugging Face model \"${modelLabel}\" needs a pi-managed Python venv with Transformers ` +\n\t\t\t\t\t\"and CPU PyTorch before it can run. Pi will install those packages into its own runtimes \" +\n\t\t\t\t\t\"folder, download the model into a pi-owned Hugging Face cache, and leave system Python, \" +\n\t\t\t\t\t\"your Ollama models, and your user HF cache untouched. Install it now?\",\n\t\t\t\t{ timeout: OLLAMA_INSTALL_CONFIRM_TIMEOUT_MS },\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.deps.emit({ type: \"routing_start\" });\n\t\t}\n\t\tif (!confirmed) return readiness;\n\n\t\tconst serverUrl = this.deriveOpenAICompatServerUrl(model.baseUrl);\n\t\tconst runtime = this.getTransformersRuntime(model.id, serverUrl);\n\t\tlet installResult: { ok: boolean; error?: string };\n\t\ttry {\n\t\t\tinstallResult = await runtime.installManaged((status) => ui.setStatus(\"transformers-install\", status));\n\t\t} finally {\n\t\t\tui.setStatus(\"transformers-install\", undefined);\n\t\t}\n\t\tif (!installResult.ok) {\n\t\t\treturn { ready: false, reason: \"install_failed\", installAttemptError: installResult.error };\n\t\t}\n\t\tlet downloadResult: { ok: boolean; error?: string };\n\t\ttry {\n\t\t\tdownloadResult = await runtime.downloadModel((status) => ui.setStatus(\"transformers-download\", status));\n\t\t} finally {\n\t\t\tui.setStatus(\"transformers-download\", undefined);\n\t\t}\n\t\tif (!downloadResult.ok) {\n\t\t\treturn { ready: false, reason: \"download_failed\", installAttemptError: downloadResult.error };\n\t\t}\n\t\treturn this.ensureTransformersModelReady(model);\n\t}\n\n\t/**\n\t * Router-swap gate (#27): a turn routed to a local model (any tier, including an executor-direct\n\t * route — both carry tier \"cheap\") must not dead-end the turn just because ollama isn't up.\n\t * Never a SILENT swap: every fallback is announced in a warning that states (i) the local model\n\t * was unavailable and WHY — binary missing surfaces the install guide inline; any other reason\n\t * gets a \"check that ollama is running\" hint — and (ii) which tier is now handling the turn, so\n\t * the cost shift is never a surprise. Escalates cheap -> medium -> expensive, skipping any\n\t * unconfigured intermediate tier, reusing the router's own existing \"model unavailable\"\n\t * resolution (resolveConfiguredTierModel) rather than inventing a new fallback mechanism.\n\t * Escalation is bounded: tier strictly increases each hop, so it terminates within two hops.\n\t *\n\t * Before the warning/escalation below: #31's consent gate gets one shot at fixing a missing\n\t * binary interactively (see maybeInstallOllamaOnConsent) — declining, timing out, running\n\t * headless, or the install attempt itself failing all fall through here unchanged, just with an\n\t * honest reason (an install that failed is worded as a failed install, not re-labeled as if\n\t * nothing was ever tried).\n\t */\n\tasync ensureRouteModelReady(\n\t\tresolved: { decision: RouteDecision; model: Model<Api> } | undefined,\n\t): Promise<{ decision: RouteDecision; model: Model<Api> } | undefined> {\n\t\tlet current = resolved;\n\t\twhile (current && this.isManagedLocalModel(current.model)) {\n\t\t\tlet readiness: { ready: boolean; reason: string; installGuide?: string[]; installAttemptError?: string } =\n\t\t\t\tawait this.ensureManagedLocalReadiness(current.model);\n\t\t\tif (!readiness.ready) {\n\t\t\t\treadiness = await this.maybeInstallManagedLocalOnConsent(current.model, readiness);\n\t\t\t}\n\t\t\tif (readiness.ready) return current;\n\n\t\t\t// Walk the remaining tiers in order (never back down to cheap) and take the first one that\n\t\t\t// actually resolves — an unconfigured intermediate tier (e.g. no mediumModel set) must be\n\t\t\t// skipped, not treated as \"no fallback available\".\n\t\t\tconst startIndex = MODEL_ROUTER_TIER_ORDER.indexOf(current.decision.tier as \"cheap\" | \"medium\" | \"expensive\");\n\t\t\tlet escalated: { tier: \"medium\" | \"expensive\"; model: Model<Api> } | undefined;\n\t\t\tfor (let i = startIndex + 1; startIndex !== -1 && i < MODEL_ROUTER_TIER_ORDER.length; i++) {\n\t\t\t\tconst tier = MODEL_ROUTER_TIER_ORDER[i] as \"medium\" | \"expensive\";\n\t\t\t\tconst model = this.deps.resolveConfiguredTierModel(tier);\n\t\t\t\tif (model) {\n\t\t\t\t\tescalated = { tier, model };\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst modelLabel = this.deps.formatModel(current.model);\n\t\t\tconst localRuntimeName =\n\t\t\t\tcurrent.model.provider === OLLAMA_PROVIDER\n\t\t\t\t\t? \"ollama\"\n\t\t\t\t\t: current.model.provider === HF_TRANSFORMERS_PROVIDER\n\t\t\t\t\t\t? \"Transformers\"\n\t\t\t\t\t\t: \"prism llama.cpp\";\n\t\t\tconst wrongStore = parseWrongOllamaStoreReason(readiness.reason);\n\t\t\tconst whyText = readiness.installAttemptError\n\t\t\t\t? `pi tried to install it just now, but the install attempt failed: ${readiness.installAttemptError}`\n\t\t\t\t: readiness.installGuide\n\t\t\t\t\t? [\n\t\t\t\t\t\t\tcurrent.model.provider === OLLAMA_PROVIDER\n\t\t\t\t\t\t\t\t? \"the ollama binary is not installed.\"\n\t\t\t\t\t\t\t\t: \"the pi-managed Transformers runtime is not installed.\",\n\t\t\t\t\t\t\t...readiness.installGuide,\n\t\t\t\t\t\t].join(\"\\n\")\n\t\t\t\t\t: wrongStore\n\t\t\t\t\t\t? `an Ollama server is already running with store ${wrongStore.activePath} (${wrongStore.modelCount} model(s)); pi's owned store is ${wrongStore.ownedPath}. Stop the other server or run /models import, then retry.`\n\t\t\t\t\t\t: current.model.provider === OLLAMA_PROVIDER\n\t\t\t\t\t\t\t? `its ${localRuntimeName} server is not reachable (${readiness.reason}) — check that ollama is running.`\n\t\t\t\t\t\t\t: `its ${localRuntimeName} server is not reachable (${readiness.reason}) — check that the runtime is running.`;\n\t\t\tconst fallbackText = escalated\n\t\t\t\t? `Falling back to the ${escalated.tier} tier for this turn.`\n\t\t\t\t: \"No other tier is configured — falling back to the session's default model.\";\n\t\t\tthis.deps.emit({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tmessage: `Local model \"${modelLabel}\" is unavailable: ${whyText}\\n${fallbackText}`,\n\t\t\t});\n\n\t\t\tif (!escalated) return undefined; // no higher tier resolves — caller falls back to the session default\n\t\t\tcurrent = {\n\t\t\t\tmodel: escalated.model,\n\t\t\t\tdecision: {\n\t\t\t\t\t...current.decision,\n\t\t\t\t\ttier: escalated.tier,\n\t\t\t\t\tfallbackFrom: current.decision.tier,\n\t\t\t\t\treasonCode: \"local_model_not_ready_fallback\",\n\t\t\t\t\treasons: [\n\t\t\t\t\t\t...current.decision.reasons,\n\t\t\t\t\t\t`Local model not ready (${readiness.reason}); escalated to ${escalated.tier}`,\n\t\t\t\t\t],\n\t\t\t\t\tmodel: this.deps.formatModel(escalated.model),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn current;\n\t}\n\n\t/**\n\t * Stop every pi-spawned local runtime (Ollama, Transformers, prism llama.cpp) that no longer\n\t * backs any model in `eligibleModels` — the caller's post-reload/profile-switch configuration\n\t * (typically: the foreground model plus whatever the model router's tiers still resolve to).\n\t * Called by the reload path so a local model dropped from the live configuration doesn't leave\n\t * its child process running untracked for the rest of the process lifetime.\n\t *\n\t * Never touches a server this session merely DETECTED (a user/system-run Ollama server, or any\n\t * server that was already up when first probed): {@link OllamaRuntime.stop}/\n\t * {@link TransformersRuntime.stop}/{@link PrismLlamaCppRuntime.stop} already no-op unless the\n\t * instance holds a live pi-spawned child-process handle (see each class's own `stop()`), so\n\t * calling `stop()` unconditionally on every cached instance below is safe by construction — it\n\t * can never kill a process pi didn't start.\n\t *\n\t * Never stops a runtime still backing an eligible model. Eligibility is checked at the SAME\n\t * granularity each map is keyed at: `_runtimes` is keyed by Ollama serverUrl (one server can host\n\t * several models), so it survives as long as ANY eligible model still points at that server;\n\t * `_transformersRuntimes` is keyed by (modelId, serverUrl) — one runtime per model, so eligibility\n\t * is exact; the prism runtime is a single pi-owned instance that survives while ANY eligible model\n\t * is pi-managed prism.\n\t *\n\t * The `_confirmedUp` cache is pruned separately, in ONE pass over every provider: since\n\t * `confirmationKey` is uniformly `${serverUrl}\\0${model.id}` across every provider, a single\n\t * eligible-keys set built the same way covers all three providers. This also correctly drops a\n\t * model's stale confirmation even when its SERVER survives (e.g. two Ollama models on one server,\n\t * only one still eligible) — a case the coarser per-server runtime eviction above can't see on\n\t * its own.\n\t */\n\treconcile(eligibleModels: readonly Model<Api>[]): void {\n\t\tconst eligibleOllamaServers = new Set(\n\t\t\teligibleModels\n\t\t\t\t.filter((model) => model.provider === OLLAMA_PROVIDER)\n\t\t\t\t.map((model) => this.deriveOllamaServerUrl(model.baseUrl)),\n\t\t);\n\t\tfor (const [serverUrl, runtime] of this._runtimes) {\n\t\t\tif (eligibleOllamaServers.has(serverUrl)) continue;\n\t\t\truntime.stop();\n\t\t\tthis._runtimes.delete(serverUrl);\n\t\t}\n\n\t\tconst eligibleTransformersRuntimeKeys = new Set(\n\t\t\teligibleModels\n\t\t\t\t.filter((model) => model.provider === HF_TRANSFORMERS_PROVIDER)\n\t\t\t\t.map((model) => this._transformersRuntimeKey(model.id, this.deriveOllamaServerUrl(model.baseUrl))),\n\t\t);\n\t\tfor (const [runtimeKey, runtime] of this._transformersRuntimes) {\n\t\t\tif (eligibleTransformersRuntimeKeys.has(runtimeKey)) continue;\n\t\t\truntime.stop();\n\t\t\tthis._transformersRuntimes.delete(runtimeKey);\n\t\t}\n\n\t\tif (this._prismLlamaCppRuntime && !eligibleModels.some((model) => isPiManagedPrismLlamaCppModel(model))) {\n\t\t\tthis._prismLlamaCppRuntime.stop();\n\t\t\tthis._prismLlamaCppRuntime = undefined;\n\t\t}\n\n\t\t// deriveOllamaServerUrl strips a trailing `/v1` regardless of provider (same body as the\n\t\t// private deriveOpenAICompatServerUrl it mirrors), so it's safe to reuse here for every\n\t\t// managed-local provider's baseUrl when building the confirmationKey-shaped eligible set.\n\t\tconst eligibleConfirmationKeys = new Set(\n\t\t\teligibleModels\n\t\t\t\t.filter((model) => this.isManagedLocalModel(model))\n\t\t\t\t.map((model) => this.confirmationKey(model, this.deriveOllamaServerUrl(model.baseUrl))),\n\t\t);\n\t\tfor (const key of this._confirmedUp) {\n\t\t\tif (!eligibleConfirmationKeys.has(key)) this._confirmedUp.delete(key);\n\t\t}\n\t}\n\n\t/** Full teardown of every pi-spawned local runtime — reconcile against an empty eligible set. */\n\tdispose(): void {\n\t\tthis.reconcile([]);\n\t}\n\n\t/** Shared cache-key shape for {@link _transformersRuntimes}, factored out so\n\t * {@link getTransformersRuntime} and {@link reconcile} can never drift apart. */\n\tprivate _transformersRuntimeKey(modelId: string, resolvedBaseUrl: string): string {\n\t\treturn `${modelId}\\0${resolvedBaseUrl}`;\n\t}\n}\n"]}
|
|
@@ -2,7 +2,7 @@ import type { MemoryPromptBudget } from "../../context/memory-prompt-budget.ts";
|
|
|
2
2
|
import type { ToolDefinition } from "../../extensions/types.ts";
|
|
3
3
|
import type { MemoryLifecycleContext, MemoryProvider } from "../memory-provider.ts";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing
|
|
6
6
|
* non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in
|
|
7
7
|
* place and return the rewritten file; otherwise return null (the caller appends normally).
|
|
8
8
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../../../src/core/memory/providers/file-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsB3F;AAiBD,qBAAa,iBAAkB,YAAW,cAAc;IACvD,SAAgB,IAAI,gBAAgB;IACpC,SAAgB,MAAM,WAAW;IAEjC,OAAO,CAAC,GAAG,CAAC,CAAyB;IACrC,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,YAAY,CAAM;IAE1B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,eAAe,CAAM;IAG7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAQ;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC,WAAW,IAAI,OAAO,CAE5B;IAEM,eAAe;;MAErB;IAEY,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBtF;IAEM,iBAAiB,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,MAAM,CA4C5D;IAEY,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrD;IAEY,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAErC;IAEM,iBAAiB,IAAI,MAAM,EAAE,CAEnC;IAEM,kBAAkB,IAAI,cAAc,EAAE,CAoK5C;CACD","sourcesContent":["import { existsSync, promises as fs, mkdirSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { type Static, Type } from \"typebox\";\nimport type { MemoryPromptBudget } from \"../../context/memory-prompt-budget.ts\";\nimport type { ToolDefinition } from \"../../extensions/types.ts\";\nimport { hasInvisibleUnicode, scanContextFileThreats, stripInvisibleUnicode } from \"../../resource-loader.ts\";\nimport { jaccard, tokenize } from \"../../tools/skill-audit.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\n\n/**\n * R5 confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing\n * non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in\n * place and return the rewritten file; otherwise return null (the caller appends normally).\n */\nexport function supersedeNearDuplicateLine(existing: string, content: string): string | null {\n\tconst NEAR_DUP_THRESHOLD = 0.6;\n\tconst contentTokens = tokenize(content);\n\tif (contentTokens.length === 0) return null;\n\tconst lines = existing.split(\"\\n\");\n\tlet bestIdx = -1;\n\tlet bestScore = NEAR_DUP_THRESHOLD;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i].trim();\n\t\tif (!line) continue;\n\t\t// Never supersede structural Markdown (headings, list markers as headings) — a fact must not\n\t\t// silently overwrite section structure (Bug #15).\n\t\tif (line.startsWith(\"#\")) continue;\n\t\tconst score = jaccard(contentTokens, tokenize(line));\n\t\tif (score >= bestScore) {\n\t\t\tbestScore = score;\n\t\t\tbestIdx = i;\n\t\t}\n\t}\n\tif (bestIdx === -1) return null;\n\tlines[bestIdx] = content;\n\treturn lines.join(\"\\n\");\n}\n\nconst memorySchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"add\"), Type.Literal(\"replace\"), Type.Literal(\"remove\")], {\n\t\tdescription: \"Action to perform: add new content, replace existing content, or remove content\",\n\t}),\n\ttarget: Type.Union([Type.Literal(\"memory\"), Type.Literal(\"user\")], {\n\t\tdescription: \"Target file: 'memory' for MEMORY.md, 'user' for USER.md\",\n\t}),\n\tcontent: Type.Optional(Type.String({ description: \"Content to write (required for 'add' or 'replace')\" })),\n\toldContent: Type.Optional(\n\t\tType.String({ description: \"Exact substring to replace or remove (required for 'replace' or 'remove')\" }),\n\t),\n});\n\ntype MemoryParams = Static<typeof memorySchema>;\n\nexport class FileStoreProvider implements MemoryProvider {\n\tpublic readonly name = \"file-store\";\n\tpublic readonly egress = \"local\";\n\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate memoryFilePath = \"\";\n\tprivate userFilePath = \"\";\n\n\tprivate lastWrittenMemory = \"\";\n\tprivate lastWrittenUser = \"\";\n\n\t// Character budgets\n\tprivate static readonly BUDGET_MEMORY = 2200;\n\tprivate static readonly BUDGET_USER = 1375;\n\n\tpublic isAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tpublic getCapabilities() {\n\t\treturn { surfaces: [\"context\" as const] };\n\t}\n\n\tpublic async initialize(_sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.memoryFilePath = join(ctx.agentDir, \"MEMORY.md\");\n\t\tthis.userFilePath = join(ctx.agentDir, \"USER.md\");\n\n\t\t// Ensure agentDir exists\n\t\tif (!existsSync(ctx.agentDir)) {\n\t\t\tmkdirSync(ctx.agentDir, { recursive: true });\n\t\t}\n\n\t\t// Initialize files if they do not exist\n\t\tif (!existsSync(this.memoryFilePath)) {\n\t\t\twriteFileSync(this.memoryFilePath, \"\", \"utf-8\");\n\t\t}\n\t\tif (!existsSync(this.userFilePath)) {\n\t\t\twriteFileSync(this.userFilePath, \"\", \"utf-8\");\n\t\t}\n\n\t\t// Load initial contents\n\t\tthis.lastWrittenMemory = await fs.readFile(this.memoryFilePath, \"utf-8\");\n\t\tthis.lastWrittenUser = await fs.readFile(this.userFilePath, \"utf-8\");\n\t}\n\n\tpublic systemPromptBlock(budget?: MemoryPromptBudget): string {\n\t\tconst sanitize = (content: string) => {\n\t\t\t// Strip hidden/bidi-control chars before injecting memory into the prompt (defence in depth: the\n\t\t\t// write path already blocks them, but a file edited out-of-band could carry them). Strip #31.\n\t\t\tconst lines = stripInvisibleUnicode(content).cleaned.split(\"\\n\");\n\t\t\tconst sanitizedLines = lines.map((line) => {\n\t\t\t\tconst threats = scanContextFileThreats(line);\n\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\treturn `[BLOCKED: potential threat detected (${threats.join(\", \")})]`;\n\t\t\t\t}\n\t\t\t\treturn line;\n\t\t\t});\n\t\t\treturn sanitizedLines.join(\"\\n\");\n\t\t};\n\n\t\t// Read-time budget guard (cost): the memory tool already caps writes at BUDGET_*, but a file edited\n\t\t// externally (or by any path that bypasses the tool) could be arbitrarily large and would then\n\t\t// bloat the system prompt on EVERY turn. Cap the injected view to the same budget so the per-turn\n\t\t// cost stays bounded; the file on disk is untouched and the model is told it was truncated.\n\t\tconst cap = (content: string, limit: number) => {\n\t\t\tif (content.length <= limit) return content;\n\t\t\treturn `${content.slice(0, limit)}\\n[…truncated to ${limit} chars for the prompt; full file is on disk]`;\n\t\t};\n\n\t\tconst mem = cap(sanitize(this.lastWrittenMemory), FileStoreProvider.BUDGET_MEMORY);\n\t\tconst usr = cap(sanitize(this.lastWrittenUser), FileStoreProvider.BUDGET_USER);\n\n\t\tconst blocks: string[] = [];\n\t\tif (mem.trim()) {\n\t\t\tblocks.push(`## MEMORY.md:\\n${mem}`);\n\t\t}\n\t\tif (usr.trim()) {\n\t\t\tblocks.push(`## USER.md:\\n${usr}`);\n\t\t}\n\n\t\tif (blocks.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tconst block = `=== Persistent Memory (file-store) ===\\n[System Note: Below is a snapshot of your persistent memory. You can update these using the 'memory' tool.]\\n\\n${blocks.join(\"\\n\\n\")}`;\n\t\tif (budget?.compact && (block.split(\"\\n\").length > budget.maxLines || block.length > budget.maxChars)) {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn block;\n\t}\n\n\tpublic async prefetch(_query: string): Promise<string> {\n\t\t// static system prompt block is sufficient for file-store default; no-op prefetch\n\t\treturn \"\";\n\t}\n\n\tpublic async shutdown(): Promise<void> {\n\t\t// no-op\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\treturn [];\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tname: \"memory\",\n\t\t\t\tlabel: \"Persistent Memory Manager\",\n\t\t\t\tdescription: \"Add, replace, or remove contents in persistent memory files (MEMORY.md/USER.md).\",\n\t\t\t\tparameters: memorySchema,\n\t\t\t\texecute: async (_toolCallId, params: MemoryParams, _signal, _onUpdate, _execCtx) => {\n\t\t\t\t\tif (this.ctx?.isChildSession) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: \"Error: Writes to persistent memory are not allowed in child sessions (subagents).\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: \"Child session write-gated\" },\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { action, target, content, oldContent } = params;\n\n\t\t\t\t\t// Strict-scope injection guard on the high-privilege WRITE path (agy #31): a poisoned\n\t\t\t\t\t// memory entry persists across sessions and is injected into every future system prompt,\n\t\t\t\t\t// so block outright rather than strip. Hidden/bidi-control chars have no legitimate place\n\t\t\t\t\t// in a memory note, so reject those too.\n\t\t\t\t\tif ((action === \"add\" || action === \"replace\") && content !== undefined) {\n\t\t\t\t\t\tif (hasInvisibleUnicode(content)) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: \"Error: memory write rejected — contains hidden/bidirectional control characters.\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Invisible unicode in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst threats = scanContextFileThreats(content, \"strict\");\n\t\t\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: memory write rejected — potential injection/exfiltration detected (${threats.join(\", \")}).`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Threat in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst filePath = target === \"memory\" ? this.memoryFilePath : this.userFilePath;\n\t\t\t\t\tconst budget = target === \"memory\" ? FileStoreProvider.BUDGET_MEMORY : FileStoreProvider.BUDGET_USER;\n\n\t\t\t\t\tlet release: (() => Promise<void>) | undefined;\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// File lock\n\t\t\t\t\t\trelease = await lockfile.lock(filePath, { realpath: false, retries: 5 });\n\n\t\t\t\t\t\tconst lastWritten = target === \"memory\" ? this.lastWrittenMemory : this.lastWrittenUser;\n\t\t\t\t\t\t// Read current file content on disk for drift detection\n\t\t\t\t\t\tconst currentOnDisk = await fs.readFile(filePath, \"utf-8\");\n\t\t\t\t\t\tif (currentOnDisk !== lastWritten) {\n\t\t\t\t\t\t\t// Drift detected. Backup current file and refuse write.\n\t\t\t\t\t\t\tconst backupPath = `${filePath}.bak.${Date.now()}`;\n\t\t\t\t\t\t\tawait fs.writeFile(backupPath, currentOnDisk, \"utf-8\");\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Drift detected. The memory file has been modified out-of-band by an external process. A backup was created at ${backupPath}. Operation aborted.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Drift detected\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet newContent = currentOnDisk;\n\t\t\t\t\t\tif (action === \"add\") {\n\t\t\t\t\t\t\tif (content === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'content' is required for action 'add'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// R5: confront before write. If this fact is a near-duplicate of an existing line,\n\t\t\t\t\t\t\t// supersede it in place instead of appending a redundant copy (prevents append-rot).\n\t\t\t\t\t\t\tconst superseded = supersedeNearDuplicateLine(currentOnDisk, content);\n\t\t\t\t\t\t\tif (superseded !== null) {\n\t\t\t\t\t\t\t\tnewContent = superseded;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnewContent =\n\t\t\t\t\t\t\t\t\tnewContent.endsWith(\"\\n\") || newContent === \"\"\n\t\t\t\t\t\t\t\t\t\t? `${newContent}${content}\\n`\n\t\t\t\t\t\t\t\t\t\t: `${newContent}\\n${content}\\n`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (action === \"replace\") {\n\t\t\t\t\t\t\tif (content === undefined || oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameters 'content' and 'oldContent' are required for action 'replace'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to replace ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, content);\n\t\t\t\t\t\t} else if (action === \"remove\") {\n\t\t\t\t\t\t\tif (oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'oldContent' is required for action 'remove'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to remove ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Budget check\n\t\t\t\t\t\tif (newContent.length > budget) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Memory budget exceeded. ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"} limit is ${budget} characters. Current operation would result in ${newContent.length} characters.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Memory budget exceeded\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Atomic write\n\t\t\t\t\t\tconst tmpPath = `${filePath}.tmp`;\n\t\t\t\t\t\tawait fs.writeFile(tmpPath, newContent, \"utf-8\");\n\t\t\t\t\t\tawait fs.rename(tmpPath, filePath);\n\n\t\t\t\t\t\t// Update in-memory tracker\n\t\t\t\t\t\tif (target === \"memory\") {\n\t\t\t\t\t\t\tthis.lastWrittenMemory = newContent;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.lastWrittenUser = newContent;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Successfully updated ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"}.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: true },\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Error: Failed to perform memory operation: ${String(err)}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: String(err) },\n\t\t\t\t\t\t};\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (release) {\n\t\t\t\t\t\t\tawait release();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../../../src/core/memory/providers/file-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsB3F;AAiBD,qBAAa,iBAAkB,YAAW,cAAc;IACvD,SAAgB,IAAI,gBAAgB;IACpC,SAAgB,MAAM,WAAW;IAEjC,OAAO,CAAC,GAAG,CAAC,CAAyB;IACrC,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,YAAY,CAAM;IAE1B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,eAAe,CAAM;IAG7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAQ;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC,WAAW,IAAI,OAAO,CAE5B;IAEM,eAAe;;MAErB;IAEY,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBtF;IAEM,iBAAiB,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,MAAM,CA4C5D;IAEY,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrD;IAEY,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAErC;IAEM,iBAAiB,IAAI,MAAM,EAAE,CAEnC;IAEM,kBAAkB,IAAI,cAAc,EAAE,CAoK5C;CACD","sourcesContent":["import { existsSync, promises as fs, mkdirSync, writeFileSync } from \"fs\";\nimport lockfile from \"proper-lockfile\";\nimport { type Static, Type } from \"typebox\";\nimport { configFile } from \"../../agent-paths.ts\";\nimport type { MemoryPromptBudget } from \"../../context/memory-prompt-budget.ts\";\nimport type { ToolDefinition } from \"../../extensions/types.ts\";\nimport { hasInvisibleUnicode, scanContextFileThreats, stripInvisibleUnicode } from \"../../resource-loader.ts\";\nimport { jaccard, tokenize } from \"../../tools/skill-audit.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\n\n/**\n * Confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing\n * non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in\n * place and return the rewritten file; otherwise return null (the caller appends normally).\n */\nexport function supersedeNearDuplicateLine(existing: string, content: string): string | null {\n\tconst NEAR_DUP_THRESHOLD = 0.6;\n\tconst contentTokens = tokenize(content);\n\tif (contentTokens.length === 0) return null;\n\tconst lines = existing.split(\"\\n\");\n\tlet bestIdx = -1;\n\tlet bestScore = NEAR_DUP_THRESHOLD;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i].trim();\n\t\tif (!line) continue;\n\t\t// Never supersede structural Markdown (headings, list markers as headings) — a fact must not\n\t\t// silently overwrite section structure.\n\t\tif (line.startsWith(\"#\")) continue;\n\t\tconst score = jaccard(contentTokens, tokenize(line));\n\t\tif (score >= bestScore) {\n\t\t\tbestScore = score;\n\t\t\tbestIdx = i;\n\t\t}\n\t}\n\tif (bestIdx === -1) return null;\n\tlines[bestIdx] = content;\n\treturn lines.join(\"\\n\");\n}\n\nconst memorySchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"add\"), Type.Literal(\"replace\"), Type.Literal(\"remove\")], {\n\t\tdescription: \"Action to perform: add new content, replace existing content, or remove content\",\n\t}),\n\ttarget: Type.Union([Type.Literal(\"memory\"), Type.Literal(\"user\")], {\n\t\tdescription: \"Target file: 'memory' for MEMORY.md, 'user' for USER.md\",\n\t}),\n\tcontent: Type.Optional(Type.String({ description: \"Content to write (required for 'add' or 'replace')\" })),\n\toldContent: Type.Optional(\n\t\tType.String({ description: \"Exact substring to replace or remove (required for 'replace' or 'remove')\" }),\n\t),\n});\n\ntype MemoryParams = Static<typeof memorySchema>;\n\nexport class FileStoreProvider implements MemoryProvider {\n\tpublic readonly name = \"file-store\";\n\tpublic readonly egress = \"local\";\n\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate memoryFilePath = \"\";\n\tprivate userFilePath = \"\";\n\n\tprivate lastWrittenMemory = \"\";\n\tprivate lastWrittenUser = \"\";\n\n\t// Character budgets\n\tprivate static readonly BUDGET_MEMORY = 2200;\n\tprivate static readonly BUDGET_USER = 1375;\n\n\tpublic isAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tpublic getCapabilities() {\n\t\treturn { surfaces: [\"context\" as const] };\n\t}\n\n\tpublic async initialize(_sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.memoryFilePath = configFile(ctx.agentDir, \"MEMORY.md\");\n\t\tthis.userFilePath = configFile(ctx.agentDir, \"USER.md\");\n\n\t\t// Ensure agentDir exists\n\t\tif (!existsSync(ctx.agentDir)) {\n\t\t\tmkdirSync(ctx.agentDir, { recursive: true });\n\t\t}\n\n\t\t// Initialize files if they do not exist\n\t\tif (!existsSync(this.memoryFilePath)) {\n\t\t\twriteFileSync(this.memoryFilePath, \"\", \"utf-8\");\n\t\t}\n\t\tif (!existsSync(this.userFilePath)) {\n\t\t\twriteFileSync(this.userFilePath, \"\", \"utf-8\");\n\t\t}\n\n\t\t// Load initial contents\n\t\tthis.lastWrittenMemory = await fs.readFile(this.memoryFilePath, \"utf-8\");\n\t\tthis.lastWrittenUser = await fs.readFile(this.userFilePath, \"utf-8\");\n\t}\n\n\tpublic systemPromptBlock(budget?: MemoryPromptBudget): string {\n\t\tconst sanitize = (content: string) => {\n\t\t\t// Strip hidden/bidi-control chars before injecting memory into the prompt (defence in depth: the\n\t\t\t// write path already blocks them, but a file edited out-of-band could carry them). Strip #31.\n\t\t\tconst lines = stripInvisibleUnicode(content).cleaned.split(\"\\n\");\n\t\t\tconst sanitizedLines = lines.map((line) => {\n\t\t\t\tconst threats = scanContextFileThreats(line);\n\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\treturn `[BLOCKED: potential threat detected (${threats.join(\", \")})]`;\n\t\t\t\t}\n\t\t\t\treturn line;\n\t\t\t});\n\t\t\treturn sanitizedLines.join(\"\\n\");\n\t\t};\n\n\t\t// Read-time budget guard (cost): the memory tool already caps writes at BUDGET_*, but a file edited\n\t\t// externally (or by any path that bypasses the tool) could be arbitrarily large and would then\n\t\t// bloat the system prompt on EVERY turn. Cap the injected view to the same budget so the per-turn\n\t\t// cost stays bounded; the file on disk is untouched and the model is told it was truncated.\n\t\tconst cap = (content: string, limit: number) => {\n\t\t\tif (content.length <= limit) return content;\n\t\t\treturn `${content.slice(0, limit)}\\n[…truncated to ${limit} chars for the prompt; full file is on disk]`;\n\t\t};\n\n\t\tconst mem = cap(sanitize(this.lastWrittenMemory), FileStoreProvider.BUDGET_MEMORY);\n\t\tconst usr = cap(sanitize(this.lastWrittenUser), FileStoreProvider.BUDGET_USER);\n\n\t\tconst blocks: string[] = [];\n\t\tif (mem.trim()) {\n\t\t\tblocks.push(`## MEMORY.md:\\n${mem}`);\n\t\t}\n\t\tif (usr.trim()) {\n\t\t\tblocks.push(`## USER.md:\\n${usr}`);\n\t\t}\n\n\t\tif (blocks.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tconst block = `=== Persistent Memory (file-store) ===\\n[System Note: Below is a snapshot of your persistent memory. You can update these using the 'memory' tool.]\\n\\n${blocks.join(\"\\n\\n\")}`;\n\t\tif (budget?.compact && (block.split(\"\\n\").length > budget.maxLines || block.length > budget.maxChars)) {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn block;\n\t}\n\n\tpublic async prefetch(_query: string): Promise<string> {\n\t\t// static system prompt block is sufficient for file-store default; no-op prefetch\n\t\treturn \"\";\n\t}\n\n\tpublic async shutdown(): Promise<void> {\n\t\t// no-op\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\treturn [];\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tname: \"memory\",\n\t\t\t\tlabel: \"Persistent Memory Manager\",\n\t\t\t\tdescription: \"Add, replace, or remove contents in persistent memory files (MEMORY.md/USER.md).\",\n\t\t\t\tparameters: memorySchema,\n\t\t\t\texecute: async (_toolCallId, params: MemoryParams, _signal, _onUpdate, _execCtx) => {\n\t\t\t\t\tif (this.ctx?.isChildSession) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: \"Error: Writes to persistent memory are not allowed in child sessions (subagents).\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: \"Child session write-gated\" },\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { action, target, content, oldContent } = params;\n\n\t\t\t\t\t// Strict-scope injection guard on the high-privilege WRITE path (agy #31): a poisoned\n\t\t\t\t\t// memory entry persists across sessions and is injected into every future system prompt,\n\t\t\t\t\t// so block outright rather than strip. Hidden/bidi-control chars have no legitimate place\n\t\t\t\t\t// in a memory note, so reject those too.\n\t\t\t\t\tif ((action === \"add\" || action === \"replace\") && content !== undefined) {\n\t\t\t\t\t\tif (hasInvisibleUnicode(content)) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: \"Error: memory write rejected — contains hidden/bidirectional control characters.\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Invisible unicode in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst threats = scanContextFileThreats(content, \"strict\");\n\t\t\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: memory write rejected — potential injection/exfiltration detected (${threats.join(\", \")}).`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Threat in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst filePath = target === \"memory\" ? this.memoryFilePath : this.userFilePath;\n\t\t\t\t\tconst budget = target === \"memory\" ? FileStoreProvider.BUDGET_MEMORY : FileStoreProvider.BUDGET_USER;\n\n\t\t\t\t\tlet release: (() => Promise<void>) | undefined;\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// File lock\n\t\t\t\t\t\trelease = await lockfile.lock(filePath, { realpath: false, retries: 5 });\n\n\t\t\t\t\t\tconst lastWritten = target === \"memory\" ? this.lastWrittenMemory : this.lastWrittenUser;\n\t\t\t\t\t\t// Read current file content on disk for drift detection\n\t\t\t\t\t\tconst currentOnDisk = await fs.readFile(filePath, \"utf-8\");\n\t\t\t\t\t\tif (currentOnDisk !== lastWritten) {\n\t\t\t\t\t\t\t// Drift detected. Backup current file and refuse write.\n\t\t\t\t\t\t\tconst backupPath = `${filePath}.bak.${Date.now()}`;\n\t\t\t\t\t\t\tawait fs.writeFile(backupPath, currentOnDisk, \"utf-8\");\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Drift detected. The memory file has been modified out-of-band by an external process. A backup was created at ${backupPath}. Operation aborted.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Drift detected\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet newContent = currentOnDisk;\n\t\t\t\t\t\tif (action === \"add\") {\n\t\t\t\t\t\t\tif (content === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'content' is required for action 'add'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Confront before write. If this fact is a near-duplicate of an existing line,\n\t\t\t\t\t\t\t// supersede it in place instead of appending a redundant copy (prevents append-rot).\n\t\t\t\t\t\t\tconst superseded = supersedeNearDuplicateLine(currentOnDisk, content);\n\t\t\t\t\t\t\tif (superseded !== null) {\n\t\t\t\t\t\t\t\tnewContent = superseded;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnewContent =\n\t\t\t\t\t\t\t\t\tnewContent.endsWith(\"\\n\") || newContent === \"\"\n\t\t\t\t\t\t\t\t\t\t? `${newContent}${content}\\n`\n\t\t\t\t\t\t\t\t\t\t: `${newContent}\\n${content}\\n`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (action === \"replace\") {\n\t\t\t\t\t\t\tif (content === undefined || oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameters 'content' and 'oldContent' are required for action 'replace'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to replace ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, content);\n\t\t\t\t\t\t} else if (action === \"remove\") {\n\t\t\t\t\t\t\tif (oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'oldContent' is required for action 'remove'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to remove ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Budget check\n\t\t\t\t\t\tif (newContent.length > budget) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Memory budget exceeded. ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"} limit is ${budget} characters. Current operation would result in ${newContent.length} characters.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Memory budget exceeded\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Atomic write\n\t\t\t\t\t\tconst tmpPath = `${filePath}.tmp`;\n\t\t\t\t\t\tawait fs.writeFile(tmpPath, newContent, \"utf-8\");\n\t\t\t\t\t\tawait fs.rename(tmpPath, filePath);\n\n\t\t\t\t\t\t// Update in-memory tracker\n\t\t\t\t\t\tif (target === \"memory\") {\n\t\t\t\t\t\t\tthis.lastWrittenMemory = newContent;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.lastWrittenUser = newContent;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Successfully updated ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"}.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: true },\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Error: Failed to perform memory operation: ${String(err)}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: String(err) },\n\t\t\t\t\t\t};\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (release) {\n\t\t\t\t\t\t\tawait release();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { existsSync, promises as fs, mkdirSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
2
|
import lockfile from "proper-lockfile";
|
|
4
3
|
import { Type } from "typebox";
|
|
4
|
+
import { configFile } from "../../agent-paths.js";
|
|
5
5
|
import { hasInvisibleUnicode, scanContextFileThreats, stripInvisibleUnicode } from "../../resource-loader.js";
|
|
6
6
|
import { jaccard, tokenize } from "../../tools/skill-audit.js";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing
|
|
9
9
|
* non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in
|
|
10
10
|
* place and return the rewritten file; otherwise return null (the caller appends normally).
|
|
11
11
|
*/
|
|
@@ -22,7 +22,7 @@ export function supersedeNearDuplicateLine(existing, content) {
|
|
|
22
22
|
if (!line)
|
|
23
23
|
continue;
|
|
24
24
|
// Never supersede structural Markdown (headings, list markers as headings) — a fact must not
|
|
25
|
-
// silently overwrite section structure
|
|
25
|
+
// silently overwrite section structure.
|
|
26
26
|
if (line.startsWith("#"))
|
|
27
27
|
continue;
|
|
28
28
|
const score = jaccard(contentTokens, tokenize(line));
|
|
@@ -65,8 +65,8 @@ export class FileStoreProvider {
|
|
|
65
65
|
}
|
|
66
66
|
async initialize(_sessionId, ctx) {
|
|
67
67
|
this.ctx = ctx;
|
|
68
|
-
this.memoryFilePath =
|
|
69
|
-
this.userFilePath =
|
|
68
|
+
this.memoryFilePath = configFile(ctx.agentDir, "MEMORY.md");
|
|
69
|
+
this.userFilePath = configFile(ctx.agentDir, "USER.md");
|
|
70
70
|
// Ensure agentDir exists
|
|
71
71
|
if (!existsSync(ctx.agentDir)) {
|
|
72
72
|
mkdirSync(ctx.agentDir, { recursive: true });
|
|
@@ -210,7 +210,7 @@ export class FileStoreProvider {
|
|
|
210
210
|
if (content === undefined) {
|
|
211
211
|
throw new Error("Parameter 'content' is required for action 'add'.");
|
|
212
212
|
}
|
|
213
|
-
//
|
|
213
|
+
// Confront before write. If this fact is a near-duplicate of an existing line,
|
|
214
214
|
// supersede it in place instead of appending a redundant copy (prevents append-rot).
|
|
215
215
|
const superseded = supersedeNearDuplicateLine(currentOnDisk, content);
|
|
216
216
|
if (superseded !== null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.js","sourceRoot":"","sources":["../../../../src/core/memory/providers/file-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, promises as fs, mkdirSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { type Static, Type } from \"typebox\";\nimport type { MemoryPromptBudget } from \"../../context/memory-prompt-budget.ts\";\nimport type { ToolDefinition } from \"../../extensions/types.ts\";\nimport { hasInvisibleUnicode, scanContextFileThreats, stripInvisibleUnicode } from \"../../resource-loader.ts\";\nimport { jaccard, tokenize } from \"../../tools/skill-audit.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\n\n/**\n * R5 confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing\n * non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in\n * place and return the rewritten file; otherwise return null (the caller appends normally).\n */\nexport function supersedeNearDuplicateLine(existing: string, content: string): string | null {\n\tconst NEAR_DUP_THRESHOLD = 0.6;\n\tconst contentTokens = tokenize(content);\n\tif (contentTokens.length === 0) return null;\n\tconst lines = existing.split(\"\\n\");\n\tlet bestIdx = -1;\n\tlet bestScore = NEAR_DUP_THRESHOLD;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i].trim();\n\t\tif (!line) continue;\n\t\t// Never supersede structural Markdown (headings, list markers as headings) — a fact must not\n\t\t// silently overwrite section structure (Bug #15).\n\t\tif (line.startsWith(\"#\")) continue;\n\t\tconst score = jaccard(contentTokens, tokenize(line));\n\t\tif (score >= bestScore) {\n\t\t\tbestScore = score;\n\t\t\tbestIdx = i;\n\t\t}\n\t}\n\tif (bestIdx === -1) return null;\n\tlines[bestIdx] = content;\n\treturn lines.join(\"\\n\");\n}\n\nconst memorySchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"add\"), Type.Literal(\"replace\"), Type.Literal(\"remove\")], {\n\t\tdescription: \"Action to perform: add new content, replace existing content, or remove content\",\n\t}),\n\ttarget: Type.Union([Type.Literal(\"memory\"), Type.Literal(\"user\")], {\n\t\tdescription: \"Target file: 'memory' for MEMORY.md, 'user' for USER.md\",\n\t}),\n\tcontent: Type.Optional(Type.String({ description: \"Content to write (required for 'add' or 'replace')\" })),\n\toldContent: Type.Optional(\n\t\tType.String({ description: \"Exact substring to replace or remove (required for 'replace' or 'remove')\" }),\n\t),\n});\n\ntype MemoryParams = Static<typeof memorySchema>;\n\nexport class FileStoreProvider implements MemoryProvider {\n\tpublic readonly name = \"file-store\";\n\tpublic readonly egress = \"local\";\n\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate memoryFilePath = \"\";\n\tprivate userFilePath = \"\";\n\n\tprivate lastWrittenMemory = \"\";\n\tprivate lastWrittenUser = \"\";\n\n\t// Character budgets\n\tprivate static readonly BUDGET_MEMORY = 2200;\n\tprivate static readonly BUDGET_USER = 1375;\n\n\tpublic isAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tpublic getCapabilities() {\n\t\treturn { surfaces: [\"context\" as const] };\n\t}\n\n\tpublic async initialize(_sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.memoryFilePath = join(ctx.agentDir, \"MEMORY.md\");\n\t\tthis.userFilePath = join(ctx.agentDir, \"USER.md\");\n\n\t\t// Ensure agentDir exists\n\t\tif (!existsSync(ctx.agentDir)) {\n\t\t\tmkdirSync(ctx.agentDir, { recursive: true });\n\t\t}\n\n\t\t// Initialize files if they do not exist\n\t\tif (!existsSync(this.memoryFilePath)) {\n\t\t\twriteFileSync(this.memoryFilePath, \"\", \"utf-8\");\n\t\t}\n\t\tif (!existsSync(this.userFilePath)) {\n\t\t\twriteFileSync(this.userFilePath, \"\", \"utf-8\");\n\t\t}\n\n\t\t// Load initial contents\n\t\tthis.lastWrittenMemory = await fs.readFile(this.memoryFilePath, \"utf-8\");\n\t\tthis.lastWrittenUser = await fs.readFile(this.userFilePath, \"utf-8\");\n\t}\n\n\tpublic systemPromptBlock(budget?: MemoryPromptBudget): string {\n\t\tconst sanitize = (content: string) => {\n\t\t\t// Strip hidden/bidi-control chars before injecting memory into the prompt (defence in depth: the\n\t\t\t// write path already blocks them, but a file edited out-of-band could carry them). Strip #31.\n\t\t\tconst lines = stripInvisibleUnicode(content).cleaned.split(\"\\n\");\n\t\t\tconst sanitizedLines = lines.map((line) => {\n\t\t\t\tconst threats = scanContextFileThreats(line);\n\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\treturn `[BLOCKED: potential threat detected (${threats.join(\", \")})]`;\n\t\t\t\t}\n\t\t\t\treturn line;\n\t\t\t});\n\t\t\treturn sanitizedLines.join(\"\\n\");\n\t\t};\n\n\t\t// Read-time budget guard (cost): the memory tool already caps writes at BUDGET_*, but a file edited\n\t\t// externally (or by any path that bypasses the tool) could be arbitrarily large and would then\n\t\t// bloat the system prompt on EVERY turn. Cap the injected view to the same budget so the per-turn\n\t\t// cost stays bounded; the file on disk is untouched and the model is told it was truncated.\n\t\tconst cap = (content: string, limit: number) => {\n\t\t\tif (content.length <= limit) return content;\n\t\t\treturn `${content.slice(0, limit)}\\n[…truncated to ${limit} chars for the prompt; full file is on disk]`;\n\t\t};\n\n\t\tconst mem = cap(sanitize(this.lastWrittenMemory), FileStoreProvider.BUDGET_MEMORY);\n\t\tconst usr = cap(sanitize(this.lastWrittenUser), FileStoreProvider.BUDGET_USER);\n\n\t\tconst blocks: string[] = [];\n\t\tif (mem.trim()) {\n\t\t\tblocks.push(`## MEMORY.md:\\n${mem}`);\n\t\t}\n\t\tif (usr.trim()) {\n\t\t\tblocks.push(`## USER.md:\\n${usr}`);\n\t\t}\n\n\t\tif (blocks.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tconst block = `=== Persistent Memory (file-store) ===\\n[System Note: Below is a snapshot of your persistent memory. You can update these using the 'memory' tool.]\\n\\n${blocks.join(\"\\n\\n\")}`;\n\t\tif (budget?.compact && (block.split(\"\\n\").length > budget.maxLines || block.length > budget.maxChars)) {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn block;\n\t}\n\n\tpublic async prefetch(_query: string): Promise<string> {\n\t\t// static system prompt block is sufficient for file-store default; no-op prefetch\n\t\treturn \"\";\n\t}\n\n\tpublic async shutdown(): Promise<void> {\n\t\t// no-op\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\treturn [];\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tname: \"memory\",\n\t\t\t\tlabel: \"Persistent Memory Manager\",\n\t\t\t\tdescription: \"Add, replace, or remove contents in persistent memory files (MEMORY.md/USER.md).\",\n\t\t\t\tparameters: memorySchema,\n\t\t\t\texecute: async (_toolCallId, params: MemoryParams, _signal, _onUpdate, _execCtx) => {\n\t\t\t\t\tif (this.ctx?.isChildSession) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: \"Error: Writes to persistent memory are not allowed in child sessions (subagents).\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: \"Child session write-gated\" },\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { action, target, content, oldContent } = params;\n\n\t\t\t\t\t// Strict-scope injection guard on the high-privilege WRITE path (agy #31): a poisoned\n\t\t\t\t\t// memory entry persists across sessions and is injected into every future system prompt,\n\t\t\t\t\t// so block outright rather than strip. Hidden/bidi-control chars have no legitimate place\n\t\t\t\t\t// in a memory note, so reject those too.\n\t\t\t\t\tif ((action === \"add\" || action === \"replace\") && content !== undefined) {\n\t\t\t\t\t\tif (hasInvisibleUnicode(content)) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: \"Error: memory write rejected — contains hidden/bidirectional control characters.\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Invisible unicode in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst threats = scanContextFileThreats(content, \"strict\");\n\t\t\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: memory write rejected — potential injection/exfiltration detected (${threats.join(\", \")}).`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Threat in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst filePath = target === \"memory\" ? this.memoryFilePath : this.userFilePath;\n\t\t\t\t\tconst budget = target === \"memory\" ? FileStoreProvider.BUDGET_MEMORY : FileStoreProvider.BUDGET_USER;\n\n\t\t\t\t\tlet release: (() => Promise<void>) | undefined;\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// File lock\n\t\t\t\t\t\trelease = await lockfile.lock(filePath, { realpath: false, retries: 5 });\n\n\t\t\t\t\t\tconst lastWritten = target === \"memory\" ? this.lastWrittenMemory : this.lastWrittenUser;\n\t\t\t\t\t\t// Read current file content on disk for drift detection\n\t\t\t\t\t\tconst currentOnDisk = await fs.readFile(filePath, \"utf-8\");\n\t\t\t\t\t\tif (currentOnDisk !== lastWritten) {\n\t\t\t\t\t\t\t// Drift detected. Backup current file and refuse write.\n\t\t\t\t\t\t\tconst backupPath = `${filePath}.bak.${Date.now()}`;\n\t\t\t\t\t\t\tawait fs.writeFile(backupPath, currentOnDisk, \"utf-8\");\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Drift detected. The memory file has been modified out-of-band by an external process. A backup was created at ${backupPath}. Operation aborted.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Drift detected\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet newContent = currentOnDisk;\n\t\t\t\t\t\tif (action === \"add\") {\n\t\t\t\t\t\t\tif (content === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'content' is required for action 'add'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// R5: confront before write. If this fact is a near-duplicate of an existing line,\n\t\t\t\t\t\t\t// supersede it in place instead of appending a redundant copy (prevents append-rot).\n\t\t\t\t\t\t\tconst superseded = supersedeNearDuplicateLine(currentOnDisk, content);\n\t\t\t\t\t\t\tif (superseded !== null) {\n\t\t\t\t\t\t\t\tnewContent = superseded;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnewContent =\n\t\t\t\t\t\t\t\t\tnewContent.endsWith(\"\\n\") || newContent === \"\"\n\t\t\t\t\t\t\t\t\t\t? `${newContent}${content}\\n`\n\t\t\t\t\t\t\t\t\t\t: `${newContent}\\n${content}\\n`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (action === \"replace\") {\n\t\t\t\t\t\t\tif (content === undefined || oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameters 'content' and 'oldContent' are required for action 'replace'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to replace ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, content);\n\t\t\t\t\t\t} else if (action === \"remove\") {\n\t\t\t\t\t\t\tif (oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'oldContent' is required for action 'remove'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to remove ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Budget check\n\t\t\t\t\t\tif (newContent.length > budget) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Memory budget exceeded. ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"} limit is ${budget} characters. Current operation would result in ${newContent.length} characters.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Memory budget exceeded\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Atomic write\n\t\t\t\t\t\tconst tmpPath = `${filePath}.tmp`;\n\t\t\t\t\t\tawait fs.writeFile(tmpPath, newContent, \"utf-8\");\n\t\t\t\t\t\tawait fs.rename(tmpPath, filePath);\n\n\t\t\t\t\t\t// Update in-memory tracker\n\t\t\t\t\t\tif (target === \"memory\") {\n\t\t\t\t\t\t\tthis.lastWrittenMemory = newContent;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.lastWrittenUser = newContent;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Successfully updated ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"}.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: true },\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Error: Failed to perform memory operation: ${String(err)}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: String(err) },\n\t\t\t\t\t\t};\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (release) {\n\t\t\t\t\t\t\tawait release();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"file-store.js","sourceRoot":"","sources":["../../../../src/core/memory/providers/file-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, promises as fs, mkdirSync, writeFileSync } from \"fs\";\nimport lockfile from \"proper-lockfile\";\nimport { type Static, Type } from \"typebox\";\nimport { configFile } from \"../../agent-paths.ts\";\nimport type { MemoryPromptBudget } from \"../../context/memory-prompt-budget.ts\";\nimport type { ToolDefinition } from \"../../extensions/types.ts\";\nimport { hasInvisibleUnicode, scanContextFileThreats, stripInvisibleUnicode } from \"../../resource-loader.ts\";\nimport { jaccard, tokenize } from \"../../tools/skill-audit.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\n\n/**\n * Confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing\n * non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in\n * place and return the rewritten file; otherwise return null (the caller appends normally).\n */\nexport function supersedeNearDuplicateLine(existing: string, content: string): string | null {\n\tconst NEAR_DUP_THRESHOLD = 0.6;\n\tconst contentTokens = tokenize(content);\n\tif (contentTokens.length === 0) return null;\n\tconst lines = existing.split(\"\\n\");\n\tlet bestIdx = -1;\n\tlet bestScore = NEAR_DUP_THRESHOLD;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i].trim();\n\t\tif (!line) continue;\n\t\t// Never supersede structural Markdown (headings, list markers as headings) — a fact must not\n\t\t// silently overwrite section structure.\n\t\tif (line.startsWith(\"#\")) continue;\n\t\tconst score = jaccard(contentTokens, tokenize(line));\n\t\tif (score >= bestScore) {\n\t\t\tbestScore = score;\n\t\t\tbestIdx = i;\n\t\t}\n\t}\n\tif (bestIdx === -1) return null;\n\tlines[bestIdx] = content;\n\treturn lines.join(\"\\n\");\n}\n\nconst memorySchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"add\"), Type.Literal(\"replace\"), Type.Literal(\"remove\")], {\n\t\tdescription: \"Action to perform: add new content, replace existing content, or remove content\",\n\t}),\n\ttarget: Type.Union([Type.Literal(\"memory\"), Type.Literal(\"user\")], {\n\t\tdescription: \"Target file: 'memory' for MEMORY.md, 'user' for USER.md\",\n\t}),\n\tcontent: Type.Optional(Type.String({ description: \"Content to write (required for 'add' or 'replace')\" })),\n\toldContent: Type.Optional(\n\t\tType.String({ description: \"Exact substring to replace or remove (required for 'replace' or 'remove')\" }),\n\t),\n});\n\ntype MemoryParams = Static<typeof memorySchema>;\n\nexport class FileStoreProvider implements MemoryProvider {\n\tpublic readonly name = \"file-store\";\n\tpublic readonly egress = \"local\";\n\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate memoryFilePath = \"\";\n\tprivate userFilePath = \"\";\n\n\tprivate lastWrittenMemory = \"\";\n\tprivate lastWrittenUser = \"\";\n\n\t// Character budgets\n\tprivate static readonly BUDGET_MEMORY = 2200;\n\tprivate static readonly BUDGET_USER = 1375;\n\n\tpublic isAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tpublic getCapabilities() {\n\t\treturn { surfaces: [\"context\" as const] };\n\t}\n\n\tpublic async initialize(_sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.memoryFilePath = configFile(ctx.agentDir, \"MEMORY.md\");\n\t\tthis.userFilePath = configFile(ctx.agentDir, \"USER.md\");\n\n\t\t// Ensure agentDir exists\n\t\tif (!existsSync(ctx.agentDir)) {\n\t\t\tmkdirSync(ctx.agentDir, { recursive: true });\n\t\t}\n\n\t\t// Initialize files if they do not exist\n\t\tif (!existsSync(this.memoryFilePath)) {\n\t\t\twriteFileSync(this.memoryFilePath, \"\", \"utf-8\");\n\t\t}\n\t\tif (!existsSync(this.userFilePath)) {\n\t\t\twriteFileSync(this.userFilePath, \"\", \"utf-8\");\n\t\t}\n\n\t\t// Load initial contents\n\t\tthis.lastWrittenMemory = await fs.readFile(this.memoryFilePath, \"utf-8\");\n\t\tthis.lastWrittenUser = await fs.readFile(this.userFilePath, \"utf-8\");\n\t}\n\n\tpublic systemPromptBlock(budget?: MemoryPromptBudget): string {\n\t\tconst sanitize = (content: string) => {\n\t\t\t// Strip hidden/bidi-control chars before injecting memory into the prompt (defence in depth: the\n\t\t\t// write path already blocks them, but a file edited out-of-band could carry them). Strip #31.\n\t\t\tconst lines = stripInvisibleUnicode(content).cleaned.split(\"\\n\");\n\t\t\tconst sanitizedLines = lines.map((line) => {\n\t\t\t\tconst threats = scanContextFileThreats(line);\n\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\treturn `[BLOCKED: potential threat detected (${threats.join(\", \")})]`;\n\t\t\t\t}\n\t\t\t\treturn line;\n\t\t\t});\n\t\t\treturn sanitizedLines.join(\"\\n\");\n\t\t};\n\n\t\t// Read-time budget guard (cost): the memory tool already caps writes at BUDGET_*, but a file edited\n\t\t// externally (or by any path that bypasses the tool) could be arbitrarily large and would then\n\t\t// bloat the system prompt on EVERY turn. Cap the injected view to the same budget so the per-turn\n\t\t// cost stays bounded; the file on disk is untouched and the model is told it was truncated.\n\t\tconst cap = (content: string, limit: number) => {\n\t\t\tif (content.length <= limit) return content;\n\t\t\treturn `${content.slice(0, limit)}\\n[…truncated to ${limit} chars for the prompt; full file is on disk]`;\n\t\t};\n\n\t\tconst mem = cap(sanitize(this.lastWrittenMemory), FileStoreProvider.BUDGET_MEMORY);\n\t\tconst usr = cap(sanitize(this.lastWrittenUser), FileStoreProvider.BUDGET_USER);\n\n\t\tconst blocks: string[] = [];\n\t\tif (mem.trim()) {\n\t\t\tblocks.push(`## MEMORY.md:\\n${mem}`);\n\t\t}\n\t\tif (usr.trim()) {\n\t\t\tblocks.push(`## USER.md:\\n${usr}`);\n\t\t}\n\n\t\tif (blocks.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tconst block = `=== Persistent Memory (file-store) ===\\n[System Note: Below is a snapshot of your persistent memory. You can update these using the 'memory' tool.]\\n\\n${blocks.join(\"\\n\\n\")}`;\n\t\tif (budget?.compact && (block.split(\"\\n\").length > budget.maxLines || block.length > budget.maxChars)) {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn block;\n\t}\n\n\tpublic async prefetch(_query: string): Promise<string> {\n\t\t// static system prompt block is sufficient for file-store default; no-op prefetch\n\t\treturn \"\";\n\t}\n\n\tpublic async shutdown(): Promise<void> {\n\t\t// no-op\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\treturn [];\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tname: \"memory\",\n\t\t\t\tlabel: \"Persistent Memory Manager\",\n\t\t\t\tdescription: \"Add, replace, or remove contents in persistent memory files (MEMORY.md/USER.md).\",\n\t\t\t\tparameters: memorySchema,\n\t\t\t\texecute: async (_toolCallId, params: MemoryParams, _signal, _onUpdate, _execCtx) => {\n\t\t\t\t\tif (this.ctx?.isChildSession) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: \"Error: Writes to persistent memory are not allowed in child sessions (subagents).\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: \"Child session write-gated\" },\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { action, target, content, oldContent } = params;\n\n\t\t\t\t\t// Strict-scope injection guard on the high-privilege WRITE path (agy #31): a poisoned\n\t\t\t\t\t// memory entry persists across sessions and is injected into every future system prompt,\n\t\t\t\t\t// so block outright rather than strip. Hidden/bidi-control chars have no legitimate place\n\t\t\t\t\t// in a memory note, so reject those too.\n\t\t\t\t\tif ((action === \"add\" || action === \"replace\") && content !== undefined) {\n\t\t\t\t\t\tif (hasInvisibleUnicode(content)) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: \"Error: memory write rejected — contains hidden/bidirectional control characters.\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Invisible unicode in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst threats = scanContextFileThreats(content, \"strict\");\n\t\t\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: memory write rejected — potential injection/exfiltration detected (${threats.join(\", \")}).`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Threat in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst filePath = target === \"memory\" ? this.memoryFilePath : this.userFilePath;\n\t\t\t\t\tconst budget = target === \"memory\" ? FileStoreProvider.BUDGET_MEMORY : FileStoreProvider.BUDGET_USER;\n\n\t\t\t\t\tlet release: (() => Promise<void>) | undefined;\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// File lock\n\t\t\t\t\t\trelease = await lockfile.lock(filePath, { realpath: false, retries: 5 });\n\n\t\t\t\t\t\tconst lastWritten = target === \"memory\" ? this.lastWrittenMemory : this.lastWrittenUser;\n\t\t\t\t\t\t// Read current file content on disk for drift detection\n\t\t\t\t\t\tconst currentOnDisk = await fs.readFile(filePath, \"utf-8\");\n\t\t\t\t\t\tif (currentOnDisk !== lastWritten) {\n\t\t\t\t\t\t\t// Drift detected. Backup current file and refuse write.\n\t\t\t\t\t\t\tconst backupPath = `${filePath}.bak.${Date.now()}`;\n\t\t\t\t\t\t\tawait fs.writeFile(backupPath, currentOnDisk, \"utf-8\");\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Drift detected. The memory file has been modified out-of-band by an external process. A backup was created at ${backupPath}. Operation aborted.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Drift detected\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet newContent = currentOnDisk;\n\t\t\t\t\t\tif (action === \"add\") {\n\t\t\t\t\t\t\tif (content === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'content' is required for action 'add'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Confront before write. If this fact is a near-duplicate of an existing line,\n\t\t\t\t\t\t\t// supersede it in place instead of appending a redundant copy (prevents append-rot).\n\t\t\t\t\t\t\tconst superseded = supersedeNearDuplicateLine(currentOnDisk, content);\n\t\t\t\t\t\t\tif (superseded !== null) {\n\t\t\t\t\t\t\t\tnewContent = superseded;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnewContent =\n\t\t\t\t\t\t\t\t\tnewContent.endsWith(\"\\n\") || newContent === \"\"\n\t\t\t\t\t\t\t\t\t\t? `${newContent}${content}\\n`\n\t\t\t\t\t\t\t\t\t\t: `${newContent}\\n${content}\\n`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (action === \"replace\") {\n\t\t\t\t\t\t\tif (content === undefined || oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameters 'content' and 'oldContent' are required for action 'replace'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to replace ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, content);\n\t\t\t\t\t\t} else if (action === \"remove\") {\n\t\t\t\t\t\t\tif (oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'oldContent' is required for action 'remove'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to remove ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Budget check\n\t\t\t\t\t\tif (newContent.length > budget) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Memory budget exceeded. ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"} limit is ${budget} characters. Current operation would result in ${newContent.length} characters.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Memory budget exceeded\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Atomic write\n\t\t\t\t\t\tconst tmpPath = `${filePath}.tmp`;\n\t\t\t\t\t\tawait fs.writeFile(tmpPath, newContent, \"utf-8\");\n\t\t\t\t\t\tawait fs.rename(tmpPath, filePath);\n\n\t\t\t\t\t\t// Update in-memory tracker\n\t\t\t\t\t\tif (target === \"memory\") {\n\t\t\t\t\t\t\tthis.lastWrittenMemory = newContent;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.lastWrittenUser = newContent;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Successfully updated ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"}.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: true },\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Error: Failed to perform memory operation: ${String(err)}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: String(err) },\n\t\t\t\t\t\t};\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (release) {\n\t\t\t\t\t\t\tawait release();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n}\n"]}
|
|
@@ -57,4 +57,38 @@ export declare const MODEL_CAPABILITY_LEAN_MAX_CONTINUE_WALL_CLOCK_MINUTES = 5;
|
|
|
57
57
|
export declare function scaleContinuationBudgetsForCapability(profile: ModelCapabilityProfile, budgets: ContinuationBudgets): ContinuationBudgets;
|
|
58
58
|
/** Apply the profile's allow/block lists to a requested tool-name list, preserving order. */
|
|
59
59
|
export declare function filterToolNamesForCapability(toolNames: readonly string[], profile: ModelCapabilityProfile): string[];
|
|
60
|
+
/**
|
|
61
|
+
* Lane-worker eligibility: a session bound to a worktree-sync lane (`--worktree-lane` /
|
|
62
|
+
* `PI_WORKTREE_LANE`, see worktree-sync/runtime.ts) is expected to drive the FULL multi-step
|
|
63
|
+
* lane-gate/recovery surface -- sync, conflict recovery, land -- unattended. A sub-full capability
|
|
64
|
+
* class or a model with no working native tool-call path cannot reliably drive that surface. This
|
|
65
|
+
* rides the SAME capability system every other adaptation in this file rides (class + context
|
|
66
|
+
* window + the `/toolprobe` verdict) -- no parallel mechanism, no new env var, no new registry field.
|
|
67
|
+
*/
|
|
68
|
+
export type LaneWorkerRefusalReason = "capability_class_below_full" | "context_window_unknown" | "tool_calling_unadvertised" | "tool_calling_demoted";
|
|
69
|
+
export interface LaneWorkerRefusal {
|
|
70
|
+
reason: LaneWorkerRefusalReason;
|
|
71
|
+
capabilityClass: ModelCapabilityClass;
|
|
72
|
+
contextWindow?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Decide whether the model described by `args` may drive a worktree-sync lane worker. First
|
|
76
|
+
* failure wins, in order: capability class below full; an unknown/undeclared context window (the
|
|
77
|
+
* classifier's own registry-derived SSOT -- see `deriveModelCapabilityProfile`; a full class can
|
|
78
|
+
* still carry an undefined window via the `unknown_context_window_defaults` fallback); no
|
|
79
|
+
* ADVERTISED native tool-call path (`Model.textToolCallProtocol` unset/false -- set true means
|
|
80
|
+
* phone-only); or a GRADED `/toolprobe` demotion to "text-protocol"/"none". An UNPROBED model (no
|
|
81
|
+
* verdict on record yet) is eligible on its advertised support alone -- unprobed is never treated
|
|
82
|
+
* as demoted. `undefined` means eligible.
|
|
83
|
+
*/
|
|
84
|
+
export declare function evaluateLaneWorkerRefusal(args: {
|
|
85
|
+
capabilityClass: ModelCapabilityClass;
|
|
86
|
+
contextWindow: number | undefined;
|
|
87
|
+
toolCallingAdvertised: boolean;
|
|
88
|
+
toolCallingDemoted: boolean;
|
|
89
|
+
}): LaneWorkerRefusal | undefined;
|
|
90
|
+
/** Stable, greppable prefix for {@link formatLaneWorkerRefusal}'s output. */
|
|
91
|
+
export declare const LANE_WORKER_REFUSAL_PREFIX = "worktree-sync lane-worker refusal:";
|
|
92
|
+
/** Format a refusal into one deterministic, greppable line naming the lane, class, window, and reason. */
|
|
93
|
+
export declare function formatLaneWorkerRefusal(refusal: LaneWorkerRefusal, laneKey?: string): string;
|
|
60
94
|
//# sourceMappingURL=model-capability.d.ts.map
|