@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":"bash-execution-controller.js","sourceRoot":"","sources":["../../src/core/bash-execution-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAmB,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EAAuB,kCAAkC,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"bash-execution-controller.js","sourceRoot":"","sources":["../../src/core/bash-execution-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Standalone bash-command execution (the `/bash` path + extension-driven bash).\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the bash abort controller\n * and the pending-message queue that defers appending a bash result while the agent is streaming (so\n * tool_use/tool_result ordering is never broken); the deferred messages are flushed by the session on\n * agent turn completion. Persists results through the session log via deps.\n */\n\nimport type { Agent, BashExecutionMessage } from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { type BashOperations, createLocalPlatformShellOperations, resolveCommandTimeoutSeconds } from \"./tools/bash.ts\";\n\nexport interface BashExecutionControllerDeps {\n\tgetAgent(): Agent;\n\tgetSessionManager(): SessionManager;\n\tgetSettingsManager(): SettingsManager;\n\t/** Whether the agent is currently streaming — defers appending a bash result if so. */\n\tisStreaming(): boolean;\n\t/** Per-agent persistent shell session key — user `!` commands share the agent's shell state. */\n\tgetShellSessionKey?(): string;\n}\n\nexport interface BashExecutionOptions {\n\texcludeFromContext?: boolean;\n\toperations?: BashOperations;\n\t/** Injectable target platform for tests and embedded runtimes. */\n\tplatform?: NodeJS.Platform;\n\t/** Wall-clock timeout in seconds; non-positive values use the bounded default. */\n\ttimeout?: number;\n\t/** Route complex/state-mutating Bash constructs to the Python engine on Windows. Default: true. */\n\tpythonEngine?: boolean;\n}\n\nexport class BashExecutionController {\n\tprivate _bashAbortControllers = new Set<AbortController>();\n\tprivate _pendingBashMessages: BashExecutionMessage[] = [];\n\n\tprivate readonly deps: BashExecutionControllerDeps;\n\n\tconstructor(deps: BashExecutionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: BashExecutionOptions,\n\t): Promise<BashResult> {\n\t\tconst abortController = new AbortController();\n\t\tthis._bashAbortControllers.add(abortController);\n\n\t\tconst commandPrefix = this.deps.getSettingsManager().getShellCommandPrefix();\n\t\tconst shellPath = this.deps.getSettingsManager().getShellPath();\n\t\tconst platform = options?.platform ?? process.platform;\n\t\tconst enableGitFilter = !options?.operations && !commandPrefix && !shellPath;\n\t\tconst operations = createLocalPlatformShellOperations(\n\t\t\t{\n\t\t\t\tshellPath,\n\t\t\t\tcommandPrefix,\n\t\t\t\toperations: options?.operations,\n\t\t\t\tsessionKey: this.deps.getShellSessionKey?.(),\n\t\t\t\tpythonEngine: options?.pythonEngine,\n\t\t\t},\n\t\t\tplatform,\n\t\t);\n\n\t\ttry {\n\t\t\tconst result = await executeBashWithOperations(command, this.deps.getSessionManager().getCwd(), operations, {\n\t\t\t\tonChunk,\n\t\t\t\tsignal: abortController.signal,\n\t\t\t\tenableGitFilter,\n\t\t\t\ttimeout: resolveCommandTimeoutSeconds(options?.timeout),\n\t\t\t});\n\n\t\t\tthis.recordBashResult(command, result, options);\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tthis._bashAbortControllers.delete(abortController);\n\t\t}\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this.deps.isStreaming()) {\n\t\t\t// Queue for later - will be flushed on agent_end\n\t\t\tthis._pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\t// Add to agent state immediately\n\t\t\tthis.deps.getAgent().state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.deps.getSessionManager().appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel running bash command.\n\t */\n\tabortBash(): void {\n\t\tfor (const controller of this._bashAbortControllers) {\n\t\t\tcontroller.abort();\n\t\t}\n\t}\n\n\t/** Whether a bash command is currently running */\n\tget isBashRunning(): boolean {\n\t\treturn this._bashAbortControllers.size > 0;\n\t}\n\n\t/** Whether there are pending bash messages waiting to be flushed */\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this._pendingBashMessages.length > 0;\n\t}\n\n\t/**\n\t * Flush pending bash messages to agent state and session.\n\t * Called after agent turn completes to maintain proper message ordering.\n\t */\n\tflushPendingBashMessages(): void {\n\t\tif (this._pendingBashMessages.length === 0) return;\n\n\t\tfor (const bashMessage of this._pendingBashMessages) {\n\t\t\t// Add to agent state\n\t\t\tthis.deps.getAgent().state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.deps.getSessionManager().appendMessage(bashMessage);\n\t\t}\n\n\t\tthis._pendingBashMessages = [];\n\t}\n}\n"]}
|
|
@@ -27,6 +27,13 @@ export interface CompactionSupportDeps {
|
|
|
27
27
|
/** Estimated tokens of the summarization input (live context; over-estimates, which is safe). */
|
|
28
28
|
estimateSummarizationInputTokens(): number;
|
|
29
29
|
emitWarning(message: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* The same readiness/residency gate every other isolated consumer uses
|
|
32
|
+
* (LocalRuntimeController.ensureIsolatedModelReady) before this model is used for an
|
|
33
|
+
* out-of-band call. No-ops for a non-managed-local model (checked internally by the gate);
|
|
34
|
+
* throws with a user-actionable reason when a managed-local model cannot be made ready.
|
|
35
|
+
*/
|
|
36
|
+
ensureModelReady(model: Model<any>): Promise<void>;
|
|
30
37
|
}
|
|
31
38
|
export declare class CompactionSupport {
|
|
32
39
|
private readonly deps;
|
|
@@ -38,12 +45,15 @@ export declare class CompactionSupport {
|
|
|
38
45
|
apiKey?: string;
|
|
39
46
|
headers?: Record<string, string>;
|
|
40
47
|
}>;
|
|
48
|
+
private readinessFailure;
|
|
41
49
|
/**
|
|
42
50
|
* Resolve the summarizer model AND its request auth for auto-compaction. The cheap auxiliary
|
|
43
51
|
* model (#30) can be nominally available yet fail key resolution at request time (expired
|
|
44
|
-
* OAuth, revoked key)
|
|
45
|
-
*
|
|
46
|
-
*
|
|
52
|
+
* OAuth, revoked key) — or, for a managed-local model, fail the readiness/residency gate (
|
|
53
|
+
* server down, model not installed, residency refused). Falling back to the session model keeps
|
|
54
|
+
* auto-compaction working in exactly the situations where manual /compact works; only when
|
|
55
|
+
* neither resolves do we fail — and then with a concrete message instead of a silent no-op or a
|
|
56
|
+
* bypass of the readiness gate.
|
|
47
57
|
*/
|
|
48
58
|
resolveModelAndAuth(compactionModel: Model<any>, sessionModel: Model<any>): Promise<{
|
|
49
59
|
model: Model<any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction-support.d.ts","sourceRoot":"","sources":["../../src/core/compaction-support.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,KAAK,kBAAkB,EAAuB,MAAM,gCAAgC,CAAC;AAC9F,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,WAAW,qBAAqB;IACrC,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACnC,kBAAkB,IAAI,eAAe,CAAC;IACtC,gBAAgB,IAAI,aAAa,CAAC;IAClC,wGAAsG;IACtG,WAAW,IAAI,OAAO,CAAC;IACvB,0FAA0F;IAC1F,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC;IAC1G,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAC;IACpE,iGAAiG;IACjG,gCAAgC,IAAI,MAAM,CAAC;IAC3C,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;IAC7C,OAAO,CAAC,mBAAmB,CAAqB;IAEhD,YAAY,IAAI,EAAE,qBAAqB,EAEtC;IAED,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAE3C;IAED,kBAAkB,IAAI,kBAAkB,CAoBvC;IAEK,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;QAChD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACjC,CAAC,CAOD;IAED;;;;;;OAMG;IACG,mBAAmB,CACxB,eAAe,EAAE,KAAK,CAAC,GAAG,CAAC,EAC3B,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,GACtB,OAAO,CAAC;QAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAwBrG;IAED,OAAO,CAAC,iCAAiC;IAKzC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,wBAAwB;IAKhC,OAAO,CAAC,mBAAmB;IAkC3B,OAAO,CAAC,cAAc;IAItB;;;;;OAKG;IACH,YAAY,CAAC,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAwBjD;IAED,0EAA0E;IAC1E,oBAAoB,CACnB,oBAAoB,EAAE,aAAa,GAAG,SAAS,EAC/C,eAAe,EAAE,KAAK,CAAC,GAAG,CAAC,EAC3B,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,GACtB,aAAa,GAAG,SAAS,CAY3B;CACD","sourcesContent":["/**\n * Compaction support: summarizer model selection (#30 cost guard), request-auth resolution\n * with session-model fallback, and window-adapted compaction settings.\n *\n * Extracted verbatim from AgentSession (agent-session.ts) as a narrow-deps collaborator; the\n * session keeps same-signature private delegations at every call-in point.\n */\nimport type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { type CompactionSettings, summarizerCanIngest } from \"@caupulican/pi-agent-core/node\";\nimport type { Model } from \"@caupulican/pi-ai\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport { resolveCliModel } from \"./model-resolver.ts\";\nimport { evaluateSurfaceFitness } from \"./model-router/fitness-gate.ts\";\nimport type { ModelFitnessReport } from \"./research/model-fitness.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface CompactionSupportDeps {\n\tgetModel(): Model<any> | undefined;\n\tgetSettingsManager(): SettingsManager;\n\tgetModelRegistry(): ModelRegistry;\n\t/** True when the agent's streamFn is (or wraps) the raw streamSimple — auth must be explicit then. */\n\tisRawStream(): boolean;\n\t/** Host auth resolution that THROWS with a user-actionable message when no key exists. */\n\tgetRequiredRequestAuth(model: Model<any>): Promise<{ apiKey?: string; headers?: Record<string, string> }>;\n\tisModelExhausted(ref: string): boolean;\n\tgetStoredFitnessReport(ref: string): ModelFitnessReport | undefined;\n\t/** Estimated tokens of the summarization input (live context; over-estimates, which is safe). */\n\testimateSummarizationInputTokens(): number;\n\temitWarning(message: string): void;\n}\n\nexport class CompactionSupport {\n\tprivate readonly deps: CompactionSupportDeps;\n\tprivate lastSelectionReason: string | undefined;\n\n\tconstructor(deps: CompactionSupportDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetLastSelectionReason(): string | undefined {\n\t\treturn this.lastSelectionReason;\n\t}\n\n\tgetAdaptedSettings(): CompactionSettings {\n\t\tconst settings = this.deps.getSettingsManager().getCompactionSettings();\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return settings;\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return settings;\n\n\t\t// Adapt reserveTokens: at most 25% of context window\n\t\tconst maxReserve = Math.floor(contextWindow * 0.25);\n\t\tconst reserveTokens = Math.min(settings.reserveTokens, maxReserve);\n\n\t\t// Adapt keepRecentTokens: at most 50% of context window\n\t\tconst maxKeepRecent = Math.floor(contextWindow * 0.5);\n\t\tconst keepRecentTokens = Math.min(settings.keepRecentTokens, maxKeepRecent);\n\n\t\treturn {\n\t\t\t...settings,\n\t\t\treserveTokens,\n\t\t\tkeepRecentTokens,\n\t\t};\n\t}\n\n\tasync getRequestAuth(model: Model<any>): Promise<{\n\t\tapiKey?: string;\n\t\theaders?: Record<string, string>;\n\t}> {\n\t\tif (this.deps.isRawStream()) {\n\t\t\treturn this.deps.getRequiredRequestAuth(model);\n\t\t}\n\n\t\tconst result = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);\n\t\treturn result.ok ? { apiKey: result.apiKey, headers: result.headers } : {};\n\t}\n\n\t/**\n\t * Resolve the summarizer model AND its request auth for auto-compaction. The cheap auxiliary\n\t * model (#30) can be nominally available yet fail key resolution at request time (expired\n\t * OAuth, revoked key). Falling back to the session model keeps auto-compaction working in\n\t * exactly the situations where manual /compact works; only when neither resolves do we fail —\n\t * and then with a concrete message instead of a silent no-op.\n\t */\n\tasync resolveModelAndAuth(\n\t\tcompactionModel: Model<any>,\n\t\tsessionModel: Model<any>,\n\t): Promise<{ model: Model<any>; apiKey?: string; headers?: Record<string, string>; failure?: string }> {\n\t\tif (this.deps.isRawStream()) {\n\t\t\tconst registry = this.deps.getModelRegistry();\n\t\t\tlet auth = await registry.getApiKeyAndHeaders(compactionModel);\n\t\t\tif (auth.ok && auth.apiKey) {\n\t\t\t\treturn { model: compactionModel, apiKey: auth.apiKey, headers: auth.headers };\n\t\t\t}\n\t\t\tconst isSameModel =\n\t\t\t\tcompactionModel.provider === sessionModel.provider && compactionModel.id === sessionModel.id;\n\t\t\tif (!isSameModel) {\n\t\t\t\tauth = await registry.getApiKeyAndHeaders(sessionModel);\n\t\t\t\tif (auth.ok && auth.apiKey) {\n\t\t\t\t\treturn { model: sessionModel, apiKey: auth.apiKey, headers: auth.headers };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tmodel: sessionModel,\n\t\t\t\tfailure: `no usable API key for the summarizer (tried ${compactionModel.id}${isSameModel ? \"\" : ` and ${sessionModel.id}`})`,\n\t\t\t};\n\t\t}\n\n\t\t// Custom streamFn owns auth injection (CLI path) — resolve best-effort, never fail here.\n\t\tconst { apiKey, headers } = await this.getRequestAuth(compactionModel);\n\t\treturn { model: compactionModel, apiKey, headers };\n\t}\n\n\tprivate getExplicitCompactionModelSetting(): string | undefined {\n\t\tconst setting = this.deps.getSettingsManager().getCompactionModel().trim();\n\t\treturn setting && setting !== \"auto\" ? setting : undefined;\n\t}\n\n\tprivate modelRef(model: Model<any>): string {\n\t\treturn `${model.provider}/${model.id}`;\n\t}\n\n\tprivate resolveConfiguredModel(pattern: string): { model?: Model<any>; cause?: \"unresolved\" | \"unauthed\" } {\n\t\tconst registry = this.deps.getModelRegistry();\n\t\tconst resolved = resolveCliModel({ cliModel: pattern, modelRegistry: registry });\n\t\tif (!resolved.model) return { cause: \"unresolved\" };\n\t\tif (!registry.hasConfiguredAuth(resolved.model)) return { cause: \"unauthed\" };\n\t\treturn { model: resolved.model };\n\t}\n\n\tprivate selectConfiguredModel(pattern: string): { model?: Model<any>; cause?: string } {\n\t\tconst resolved = this.resolveConfiguredModel(pattern);\n\t\tif (!resolved.model) return { cause: resolved.cause };\n\t\tif (this.deps.isModelExhausted(this.modelRef(resolved.model))) return { cause: \"exhausted\" };\n\t\treturn { model: resolved.model };\n\t}\n\n\tprivate effectiveContextWindow(model: Model<any>): number {\n\t\tconst registered = model.contextWindow > 0 ? model.contextWindow : Number.POSITIVE_INFINITY;\n\t\tconst served = this.deps.getStoredFitnessReport(this.modelRef(model))?.capacity?.servedContextWindow;\n\t\treturn served && served > 0 ? Math.min(registered, served) : registered;\n\t}\n\n\tprivate modelWithEffectiveWindow(model: Model<any>): Model<any> {\n\t\tconst contextWindow = this.effectiveContextWindow(model);\n\t\treturn contextWindow === model.contextWindow ? model : { ...model, contextWindow };\n\t}\n\n\tprivate resolveDefaultModel(sessionModel: Model<any>): Model<any> {\n\t\tconst router = this.deps.getSettingsManager().getModelRouterSettings();\n\t\tif (!router.enabled || !router.cheapModel) {\n\t\t\tthis.lastSelectionReason = \"session_default\";\n\t\t\treturn sessionModel;\n\t\t}\n\t\tconst selected = this.selectConfiguredModel(router.cheapModel);\n\t\tif (!selected.model) {\n\t\t\tthis.lastSelectionReason = `fallback:${selected.cause}`;\n\t\t\treturn sessionModel;\n\t\t}\n\t\t// Capacity is a hard constraint, independent of the fitness doctrine: a summarizer whose\n\t\t// window cannot hold the actual span produces recall-empty checkpoints (local servers\n\t\t// silently truncate over-window prompts instead of erroring), and the verification gate\n\t\t// then fails deterministically.\n\t\tconst estimatedInputTokens = this.deps.estimateSummarizationInputTokens();\n\t\tconst effectiveWindow = this.effectiveContextWindow(selected.model);\n\t\tif (!summarizerCanIngest(this.modelWithEffectiveWindow(selected.model), estimatedInputTokens)) {\n\t\t\tthis.lastSelectionReason = `fallback:window_too_small(~${Math.ceil(estimatedInputTokens / 1000)}k input vs ${effectiveWindow} window)`;\n\t\t\treturn sessionModel;\n\t\t}\n\t\tconst fitness = this.deps.getStoredFitnessReport(this.modelRef(selected.model));\n\t\tconst verdict = evaluateSurfaceFitness(\"compaction\", fitness);\n\t\tif (!verdict.fit) {\n\t\t\tthis.lastSelectionReason =\n\t\t\t\tverdict.reason === \"lane_failed\"\n\t\t\t\t\t? `fallback:digest_unfit(${verdict.succeeded}/${verdict.total})`\n\t\t\t\t\t: \"fallback:unprobed\";\n\t\t\treturn sessionModel;\n\t\t}\n\t\tthis.lastSelectionReason = \"router_cheap\";\n\t\treturn selected.model;\n\t}\n\n\tprivate modelsAreEqual(left: Model<any>, right: Model<any>): boolean {\n\t\treturn left.provider === right.provider && left.id === right.id;\n\t}\n\n\t/**\n\t * Resolve the model used to SUMMARIZE during compaction. Selection:\n\t * - an explicit `compaction.model` setting wins, but only if its provider is authed (else fall back);\n\t * - `\"auto\"`/unset follows the model router's configured cheap model when the router is enabled;\n\t * - otherwise the session model is used (safe default).\n\t */\n\tresolveModel(sessionModel: Model<any>): Model<any> {\n\t\tconst explicitSetting = this.getExplicitCompactionModelSetting();\n\t\tif (explicitSetting) {\n\t\t\tconst selected = this.selectConfiguredModel(explicitSetting);\n\t\t\tthis.lastSelectionReason = selected.model ? \"explicit\" : `fallback:${selected.cause}`;\n\t\t\tif (!selected.model) this.deps.emitWarning(`Compaction summarizer ${this.lastSelectionReason}`);\n\t\t\t// An explicit user choice is honored (Class C doctrine), but silently sending an\n\t\t\t// over-window prompt yields a recall-empty summary — warn with the numbers.\n\t\t\tif (selected.model) {\n\t\t\t\tconst estimatedInputTokens = this.deps.estimateSummarizationInputTokens();\n\t\t\t\tconst effectiveWindow = this.effectiveContextWindow(selected.model);\n\t\t\t\tif (!summarizerCanIngest(this.modelWithEffectiveWindow(selected.model), estimatedInputTokens)) {\n\t\t\t\t\tthis.deps.emitWarning(\n\t\t\t\t\t\t`Compaction summarizer (explicit setting) likely cannot ingest the current context: ~${Math.ceil(estimatedInputTokens / 1000)}k input tokens vs a ${effectiveWindow}-token window`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn selected.model ?? sessionModel;\n\t\t}\n\t\tconst model = this.resolveDefaultModel(sessionModel);\n\t\tif (this.lastSelectionReason?.startsWith(\"fallback:\")) {\n\t\t\tthis.deps.emitWarning(`Compaction summarizer ${this.lastSelectionReason}`);\n\t\t}\n\t\treturn model;\n\t}\n\n\t/** Default compaction should never inherit expensive session thinking. */\n\tresolveThinkingLevel(\n\t\tsessionThinkingLevel: ThinkingLevel | undefined,\n\t\tcompactionModel: Model<any>,\n\t\tsessionModel: Model<any>,\n\t): ThinkingLevel | undefined {\n\t\tif (this.getExplicitCompactionModelSetting()) return sessionThinkingLevel;\n\n\t\tconst router = this.deps.getSettingsManager().getModelRouterSettings();\n\t\tif (router.enabled && router.cheapModel) {\n\t\t\tconst routerModel = this.resolveConfiguredModel(router.cheapModel).model;\n\t\t\tif (routerModel && this.modelsAreEqual(routerModel, compactionModel)) {\n\t\t\t\treturn router.cheapThinking ?? \"low\";\n\t\t\t}\n\t\t}\n\n\t\treturn this.modelsAreEqual(compactionModel, sessionModel) ? \"low\" : sessionThinkingLevel;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"compaction-support.d.ts","sourceRoot":"","sources":["../../src/core/compaction-support.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Compaction support: summarizer model selection (#30 cost guard), request-auth resolution\n * with session-model fallback, and window-adapted compaction settings.\n *\n * Extracted verbatim from AgentSession (agent-session.ts) as a narrow-deps collaborator; the\n * session keeps same-signature private delegations at every call-in point.\n */\nimport type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { type CompactionSettings, summarizerCanIngest } from \"@caupulican/pi-agent-core/node\";\nimport type { Model } from \"@caupulican/pi-ai\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport { resolveCliModel } from \"./model-resolver.ts\";\nimport { evaluateSurfaceFitness } from \"./model-router/fitness-gate.ts\";\nimport type { ModelFitnessReport } from \"./research/model-fitness.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface CompactionSupportDeps {\n\tgetModel(): Model<any> | undefined;\n\tgetSettingsManager(): SettingsManager;\n\tgetModelRegistry(): ModelRegistry;\n\t/** True when the agent's streamFn is (or wraps) the raw streamSimple — auth must be explicit then. */\n\tisRawStream(): boolean;\n\t/** Host auth resolution that THROWS with a user-actionable message when no key exists. */\n\tgetRequiredRequestAuth(model: Model<any>): Promise<{ apiKey?: string; headers?: Record<string, string> }>;\n\tisModelExhausted(ref: string): boolean;\n\tgetStoredFitnessReport(ref: string): ModelFitnessReport | undefined;\n\t/** Estimated tokens of the summarization input (live context; over-estimates, which is safe). */\n\testimateSummarizationInputTokens(): number;\n\temitWarning(message: string): void;\n\t/**\n\t * The same readiness/residency gate every other isolated consumer uses\n\t * (LocalRuntimeController.ensureIsolatedModelReady) before this model is used for an\n\t * out-of-band call. No-ops for a non-managed-local model (checked internally by the gate);\n\t * throws with a user-actionable reason when a managed-local model cannot be made ready.\n\t */\n\tensureModelReady(model: Model<any>): Promise<void>;\n}\n\nexport class CompactionSupport {\n\tprivate readonly deps: CompactionSupportDeps;\n\tprivate lastSelectionReason: string | undefined;\n\n\tconstructor(deps: CompactionSupportDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetLastSelectionReason(): string | undefined {\n\t\treturn this.lastSelectionReason;\n\t}\n\n\tgetAdaptedSettings(): CompactionSettings {\n\t\tconst settings = this.deps.getSettingsManager().getCompactionSettings();\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return settings;\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return settings;\n\n\t\t// Adapt reserveTokens: at most 25% of context window\n\t\tconst maxReserve = Math.floor(contextWindow * 0.25);\n\t\tconst reserveTokens = Math.min(settings.reserveTokens, maxReserve);\n\n\t\t// Adapt keepRecentTokens: at most 50% of context window\n\t\tconst maxKeepRecent = Math.floor(contextWindow * 0.5);\n\t\tconst keepRecentTokens = Math.min(settings.keepRecentTokens, maxKeepRecent);\n\n\t\treturn {\n\t\t\t...settings,\n\t\t\treserveTokens,\n\t\t\tkeepRecentTokens,\n\t\t};\n\t}\n\n\tasync getRequestAuth(model: Model<any>): Promise<{\n\t\tapiKey?: string;\n\t\theaders?: Record<string, string>;\n\t}> {\n\t\tif (this.deps.isRawStream()) {\n\t\t\treturn this.deps.getRequiredRequestAuth(model);\n\t\t}\n\n\t\tconst result = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);\n\t\treturn result.ok ? { apiKey: result.apiKey, headers: result.headers } : {};\n\t}\n\n\t/**\n\t * Readiness/residency gate a candidate summarizer model right before it is handed back for\n\t * use. A no-op for a non-managed-local model (checked internally by\n\t * {@link CompactionSupportDeps.ensureModelReady}); a managed-local model that cannot be made\n\t * ready (server down, model missing, residency refused) returns its failure reason instead of\n\t * throwing, so callers can still try the next candidate in the existing auth-fallback ladder\n\t * rather than aborting resolution outright.\n\t */\n\tprivate async readinessFailure(model: Model<any>): Promise<string | undefined> {\n\t\ttry {\n\t\t\tawait this.deps.ensureModelReady(model);\n\t\t\treturn undefined;\n\t\t} catch (error) {\n\t\t\treturn error instanceof Error ? error.message : String(error);\n\t\t}\n\t}\n\n\t/**\n\t * Resolve the summarizer model AND its request auth for auto-compaction. The cheap auxiliary\n\t * model (#30) can be nominally available yet fail key resolution at request time (expired\n\t * OAuth, revoked key) — or, for a managed-local model, fail the readiness/residency gate (\n\t * server down, model not installed, residency refused). Falling back to the session model keeps\n\t * auto-compaction working in exactly the situations where manual /compact works; only when\n\t * neither resolves do we fail — and then with a concrete message instead of a silent no-op or a\n\t * bypass of the readiness gate.\n\t */\n\tasync resolveModelAndAuth(\n\t\tcompactionModel: Model<any>,\n\t\tsessionModel: Model<any>,\n\t): Promise<{ model: Model<any>; apiKey?: string; headers?: Record<string, string>; failure?: string }> {\n\t\tif (this.deps.isRawStream()) {\n\t\t\tconst registry = this.deps.getModelRegistry();\n\t\t\tlet auth = await registry.getApiKeyAndHeaders(compactionModel);\n\t\t\tlet readiness: string | undefined;\n\t\t\tif (auth.ok && auth.apiKey) {\n\t\t\t\treadiness = await this.readinessFailure(compactionModel);\n\t\t\t\tif (!readiness) return { model: compactionModel, apiKey: auth.apiKey, headers: auth.headers };\n\t\t\t}\n\t\t\tconst isSameModel =\n\t\t\t\tcompactionModel.provider === sessionModel.provider && compactionModel.id === sessionModel.id;\n\t\t\tif (!isSameModel) {\n\t\t\t\tauth = await registry.getApiKeyAndHeaders(sessionModel);\n\t\t\t\tif (auth.ok && auth.apiKey) {\n\t\t\t\t\treadiness = await this.readinessFailure(sessionModel);\n\t\t\t\t\tif (!readiness) return { model: sessionModel, apiKey: auth.apiKey, headers: auth.headers };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tmodel: sessionModel,\n\t\t\t\tfailure: readiness\n\t\t\t\t\t? `summarizer ${isSameModel ? compactionModel.id : sessionModel.id} not ready: ${readiness}`\n\t\t\t\t\t: `no usable API key for the summarizer (tried ${compactionModel.id}${isSameModel ? \"\" : ` and ${sessionModel.id}`})`,\n\t\t\t};\n\t\t}\n\n\t\t// Custom streamFn owns auth injection (CLI path) — resolve best-effort, never fail on auth\n\t\t// here; a managed-local summarizer must still pass the readiness gate before compact() runs.\n\t\tconst { apiKey, headers } = await this.getRequestAuth(compactionModel);\n\t\tconst readiness = await this.readinessFailure(compactionModel);\n\t\tif (readiness) {\n\t\t\treturn {\n\t\t\t\tmodel: compactionModel,\n\t\t\t\tapiKey,\n\t\t\t\theaders,\n\t\t\t\tfailure: `summarizer ${compactionModel.id} not ready: ${readiness}`,\n\t\t\t};\n\t\t}\n\t\treturn { model: compactionModel, apiKey, headers };\n\t}\n\n\tprivate getExplicitCompactionModelSetting(): string | undefined {\n\t\tconst setting = this.deps.getSettingsManager().getCompactionModel().trim();\n\t\treturn setting && setting !== \"auto\" ? setting : undefined;\n\t}\n\n\tprivate modelRef(model: Model<any>): string {\n\t\treturn `${model.provider}/${model.id}`;\n\t}\n\n\tprivate resolveConfiguredModel(pattern: string): { model?: Model<any>; cause?: \"unresolved\" | \"unauthed\" } {\n\t\tconst registry = this.deps.getModelRegistry();\n\t\tconst resolved = resolveCliModel({ cliModel: pattern, modelRegistry: registry });\n\t\tif (!resolved.model) return { cause: \"unresolved\" };\n\t\tif (!registry.hasConfiguredAuth(resolved.model)) return { cause: \"unauthed\" };\n\t\treturn { model: resolved.model };\n\t}\n\n\tprivate selectConfiguredModel(pattern: string): { model?: Model<any>; cause?: string } {\n\t\tconst resolved = this.resolveConfiguredModel(pattern);\n\t\tif (!resolved.model) return { cause: resolved.cause };\n\t\tif (this.deps.isModelExhausted(this.modelRef(resolved.model))) return { cause: \"exhausted\" };\n\t\treturn { model: resolved.model };\n\t}\n\n\tprivate effectiveContextWindow(model: Model<any>): number {\n\t\tconst registered = model.contextWindow > 0 ? model.contextWindow : Number.POSITIVE_INFINITY;\n\t\tconst served = this.deps.getStoredFitnessReport(this.modelRef(model))?.capacity?.servedContextWindow;\n\t\treturn served && served > 0 ? Math.min(registered, served) : registered;\n\t}\n\n\tprivate modelWithEffectiveWindow(model: Model<any>): Model<any> {\n\t\tconst contextWindow = this.effectiveContextWindow(model);\n\t\treturn contextWindow === model.contextWindow ? model : { ...model, contextWindow };\n\t}\n\n\tprivate resolveDefaultModel(sessionModel: Model<any>): Model<any> {\n\t\tconst router = this.deps.getSettingsManager().getModelRouterSettings();\n\t\tif (!router.enabled || !router.cheapModel) {\n\t\t\tthis.lastSelectionReason = \"session_default\";\n\t\t\treturn sessionModel;\n\t\t}\n\t\tconst selected = this.selectConfiguredModel(router.cheapModel);\n\t\tif (!selected.model) {\n\t\t\tthis.lastSelectionReason = `fallback:${selected.cause}`;\n\t\t\treturn sessionModel;\n\t\t}\n\t\t// Capacity is a hard constraint, independent of the fitness doctrine: a summarizer whose\n\t\t// window cannot hold the actual span produces recall-empty checkpoints (local servers\n\t\t// silently truncate over-window prompts instead of erroring), and the verification gate\n\t\t// then fails deterministically.\n\t\tconst estimatedInputTokens = this.deps.estimateSummarizationInputTokens();\n\t\tconst effectiveWindow = this.effectiveContextWindow(selected.model);\n\t\tif (!summarizerCanIngest(this.modelWithEffectiveWindow(selected.model), estimatedInputTokens)) {\n\t\t\tthis.lastSelectionReason = `fallback:window_too_small(~${Math.ceil(estimatedInputTokens / 1000)}k input vs ${effectiveWindow} window)`;\n\t\t\treturn sessionModel;\n\t\t}\n\t\tconst fitness = this.deps.getStoredFitnessReport(this.modelRef(selected.model));\n\t\tconst verdict = evaluateSurfaceFitness(\"compaction\", fitness);\n\t\tif (!verdict.fit) {\n\t\t\tthis.lastSelectionReason =\n\t\t\t\tverdict.reason === \"lane_failed\"\n\t\t\t\t\t? `fallback:digest_unfit(${verdict.succeeded}/${verdict.total})`\n\t\t\t\t\t: \"fallback:unprobed\";\n\t\t\treturn sessionModel;\n\t\t}\n\t\tthis.lastSelectionReason = \"router_cheap\";\n\t\treturn selected.model;\n\t}\n\n\tprivate modelsAreEqual(left: Model<any>, right: Model<any>): boolean {\n\t\treturn left.provider === right.provider && left.id === right.id;\n\t}\n\n\t/**\n\t * Resolve the model used to SUMMARIZE during compaction. Selection:\n\t * - an explicit `compaction.model` setting wins, but only if its provider is authed (else fall back);\n\t * - `\"auto\"`/unset follows the model router's configured cheap model when the router is enabled;\n\t * - otherwise the session model is used (safe default).\n\t */\n\tresolveModel(sessionModel: Model<any>): Model<any> {\n\t\tconst explicitSetting = this.getExplicitCompactionModelSetting();\n\t\tif (explicitSetting) {\n\t\t\tconst selected = this.selectConfiguredModel(explicitSetting);\n\t\t\tthis.lastSelectionReason = selected.model ? \"explicit\" : `fallback:${selected.cause}`;\n\t\t\tif (!selected.model) this.deps.emitWarning(`Compaction summarizer ${this.lastSelectionReason}`);\n\t\t\t// An explicit user choice is honored (Class C doctrine), but silently sending an\n\t\t\t// over-window prompt yields a recall-empty summary — warn with the numbers.\n\t\t\tif (selected.model) {\n\t\t\t\tconst estimatedInputTokens = this.deps.estimateSummarizationInputTokens();\n\t\t\t\tconst effectiveWindow = this.effectiveContextWindow(selected.model);\n\t\t\t\tif (!summarizerCanIngest(this.modelWithEffectiveWindow(selected.model), estimatedInputTokens)) {\n\t\t\t\t\tthis.deps.emitWarning(\n\t\t\t\t\t\t`Compaction summarizer (explicit setting) likely cannot ingest the current context: ~${Math.ceil(estimatedInputTokens / 1000)}k input tokens vs a ${effectiveWindow}-token window`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn selected.model ?? sessionModel;\n\t\t}\n\t\tconst model = this.resolveDefaultModel(sessionModel);\n\t\tif (this.lastSelectionReason?.startsWith(\"fallback:\")) {\n\t\t\tthis.deps.emitWarning(`Compaction summarizer ${this.lastSelectionReason}`);\n\t\t}\n\t\treturn model;\n\t}\n\n\t/** Default compaction should never inherit expensive session thinking. */\n\tresolveThinkingLevel(\n\t\tsessionThinkingLevel: ThinkingLevel | undefined,\n\t\tcompactionModel: Model<any>,\n\t\tsessionModel: Model<any>,\n\t): ThinkingLevel | undefined {\n\t\tif (this.getExplicitCompactionModelSetting()) return sessionThinkingLevel;\n\n\t\tconst router = this.deps.getSettingsManager().getModelRouterSettings();\n\t\tif (router.enabled && router.cheapModel) {\n\t\t\tconst routerModel = this.resolveConfiguredModel(router.cheapModel).model;\n\t\t\tif (routerModel && this.modelsAreEqual(routerModel, compactionModel)) {\n\t\t\t\treturn router.cheapThinking ?? \"low\";\n\t\t\t}\n\t\t}\n\n\t\treturn this.modelsAreEqual(compactionModel, sessionModel) ? \"low\" : sessionThinkingLevel;\n\t}\n}\n"]}
|
|
@@ -37,34 +37,70 @@ export class CompactionSupport {
|
|
|
37
37
|
const result = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);
|
|
38
38
|
return result.ok ? { apiKey: result.apiKey, headers: result.headers } : {};
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Readiness/residency gate a candidate summarizer model right before it is handed back for
|
|
42
|
+
* use. A no-op for a non-managed-local model (checked internally by
|
|
43
|
+
* {@link CompactionSupportDeps.ensureModelReady}); a managed-local model that cannot be made
|
|
44
|
+
* ready (server down, model missing, residency refused) returns its failure reason instead of
|
|
45
|
+
* throwing, so callers can still try the next candidate in the existing auth-fallback ladder
|
|
46
|
+
* rather than aborting resolution outright.
|
|
47
|
+
*/
|
|
48
|
+
async readinessFailure(model) {
|
|
49
|
+
try {
|
|
50
|
+
await this.deps.ensureModelReady(model);
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return error instanceof Error ? error.message : String(error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
40
57
|
/**
|
|
41
58
|
* Resolve the summarizer model AND its request auth for auto-compaction. The cheap auxiliary
|
|
42
59
|
* model (#30) can be nominally available yet fail key resolution at request time (expired
|
|
43
|
-
* OAuth, revoked key)
|
|
44
|
-
*
|
|
45
|
-
*
|
|
60
|
+
* OAuth, revoked key) — or, for a managed-local model, fail the readiness/residency gate (
|
|
61
|
+
* server down, model not installed, residency refused). Falling back to the session model keeps
|
|
62
|
+
* auto-compaction working in exactly the situations where manual /compact works; only when
|
|
63
|
+
* neither resolves do we fail — and then with a concrete message instead of a silent no-op or a
|
|
64
|
+
* bypass of the readiness gate.
|
|
46
65
|
*/
|
|
47
66
|
async resolveModelAndAuth(compactionModel, sessionModel) {
|
|
48
67
|
if (this.deps.isRawStream()) {
|
|
49
68
|
const registry = this.deps.getModelRegistry();
|
|
50
69
|
let auth = await registry.getApiKeyAndHeaders(compactionModel);
|
|
70
|
+
let readiness;
|
|
51
71
|
if (auth.ok && auth.apiKey) {
|
|
52
|
-
|
|
72
|
+
readiness = await this.readinessFailure(compactionModel);
|
|
73
|
+
if (!readiness)
|
|
74
|
+
return { model: compactionModel, apiKey: auth.apiKey, headers: auth.headers };
|
|
53
75
|
}
|
|
54
76
|
const isSameModel = compactionModel.provider === sessionModel.provider && compactionModel.id === sessionModel.id;
|
|
55
77
|
if (!isSameModel) {
|
|
56
78
|
auth = await registry.getApiKeyAndHeaders(sessionModel);
|
|
57
79
|
if (auth.ok && auth.apiKey) {
|
|
58
|
-
|
|
80
|
+
readiness = await this.readinessFailure(sessionModel);
|
|
81
|
+
if (!readiness)
|
|
82
|
+
return { model: sessionModel, apiKey: auth.apiKey, headers: auth.headers };
|
|
59
83
|
}
|
|
60
84
|
}
|
|
61
85
|
return {
|
|
62
86
|
model: sessionModel,
|
|
63
|
-
failure:
|
|
87
|
+
failure: readiness
|
|
88
|
+
? `summarizer ${isSameModel ? compactionModel.id : sessionModel.id} not ready: ${readiness}`
|
|
89
|
+
: `no usable API key for the summarizer (tried ${compactionModel.id}${isSameModel ? "" : ` and ${sessionModel.id}`})`,
|
|
64
90
|
};
|
|
65
91
|
}
|
|
66
|
-
// Custom streamFn owns auth injection (CLI path) — resolve best-effort, never fail
|
|
92
|
+
// Custom streamFn owns auth injection (CLI path) — resolve best-effort, never fail on auth
|
|
93
|
+
// here; a managed-local summarizer must still pass the readiness gate before compact() runs.
|
|
67
94
|
const { apiKey, headers } = await this.getRequestAuth(compactionModel);
|
|
95
|
+
const readiness = await this.readinessFailure(compactionModel);
|
|
96
|
+
if (readiness) {
|
|
97
|
+
return {
|
|
98
|
+
model: compactionModel,
|
|
99
|
+
apiKey,
|
|
100
|
+
headers,
|
|
101
|
+
failure: `summarizer ${compactionModel.id} not ready: ${readiness}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
68
104
|
return { model: compactionModel, apiKey, headers };
|
|
69
105
|
}
|
|
70
106
|
getExplicitCompactionModelSetting() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction-support.js","sourceRoot":"","sources":["../../src/core/compaction-support.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Compaction support: summarizer model selection (#30 cost guard), request-auth resolution\n * with session-model fallback, and window-adapted compaction settings.\n *\n * Extracted verbatim from AgentSession (agent-session.ts) as a narrow-deps collaborator; the\n * session keeps same-signature private delegations at every call-in point.\n */\nimport type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { type CompactionSettings, summarizerCanIngest } from \"@caupulican/pi-agent-core/node\";\nimport type { Model } from \"@caupulican/pi-ai\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport { resolveCliModel } from \"./model-resolver.ts\";\nimport { evaluateSurfaceFitness } from \"./model-router/fitness-gate.ts\";\nimport type { ModelFitnessReport } from \"./research/model-fitness.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface CompactionSupportDeps {\n\tgetModel(): Model<any> | undefined;\n\tgetSettingsManager(): SettingsManager;\n\tgetModelRegistry(): ModelRegistry;\n\t/** True when the agent's streamFn is (or wraps) the raw streamSimple — auth must be explicit then. */\n\tisRawStream(): boolean;\n\t/** Host auth resolution that THROWS with a user-actionable message when no key exists. */\n\tgetRequiredRequestAuth(model: Model<any>): Promise<{ apiKey?: string; headers?: Record<string, string> }>;\n\tisModelExhausted(ref: string): boolean;\n\tgetStoredFitnessReport(ref: string): ModelFitnessReport | undefined;\n\t/** Estimated tokens of the summarization input (live context; over-estimates, which is safe). */\n\testimateSummarizationInputTokens(): number;\n\temitWarning(message: string): void;\n}\n\nexport class CompactionSupport {\n\tprivate readonly deps: CompactionSupportDeps;\n\tprivate lastSelectionReason: string | undefined;\n\n\tconstructor(deps: CompactionSupportDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetLastSelectionReason(): string | undefined {\n\t\treturn this.lastSelectionReason;\n\t}\n\n\tgetAdaptedSettings(): CompactionSettings {\n\t\tconst settings = this.deps.getSettingsManager().getCompactionSettings();\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return settings;\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return settings;\n\n\t\t// Adapt reserveTokens: at most 25% of context window\n\t\tconst maxReserve = Math.floor(contextWindow * 0.25);\n\t\tconst reserveTokens = Math.min(settings.reserveTokens, maxReserve);\n\n\t\t// Adapt keepRecentTokens: at most 50% of context window\n\t\tconst maxKeepRecent = Math.floor(contextWindow * 0.5);\n\t\tconst keepRecentTokens = Math.min(settings.keepRecentTokens, maxKeepRecent);\n\n\t\treturn {\n\t\t\t...settings,\n\t\t\treserveTokens,\n\t\t\tkeepRecentTokens,\n\t\t};\n\t}\n\n\tasync getRequestAuth(model: Model<any>): Promise<{\n\t\tapiKey?: string;\n\t\theaders?: Record<string, string>;\n\t}> {\n\t\tif (this.deps.isRawStream()) {\n\t\t\treturn this.deps.getRequiredRequestAuth(model);\n\t\t}\n\n\t\tconst result = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);\n\t\treturn result.ok ? { apiKey: result.apiKey, headers: result.headers } : {};\n\t}\n\n\t/**\n\t * Resolve the summarizer model AND its request auth for auto-compaction. The cheap auxiliary\n\t * model (#30) can be nominally available yet fail key resolution at request time (expired\n\t * OAuth, revoked key). Falling back to the session model keeps auto-compaction working in\n\t * exactly the situations where manual /compact works; only when neither resolves do we fail —\n\t * and then with a concrete message instead of a silent no-op.\n\t */\n\tasync resolveModelAndAuth(\n\t\tcompactionModel: Model<any>,\n\t\tsessionModel: Model<any>,\n\t): Promise<{ model: Model<any>; apiKey?: string; headers?: Record<string, string>; failure?: string }> {\n\t\tif (this.deps.isRawStream()) {\n\t\t\tconst registry = this.deps.getModelRegistry();\n\t\t\tlet auth = await registry.getApiKeyAndHeaders(compactionModel);\n\t\t\tif (auth.ok && auth.apiKey) {\n\t\t\t\treturn { model: compactionModel, apiKey: auth.apiKey, headers: auth.headers };\n\t\t\t}\n\t\t\tconst isSameModel =\n\t\t\t\tcompactionModel.provider === sessionModel.provider && compactionModel.id === sessionModel.id;\n\t\t\tif (!isSameModel) {\n\t\t\t\tauth = await registry.getApiKeyAndHeaders(sessionModel);\n\t\t\t\tif (auth.ok && auth.apiKey) {\n\t\t\t\t\treturn { model: sessionModel, apiKey: auth.apiKey, headers: auth.headers };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tmodel: sessionModel,\n\t\t\t\tfailure: `no usable API key for the summarizer (tried ${compactionModel.id}${isSameModel ? \"\" : ` and ${sessionModel.id}`})`,\n\t\t\t};\n\t\t}\n\n\t\t// Custom streamFn owns auth injection (CLI path) — resolve best-effort, never fail here.\n\t\tconst { apiKey, headers } = await this.getRequestAuth(compactionModel);\n\t\treturn { model: compactionModel, apiKey, headers };\n\t}\n\n\tprivate getExplicitCompactionModelSetting(): string | undefined {\n\t\tconst setting = this.deps.getSettingsManager().getCompactionModel().trim();\n\t\treturn setting && setting !== \"auto\" ? setting : undefined;\n\t}\n\n\tprivate modelRef(model: Model<any>): string {\n\t\treturn `${model.provider}/${model.id}`;\n\t}\n\n\tprivate resolveConfiguredModel(pattern: string): { model?: Model<any>; cause?: \"unresolved\" | \"unauthed\" } {\n\t\tconst registry = this.deps.getModelRegistry();\n\t\tconst resolved = resolveCliModel({ cliModel: pattern, modelRegistry: registry });\n\t\tif (!resolved.model) return { cause: \"unresolved\" };\n\t\tif (!registry.hasConfiguredAuth(resolved.model)) return { cause: \"unauthed\" };\n\t\treturn { model: resolved.model };\n\t}\n\n\tprivate selectConfiguredModel(pattern: string): { model?: Model<any>; cause?: string } {\n\t\tconst resolved = this.resolveConfiguredModel(pattern);\n\t\tif (!resolved.model) return { cause: resolved.cause };\n\t\tif (this.deps.isModelExhausted(this.modelRef(resolved.model))) return { cause: \"exhausted\" };\n\t\treturn { model: resolved.model };\n\t}\n\n\tprivate effectiveContextWindow(model: Model<any>): number {\n\t\tconst registered = model.contextWindow > 0 ? model.contextWindow : Number.POSITIVE_INFINITY;\n\t\tconst served = this.deps.getStoredFitnessReport(this.modelRef(model))?.capacity?.servedContextWindow;\n\t\treturn served && served > 0 ? Math.min(registered, served) : registered;\n\t}\n\n\tprivate modelWithEffectiveWindow(model: Model<any>): Model<any> {\n\t\tconst contextWindow = this.effectiveContextWindow(model);\n\t\treturn contextWindow === model.contextWindow ? model : { ...model, contextWindow };\n\t}\n\n\tprivate resolveDefaultModel(sessionModel: Model<any>): Model<any> {\n\t\tconst router = this.deps.getSettingsManager().getModelRouterSettings();\n\t\tif (!router.enabled || !router.cheapModel) {\n\t\t\tthis.lastSelectionReason = \"session_default\";\n\t\t\treturn sessionModel;\n\t\t}\n\t\tconst selected = this.selectConfiguredModel(router.cheapModel);\n\t\tif (!selected.model) {\n\t\t\tthis.lastSelectionReason = `fallback:${selected.cause}`;\n\t\t\treturn sessionModel;\n\t\t}\n\t\t// Capacity is a hard constraint, independent of the fitness doctrine: a summarizer whose\n\t\t// window cannot hold the actual span produces recall-empty checkpoints (local servers\n\t\t// silently truncate over-window prompts instead of erroring), and the verification gate\n\t\t// then fails deterministically.\n\t\tconst estimatedInputTokens = this.deps.estimateSummarizationInputTokens();\n\t\tconst effectiveWindow = this.effectiveContextWindow(selected.model);\n\t\tif (!summarizerCanIngest(this.modelWithEffectiveWindow(selected.model), estimatedInputTokens)) {\n\t\t\tthis.lastSelectionReason = `fallback:window_too_small(~${Math.ceil(estimatedInputTokens / 1000)}k input vs ${effectiveWindow} window)`;\n\t\t\treturn sessionModel;\n\t\t}\n\t\tconst fitness = this.deps.getStoredFitnessReport(this.modelRef(selected.model));\n\t\tconst verdict = evaluateSurfaceFitness(\"compaction\", fitness);\n\t\tif (!verdict.fit) {\n\t\t\tthis.lastSelectionReason =\n\t\t\t\tverdict.reason === \"lane_failed\"\n\t\t\t\t\t? `fallback:digest_unfit(${verdict.succeeded}/${verdict.total})`\n\t\t\t\t\t: \"fallback:unprobed\";\n\t\t\treturn sessionModel;\n\t\t}\n\t\tthis.lastSelectionReason = \"router_cheap\";\n\t\treturn selected.model;\n\t}\n\n\tprivate modelsAreEqual(left: Model<any>, right: Model<any>): boolean {\n\t\treturn left.provider === right.provider && left.id === right.id;\n\t}\n\n\t/**\n\t * Resolve the model used to SUMMARIZE during compaction. Selection:\n\t * - an explicit `compaction.model` setting wins, but only if its provider is authed (else fall back);\n\t * - `\"auto\"`/unset follows the model router's configured cheap model when the router is enabled;\n\t * - otherwise the session model is used (safe default).\n\t */\n\tresolveModel(sessionModel: Model<any>): Model<any> {\n\t\tconst explicitSetting = this.getExplicitCompactionModelSetting();\n\t\tif (explicitSetting) {\n\t\t\tconst selected = this.selectConfiguredModel(explicitSetting);\n\t\t\tthis.lastSelectionReason = selected.model ? \"explicit\" : `fallback:${selected.cause}`;\n\t\t\tif (!selected.model) this.deps.emitWarning(`Compaction summarizer ${this.lastSelectionReason}`);\n\t\t\t// An explicit user choice is honored (Class C doctrine), but silently sending an\n\t\t\t// over-window prompt yields a recall-empty summary — warn with the numbers.\n\t\t\tif (selected.model) {\n\t\t\t\tconst estimatedInputTokens = this.deps.estimateSummarizationInputTokens();\n\t\t\t\tconst effectiveWindow = this.effectiveContextWindow(selected.model);\n\t\t\t\tif (!summarizerCanIngest(this.modelWithEffectiveWindow(selected.model), estimatedInputTokens)) {\n\t\t\t\t\tthis.deps.emitWarning(\n\t\t\t\t\t\t`Compaction summarizer (explicit setting) likely cannot ingest the current context: ~${Math.ceil(estimatedInputTokens / 1000)}k input tokens vs a ${effectiveWindow}-token window`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn selected.model ?? sessionModel;\n\t\t}\n\t\tconst model = this.resolveDefaultModel(sessionModel);\n\t\tif (this.lastSelectionReason?.startsWith(\"fallback:\")) {\n\t\t\tthis.deps.emitWarning(`Compaction summarizer ${this.lastSelectionReason}`);\n\t\t}\n\t\treturn model;\n\t}\n\n\t/** Default compaction should never inherit expensive session thinking. */\n\tresolveThinkingLevel(\n\t\tsessionThinkingLevel: ThinkingLevel | undefined,\n\t\tcompactionModel: Model<any>,\n\t\tsessionModel: Model<any>,\n\t): ThinkingLevel | undefined {\n\t\tif (this.getExplicitCompactionModelSetting()) return sessionThinkingLevel;\n\n\t\tconst router = this.deps.getSettingsManager().getModelRouterSettings();\n\t\tif (router.enabled && router.cheapModel) {\n\t\t\tconst routerModel = this.resolveConfiguredModel(router.cheapModel).model;\n\t\t\tif (routerModel && this.modelsAreEqual(routerModel, compactionModel)) {\n\t\t\t\treturn router.cheapThinking ?? \"low\";\n\t\t\t}\n\t\t}\n\n\t\treturn this.modelsAreEqual(compactionModel, sessionModel) ? \"low\" : sessionThinkingLevel;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"compaction-support.js","sourceRoot":"","sources":["../../src/core/compaction-support.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Compaction support: summarizer model selection (#30 cost guard), request-auth resolution\n * with session-model fallback, and window-adapted compaction settings.\n *\n * Extracted verbatim from AgentSession (agent-session.ts) as a narrow-deps collaborator; the\n * session keeps same-signature private delegations at every call-in point.\n */\nimport type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { type CompactionSettings, summarizerCanIngest } from \"@caupulican/pi-agent-core/node\";\nimport type { Model } from \"@caupulican/pi-ai\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport { resolveCliModel } from \"./model-resolver.ts\";\nimport { evaluateSurfaceFitness } from \"./model-router/fitness-gate.ts\";\nimport type { ModelFitnessReport } from \"./research/model-fitness.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface CompactionSupportDeps {\n\tgetModel(): Model<any> | undefined;\n\tgetSettingsManager(): SettingsManager;\n\tgetModelRegistry(): ModelRegistry;\n\t/** True when the agent's streamFn is (or wraps) the raw streamSimple — auth must be explicit then. */\n\tisRawStream(): boolean;\n\t/** Host auth resolution that THROWS with a user-actionable message when no key exists. */\n\tgetRequiredRequestAuth(model: Model<any>): Promise<{ apiKey?: string; headers?: Record<string, string> }>;\n\tisModelExhausted(ref: string): boolean;\n\tgetStoredFitnessReport(ref: string): ModelFitnessReport | undefined;\n\t/** Estimated tokens of the summarization input (live context; over-estimates, which is safe). */\n\testimateSummarizationInputTokens(): number;\n\temitWarning(message: string): void;\n\t/**\n\t * The same readiness/residency gate every other isolated consumer uses\n\t * (LocalRuntimeController.ensureIsolatedModelReady) before this model is used for an\n\t * out-of-band call. No-ops for a non-managed-local model (checked internally by the gate);\n\t * throws with a user-actionable reason when a managed-local model cannot be made ready.\n\t */\n\tensureModelReady(model: Model<any>): Promise<void>;\n}\n\nexport class CompactionSupport {\n\tprivate readonly deps: CompactionSupportDeps;\n\tprivate lastSelectionReason: string | undefined;\n\n\tconstructor(deps: CompactionSupportDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetLastSelectionReason(): string | undefined {\n\t\treturn this.lastSelectionReason;\n\t}\n\n\tgetAdaptedSettings(): CompactionSettings {\n\t\tconst settings = this.deps.getSettingsManager().getCompactionSettings();\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return settings;\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return settings;\n\n\t\t// Adapt reserveTokens: at most 25% of context window\n\t\tconst maxReserve = Math.floor(contextWindow * 0.25);\n\t\tconst reserveTokens = Math.min(settings.reserveTokens, maxReserve);\n\n\t\t// Adapt keepRecentTokens: at most 50% of context window\n\t\tconst maxKeepRecent = Math.floor(contextWindow * 0.5);\n\t\tconst keepRecentTokens = Math.min(settings.keepRecentTokens, maxKeepRecent);\n\n\t\treturn {\n\t\t\t...settings,\n\t\t\treserveTokens,\n\t\t\tkeepRecentTokens,\n\t\t};\n\t}\n\n\tasync getRequestAuth(model: Model<any>): Promise<{\n\t\tapiKey?: string;\n\t\theaders?: Record<string, string>;\n\t}> {\n\t\tif (this.deps.isRawStream()) {\n\t\t\treturn this.deps.getRequiredRequestAuth(model);\n\t\t}\n\n\t\tconst result = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);\n\t\treturn result.ok ? { apiKey: result.apiKey, headers: result.headers } : {};\n\t}\n\n\t/**\n\t * Readiness/residency gate a candidate summarizer model right before it is handed back for\n\t * use. A no-op for a non-managed-local model (checked internally by\n\t * {@link CompactionSupportDeps.ensureModelReady}); a managed-local model that cannot be made\n\t * ready (server down, model missing, residency refused) returns its failure reason instead of\n\t * throwing, so callers can still try the next candidate in the existing auth-fallback ladder\n\t * rather than aborting resolution outright.\n\t */\n\tprivate async readinessFailure(model: Model<any>): Promise<string | undefined> {\n\t\ttry {\n\t\t\tawait this.deps.ensureModelReady(model);\n\t\t\treturn undefined;\n\t\t} catch (error) {\n\t\t\treturn error instanceof Error ? error.message : String(error);\n\t\t}\n\t}\n\n\t/**\n\t * Resolve the summarizer model AND its request auth for auto-compaction. The cheap auxiliary\n\t * model (#30) can be nominally available yet fail key resolution at request time (expired\n\t * OAuth, revoked key) — or, for a managed-local model, fail the readiness/residency gate (\n\t * server down, model not installed, residency refused). Falling back to the session model keeps\n\t * auto-compaction working in exactly the situations where manual /compact works; only when\n\t * neither resolves do we fail — and then with a concrete message instead of a silent no-op or a\n\t * bypass of the readiness gate.\n\t */\n\tasync resolveModelAndAuth(\n\t\tcompactionModel: Model<any>,\n\t\tsessionModel: Model<any>,\n\t): Promise<{ model: Model<any>; apiKey?: string; headers?: Record<string, string>; failure?: string }> {\n\t\tif (this.deps.isRawStream()) {\n\t\t\tconst registry = this.deps.getModelRegistry();\n\t\t\tlet auth = await registry.getApiKeyAndHeaders(compactionModel);\n\t\t\tlet readiness: string | undefined;\n\t\t\tif (auth.ok && auth.apiKey) {\n\t\t\t\treadiness = await this.readinessFailure(compactionModel);\n\t\t\t\tif (!readiness) return { model: compactionModel, apiKey: auth.apiKey, headers: auth.headers };\n\t\t\t}\n\t\t\tconst isSameModel =\n\t\t\t\tcompactionModel.provider === sessionModel.provider && compactionModel.id === sessionModel.id;\n\t\t\tif (!isSameModel) {\n\t\t\t\tauth = await registry.getApiKeyAndHeaders(sessionModel);\n\t\t\t\tif (auth.ok && auth.apiKey) {\n\t\t\t\t\treadiness = await this.readinessFailure(sessionModel);\n\t\t\t\t\tif (!readiness) return { model: sessionModel, apiKey: auth.apiKey, headers: auth.headers };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tmodel: sessionModel,\n\t\t\t\tfailure: readiness\n\t\t\t\t\t? `summarizer ${isSameModel ? compactionModel.id : sessionModel.id} not ready: ${readiness}`\n\t\t\t\t\t: `no usable API key for the summarizer (tried ${compactionModel.id}${isSameModel ? \"\" : ` and ${sessionModel.id}`})`,\n\t\t\t};\n\t\t}\n\n\t\t// Custom streamFn owns auth injection (CLI path) — resolve best-effort, never fail on auth\n\t\t// here; a managed-local summarizer must still pass the readiness gate before compact() runs.\n\t\tconst { apiKey, headers } = await this.getRequestAuth(compactionModel);\n\t\tconst readiness = await this.readinessFailure(compactionModel);\n\t\tif (readiness) {\n\t\t\treturn {\n\t\t\t\tmodel: compactionModel,\n\t\t\t\tapiKey,\n\t\t\t\theaders,\n\t\t\t\tfailure: `summarizer ${compactionModel.id} not ready: ${readiness}`,\n\t\t\t};\n\t\t}\n\t\treturn { model: compactionModel, apiKey, headers };\n\t}\n\n\tprivate getExplicitCompactionModelSetting(): string | undefined {\n\t\tconst setting = this.deps.getSettingsManager().getCompactionModel().trim();\n\t\treturn setting && setting !== \"auto\" ? setting : undefined;\n\t}\n\n\tprivate modelRef(model: Model<any>): string {\n\t\treturn `${model.provider}/${model.id}`;\n\t}\n\n\tprivate resolveConfiguredModel(pattern: string): { model?: Model<any>; cause?: \"unresolved\" | \"unauthed\" } {\n\t\tconst registry = this.deps.getModelRegistry();\n\t\tconst resolved = resolveCliModel({ cliModel: pattern, modelRegistry: registry });\n\t\tif (!resolved.model) return { cause: \"unresolved\" };\n\t\tif (!registry.hasConfiguredAuth(resolved.model)) return { cause: \"unauthed\" };\n\t\treturn { model: resolved.model };\n\t}\n\n\tprivate selectConfiguredModel(pattern: string): { model?: Model<any>; cause?: string } {\n\t\tconst resolved = this.resolveConfiguredModel(pattern);\n\t\tif (!resolved.model) return { cause: resolved.cause };\n\t\tif (this.deps.isModelExhausted(this.modelRef(resolved.model))) return { cause: \"exhausted\" };\n\t\treturn { model: resolved.model };\n\t}\n\n\tprivate effectiveContextWindow(model: Model<any>): number {\n\t\tconst registered = model.contextWindow > 0 ? model.contextWindow : Number.POSITIVE_INFINITY;\n\t\tconst served = this.deps.getStoredFitnessReport(this.modelRef(model))?.capacity?.servedContextWindow;\n\t\treturn served && served > 0 ? Math.min(registered, served) : registered;\n\t}\n\n\tprivate modelWithEffectiveWindow(model: Model<any>): Model<any> {\n\t\tconst contextWindow = this.effectiveContextWindow(model);\n\t\treturn contextWindow === model.contextWindow ? model : { ...model, contextWindow };\n\t}\n\n\tprivate resolveDefaultModel(sessionModel: Model<any>): Model<any> {\n\t\tconst router = this.deps.getSettingsManager().getModelRouterSettings();\n\t\tif (!router.enabled || !router.cheapModel) {\n\t\t\tthis.lastSelectionReason = \"session_default\";\n\t\t\treturn sessionModel;\n\t\t}\n\t\tconst selected = this.selectConfiguredModel(router.cheapModel);\n\t\tif (!selected.model) {\n\t\t\tthis.lastSelectionReason = `fallback:${selected.cause}`;\n\t\t\treturn sessionModel;\n\t\t}\n\t\t// Capacity is a hard constraint, independent of the fitness doctrine: a summarizer whose\n\t\t// window cannot hold the actual span produces recall-empty checkpoints (local servers\n\t\t// silently truncate over-window prompts instead of erroring), and the verification gate\n\t\t// then fails deterministically.\n\t\tconst estimatedInputTokens = this.deps.estimateSummarizationInputTokens();\n\t\tconst effectiveWindow = this.effectiveContextWindow(selected.model);\n\t\tif (!summarizerCanIngest(this.modelWithEffectiveWindow(selected.model), estimatedInputTokens)) {\n\t\t\tthis.lastSelectionReason = `fallback:window_too_small(~${Math.ceil(estimatedInputTokens / 1000)}k input vs ${effectiveWindow} window)`;\n\t\t\treturn sessionModel;\n\t\t}\n\t\tconst fitness = this.deps.getStoredFitnessReport(this.modelRef(selected.model));\n\t\tconst verdict = evaluateSurfaceFitness(\"compaction\", fitness);\n\t\tif (!verdict.fit) {\n\t\t\tthis.lastSelectionReason =\n\t\t\t\tverdict.reason === \"lane_failed\"\n\t\t\t\t\t? `fallback:digest_unfit(${verdict.succeeded}/${verdict.total})`\n\t\t\t\t\t: \"fallback:unprobed\";\n\t\t\treturn sessionModel;\n\t\t}\n\t\tthis.lastSelectionReason = \"router_cheap\";\n\t\treturn selected.model;\n\t}\n\n\tprivate modelsAreEqual(left: Model<any>, right: Model<any>): boolean {\n\t\treturn left.provider === right.provider && left.id === right.id;\n\t}\n\n\t/**\n\t * Resolve the model used to SUMMARIZE during compaction. Selection:\n\t * - an explicit `compaction.model` setting wins, but only if its provider is authed (else fall back);\n\t * - `\"auto\"`/unset follows the model router's configured cheap model when the router is enabled;\n\t * - otherwise the session model is used (safe default).\n\t */\n\tresolveModel(sessionModel: Model<any>): Model<any> {\n\t\tconst explicitSetting = this.getExplicitCompactionModelSetting();\n\t\tif (explicitSetting) {\n\t\t\tconst selected = this.selectConfiguredModel(explicitSetting);\n\t\t\tthis.lastSelectionReason = selected.model ? \"explicit\" : `fallback:${selected.cause}`;\n\t\t\tif (!selected.model) this.deps.emitWarning(`Compaction summarizer ${this.lastSelectionReason}`);\n\t\t\t// An explicit user choice is honored (Class C doctrine), but silently sending an\n\t\t\t// over-window prompt yields a recall-empty summary — warn with the numbers.\n\t\t\tif (selected.model) {\n\t\t\t\tconst estimatedInputTokens = this.deps.estimateSummarizationInputTokens();\n\t\t\t\tconst effectiveWindow = this.effectiveContextWindow(selected.model);\n\t\t\t\tif (!summarizerCanIngest(this.modelWithEffectiveWindow(selected.model), estimatedInputTokens)) {\n\t\t\t\t\tthis.deps.emitWarning(\n\t\t\t\t\t\t`Compaction summarizer (explicit setting) likely cannot ingest the current context: ~${Math.ceil(estimatedInputTokens / 1000)}k input tokens vs a ${effectiveWindow}-token window`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn selected.model ?? sessionModel;\n\t\t}\n\t\tconst model = this.resolveDefaultModel(sessionModel);\n\t\tif (this.lastSelectionReason?.startsWith(\"fallback:\")) {\n\t\t\tthis.deps.emitWarning(`Compaction summarizer ${this.lastSelectionReason}`);\n\t\t}\n\t\treturn model;\n\t}\n\n\t/** Default compaction should never inherit expensive session thinking. */\n\tresolveThinkingLevel(\n\t\tsessionThinkingLevel: ThinkingLevel | undefined,\n\t\tcompactionModel: Model<any>,\n\t\tsessionModel: Model<any>,\n\t): ThinkingLevel | undefined {\n\t\tif (this.getExplicitCompactionModelSetting()) return sessionThinkingLevel;\n\n\t\tconst router = this.deps.getSettingsManager().getModelRouterSettings();\n\t\tif (router.enabled && router.cheapModel) {\n\t\t\tconst routerModel = this.resolveConfiguredModel(router.cheapModel).model;\n\t\t\tif (routerModel && this.modelsAreEqual(routerModel, compactionModel)) {\n\t\t\t\treturn router.cheapThinking ?? \"low\";\n\t\t\t}\n\t\t}\n\n\t\treturn this.modelsAreEqual(compactionModel, sessionModel) ? \"low\" : sessionThinkingLevel;\n\t}\n}\n"]}
|
|
@@ -53,6 +53,30 @@ export interface ContextAuditReport {
|
|
|
53
53
|
turnIndex: number;
|
|
54
54
|
items: ContextAuditItemReport[];
|
|
55
55
|
}
|
|
56
|
+
export interface BuiltToolOutputItem {
|
|
57
|
+
item: ContextItem;
|
|
58
|
+
/** True only if an artifact ref was found AND resolved against a live store. */
|
|
59
|
+
hasResolvedArtifact: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Per-message memo of the expensive part of {@link buildToolOutputItem} (text extraction +
|
|
63
|
+
* token/byte estimate + artifact `readRef` metadata), keyed by `AgentMessage` object identity
|
|
64
|
+
* -- a replaced/rewritten message object is a new key, so it misses and recomputes with no
|
|
65
|
+
* extra bookkeeping. Entries also carry the `messageIndex` they were built at: a message that
|
|
66
|
+
* kept its identity but shifted index (e.g. a compaction survivor) is treated as a miss too,
|
|
67
|
+
* since `messageIndex` feeds the transcript evidence ref.
|
|
68
|
+
*
|
|
69
|
+
* Ownership: the caller (`ContextPipeline`) owns a single long-lived instance across calls and
|
|
70
|
+
* passes it in every pass; `runContextAudit` treats it as the previous pass's cache on read and
|
|
71
|
+
* replaces its contents with exactly this pass's live entries before returning (a message no
|
|
72
|
+
* longer present in `messages` is dropped, not retained) -- so the map never grows unbounded
|
|
73
|
+
* across compaction rewrites or branch switches. Callers that must stay pure full-scans (tests,
|
|
74
|
+
* read-only recompute paths) simply omit this parameter.
|
|
75
|
+
*/
|
|
76
|
+
export type ContextAuditMemo = Map<AgentMessage, {
|
|
77
|
+
messageIndex: number;
|
|
78
|
+
built: BuiltToolOutputItem;
|
|
79
|
+
}>;
|
|
56
80
|
/** Build the ContextItem for a single toolResult message, for direct unit testing. */
|
|
57
81
|
export declare function buildToolResultContextItem(message: ToolResultMessage, messageIndex: number, options: ContextAuditOptions): ContextItem;
|
|
58
82
|
/**
|
|
@@ -61,6 +85,14 @@ export declare function buildToolResultContextItem(message: ToolResultMessage, m
|
|
|
61
85
|
* the transcript, or artifact references -- deterministic given the same messages,
|
|
62
86
|
* `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex
|
|
63
87
|
* and artifact-backed createdAtTurn values are expected to change turn over turn).
|
|
88
|
+
*
|
|
89
|
+
* `memo`, if supplied, is an incremental-memo seam (see {@link ContextAuditMemo}): the
|
|
90
|
+
* expensive `buildToolOutputItem` work is skipped and reused for a message whose object
|
|
91
|
+
* identity AND index are unchanged from the previous pass. The turnIndex-dependent tail
|
|
92
|
+
* (`createdAtTurn` for non-artifact-backed items) is re-derived fresh on every call
|
|
93
|
+
* regardless of cache hit/miss, so output is byte-identical to a full recompute either way
|
|
94
|
+
* -- omitting `memo` (or passing a throwaway `new Map()`) makes this a pure full scan, exactly
|
|
95
|
+
* as before this parameter existed.
|
|
64
96
|
*/
|
|
65
|
-
export declare function runContextAudit(messages: AgentMessage[], options: ContextAuditOptions): ContextAuditReport;
|
|
97
|
+
export declare function runContextAudit(messages: AgentMessage[], options: ContextAuditOptions, memo?: ContextAuditMemo): ContextAuditReport;
|
|
66
98
|
//# sourceMappingURL=context-audit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-audit.d.ts","sourceRoot":"","sources":["../../../src/core/context/context-audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAEN,KAAK,WAAW,EAGhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAgC,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEjG,OAAO,KAAK,EAAuC,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAEvG,MAAM,WAAW,mBAAmB;IACnC,wFAAwF;IACxF,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,8FAA8F;IAC9F,2BAA2B,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACzE;AAED,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gGAAgG;IAChG,SAAS,EAAE,oBAAoB,CAAC;IAChC,mIAAmI;IACnI,sBAAsB,EAAE,wBAAwB,EAAE,CAAC;IACnD,6FAA6F;IAC7F,6BAA6B,EAAE,wBAAwB,EAAE,CAAC;IAC1D,6FAA6F;IAC7F,6BAA6B,EAAE,wBAAwB,EAAE,CAAC;IAC1D,sFAAsF;IACtF,wBAAwB,EAAE,wBAAwB,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,sBAAsB,EAAE,CAAC;CAChC;AAqID,sFAAsF;AACtF,wBAAgB,0BAA0B,CACzC,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,mBAAmB,GAC1B,WAAW,CAEb;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAmB1G","sourcesContent":["/**\n * Live observe-only bridge from AgentMessage[] to the ContextItem/policy-engine layer\n * (Phase 1 audit pass). This module only ever reads messages and the artifact store; it\n * never mutates messages, the transcript, or artifact references. It is the first thing to\n * consume the context-item.ts/context-retention.ts/policy-engine.ts contracts against live\n * session state, but it does not yet change what the model sees -- see\n * docs/context-management-rework/implementation-phases.md for where this sits.\n *\n * Scope for this pass: only `toolResult` messages become `ContextItem`s (kind\n * \"tool_output\"). Other roles (user/assistant) are skipped; representing them is later\n * work once their ContextItemKind mapping (user_instruction/approval/etc.) is designed.\n *\n * Retrieval-path semantics (deliberately narrower than context-retention.ts's item-level\n * `hasReferencedEvidence`): a transcript ref is attached to every item as provenance\n * evidence (which live message this item came from), but it never counts toward\n * `HardConstraintFlags.hasAvailableRetrievalPath` on its own -- there is no live mechanism\n * today for the model to fetch an older message back into context by session-entry id, so\n * claiming that would overclaim retrievability (the same fail-closed principle\n * `artifact_retrieve` follows). Only a resolved artifact ref (the store still has the\n * payload) counts as an available retrieval path.\n */\n\nimport type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport type { ToolResultMessage } from \"@caupulican/pi-ai\";\nimport type { ArtifactStore } from \"./context-artifacts.ts\";\nimport {\n\ttype ContextEvidenceRef,\n\ttype ContextItem,\n\testimateByteLength,\n\testimateTokensFromText,\n} from \"./context-item.ts\";\nimport { evaluateRetentionEligibility, type RetentionEligibility } from \"./context-retention.ts\";\nimport { evaluateHardConstraints } from \"./policy-engine.ts\";\nimport type { HardConstraintFlags, PolicyFeatures, PolicyHardConstraintCode } from \"./policy-types.ts\";\n\nexport interface ContextAuditOptions {\n\t/** Current turn index (AgentSession's own per-run counter); used as `createdAtTurn`. */\n\tturnIndex: number;\n\t/** Session-scoped artifact store, if one has been constructed. Read-only here. */\n\tartifactStore?: ArtifactStore;\n\t/** Resolve the persisted session-entry id for a toolResult message's toolCallId, if known. */\n\tsessionEntryIdForToolCallId?: (toolCallId: string) => string | undefined;\n}\n\nexport interface ContextAuditItemReport {\n\titem: ContextItem;\n\t/** The source toolResult message's own id and position, always available (unlike refs). */\n\ttoolCallId: string;\n\tmessageIndex: number;\n\t/** Coarse, store-free eligibility from context-retention.ts (treats any ref as retrievable). */\n\tretention: RetentionEligibility;\n\t/** Store-aware hard-constraint codes for keep_raw; always empty (no evaluated action restricts it), included for reportability. */\n\tkeepRawHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for pack_to_artifact; empty means no hard rejection. */\n\tpackToArtifactHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for drop_from_prompt; empty means no hard rejection. */\n\tdropFromPromptHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for summarize; empty means no hard rejection. */\n\tsummarizeHardConstraints: PolicyHardConstraintCode[];\n}\n\nexport interface ContextAuditReport {\n\tturnIndex: number;\n\titems: ContextAuditItemReport[];\n}\n\nfunction extractToolResultArtifactId(message: ToolResultMessage): string | undefined {\n\tconst details = message.details;\n\tif (typeof details !== \"object\" || details === null) return undefined;\n\tconst artifactId = (details as { artifactId?: unknown }).artifactId;\n\treturn typeof artifactId === \"string\" ? artifactId : undefined;\n}\n\nfunction toolResultText(message: ToolResultMessage): string {\n\tconst parts: string[] = [];\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") parts.push(part.text);\n\t}\n\treturn parts.join(\"\\n\");\n}\n\ninterface BuiltToolOutputItem {\n\titem: ContextItem;\n\t/** True only if an artifact ref was found AND resolved against a live store. */\n\thasResolvedArtifact: boolean;\n}\n\nfunction buildToolOutputItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): BuiltToolOutputItem {\n\tconst text = toolResultText(message);\n\tconst artifactId = extractToolResultArtifactId(message);\n\tconst sessionEntryId = options.sessionEntryIdForToolCallId?.(message.toolCallId);\n\n\tlet primaryRef: ContextEvidenceRef | undefined;\n\tlet hasResolvedArtifact = false;\n\t// createdAtTurn defaults to the current audit turn (an approximation -- see below) and\n\t// is overridden with the real capture turn when an artifact ref resolves.\n\tlet createdAtTurn = options.turnIndex;\n\tif (artifactId && options.artifactStore) {\n\t\t// Metadata-only: never loads the artifact's payload just to check resolvability and\n\t\t// grab ref fields (a per-turn audit pass over every artifact-backed result would\n\t\t// otherwise re-read every payload off disk each turn for no reason).\n\t\tconst ref = options.artifactStore.readRef(artifactId);\n\t\tif (ref) {\n\t\t\tprimaryRef = { type: \"artifact\", ref };\n\t\t\thasResolvedArtifact = true;\n\t\t\tcreatedAtTurn = ref.createdAtTurn;\n\t\t}\n\t}\n\n\tconst evidenceRefs: ContextEvidenceRef[] = [];\n\tif (sessionEntryId) {\n\t\tconst transcriptRef: ContextEvidenceRef = {\n\t\t\ttype: \"transcript\",\n\t\t\tref: { sessionEntryId, messageIndex },\n\t\t};\n\t\tif (!primaryRef) primaryRef = transcriptRef;\n\t\telse evidenceRefs.push(transcriptRef);\n\t}\n\n\tconst item: ContextItem = {\n\t\tid: `tool-output:${message.toolCallId}`,\n\t\tkind: \"tool_output\",\n\t\tretentionClass: \"ephemeral\",\n\t\tsource: \"tool\",\n\t\t// Transcript-only items (no artifact ref) have no real creation-turn source threaded\n\t\t// in yet, so this falls back to the current audit turn as an approximation --\n\t\t// deliberately not left silently wrong: no evaluator reads createdAtTurn today, but\n\t\t// any future staleness/break-even math must use the real per-item value above, not\n\t\t// this fallback, once one exists for non-artifact items too.\n\t\tcreatedAtTurn,\n\t\tsummary: `${message.toolName} tool result`,\n\t\tprimaryRef,\n\t\tevidenceRefs: evidenceRefs.length > 0 ? evidenceRefs : undefined,\n\t\ttokenEstimate: estimateTokensFromText(text),\n\t\tbyteEstimate: estimateByteLength(text),\n\t};\n\n\treturn { item, hasResolvedArtifact };\n}\n\n/**\n * Economic/probability fields are zeroed: `evaluateHardConstraints` does not read them for\n * pack_to_artifact/drop_from_prompt (only the break-even scoring math this audit\n * deliberately does not invoke would need real numbers here).\n */\nfunction buildPolicyFeatures(item: ContextItem, built: BuiltToolOutputItem, turnIndex: number): PolicyFeatures {\n\treturn {\n\t\tturnIndex,\n\t\texpectedRemainingTurns: 0,\n\t\tinputTokens: 0,\n\t\toutputTokens: 0,\n\t\tcacheReadTokens: 0,\n\t\tcacheWriteTokens: 0,\n\t\tartifactBytes: built.hasResolvedArtifact ? item.byteEstimate : 0,\n\t\tcharEstimate: item.byteEstimate,\n\t\tcalibratedTokenEstimate: item.tokenEstimate,\n\t\tpromptSection: \"volatile_tail\",\n\t\tretentionClass: item.retentionClass,\n\t\tisReproducible: true,\n\t\tisDecisionBearing: false,\n\t\tisPinned: false,\n\t\tisOpenRequirement: false,\n\t\tisLatestFailure: false,\n\t\tisCurrentDiff: false,\n\t\tprobabilityNeededAgain: 0,\n\t\tprobabilityErrorIfDropped: 0,\n\t\tretrievalCostTokens: 0,\n\t\tpackCostTokens: 0,\n\t\tretryCostTokens: 0,\n\t\tfailureCostTokens: 0,\n\t\tvalidationCostTokens: 0,\n\t};\n}\n\nfunction buildHardConstraintFlags(built: BuiltToolOutputItem, artifactStoreAvailable: boolean): HardConstraintFlags {\n\treturn {\n\t\tisApprovalOrDenial: false,\n\t\tisSafetyConstraint: false,\n\t\tisActiveBlocker: false,\n\t\tisCurrentValidationResult: false,\n\t\tisPathOrToolScope: false,\n\t\t// Only a resolved artifact counts; a transcript ref is provenance, not a live\n\t\t// retrieval mechanism -- see the module doc comment.\n\t\thasAvailableRetrievalPath: built.hasResolvedArtifact,\n\t\tartifactStoreAvailable,\n\t\thasEvidenceRefForSummary: built.item.primaryRef !== undefined || (built.item.evidenceRefs?.length ?? 0) > 0,\n\t\tpathOrToolBoundariesEnforced: true,\n\t\tvalidationAvailableAndStrong: false,\n\t\tpriorAttemptFailedForReasoningOrArchitecture: false,\n\t\tisHighImpactOrBroadMultiFileEdit: false,\n\t};\n}\n\n/** Build the ContextItem for a single toolResult message, for direct unit testing. */\nexport function buildToolResultContextItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): ContextItem {\n\treturn buildToolOutputItem(message, messageIndex, options).item;\n}\n\n/**\n * Read-only audit pass: converts live toolResult messages into ContextItems and runs the\n * existing pure retention/hard-constraint evaluators over them. Never mutates `messages`,\n * the transcript, or artifact references -- deterministic given the same messages,\n * `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex\n * and artifact-backed createdAtTurn values are expected to change turn over turn).\n */\nexport function runContextAudit(messages: AgentMessage[], options: ContextAuditOptions): ContextAuditReport {\n\tconst items: ContextAuditItemReport[] = [];\n\tmessages.forEach((message, messageIndex) => {\n\t\tif (message.role !== \"toolResult\") return;\n\t\tconst built = buildToolOutputItem(message, messageIndex, options);\n\t\tconst features = buildPolicyFeatures(built.item, built, options.turnIndex);\n\t\tconst flags = buildHardConstraintFlags(built, options.artifactStore !== undefined);\n\t\titems.push({\n\t\t\titem: built.item,\n\t\t\ttoolCallId: message.toolCallId,\n\t\t\tmessageIndex,\n\t\t\tretention: evaluateRetentionEligibility(built.item),\n\t\t\tkeepRawHardConstraints: evaluateHardConstraints(\"keep_raw\", features, flags),\n\t\t\tpackToArtifactHardConstraints: evaluateHardConstraints(\"pack_to_artifact\", features, flags),\n\t\t\tdropFromPromptHardConstraints: evaluateHardConstraints(\"drop_from_prompt\", features, flags),\n\t\t\tsummarizeHardConstraints: evaluateHardConstraints(\"summarize\", features, flags),\n\t\t});\n\t});\n\treturn { turnIndex: options.turnIndex, items };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context-audit.d.ts","sourceRoot":"","sources":["../../../src/core/context/context-audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAEN,KAAK,WAAW,EAGhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAgC,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEjG,OAAO,KAAK,EAAuC,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAEvG,MAAM,WAAW,mBAAmB;IACnC,wFAAwF;IACxF,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,8FAA8F;IAC9F,2BAA2B,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACzE;AAED,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gGAAgG;IAChG,SAAS,EAAE,oBAAoB,CAAC;IAChC,mIAAmI;IACnI,sBAAsB,EAAE,wBAAwB,EAAE,CAAC;IACnD,6FAA6F;IAC7F,6BAA6B,EAAE,wBAAwB,EAAE,CAAC;IAC1D,6FAA6F;IAC7F,6BAA6B,EAAE,wBAAwB,EAAE,CAAC;IAC1D,sFAAsF;IACtF,wBAAwB,EAAE,wBAAwB,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,sBAAsB,EAAE,CAAC;CAChC;AAiBD,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,WAAW,CAAC;IAClB,gFAAgF;IAChF,mBAAmB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,gBAAgB,GAAG,GAAG,CAAC,YAAY,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAAC;AAgHvG,sFAAsF;AACtF,wBAAgB,0BAA0B,CACzC,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,mBAAmB,GAC1B,WAAW,CAEb;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,YAAY,EAAE,EACxB,OAAO,EAAE,mBAAmB,EAC5B,IAAI,CAAC,EAAE,gBAAgB,GACrB,kBAAkB,CAqCpB","sourcesContent":["/**\n * Live observe-only bridge from AgentMessage[] to the ContextItem/policy-engine layer\n * (Phase 1 audit pass). This module only ever reads messages and the artifact store; it\n * never mutates messages, the transcript, or artifact references. It is the first thing to\n * consume the context-item.ts/context-retention.ts/policy-engine.ts contracts against live\n * session state, but it does not yet change what the model sees -- see\n * docs/context-management-rework/implementation-phases.md for where this sits.\n *\n * Scope for this pass: only `toolResult` messages become `ContextItem`s (kind\n * \"tool_output\"). Other roles (user/assistant) are skipped; representing them is later\n * work once their ContextItemKind mapping (user_instruction/approval/etc.) is designed.\n *\n * Retrieval-path semantics (deliberately narrower than context-retention.ts's item-level\n * `hasReferencedEvidence`): a transcript ref is attached to every item as provenance\n * evidence (which live message this item came from), but it never counts toward\n * `HardConstraintFlags.hasAvailableRetrievalPath` on its own -- there is no live mechanism\n * today for the model to fetch an older message back into context by session-entry id, so\n * claiming that would overclaim retrievability (the same fail-closed principle\n * `artifact_retrieve` follows). Only a resolved artifact ref (the store still has the\n * payload) counts as an available retrieval path.\n */\n\nimport type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport type { ToolResultMessage } from \"@caupulican/pi-ai\";\nimport type { ArtifactStore } from \"./context-artifacts.ts\";\nimport {\n\ttype ContextEvidenceRef,\n\ttype ContextItem,\n\testimateByteLength,\n\testimateTokensFromText,\n} from \"./context-item.ts\";\nimport { evaluateRetentionEligibility, type RetentionEligibility } from \"./context-retention.ts\";\nimport { evaluateHardConstraints } from \"./policy-engine.ts\";\nimport type { HardConstraintFlags, PolicyFeatures, PolicyHardConstraintCode } from \"./policy-types.ts\";\n\nexport interface ContextAuditOptions {\n\t/** Current turn index (AgentSession's own per-run counter); used as `createdAtTurn`. */\n\tturnIndex: number;\n\t/** Session-scoped artifact store, if one has been constructed. Read-only here. */\n\tartifactStore?: ArtifactStore;\n\t/** Resolve the persisted session-entry id for a toolResult message's toolCallId, if known. */\n\tsessionEntryIdForToolCallId?: (toolCallId: string) => string | undefined;\n}\n\nexport interface ContextAuditItemReport {\n\titem: ContextItem;\n\t/** The source toolResult message's own id and position, always available (unlike refs). */\n\ttoolCallId: string;\n\tmessageIndex: number;\n\t/** Coarse, store-free eligibility from context-retention.ts (treats any ref as retrievable). */\n\tretention: RetentionEligibility;\n\t/** Store-aware hard-constraint codes for keep_raw; always empty (no evaluated action restricts it), included for reportability. */\n\tkeepRawHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for pack_to_artifact; empty means no hard rejection. */\n\tpackToArtifactHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for drop_from_prompt; empty means no hard rejection. */\n\tdropFromPromptHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for summarize; empty means no hard rejection. */\n\tsummarizeHardConstraints: PolicyHardConstraintCode[];\n}\n\nexport interface ContextAuditReport {\n\tturnIndex: number;\n\titems: ContextAuditItemReport[];\n}\n\nfunction extractToolResultArtifactId(message: ToolResultMessage): string | undefined {\n\tconst details = message.details;\n\tif (typeof details !== \"object\" || details === null) return undefined;\n\tconst artifactId = (details as { artifactId?: unknown }).artifactId;\n\treturn typeof artifactId === \"string\" ? artifactId : undefined;\n}\n\nfunction toolResultText(message: ToolResultMessage): string {\n\tconst parts: string[] = [];\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") parts.push(part.text);\n\t}\n\treturn parts.join(\"\\n\");\n}\n\nexport interface BuiltToolOutputItem {\n\titem: ContextItem;\n\t/** True only if an artifact ref was found AND resolved against a live store. */\n\thasResolvedArtifact: boolean;\n}\n\n/**\n * Per-message memo of the expensive part of {@link buildToolOutputItem} (text extraction +\n * token/byte estimate + artifact `readRef` metadata), keyed by `AgentMessage` object identity\n * -- a replaced/rewritten message object is a new key, so it misses and recomputes with no\n * extra bookkeeping. Entries also carry the `messageIndex` they were built at: a message that\n * kept its identity but shifted index (e.g. a compaction survivor) is treated as a miss too,\n * since `messageIndex` feeds the transcript evidence ref.\n *\n * Ownership: the caller (`ContextPipeline`) owns a single long-lived instance across calls and\n * passes it in every pass; `runContextAudit` treats it as the previous pass's cache on read and\n * replaces its contents with exactly this pass's live entries before returning (a message no\n * longer present in `messages` is dropped, not retained) -- so the map never grows unbounded\n * across compaction rewrites or branch switches. Callers that must stay pure full-scans (tests,\n * read-only recompute paths) simply omit this parameter.\n */\nexport type ContextAuditMemo = Map<AgentMessage, { messageIndex: number; built: BuiltToolOutputItem }>;\n\nfunction buildToolOutputItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): BuiltToolOutputItem {\n\tconst text = toolResultText(message);\n\tconst artifactId = extractToolResultArtifactId(message);\n\tconst sessionEntryId = options.sessionEntryIdForToolCallId?.(message.toolCallId);\n\n\tlet primaryRef: ContextEvidenceRef | undefined;\n\tlet hasResolvedArtifact = false;\n\t// createdAtTurn defaults to the current audit turn (an approximation -- see below) and\n\t// is overridden with the real capture turn when an artifact ref resolves.\n\tlet createdAtTurn = options.turnIndex;\n\tif (artifactId && options.artifactStore) {\n\t\t// Metadata-only: never loads the artifact's payload just to check resolvability and\n\t\t// grab ref fields (a per-turn audit pass over every artifact-backed result would\n\t\t// otherwise re-read every payload off disk each turn for no reason).\n\t\tconst ref = options.artifactStore.readRef(artifactId);\n\t\tif (ref) {\n\t\t\tprimaryRef = { type: \"artifact\", ref };\n\t\t\thasResolvedArtifact = true;\n\t\t\tcreatedAtTurn = ref.createdAtTurn;\n\t\t}\n\t}\n\n\tconst evidenceRefs: ContextEvidenceRef[] = [];\n\tif (sessionEntryId) {\n\t\tconst transcriptRef: ContextEvidenceRef = {\n\t\t\ttype: \"transcript\",\n\t\t\tref: { sessionEntryId, messageIndex },\n\t\t};\n\t\tif (!primaryRef) primaryRef = transcriptRef;\n\t\telse evidenceRefs.push(transcriptRef);\n\t}\n\n\tconst item: ContextItem = {\n\t\tid: `tool-output:${message.toolCallId}`,\n\t\tkind: \"tool_output\",\n\t\tretentionClass: \"ephemeral\",\n\t\tsource: \"tool\",\n\t\t// Transcript-only items (no artifact ref) have no real creation-turn source threaded\n\t\t// in yet, so this falls back to the current audit turn as an approximation --\n\t\t// deliberately not left silently wrong: no evaluator reads createdAtTurn today, but\n\t\t// any future staleness/break-even math must use the real per-item value above, not\n\t\t// this fallback, once one exists for non-artifact items too.\n\t\tcreatedAtTurn,\n\t\tsummary: `${message.toolName} tool result`,\n\t\tprimaryRef,\n\t\tevidenceRefs: evidenceRefs.length > 0 ? evidenceRefs : undefined,\n\t\ttokenEstimate: estimateTokensFromText(text),\n\t\tbyteEstimate: estimateByteLength(text),\n\t};\n\n\treturn { item, hasResolvedArtifact };\n}\n\n/**\n * Economic/probability fields are zeroed: `evaluateHardConstraints` does not read them for\n * pack_to_artifact/drop_from_prompt (only the break-even scoring math this audit\n * deliberately does not invoke would need real numbers here).\n */\nfunction buildPolicyFeatures(item: ContextItem, built: BuiltToolOutputItem, turnIndex: number): PolicyFeatures {\n\treturn {\n\t\tturnIndex,\n\t\texpectedRemainingTurns: 0,\n\t\tinputTokens: 0,\n\t\toutputTokens: 0,\n\t\tcacheReadTokens: 0,\n\t\tcacheWriteTokens: 0,\n\t\tartifactBytes: built.hasResolvedArtifact ? item.byteEstimate : 0,\n\t\tcharEstimate: item.byteEstimate,\n\t\tcalibratedTokenEstimate: item.tokenEstimate,\n\t\tpromptSection: \"volatile_tail\",\n\t\tretentionClass: item.retentionClass,\n\t\tisReproducible: true,\n\t\tisDecisionBearing: false,\n\t\tisPinned: false,\n\t\tisOpenRequirement: false,\n\t\tisLatestFailure: false,\n\t\tisCurrentDiff: false,\n\t\tprobabilityNeededAgain: 0,\n\t\tprobabilityErrorIfDropped: 0,\n\t\tretrievalCostTokens: 0,\n\t\tpackCostTokens: 0,\n\t\tretryCostTokens: 0,\n\t\tfailureCostTokens: 0,\n\t\tvalidationCostTokens: 0,\n\t};\n}\n\nfunction buildHardConstraintFlags(built: BuiltToolOutputItem, artifactStoreAvailable: boolean): HardConstraintFlags {\n\treturn {\n\t\tisApprovalOrDenial: false,\n\t\tisSafetyConstraint: false,\n\t\tisActiveBlocker: false,\n\t\tisCurrentValidationResult: false,\n\t\tisPathOrToolScope: false,\n\t\t// Only a resolved artifact counts; a transcript ref is provenance, not a live\n\t\t// retrieval mechanism -- see the module doc comment.\n\t\thasAvailableRetrievalPath: built.hasResolvedArtifact,\n\t\tartifactStoreAvailable,\n\t\thasEvidenceRefForSummary: built.item.primaryRef !== undefined || (built.item.evidenceRefs?.length ?? 0) > 0,\n\t\tpathOrToolBoundariesEnforced: true,\n\t\tvalidationAvailableAndStrong: false,\n\t\tpriorAttemptFailedForReasoningOrArchitecture: false,\n\t\tisHighImpactOrBroadMultiFileEdit: false,\n\t};\n}\n\n/** Build the ContextItem for a single toolResult message, for direct unit testing. */\nexport function buildToolResultContextItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): ContextItem {\n\treturn buildToolOutputItem(message, messageIndex, options).item;\n}\n\n/**\n * Read-only audit pass: converts live toolResult messages into ContextItems and runs the\n * existing pure retention/hard-constraint evaluators over them. Never mutates `messages`,\n * the transcript, or artifact references -- deterministic given the same messages,\n * `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex\n * and artifact-backed createdAtTurn values are expected to change turn over turn).\n *\n * `memo`, if supplied, is an incremental-memo seam (see {@link ContextAuditMemo}): the\n * expensive `buildToolOutputItem` work is skipped and reused for a message whose object\n * identity AND index are unchanged from the previous pass. The turnIndex-dependent tail\n * (`createdAtTurn` for non-artifact-backed items) is re-derived fresh on every call\n * regardless of cache hit/miss, so output is byte-identical to a full recompute either way\n * -- omitting `memo` (or passing a throwaway `new Map()`) makes this a pure full scan, exactly\n * as before this parameter existed.\n */\nexport function runContextAudit(\n\tmessages: AgentMessage[],\n\toptions: ContextAuditOptions,\n\tmemo?: ContextAuditMemo,\n): ContextAuditReport {\n\tconst freshMemo: ContextAuditMemo | undefined = memo ? new Map() : undefined;\n\tconst items: ContextAuditItemReport[] = [];\n\tmessages.forEach((message, messageIndex) => {\n\t\tif (message.role !== \"toolResult\") return;\n\t\tconst cached = memo?.get(message);\n\t\tconst built =\n\t\t\tcached && cached.messageIndex === messageIndex\n\t\t\t\t? cached.built\n\t\t\t\t: buildToolOutputItem(message, messageIndex, options);\n\t\tfreshMemo?.set(message, { messageIndex, built });\n\t\t// Re-derive the turnIndex-dependent tail every call, cache hit or miss: a resolved\n\t\t// artifact's real capture turn never changes (see buildToolOutputItem), while a\n\t\t// non-artifact item's createdAtTurn is always the CURRENT audit turn -- exactly what a\n\t\t// fresh buildToolOutputItem call would have produced, so this is a no-op on a cache\n\t\t// miss and a correctness fix on a cache hit.\n\t\tconst item: ContextItem = built.hasResolvedArtifact\n\t\t\t? built.item\n\t\t\t: { ...built.item, createdAtTurn: options.turnIndex };\n\t\tconst features = buildPolicyFeatures(item, built, options.turnIndex);\n\t\tconst flags = buildHardConstraintFlags(built, options.artifactStore !== undefined);\n\t\titems.push({\n\t\t\titem,\n\t\t\ttoolCallId: message.toolCallId,\n\t\t\tmessageIndex,\n\t\t\tretention: evaluateRetentionEligibility(item),\n\t\t\tkeepRawHardConstraints: evaluateHardConstraints(\"keep_raw\", features, flags),\n\t\t\tpackToArtifactHardConstraints: evaluateHardConstraints(\"pack_to_artifact\", features, flags),\n\t\t\tdropFromPromptHardConstraints: evaluateHardConstraints(\"drop_from_prompt\", features, flags),\n\t\t\tsummarizeHardConstraints: evaluateHardConstraints(\"summarize\", features, flags),\n\t\t});\n\t});\n\tif (memo && freshMemo) {\n\t\tmemo.clear();\n\t\tfor (const [key, entry] of freshMemo) memo.set(key, entry);\n\t}\n\treturn { turnIndex: options.turnIndex, items };\n}\n"]}
|
|
@@ -148,26 +148,52 @@ export function buildToolResultContextItem(message, messageIndex, options) {
|
|
|
148
148
|
* the transcript, or artifact references -- deterministic given the same messages,
|
|
149
149
|
* `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex
|
|
150
150
|
* and artifact-backed createdAtTurn values are expected to change turn over turn).
|
|
151
|
+
*
|
|
152
|
+
* `memo`, if supplied, is an incremental-memo seam (see {@link ContextAuditMemo}): the
|
|
153
|
+
* expensive `buildToolOutputItem` work is skipped and reused for a message whose object
|
|
154
|
+
* identity AND index are unchanged from the previous pass. The turnIndex-dependent tail
|
|
155
|
+
* (`createdAtTurn` for non-artifact-backed items) is re-derived fresh on every call
|
|
156
|
+
* regardless of cache hit/miss, so output is byte-identical to a full recompute either way
|
|
157
|
+
* -- omitting `memo` (or passing a throwaway `new Map()`) makes this a pure full scan, exactly
|
|
158
|
+
* as before this parameter existed.
|
|
151
159
|
*/
|
|
152
|
-
export function runContextAudit(messages, options) {
|
|
160
|
+
export function runContextAudit(messages, options, memo) {
|
|
161
|
+
const freshMemo = memo ? new Map() : undefined;
|
|
153
162
|
const items = [];
|
|
154
163
|
messages.forEach((message, messageIndex) => {
|
|
155
164
|
if (message.role !== "toolResult")
|
|
156
165
|
return;
|
|
157
|
-
const
|
|
158
|
-
const
|
|
166
|
+
const cached = memo?.get(message);
|
|
167
|
+
const built = cached && cached.messageIndex === messageIndex
|
|
168
|
+
? cached.built
|
|
169
|
+
: buildToolOutputItem(message, messageIndex, options);
|
|
170
|
+
freshMemo?.set(message, { messageIndex, built });
|
|
171
|
+
// Re-derive the turnIndex-dependent tail every call, cache hit or miss: a resolved
|
|
172
|
+
// artifact's real capture turn never changes (see buildToolOutputItem), while a
|
|
173
|
+
// non-artifact item's createdAtTurn is always the CURRENT audit turn -- exactly what a
|
|
174
|
+
// fresh buildToolOutputItem call would have produced, so this is a no-op on a cache
|
|
175
|
+
// miss and a correctness fix on a cache hit.
|
|
176
|
+
const item = built.hasResolvedArtifact
|
|
177
|
+
? built.item
|
|
178
|
+
: { ...built.item, createdAtTurn: options.turnIndex };
|
|
179
|
+
const features = buildPolicyFeatures(item, built, options.turnIndex);
|
|
159
180
|
const flags = buildHardConstraintFlags(built, options.artifactStore !== undefined);
|
|
160
181
|
items.push({
|
|
161
|
-
item
|
|
182
|
+
item,
|
|
162
183
|
toolCallId: message.toolCallId,
|
|
163
184
|
messageIndex,
|
|
164
|
-
retention: evaluateRetentionEligibility(
|
|
185
|
+
retention: evaluateRetentionEligibility(item),
|
|
165
186
|
keepRawHardConstraints: evaluateHardConstraints("keep_raw", features, flags),
|
|
166
187
|
packToArtifactHardConstraints: evaluateHardConstraints("pack_to_artifact", features, flags),
|
|
167
188
|
dropFromPromptHardConstraints: evaluateHardConstraints("drop_from_prompt", features, flags),
|
|
168
189
|
summarizeHardConstraints: evaluateHardConstraints("summarize", features, flags),
|
|
169
190
|
});
|
|
170
191
|
});
|
|
192
|
+
if (memo && freshMemo) {
|
|
193
|
+
memo.clear();
|
|
194
|
+
for (const [key, entry] of freshMemo)
|
|
195
|
+
memo.set(key, entry);
|
|
196
|
+
}
|
|
171
197
|
return { turnIndex: options.turnIndex, items };
|
|
172
198
|
}
|
|
173
199
|
//# sourceMappingURL=context-audit.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-audit.js","sourceRoot":"","sources":["../../../src/core/context/context-audit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Live observe-only bridge from AgentMessage[] to the ContextItem/policy-engine layer\n * (Phase 1 audit pass). This module only ever reads messages and the artifact store; it\n * never mutates messages, the transcript, or artifact references. It is the first thing to\n * consume the context-item.ts/context-retention.ts/policy-engine.ts contracts against live\n * session state, but it does not yet change what the model sees -- see\n * docs/context-management-rework/implementation-phases.md for where this sits.\n *\n * Scope for this pass: only `toolResult` messages become `ContextItem`s (kind\n * \"tool_output\"). Other roles (user/assistant) are skipped; representing them is later\n * work once their ContextItemKind mapping (user_instruction/approval/etc.) is designed.\n *\n * Retrieval-path semantics (deliberately narrower than context-retention.ts's item-level\n * `hasReferencedEvidence`): a transcript ref is attached to every item as provenance\n * evidence (which live message this item came from), but it never counts toward\n * `HardConstraintFlags.hasAvailableRetrievalPath` on its own -- there is no live mechanism\n * today for the model to fetch an older message back into context by session-entry id, so\n * claiming that would overclaim retrievability (the same fail-closed principle\n * `artifact_retrieve` follows). Only a resolved artifact ref (the store still has the\n * payload) counts as an available retrieval path.\n */\n\nimport type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport type { ToolResultMessage } from \"@caupulican/pi-ai\";\nimport type { ArtifactStore } from \"./context-artifacts.ts\";\nimport {\n\ttype ContextEvidenceRef,\n\ttype ContextItem,\n\testimateByteLength,\n\testimateTokensFromText,\n} from \"./context-item.ts\";\nimport { evaluateRetentionEligibility, type RetentionEligibility } from \"./context-retention.ts\";\nimport { evaluateHardConstraints } from \"./policy-engine.ts\";\nimport type { HardConstraintFlags, PolicyFeatures, PolicyHardConstraintCode } from \"./policy-types.ts\";\n\nexport interface ContextAuditOptions {\n\t/** Current turn index (AgentSession's own per-run counter); used as `createdAtTurn`. */\n\tturnIndex: number;\n\t/** Session-scoped artifact store, if one has been constructed. Read-only here. */\n\tartifactStore?: ArtifactStore;\n\t/** Resolve the persisted session-entry id for a toolResult message's toolCallId, if known. */\n\tsessionEntryIdForToolCallId?: (toolCallId: string) => string | undefined;\n}\n\nexport interface ContextAuditItemReport {\n\titem: ContextItem;\n\t/** The source toolResult message's own id and position, always available (unlike refs). */\n\ttoolCallId: string;\n\tmessageIndex: number;\n\t/** Coarse, store-free eligibility from context-retention.ts (treats any ref as retrievable). */\n\tretention: RetentionEligibility;\n\t/** Store-aware hard-constraint codes for keep_raw; always empty (no evaluated action restricts it), included for reportability. */\n\tkeepRawHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for pack_to_artifact; empty means no hard rejection. */\n\tpackToArtifactHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for drop_from_prompt; empty means no hard rejection. */\n\tdropFromPromptHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for summarize; empty means no hard rejection. */\n\tsummarizeHardConstraints: PolicyHardConstraintCode[];\n}\n\nexport interface ContextAuditReport {\n\tturnIndex: number;\n\titems: ContextAuditItemReport[];\n}\n\nfunction extractToolResultArtifactId(message: ToolResultMessage): string | undefined {\n\tconst details = message.details;\n\tif (typeof details !== \"object\" || details === null) return undefined;\n\tconst artifactId = (details as { artifactId?: unknown }).artifactId;\n\treturn typeof artifactId === \"string\" ? artifactId : undefined;\n}\n\nfunction toolResultText(message: ToolResultMessage): string {\n\tconst parts: string[] = [];\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") parts.push(part.text);\n\t}\n\treturn parts.join(\"\\n\");\n}\n\ninterface BuiltToolOutputItem {\n\titem: ContextItem;\n\t/** True only if an artifact ref was found AND resolved against a live store. */\n\thasResolvedArtifact: boolean;\n}\n\nfunction buildToolOutputItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): BuiltToolOutputItem {\n\tconst text = toolResultText(message);\n\tconst artifactId = extractToolResultArtifactId(message);\n\tconst sessionEntryId = options.sessionEntryIdForToolCallId?.(message.toolCallId);\n\n\tlet primaryRef: ContextEvidenceRef | undefined;\n\tlet hasResolvedArtifact = false;\n\t// createdAtTurn defaults to the current audit turn (an approximation -- see below) and\n\t// is overridden with the real capture turn when an artifact ref resolves.\n\tlet createdAtTurn = options.turnIndex;\n\tif (artifactId && options.artifactStore) {\n\t\t// Metadata-only: never loads the artifact's payload just to check resolvability and\n\t\t// grab ref fields (a per-turn audit pass over every artifact-backed result would\n\t\t// otherwise re-read every payload off disk each turn for no reason).\n\t\tconst ref = options.artifactStore.readRef(artifactId);\n\t\tif (ref) {\n\t\t\tprimaryRef = { type: \"artifact\", ref };\n\t\t\thasResolvedArtifact = true;\n\t\t\tcreatedAtTurn = ref.createdAtTurn;\n\t\t}\n\t}\n\n\tconst evidenceRefs: ContextEvidenceRef[] = [];\n\tif (sessionEntryId) {\n\t\tconst transcriptRef: ContextEvidenceRef = {\n\t\t\ttype: \"transcript\",\n\t\t\tref: { sessionEntryId, messageIndex },\n\t\t};\n\t\tif (!primaryRef) primaryRef = transcriptRef;\n\t\telse evidenceRefs.push(transcriptRef);\n\t}\n\n\tconst item: ContextItem = {\n\t\tid: `tool-output:${message.toolCallId}`,\n\t\tkind: \"tool_output\",\n\t\tretentionClass: \"ephemeral\",\n\t\tsource: \"tool\",\n\t\t// Transcript-only items (no artifact ref) have no real creation-turn source threaded\n\t\t// in yet, so this falls back to the current audit turn as an approximation --\n\t\t// deliberately not left silently wrong: no evaluator reads createdAtTurn today, but\n\t\t// any future staleness/break-even math must use the real per-item value above, not\n\t\t// this fallback, once one exists for non-artifact items too.\n\t\tcreatedAtTurn,\n\t\tsummary: `${message.toolName} tool result`,\n\t\tprimaryRef,\n\t\tevidenceRefs: evidenceRefs.length > 0 ? evidenceRefs : undefined,\n\t\ttokenEstimate: estimateTokensFromText(text),\n\t\tbyteEstimate: estimateByteLength(text),\n\t};\n\n\treturn { item, hasResolvedArtifact };\n}\n\n/**\n * Economic/probability fields are zeroed: `evaluateHardConstraints` does not read them for\n * pack_to_artifact/drop_from_prompt (only the break-even scoring math this audit\n * deliberately does not invoke would need real numbers here).\n */\nfunction buildPolicyFeatures(item: ContextItem, built: BuiltToolOutputItem, turnIndex: number): PolicyFeatures {\n\treturn {\n\t\tturnIndex,\n\t\texpectedRemainingTurns: 0,\n\t\tinputTokens: 0,\n\t\toutputTokens: 0,\n\t\tcacheReadTokens: 0,\n\t\tcacheWriteTokens: 0,\n\t\tartifactBytes: built.hasResolvedArtifact ? item.byteEstimate : 0,\n\t\tcharEstimate: item.byteEstimate,\n\t\tcalibratedTokenEstimate: item.tokenEstimate,\n\t\tpromptSection: \"volatile_tail\",\n\t\tretentionClass: item.retentionClass,\n\t\tisReproducible: true,\n\t\tisDecisionBearing: false,\n\t\tisPinned: false,\n\t\tisOpenRequirement: false,\n\t\tisLatestFailure: false,\n\t\tisCurrentDiff: false,\n\t\tprobabilityNeededAgain: 0,\n\t\tprobabilityErrorIfDropped: 0,\n\t\tretrievalCostTokens: 0,\n\t\tpackCostTokens: 0,\n\t\tretryCostTokens: 0,\n\t\tfailureCostTokens: 0,\n\t\tvalidationCostTokens: 0,\n\t};\n}\n\nfunction buildHardConstraintFlags(built: BuiltToolOutputItem, artifactStoreAvailable: boolean): HardConstraintFlags {\n\treturn {\n\t\tisApprovalOrDenial: false,\n\t\tisSafetyConstraint: false,\n\t\tisActiveBlocker: false,\n\t\tisCurrentValidationResult: false,\n\t\tisPathOrToolScope: false,\n\t\t// Only a resolved artifact counts; a transcript ref is provenance, not a live\n\t\t// retrieval mechanism -- see the module doc comment.\n\t\thasAvailableRetrievalPath: built.hasResolvedArtifact,\n\t\tartifactStoreAvailable,\n\t\thasEvidenceRefForSummary: built.item.primaryRef !== undefined || (built.item.evidenceRefs?.length ?? 0) > 0,\n\t\tpathOrToolBoundariesEnforced: true,\n\t\tvalidationAvailableAndStrong: false,\n\t\tpriorAttemptFailedForReasoningOrArchitecture: false,\n\t\tisHighImpactOrBroadMultiFileEdit: false,\n\t};\n}\n\n/** Build the ContextItem for a single toolResult message, for direct unit testing. */\nexport function buildToolResultContextItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): ContextItem {\n\treturn buildToolOutputItem(message, messageIndex, options).item;\n}\n\n/**\n * Read-only audit pass: converts live toolResult messages into ContextItems and runs the\n * existing pure retention/hard-constraint evaluators over them. Never mutates `messages`,\n * the transcript, or artifact references -- deterministic given the same messages,\n * `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex\n * and artifact-backed createdAtTurn values are expected to change turn over turn).\n */\nexport function runContextAudit(messages: AgentMessage[], options: ContextAuditOptions): ContextAuditReport {\n\tconst items: ContextAuditItemReport[] = [];\n\tmessages.forEach((message, messageIndex) => {\n\t\tif (message.role !== \"toolResult\") return;\n\t\tconst built = buildToolOutputItem(message, messageIndex, options);\n\t\tconst features = buildPolicyFeatures(built.item, built, options.turnIndex);\n\t\tconst flags = buildHardConstraintFlags(built, options.artifactStore !== undefined);\n\t\titems.push({\n\t\t\titem: built.item,\n\t\t\ttoolCallId: message.toolCallId,\n\t\t\tmessageIndex,\n\t\t\tretention: evaluateRetentionEligibility(built.item),\n\t\t\tkeepRawHardConstraints: evaluateHardConstraints(\"keep_raw\", features, flags),\n\t\t\tpackToArtifactHardConstraints: evaluateHardConstraints(\"pack_to_artifact\", features, flags),\n\t\t\tdropFromPromptHardConstraints: evaluateHardConstraints(\"drop_from_prompt\", features, flags),\n\t\t\tsummarizeHardConstraints: evaluateHardConstraints(\"summarize\", features, flags),\n\t\t});\n\t});\n\treturn { turnIndex: options.turnIndex, items };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context-audit.js","sourceRoot":"","sources":["../../../src/core/context/context-audit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Live observe-only bridge from AgentMessage[] to the ContextItem/policy-engine layer\n * (Phase 1 audit pass). This module only ever reads messages and the artifact store; it\n * never mutates messages, the transcript, or artifact references. It is the first thing to\n * consume the context-item.ts/context-retention.ts/policy-engine.ts contracts against live\n * session state, but it does not yet change what the model sees -- see\n * docs/context-management-rework/implementation-phases.md for where this sits.\n *\n * Scope for this pass: only `toolResult` messages become `ContextItem`s (kind\n * \"tool_output\"). Other roles (user/assistant) are skipped; representing them is later\n * work once their ContextItemKind mapping (user_instruction/approval/etc.) is designed.\n *\n * Retrieval-path semantics (deliberately narrower than context-retention.ts's item-level\n * `hasReferencedEvidence`): a transcript ref is attached to every item as provenance\n * evidence (which live message this item came from), but it never counts toward\n * `HardConstraintFlags.hasAvailableRetrievalPath` on its own -- there is no live mechanism\n * today for the model to fetch an older message back into context by session-entry id, so\n * claiming that would overclaim retrievability (the same fail-closed principle\n * `artifact_retrieve` follows). Only a resolved artifact ref (the store still has the\n * payload) counts as an available retrieval path.\n */\n\nimport type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport type { ToolResultMessage } from \"@caupulican/pi-ai\";\nimport type { ArtifactStore } from \"./context-artifacts.ts\";\nimport {\n\ttype ContextEvidenceRef,\n\ttype ContextItem,\n\testimateByteLength,\n\testimateTokensFromText,\n} from \"./context-item.ts\";\nimport { evaluateRetentionEligibility, type RetentionEligibility } from \"./context-retention.ts\";\nimport { evaluateHardConstraints } from \"./policy-engine.ts\";\nimport type { HardConstraintFlags, PolicyFeatures, PolicyHardConstraintCode } from \"./policy-types.ts\";\n\nexport interface ContextAuditOptions {\n\t/** Current turn index (AgentSession's own per-run counter); used as `createdAtTurn`. */\n\tturnIndex: number;\n\t/** Session-scoped artifact store, if one has been constructed. Read-only here. */\n\tartifactStore?: ArtifactStore;\n\t/** Resolve the persisted session-entry id for a toolResult message's toolCallId, if known. */\n\tsessionEntryIdForToolCallId?: (toolCallId: string) => string | undefined;\n}\n\nexport interface ContextAuditItemReport {\n\titem: ContextItem;\n\t/** The source toolResult message's own id and position, always available (unlike refs). */\n\ttoolCallId: string;\n\tmessageIndex: number;\n\t/** Coarse, store-free eligibility from context-retention.ts (treats any ref as retrievable). */\n\tretention: RetentionEligibility;\n\t/** Store-aware hard-constraint codes for keep_raw; always empty (no evaluated action restricts it), included for reportability. */\n\tkeepRawHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for pack_to_artifact; empty means no hard rejection. */\n\tpackToArtifactHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for drop_from_prompt; empty means no hard rejection. */\n\tdropFromPromptHardConstraints: PolicyHardConstraintCode[];\n\t/** Store-aware hard-constraint codes for summarize; empty means no hard rejection. */\n\tsummarizeHardConstraints: PolicyHardConstraintCode[];\n}\n\nexport interface ContextAuditReport {\n\tturnIndex: number;\n\titems: ContextAuditItemReport[];\n}\n\nfunction extractToolResultArtifactId(message: ToolResultMessage): string | undefined {\n\tconst details = message.details;\n\tif (typeof details !== \"object\" || details === null) return undefined;\n\tconst artifactId = (details as { artifactId?: unknown }).artifactId;\n\treturn typeof artifactId === \"string\" ? artifactId : undefined;\n}\n\nfunction toolResultText(message: ToolResultMessage): string {\n\tconst parts: string[] = [];\n\tfor (const part of message.content) {\n\t\tif (part.type === \"text\") parts.push(part.text);\n\t}\n\treturn parts.join(\"\\n\");\n}\n\nexport interface BuiltToolOutputItem {\n\titem: ContextItem;\n\t/** True only if an artifact ref was found AND resolved against a live store. */\n\thasResolvedArtifact: boolean;\n}\n\n/**\n * Per-message memo of the expensive part of {@link buildToolOutputItem} (text extraction +\n * token/byte estimate + artifact `readRef` metadata), keyed by `AgentMessage` object identity\n * -- a replaced/rewritten message object is a new key, so it misses and recomputes with no\n * extra bookkeeping. Entries also carry the `messageIndex` they were built at: a message that\n * kept its identity but shifted index (e.g. a compaction survivor) is treated as a miss too,\n * since `messageIndex` feeds the transcript evidence ref.\n *\n * Ownership: the caller (`ContextPipeline`) owns a single long-lived instance across calls and\n * passes it in every pass; `runContextAudit` treats it as the previous pass's cache on read and\n * replaces its contents with exactly this pass's live entries before returning (a message no\n * longer present in `messages` is dropped, not retained) -- so the map never grows unbounded\n * across compaction rewrites or branch switches. Callers that must stay pure full-scans (tests,\n * read-only recompute paths) simply omit this parameter.\n */\nexport type ContextAuditMemo = Map<AgentMessage, { messageIndex: number; built: BuiltToolOutputItem }>;\n\nfunction buildToolOutputItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): BuiltToolOutputItem {\n\tconst text = toolResultText(message);\n\tconst artifactId = extractToolResultArtifactId(message);\n\tconst sessionEntryId = options.sessionEntryIdForToolCallId?.(message.toolCallId);\n\n\tlet primaryRef: ContextEvidenceRef | undefined;\n\tlet hasResolvedArtifact = false;\n\t// createdAtTurn defaults to the current audit turn (an approximation -- see below) and\n\t// is overridden with the real capture turn when an artifact ref resolves.\n\tlet createdAtTurn = options.turnIndex;\n\tif (artifactId && options.artifactStore) {\n\t\t// Metadata-only: never loads the artifact's payload just to check resolvability and\n\t\t// grab ref fields (a per-turn audit pass over every artifact-backed result would\n\t\t// otherwise re-read every payload off disk each turn for no reason).\n\t\tconst ref = options.artifactStore.readRef(artifactId);\n\t\tif (ref) {\n\t\t\tprimaryRef = { type: \"artifact\", ref };\n\t\t\thasResolvedArtifact = true;\n\t\t\tcreatedAtTurn = ref.createdAtTurn;\n\t\t}\n\t}\n\n\tconst evidenceRefs: ContextEvidenceRef[] = [];\n\tif (sessionEntryId) {\n\t\tconst transcriptRef: ContextEvidenceRef = {\n\t\t\ttype: \"transcript\",\n\t\t\tref: { sessionEntryId, messageIndex },\n\t\t};\n\t\tif (!primaryRef) primaryRef = transcriptRef;\n\t\telse evidenceRefs.push(transcriptRef);\n\t}\n\n\tconst item: ContextItem = {\n\t\tid: `tool-output:${message.toolCallId}`,\n\t\tkind: \"tool_output\",\n\t\tretentionClass: \"ephemeral\",\n\t\tsource: \"tool\",\n\t\t// Transcript-only items (no artifact ref) have no real creation-turn source threaded\n\t\t// in yet, so this falls back to the current audit turn as an approximation --\n\t\t// deliberately not left silently wrong: no evaluator reads createdAtTurn today, but\n\t\t// any future staleness/break-even math must use the real per-item value above, not\n\t\t// this fallback, once one exists for non-artifact items too.\n\t\tcreatedAtTurn,\n\t\tsummary: `${message.toolName} tool result`,\n\t\tprimaryRef,\n\t\tevidenceRefs: evidenceRefs.length > 0 ? evidenceRefs : undefined,\n\t\ttokenEstimate: estimateTokensFromText(text),\n\t\tbyteEstimate: estimateByteLength(text),\n\t};\n\n\treturn { item, hasResolvedArtifact };\n}\n\n/**\n * Economic/probability fields are zeroed: `evaluateHardConstraints` does not read them for\n * pack_to_artifact/drop_from_prompt (only the break-even scoring math this audit\n * deliberately does not invoke would need real numbers here).\n */\nfunction buildPolicyFeatures(item: ContextItem, built: BuiltToolOutputItem, turnIndex: number): PolicyFeatures {\n\treturn {\n\t\tturnIndex,\n\t\texpectedRemainingTurns: 0,\n\t\tinputTokens: 0,\n\t\toutputTokens: 0,\n\t\tcacheReadTokens: 0,\n\t\tcacheWriteTokens: 0,\n\t\tartifactBytes: built.hasResolvedArtifact ? item.byteEstimate : 0,\n\t\tcharEstimate: item.byteEstimate,\n\t\tcalibratedTokenEstimate: item.tokenEstimate,\n\t\tpromptSection: \"volatile_tail\",\n\t\tretentionClass: item.retentionClass,\n\t\tisReproducible: true,\n\t\tisDecisionBearing: false,\n\t\tisPinned: false,\n\t\tisOpenRequirement: false,\n\t\tisLatestFailure: false,\n\t\tisCurrentDiff: false,\n\t\tprobabilityNeededAgain: 0,\n\t\tprobabilityErrorIfDropped: 0,\n\t\tretrievalCostTokens: 0,\n\t\tpackCostTokens: 0,\n\t\tretryCostTokens: 0,\n\t\tfailureCostTokens: 0,\n\t\tvalidationCostTokens: 0,\n\t};\n}\n\nfunction buildHardConstraintFlags(built: BuiltToolOutputItem, artifactStoreAvailable: boolean): HardConstraintFlags {\n\treturn {\n\t\tisApprovalOrDenial: false,\n\t\tisSafetyConstraint: false,\n\t\tisActiveBlocker: false,\n\t\tisCurrentValidationResult: false,\n\t\tisPathOrToolScope: false,\n\t\t// Only a resolved artifact counts; a transcript ref is provenance, not a live\n\t\t// retrieval mechanism -- see the module doc comment.\n\t\thasAvailableRetrievalPath: built.hasResolvedArtifact,\n\t\tartifactStoreAvailable,\n\t\thasEvidenceRefForSummary: built.item.primaryRef !== undefined || (built.item.evidenceRefs?.length ?? 0) > 0,\n\t\tpathOrToolBoundariesEnforced: true,\n\t\tvalidationAvailableAndStrong: false,\n\t\tpriorAttemptFailedForReasoningOrArchitecture: false,\n\t\tisHighImpactOrBroadMultiFileEdit: false,\n\t};\n}\n\n/** Build the ContextItem for a single toolResult message, for direct unit testing. */\nexport function buildToolResultContextItem(\n\tmessage: ToolResultMessage,\n\tmessageIndex: number,\n\toptions: ContextAuditOptions,\n): ContextItem {\n\treturn buildToolOutputItem(message, messageIndex, options).item;\n}\n\n/**\n * Read-only audit pass: converts live toolResult messages into ContextItems and runs the\n * existing pure retention/hard-constraint evaluators over them. Never mutates `messages`,\n * the transcript, or artifact references -- deterministic given the same messages,\n * `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex\n * and artifact-backed createdAtTurn values are expected to change turn over turn).\n *\n * `memo`, if supplied, is an incremental-memo seam (see {@link ContextAuditMemo}): the\n * expensive `buildToolOutputItem` work is skipped and reused for a message whose object\n * identity AND index are unchanged from the previous pass. The turnIndex-dependent tail\n * (`createdAtTurn` for non-artifact-backed items) is re-derived fresh on every call\n * regardless of cache hit/miss, so output is byte-identical to a full recompute either way\n * -- omitting `memo` (or passing a throwaway `new Map()`) makes this a pure full scan, exactly\n * as before this parameter existed.\n */\nexport function runContextAudit(\n\tmessages: AgentMessage[],\n\toptions: ContextAuditOptions,\n\tmemo?: ContextAuditMemo,\n): ContextAuditReport {\n\tconst freshMemo: ContextAuditMemo | undefined = memo ? new Map() : undefined;\n\tconst items: ContextAuditItemReport[] = [];\n\tmessages.forEach((message, messageIndex) => {\n\t\tif (message.role !== \"toolResult\") return;\n\t\tconst cached = memo?.get(message);\n\t\tconst built =\n\t\t\tcached && cached.messageIndex === messageIndex\n\t\t\t\t? cached.built\n\t\t\t\t: buildToolOutputItem(message, messageIndex, options);\n\t\tfreshMemo?.set(message, { messageIndex, built });\n\t\t// Re-derive the turnIndex-dependent tail every call, cache hit or miss: a resolved\n\t\t// artifact's real capture turn never changes (see buildToolOutputItem), while a\n\t\t// non-artifact item's createdAtTurn is always the CURRENT audit turn -- exactly what a\n\t\t// fresh buildToolOutputItem call would have produced, so this is a no-op on a cache\n\t\t// miss and a correctness fix on a cache hit.\n\t\tconst item: ContextItem = built.hasResolvedArtifact\n\t\t\t? built.item\n\t\t\t: { ...built.item, createdAtTurn: options.turnIndex };\n\t\tconst features = buildPolicyFeatures(item, built, options.turnIndex);\n\t\tconst flags = buildHardConstraintFlags(built, options.artifactStore !== undefined);\n\t\titems.push({\n\t\t\titem,\n\t\t\ttoolCallId: message.toolCallId,\n\t\t\tmessageIndex,\n\t\t\tretention: evaluateRetentionEligibility(item),\n\t\t\tkeepRawHardConstraints: evaluateHardConstraints(\"keep_raw\", features, flags),\n\t\t\tpackToArtifactHardConstraints: evaluateHardConstraints(\"pack_to_artifact\", features, flags),\n\t\t\tdropFromPromptHardConstraints: evaluateHardConstraints(\"drop_from_prompt\", features, flags),\n\t\t\tsummarizeHardConstraints: evaluateHardConstraints(\"summarize\", features, flags),\n\t\t});\n\t});\n\tif (memo && freshMemo) {\n\t\tmemo.clear();\n\t\tfor (const [key, entry] of freshMemo) memo.set(key, entry);\n\t}\n\treturn { turnIndex: options.turnIndex, items };\n}\n"]}
|