@caupulican/pi-adaptative 0.80.103 → 0.81.0
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 +120 -0
- package/dist/cli/session-picker.d.ts +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +6 -6
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +152 -512
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +635 -4590
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/session-lane-record.d.ts +1 -1
- package/dist/core/autonomy/session-lane-record.d.ts.map +1 -1
- package/dist/core/autonomy/session-lane-record.js.map +1 -1
- package/dist/core/autonomy/status.d.ts +1 -0
- package/dist/core/autonomy/status.d.ts.map +1 -1
- package/dist/core/autonomy/status.js +1 -0
- package/dist/core/autonomy/status.js.map +1 -1
- package/dist/core/autonomy-telemetry.d.ts +80 -0
- package/dist/core/autonomy-telemetry.d.ts.map +1 -0
- package/dist/core/autonomy-telemetry.js +266 -0
- package/dist/core/autonomy-telemetry.js.map +1 -0
- package/dist/core/background-lane-controller.d.ts +186 -0
- package/dist/core/background-lane-controller.d.ts.map +1 -0
- package/dist/core/background-lane-controller.js +691 -0
- package/dist/core/background-lane-controller.js.map +1 -0
- package/dist/core/bash-execution-controller.d.ts +51 -0
- package/dist/core/bash-execution-controller.d.ts.map +1 -0
- package/dist/core/bash-execution-controller.js +96 -0
- package/dist/core/bash-execution-controller.js.map +1 -0
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +9 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction-support.d.ts +56 -0
- package/dist/core/compaction-support.d.ts.map +1 -0
- package/dist/core/compaction-support.js +99 -0
- package/dist/core/compaction-support.js.map +1 -0
- package/dist/core/context/artifact-retrieval.d.ts +1 -1
- package/dist/core/context/artifact-retrieval.d.ts.map +1 -1
- package/dist/core/context/artifact-retrieval.js +1 -1
- package/dist/core/context/artifact-retrieval.js.map +1 -1
- package/dist/core/context/context-composition.d.ts.map +1 -1
- package/dist/core/context/context-composition.js +1 -1
- package/dist/core/context/context-composition.js.map +1 -1
- package/dist/core/context/tool-output-packer.d.ts +1 -1
- package/dist/core/context/tool-output-packer.d.ts.map +1 -1
- package/dist/core/context/tool-output-packer.js +1 -1
- package/dist/core/context/tool-output-packer.js.map +1 -1
- package/dist/core/context-gc.d.ts.map +1 -1
- package/dist/core/context-gc.js +1 -1
- package/dist/core/context-gc.js.map +1 -1
- package/dist/core/context-pipeline.d.ts +223 -0
- package/dist/core/context-pipeline.d.ts.map +1 -0
- package/dist/core/context-pipeline.js +594 -0
- package/dist/core/context-pipeline.js.map +1 -0
- package/dist/core/cost/daily-usage.d.ts +1 -1
- package/dist/core/cost/daily-usage.d.ts.map +1 -1
- package/dist/core/cost/daily-usage.js +1 -1
- package/dist/core/cost/daily-usage.js.map +1 -1
- package/dist/core/cost/session-usage.d.ts +1 -1
- package/dist/core/cost/session-usage.d.ts.map +1 -1
- package/dist/core/cost/session-usage.js +1 -1
- package/dist/core/cost/session-usage.js.map +1 -1
- package/dist/core/delegation/session-worker-result.d.ts +1 -1
- package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
- package/dist/core/delegation/session-worker-result.js.map +1 -1
- package/dist/core/exec.d.ts +2 -0
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +15 -9
- package/dist/core/exec.js.map +1 -1
- package/dist/core/export-html/index.d.ts +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +2 -2
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/extensions/builtin.d.ts.map +1 -1
- package/dist/core/extensions/builtin.js +2 -2
- package/dist/core/extensions/builtin.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -4
- 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 +24 -0
- package/dist/core/goal-loop-controller.d.ts.map +1 -0
- package/dist/core/goal-loop-controller.js +83 -0
- package/dist/core/goal-loop-controller.js.map +1 -0
- package/dist/core/goals/goal-runtime-snapshot.d.ts +1 -1
- package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
- package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
- package/dist/core/goals/session-goal-state.d.ts +1 -1
- package/dist/core/goals/session-goal-state.d.ts.map +1 -1
- package/dist/core/goals/session-goal-state.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/learning/learning-audit.d.ts +1 -1
- package/dist/core/learning/learning-audit.d.ts.map +1 -1
- package/dist/core/learning/learning-audit.js.map +1 -1
- package/dist/core/learning/session-learning-decision.d.ts +1 -1
- package/dist/core/learning/session-learning-decision.d.ts.map +1 -1
- package/dist/core/learning/session-learning-decision.js.map +1 -1
- package/dist/core/local-runtime-controller.d.ts +99 -0
- package/dist/core/local-runtime-controller.d.ts.map +1 -0
- package/dist/core/local-runtime-controller.js +214 -0
- package/dist/core/local-runtime-controller.js.map +1 -0
- package/dist/core/memory/providers/transcript-recall.d.ts.map +1 -1
- package/dist/core/memory/providers/transcript-recall.js +1 -1
- package/dist/core/memory/providers/transcript-recall.js.map +1 -1
- package/dist/core/memory-controller.d.ts +139 -0
- package/dist/core/memory-controller.d.ts.map +1 -0
- package/dist/core/memory-controller.js +323 -0
- package/dist/core/memory-controller.js.map +1 -0
- package/dist/core/model-router/status.d.ts +1 -1
- package/dist/core/model-router/status.d.ts.map +1 -1
- package/dist/core/model-router/status.js.map +1 -1
- package/dist/core/model-router-controller.d.ts +140 -0
- package/dist/core/model-router-controller.d.ts.map +1 -0
- package/dist/core/model-router-controller.js +623 -0
- package/dist/core/model-router-controller.js.map +1 -0
- package/dist/core/model-selection-controller.d.ts +95 -0
- package/dist/core/model-selection-controller.d.ts.map +1 -0
- package/dist/core/model-selection-controller.js +252 -0
- package/dist/core/model-selection-controller.js.map +1 -0
- package/dist/core/models/model-ref.d.ts +13 -0
- package/dist/core/models/model-ref.d.ts.map +1 -1
- package/dist/core/models/model-ref.js +22 -0
- package/dist/core/models/model-ref.js.map +1 -1
- package/dist/core/profile-filter-controller.d.ts +77 -0
- package/dist/core/profile-filter-controller.d.ts.map +1 -0
- package/dist/core/profile-filter-controller.js +159 -0
- package/dist/core/profile-filter-controller.js.map +1 -0
- package/dist/core/reflection-controller.d.ts +111 -0
- package/dist/core/reflection-controller.d.ts.map +1 -0
- package/dist/core/reflection-controller.js +412 -0
- package/dist/core/reflection-controller.js.map +1 -0
- package/dist/core/research/model-fitness.d.ts +12 -0
- package/dist/core/research/model-fitness.d.ts.map +1 -1
- package/dist/core/research/model-fitness.js +21 -0
- package/dist/core/research/model-fitness.js.map +1 -1
- package/dist/core/research/session-evidence-bundle.d.ts +1 -1
- package/dist/core/research/session-evidence-bundle.d.ts.map +1 -1
- package/dist/core/research/session-evidence-bundle.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +222 -0
- package/dist/core/runtime-builder.d.ts.map +1 -0
- package/dist/core/runtime-builder.js +640 -0
- package/dist/core/runtime-builder.js.map +1 -0
- package/dist/core/sdk.d.ts +2 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-analytics.d.ts +96 -0
- package/dist/core/session-analytics.d.ts.map +1 -0
- package/dist/core/session-analytics.js +331 -0
- package/dist/core/session-analytics.js.map +1 -0
- package/dist/core/session-manager-factory.d.ts +18 -0
- package/dist/core/session-manager-factory.d.ts.map +1 -0
- package/dist/core/session-manager-factory.js +32 -0
- package/dist/core/session-manager-factory.js.map +1 -0
- package/dist/core/session-tree-navigator.d.ts +68 -0
- package/dist/core/session-tree-navigator.d.ts.map +1 -0
- package/dist/core/session-tree-navigator.js +217 -0
- package/dist/core/session-tree-navigator.js.map +1 -0
- package/dist/core/system-prompt-builder.d.ts +63 -0
- package/dist/core/system-prompt-builder.d.ts.map +1 -0
- package/dist/core/system-prompt-builder.js +181 -0
- package/dist/core/system-prompt-builder.js.map +1 -0
- package/dist/core/tool-gate-controller.d.ts +36 -0
- package/dist/core/tool-gate-controller.d.ts.map +1 -0
- package/dist/core/tool-gate-controller.js +94 -0
- package/dist/core/tool-gate-controller.js.map +1 -0
- package/dist/core/tools/artifact-retrieve.d.ts.map +1 -1
- package/dist/core/tools/artifact-retrieve.js +1 -1
- package/dist/core/tools/artifact-retrieve.js.map +1 -1
- package/dist/core/tools/bash.d.ts +4 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +49 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts +6 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +61 -1
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/find.d.ts +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +1 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +1 -1
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +1 -1
- package/dist/core/tools/read.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/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +13 -12
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.d.ts +59 -0
- package/dist/modes/interactive/auth-dialogs-controller.d.ts.map +1 -0
- package/dist/modes/interactive/auth-dialogs-controller.js +398 -0
- package/dist/modes/interactive/auth-dialogs-controller.js.map +1 -0
- package/dist/modes/interactive/auto-learn-controller.d.ts +168 -0
- package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -0
- package/dist/modes/interactive/auto-learn-controller.js +1239 -0
- package/dist/modes/interactive/auto-learn-controller.js.map +1 -0
- package/dist/modes/interactive/autocomplete-provider.d.ts +26 -0
- package/dist/modes/interactive/autocomplete-provider.d.ts.map +1 -0
- package/dist/modes/interactive/autocomplete-provider.js +103 -0
- package/dist/modes/interactive/autocomplete-provider.js.map +1 -0
- package/dist/modes/interactive/autonomy-commands.d.ts +38 -0
- package/dist/modes/interactive/autonomy-commands.d.ts.map +1 -0
- package/dist/modes/interactive/autonomy-commands.js +142 -0
- package/dist/modes/interactive/autonomy-commands.js.map +1 -0
- package/dist/modes/interactive/clipboard-input.d.ts +37 -0
- package/dist/modes/interactive/clipboard-input.d.ts.map +1 -0
- package/dist/modes/interactive/clipboard-input.js +59 -0
- package/dist/modes/interactive/clipboard-input.js.map +1 -0
- package/dist/modes/interactive/compaction-queue.d.ts +30 -0
- package/dist/modes/interactive/compaction-queue.d.ts.map +1 -0
- package/dist/modes/interactive/compaction-queue.js +91 -0
- package/dist/modes/interactive/compaction-queue.js.map +1 -0
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +1 -1
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +2 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/config-backup.d.ts +27 -0
- package/dist/modes/interactive/config-backup.d.ts.map +1 -0
- package/dist/modes/interactive/config-backup.js +146 -0
- package/dist/modes/interactive/config-backup.js.map +1 -0
- package/dist/modes/interactive/editor-overlay-host.d.ts +48 -0
- package/dist/modes/interactive/editor-overlay-host.d.ts.map +1 -0
- package/dist/modes/interactive/editor-overlay-host.js +43 -0
- package/dist/modes/interactive/editor-overlay-host.js.map +1 -0
- package/dist/modes/interactive/extension-ui-host.d.ts +165 -0
- package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -0
- package/dist/modes/interactive/extension-ui-host.js +610 -0
- package/dist/modes/interactive/extension-ui-host.js.map +1 -0
- package/dist/modes/interactive/external-editor.d.ts +18 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -0
- package/dist/modes/interactive/external-editor.js +107 -0
- package/dist/modes/interactive/external-editor.js.map +1 -0
- package/dist/modes/interactive/history-reload-math.d.ts +24 -0
- package/dist/modes/interactive/history-reload-math.d.ts.map +1 -0
- package/dist/modes/interactive/history-reload-math.js +129 -0
- package/dist/modes/interactive/history-reload-math.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +22 -238
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +714 -5910
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/key-handlers.d.ts +46 -0
- package/dist/modes/interactive/key-handlers.d.ts.map +1 -0
- package/dist/modes/interactive/key-handlers.js +80 -0
- package/dist/modes/interactive/key-handlers.js.map +1 -0
- package/dist/modes/interactive/local-model-commands.d.ts +68 -0
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -0
- package/dist/modes/interactive/local-model-commands.js +288 -0
- package/dist/modes/interactive/local-model-commands.js.map +1 -0
- package/dist/modes/interactive/profile-menu-controller.d.ts +77 -0
- package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -0
- package/dist/modes/interactive/profile-menu-controller.js +917 -0
- package/dist/modes/interactive/profile-menu-controller.js.map +1 -0
- package/dist/modes/interactive/report-commands.d.ts +46 -0
- package/dist/modes/interactive/report-commands.d.ts.map +1 -0
- package/dist/modes/interactive/report-commands.js +255 -0
- package/dist/modes/interactive/report-commands.js.map +1 -0
- package/dist/modes/interactive/resource-display.d.ts +73 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -0
- package/dist/modes/interactive/resource-display.js +278 -0
- package/dist/modes/interactive/resource-display.js.map +1 -0
- package/dist/modes/interactive/resource-shell-commands.d.ts +64 -0
- package/dist/modes/interactive/resource-shell-commands.d.ts.map +1 -0
- package/dist/modes/interactive/resource-shell-commands.js +202 -0
- package/dist/modes/interactive/resource-shell-commands.js.map +1 -0
- package/dist/modes/interactive/session-flow-commands.d.ts +139 -0
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -0
- package/dist/modes/interactive/session-flow-commands.js +458 -0
- package/dist/modes/interactive/session-flow-commands.js.map +1 -0
- package/dist/modes/interactive/session-io-commands.d.ts +73 -0
- package/dist/modes/interactive/session-io-commands.d.ts.map +1 -0
- package/dist/modes/interactive/session-io-commands.js +202 -0
- package/dist/modes/interactive/session-io-commands.js.map +1 -0
- package/dist/modes/interactive/settings-selector-flow.d.ts +42 -0
- package/dist/modes/interactive/settings-selector-flow.d.ts.map +1 -0
- package/dist/modes/interactive/settings-selector-flow.js +287 -0
- package/dist/modes/interactive/settings-selector-flow.js.map +1 -0
- package/dist/modes/interactive/signal-lifecycle.d.ts +55 -0
- package/dist/modes/interactive/signal-lifecycle.d.ts.map +1 -0
- package/dist/modes/interactive/signal-lifecycle.js +177 -0
- package/dist/modes/interactive/signal-lifecycle.js.map +1 -0
- package/dist/modes/interactive/startup-checks.d.ts +38 -0
- package/dist/modes/interactive/startup-checks.d.ts.map +1 -0
- package/dist/modes/interactive/startup-checks.js +189 -0
- package/dist/modes/interactive/startup-checks.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +1 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -14
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +5 -30
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/process-memory.d.ts +8 -0
- package/dist/utils/process-memory.d.ts.map +1 -0
- package/dist/utils/process-memory.js +11 -0
- package/dist/utils/process-memory.js.map +1 -0
- package/dist/utils/shell.d.ts +0 -9
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +0 -37
- package/dist/utils/shell.js.map +1 -1
- 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/examples/sdk/11-sessions.ts +8 -8
- package/examples/sdk/13-session-runtime.ts +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
- package/dist/core/compaction/branch-summarization.d.ts +0 -88
- package/dist/core/compaction/branch-summarization.d.ts.map +0 -1
- package/dist/core/compaction/branch-summarization.js +0 -243
- package/dist/core/compaction/branch-summarization.js.map +0 -1
- package/dist/core/compaction/compaction.d.ts +0 -143
- package/dist/core/compaction/compaction.d.ts.map +0 -1
- package/dist/core/compaction/compaction.js +0 -659
- package/dist/core/compaction/compaction.js.map +0 -1
- package/dist/core/compaction/index.d.ts +0 -7
- package/dist/core/compaction/index.d.ts.map +0 -1
- package/dist/core/compaction/index.js +0 -7
- package/dist/core/compaction/index.js.map +0 -1
- package/dist/core/compaction/utils.d.ts +0 -38
- package/dist/core/compaction/utils.d.ts.map +0 -1
- package/dist/core/compaction/utils.js +0 -153
- package/dist/core/compaction/utils.js.map +0 -1
- package/dist/core/message-retention.d.ts +0 -26
- package/dist/core/message-retention.d.ts.map +0 -1
- package/dist/core/message-retention.js +0 -95
- package/dist/core/message-retention.js.map +0 -1
- package/dist/core/messages.d.ts +0 -77
- package/dist/core/messages.d.ts.map +0 -1
- package/dist/core/messages.js +0 -123
- package/dist/core/messages.js.map +0 -1
- package/dist/core/session-manager.d.ts +0 -337
- package/dist/core/session-manager.d.ts.map +0 -1
- package/dist/core/session-manager.js +0 -1328
- package/dist/core/session-manager.js.map +0 -1
- package/dist/core/tools/truncate.d.ts +0 -70
- package/dist/core/tools/truncate.d.ts.map +0 -1
- package/dist/core/tools/truncate.js +0 -215
- package/dist/core/tools/truncate.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript-recall.d.ts","sourceRoot":"","sources":["../../../../src/core/memory/providers/transcript-recall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAaxG,qBAAa,wBAAyB,YAAW,cAAc;IAC9D,QAAQ,CAAC,IAAI,uBAAuB;IACpC,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,QAAQ,CAAM;IAEtB,WAAW,IAAI,OAAO,CAErB;IAED,eAAe,IAAI,kBAAkB,CAEpC;IAEK,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9E;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAE9B;IAED,wFAAwF;IACxF,iBAAiB,IAAI,MAAM,EAAE,CAE5B;IAEK,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqB7C;IAED,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,SAAS;CAsDjB","sourcesContent":["/**\n * TranscriptRecallProvider — cross-session similarity recall (adaptive-agent design R3).\n *\n * A read-only CONTEXT memory provider: it indexes the most-recent past session transcripts (the JSONL\n * corpus) with a dependency-free token/Jaccard index ({@link TranscriptIndex}, reusing skill_audit's\n * tokenizer) and answers `prefetch(query)` with a small `<memory_context>` recall page of the most\n * relevant past snippets. The current session and auto-learn sessions are excluded. It never writes —\n * the file-store remains the write target; this is the recall corpus.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport {
|
|
1
|
+
{"version":3,"file":"transcript-recall.d.ts","sourceRoot":"","sources":["../../../../src/core/memory/providers/transcript-recall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAaxG,qBAAa,wBAAyB,YAAW,cAAc;IAC9D,QAAQ,CAAC,IAAI,uBAAuB;IACpC,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,QAAQ,CAAM;IAEtB,WAAW,IAAI,OAAO,CAErB;IAED,eAAe,IAAI,kBAAkB,CAEpC;IAEK,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9E;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAE9B;IAED,wFAAwF;IACxF,iBAAiB,IAAI,MAAM,EAAE,CAE5B;IAEK,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqB7C;IAED,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,SAAS;CAsDjB","sourcesContent":["/**\n * TranscriptRecallProvider — cross-session similarity recall (adaptive-agent design R3).\n *\n * A read-only CONTEXT memory provider: it indexes the most-recent past session transcripts (the JSONL\n * corpus) with a dependency-free token/Jaccard index ({@link TranscriptIndex}, reusing skill_audit's\n * tokenizer) and answers `prefetch(query)` with a small `<memory_context>` recall page of the most\n * relevant past snippets. The current session and auto-learn sessions are excluded. It never writes —\n * the file-store remains the write target; this is the recall corpus.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport {\n\ttype FileEntry,\n\tgetDefaultSessionDir,\n\tisAutoLearnSessionId,\n\tloadEntriesFromFile,\n} from \"@caupulican/pi-agent-core/node\";\nimport { wrapUntrustedText } from \"../../security/untrusted-boundary.ts\";\nimport type { MemoryCapabilities, MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\nimport { type TranscriptDoc, TranscriptIndex } from \"../transcript-index.ts\";\n\n/** Most-recent past sessions to consider. */\nconst MAX_SESSIONS = 60;\n/** Per-session text cap (keeps the index light and snippets relevant). */\nconst MAX_DOC_CHARS = 8_000;\n/** Overall corpus cap across all docs. */\nconst MAX_TOTAL_CHARS = 500_000;\n/** Skip transcript files larger than this before parsing them, so a huge log can't block/bloat the\n * first recalled turn (Bug #9). Far above a normal session; only pathological logs exceed it. */\nconst MAX_FILE_BYTES = 8_000_000;\n\nexport class TranscriptRecallProvider implements MemoryProvider {\n\treadonly name = \"transcript-recall\";\n\tprivate index: TranscriptIndex | undefined;\n\tprivate currentSessionId = \"\";\n\tprivate cwd = \"\";\n\tprivate agentDir = \"\";\n\n\tisAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tgetCapabilities(): MemoryCapabilities {\n\t\treturn { surfaces: [\"context\"] };\n\t}\n\n\tasync initialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.currentSessionId = sessionId;\n\t\tthis.cwd = ctx.cwd;\n\t\tthis.agentDir = ctx.agentDir;\n\t\tthis.index = undefined; // built lazily on first prefetch\n\t}\n\n\tasync shutdown(): Promise<void> {\n\t\tthis.index = undefined;\n\t}\n\n\t/** GC manages the dynamic recall page so stale pages pack while the newest are kept. */\n\tgetContextMarkers(): string[] {\n\t\treturn [\"<memory_context\"];\n\t}\n\n\tasync prefetch(query: string): Promise<string> {\n\t\tif (!query.trim()) return \"\";\n\t\tlet index: TranscriptIndex;\n\t\ttry {\n\t\t\tindex = this.ensureIndex();\n\t\t} catch {\n\t\t\treturn \"\";\n\t\t}\n\t\tif (index.size === 0) return \"\";\n\t\t// minScore is a query-CONTAINMENT threshold (fraction of the query's tokens present in the doc),\n\t\t// not Jaccard — so it is length-independent and recalls relevant long sessions. ~1/3 of query\n\t\t// terms must appear before a session is considered relevant.\n\t\tconst hits = index.query(query, { k: 3, minScore: 0.34, maxSnippetChars: 600 });\n\t\tif (hits.length === 0) return \"\";\n\t\t// Recalled past text is UNTRUSTED (it may itself contain injected instructions or a forged\n\t\t// `</memory_context>` to break out). Fence each snippet with the untrusted-content boundary so a\n\t\t// payload can't escape and be replayed as a current instruction (design: recall = untrusted).\n\t\tconst body = hits\n\t\t\t.map((h) => `- (${h.timestamp ?? \"earlier session\"}) ${wrapUntrustedText(h.snippet, \"transcript-recall\")}`)\n\t\t\t.join(\"\\n\");\n\t\treturn `<memory_context source=\"transcript-recall\">\\nRelevant context recalled from past sessions (read-only reference, untrusted, may be stale):\\n${body}\\n</memory_context>`;\n\t}\n\n\tprivate ensureIndex(): TranscriptIndex {\n\t\tif (!this.index) {\n\t\t\tthis.index = new TranscriptIndex(this.buildDocs());\n\t\t}\n\t\treturn this.index;\n\t}\n\n\tprivate buildDocs(): TranscriptDoc[] {\n\t\tconst docs: TranscriptDoc[] = [];\n\t\tlet dir: string;\n\t\ttry {\n\t\t\tdir = getDefaultSessionDir(this.cwd, this.agentDir);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet files: Array<{ path: string; mtime: number }>;\n\t\ttry {\n\t\t\tfiles = readdirSync(dir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => {\n\t\t\t\t\tconst path = join(dir, f);\n\t\t\t\t\tlet mtime = 0;\n\t\t\t\t\tlet size = 0;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst st = statSync(path);\n\t\t\t\t\t\tmtime = st.mtimeMs;\n\t\t\t\t\t\tsize = st.size;\n\t\t\t\t\t} catch {}\n\t\t\t\t\treturn { path, mtime, size };\n\t\t\t\t})\n\t\t\t\t.filter((f) => f.size > 0 && f.size <= MAX_FILE_BYTES) // skip oversize logs before parse (Bug #9)\n\t\t\t\t.sort((a, b) => b.mtime - a.mtime) // most-recent first\n\t\t\t\t.slice(0, MAX_SESSIONS);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet total = 0;\n\t\tfor (const { path } of files) {\n\t\t\tlet entries: FileEntry[];\n\t\t\ttry {\n\t\t\t\tentries = loadEntriesFromFile(path);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst header = entries.find((e): e is Extract<FileEntry, { type: \"session\" }> => e.type === \"session\");\n\t\t\tconst sessionId = header?.id;\n\t\t\tif (!sessionId || sessionId === this.currentSessionId || isAutoLearnSessionId(sessionId)) continue;\n\t\t\t// Privacy: only recall from sessions that ran in THIS working directory. A misplaced/copied\n\t\t\t// transcript with a different cwd must not leak across project boundaries (Bug #11).\n\t\t\tif (header?.cwd && resolve(header.cwd) !== resolve(this.cwd)) continue;\n\n\t\t\tconst text = extractSessionText(entries, MAX_DOC_CHARS);\n\t\t\tif (!text.trim()) continue;\n\t\t\tdocs.push({ sessionId, timestamp: header?.timestamp, text });\n\t\t\ttotal += text.length;\n\t\t\tif (total >= MAX_TOTAL_CHARS) break;\n\t\t}\n\t\treturn docs;\n\t}\n}\n\n/** Concatenate user+assistant text from a session's entries, capped to `maxChars`. */\nfunction extractSessionText(entries: FileEntry[], maxChars: number): string {\n\tconst parts: string[] = [];\n\tlet len = 0;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst message = entry.message;\n\t\tif (message.role !== \"user\" && message.role !== \"assistant\") continue;\n\t\tconst content = message.content;\n\t\tlet text = \"\";\n\t\tif (typeof content === \"string\") {\n\t\t\ttext = content;\n\t\t} else if (Array.isArray(content)) {\n\t\t\ttext = content\n\t\t\t\t.map((b) => (b && typeof b === \"object\" && \"type\" in b && b.type === \"text\" ? (b.text ?? \"\") : \"\"))\n\t\t\t\t.join(\" \");\n\t\t}\n\t\ttext = text.trim();\n\t\tif (!text) continue;\n\t\t// Skip our own previously-injected recall pages so recalled snippets don't recirculate and\n\t\t// amplify across sessions (Bug #10).\n\t\tif (text.includes('<memory_context source=\"transcript-recall\"')) continue;\n\t\tparts.push(text);\n\t\tlen += text.length;\n\t\tif (len >= maxChars) break;\n\t}\n\treturn parts.join(\"\\n\").slice(0, maxChars);\n}\n"]}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { readdirSync, statSync } from "node:fs";
|
|
11
11
|
import { join, resolve } from "node:path";
|
|
12
|
+
import { getDefaultSessionDir, isAutoLearnSessionId, loadEntriesFromFile, } from "@caupulican/pi-agent-core/node";
|
|
12
13
|
import { wrapUntrustedText } from "../../security/untrusted-boundary.js";
|
|
13
|
-
import { getDefaultSessionDir, isAutoLearnSessionId, loadEntriesFromFile, } from "../../session-manager.js";
|
|
14
14
|
import { TranscriptIndex } from "../transcript-index.js";
|
|
15
15
|
/** Most-recent past sessions to consider. */
|
|
16
16
|
const MAX_SESSIONS = 60;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript-recall.js","sourceRoot":"","sources":["../../../../src/core/memory/providers/transcript-recall.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TranscriptRecallProvider — cross-session similarity recall (adaptive-agent design R3).\n *\n * A read-only CONTEXT memory provider: it indexes the most-recent past session transcripts (the JSONL\n * corpus) with a dependency-free token/Jaccard index ({@link TranscriptIndex}, reusing skill_audit's\n * tokenizer) and answers `prefetch(query)` with a small `<memory_context>` recall page of the most\n * relevant past snippets. The current session and auto-learn sessions are excluded. It never writes —\n * the file-store remains the write target; this is the recall corpus.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { wrapUntrustedText } from \"../../security/untrusted-boundary.ts\";\nimport {\n\ttype FileEntry,\n\tgetDefaultSessionDir,\n\tisAutoLearnSessionId,\n\tloadEntriesFromFile,\n} from \"../../session-manager.ts\";\nimport type { MemoryCapabilities, MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\nimport { type TranscriptDoc, TranscriptIndex } from \"../transcript-index.ts\";\n\n/** Most-recent past sessions to consider. */\nconst MAX_SESSIONS = 60;\n/** Per-session text cap (keeps the index light and snippets relevant). */\nconst MAX_DOC_CHARS = 8_000;\n/** Overall corpus cap across all docs. */\nconst MAX_TOTAL_CHARS = 500_000;\n/** Skip transcript files larger than this before parsing them, so a huge log can't block/bloat the\n * first recalled turn (Bug #9). Far above a normal session; only pathological logs exceed it. */\nconst MAX_FILE_BYTES = 8_000_000;\n\nexport class TranscriptRecallProvider implements MemoryProvider {\n\treadonly name = \"transcript-recall\";\n\tprivate index: TranscriptIndex | undefined;\n\tprivate currentSessionId = \"\";\n\tprivate cwd = \"\";\n\tprivate agentDir = \"\";\n\n\tisAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tgetCapabilities(): MemoryCapabilities {\n\t\treturn { surfaces: [\"context\"] };\n\t}\n\n\tasync initialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.currentSessionId = sessionId;\n\t\tthis.cwd = ctx.cwd;\n\t\tthis.agentDir = ctx.agentDir;\n\t\tthis.index = undefined; // built lazily on first prefetch\n\t}\n\n\tasync shutdown(): Promise<void> {\n\t\tthis.index = undefined;\n\t}\n\n\t/** GC manages the dynamic recall page so stale pages pack while the newest are kept. */\n\tgetContextMarkers(): string[] {\n\t\treturn [\"<memory_context\"];\n\t}\n\n\tasync prefetch(query: string): Promise<string> {\n\t\tif (!query.trim()) return \"\";\n\t\tlet index: TranscriptIndex;\n\t\ttry {\n\t\t\tindex = this.ensureIndex();\n\t\t} catch {\n\t\t\treturn \"\";\n\t\t}\n\t\tif (index.size === 0) return \"\";\n\t\t// minScore is a query-CONTAINMENT threshold (fraction of the query's tokens present in the doc),\n\t\t// not Jaccard — so it is length-independent and recalls relevant long sessions. ~1/3 of query\n\t\t// terms must appear before a session is considered relevant.\n\t\tconst hits = index.query(query, { k: 3, minScore: 0.34, maxSnippetChars: 600 });\n\t\tif (hits.length === 0) return \"\";\n\t\t// Recalled past text is UNTRUSTED (it may itself contain injected instructions or a forged\n\t\t// `</memory_context>` to break out). Fence each snippet with the untrusted-content boundary so a\n\t\t// payload can't escape and be replayed as a current instruction (design: recall = untrusted).\n\t\tconst body = hits\n\t\t\t.map((h) => `- (${h.timestamp ?? \"earlier session\"}) ${wrapUntrustedText(h.snippet, \"transcript-recall\")}`)\n\t\t\t.join(\"\\n\");\n\t\treturn `<memory_context source=\"transcript-recall\">\\nRelevant context recalled from past sessions (read-only reference, untrusted, may be stale):\\n${body}\\n</memory_context>`;\n\t}\n\n\tprivate ensureIndex(): TranscriptIndex {\n\t\tif (!this.index) {\n\t\t\tthis.index = new TranscriptIndex(this.buildDocs());\n\t\t}\n\t\treturn this.index;\n\t}\n\n\tprivate buildDocs(): TranscriptDoc[] {\n\t\tconst docs: TranscriptDoc[] = [];\n\t\tlet dir: string;\n\t\ttry {\n\t\t\tdir = getDefaultSessionDir(this.cwd, this.agentDir);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet files: Array<{ path: string; mtime: number }>;\n\t\ttry {\n\t\t\tfiles = readdirSync(dir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => {\n\t\t\t\t\tconst path = join(dir, f);\n\t\t\t\t\tlet mtime = 0;\n\t\t\t\t\tlet size = 0;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst st = statSync(path);\n\t\t\t\t\t\tmtime = st.mtimeMs;\n\t\t\t\t\t\tsize = st.size;\n\t\t\t\t\t} catch {}\n\t\t\t\t\treturn { path, mtime, size };\n\t\t\t\t})\n\t\t\t\t.filter((f) => f.size > 0 && f.size <= MAX_FILE_BYTES) // skip oversize logs before parse (Bug #9)\n\t\t\t\t.sort((a, b) => b.mtime - a.mtime) // most-recent first\n\t\t\t\t.slice(0, MAX_SESSIONS);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet total = 0;\n\t\tfor (const { path } of files) {\n\t\t\tlet entries: FileEntry[];\n\t\t\ttry {\n\t\t\t\tentries = loadEntriesFromFile(path);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst header = entries.find((e): e is Extract<FileEntry, { type: \"session\" }> => e.type === \"session\");\n\t\t\tconst sessionId = header?.id;\n\t\t\tif (!sessionId || sessionId === this.currentSessionId || isAutoLearnSessionId(sessionId)) continue;\n\t\t\t// Privacy: only recall from sessions that ran in THIS working directory. A misplaced/copied\n\t\t\t// transcript with a different cwd must not leak across project boundaries (Bug #11).\n\t\t\tif (header?.cwd && resolve(header.cwd) !== resolve(this.cwd)) continue;\n\n\t\t\tconst text = extractSessionText(entries, MAX_DOC_CHARS);\n\t\t\tif (!text.trim()) continue;\n\t\t\tdocs.push({ sessionId, timestamp: header?.timestamp, text });\n\t\t\ttotal += text.length;\n\t\t\tif (total >= MAX_TOTAL_CHARS) break;\n\t\t}\n\t\treturn docs;\n\t}\n}\n\n/** Concatenate user+assistant text from a session's entries, capped to `maxChars`. */\nfunction extractSessionText(entries: FileEntry[], maxChars: number): string {\n\tconst parts: string[] = [];\n\tlet len = 0;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst message = entry.message;\n\t\tif (message.role !== \"user\" && message.role !== \"assistant\") continue;\n\t\tconst content = message.content;\n\t\tlet text = \"\";\n\t\tif (typeof content === \"string\") {\n\t\t\ttext = content;\n\t\t} else if (Array.isArray(content)) {\n\t\t\ttext = content\n\t\t\t\t.map((b) => (b && typeof b === \"object\" && \"type\" in b && b.type === \"text\" ? (b.text ?? \"\") : \"\"))\n\t\t\t\t.join(\" \");\n\t\t}\n\t\ttext = text.trim();\n\t\tif (!text) continue;\n\t\t// Skip our own previously-injected recall pages so recalled snippets don't recirculate and\n\t\t// amplify across sessions (Bug #10).\n\t\tif (text.includes('<memory_context source=\"transcript-recall\"')) continue;\n\t\tparts.push(text);\n\t\tlen += text.length;\n\t\tif (len >= maxChars) break;\n\t}\n\treturn parts.join(\"\\n\").slice(0, maxChars);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"transcript-recall.js","sourceRoot":"","sources":["../../../../src/core/memory/providers/transcript-recall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAEN,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,GACnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAEzE,OAAO,EAAsB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE7E,6CAA6C;AAC7C,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,0EAA0E;AAC1E,MAAM,aAAa,GAAG,KAAK,CAAC;AAC5B,0CAA0C;AAC1C,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC;iGACiG;AACjG,MAAM,cAAc,GAAG,SAAS,CAAC;AAEjC,MAAM,OAAO,wBAAwB;IAC3B,IAAI,GAAG,mBAAmB,CAAC;IAC5B,KAAK,CAA8B;IACnC,gBAAgB,GAAG,EAAE,CAAC;IACtB,GAAG,GAAG,EAAE,CAAC;IACT,QAAQ,GAAG,EAAE,CAAC;IAEtB,WAAW,GAAY;QACtB,OAAO,IAAI,CAAC;IAAA,CACZ;IAED,eAAe,GAAuB;QACrC,OAAO,EAAE,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;IAAA,CACjC;IAED,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,GAA2B,EAAiB;QAC/E,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAC;QAClC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;QACnB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,iCAAiC;IAAlC,CACvB;IAED,KAAK,CAAC,QAAQ,GAAkB;QAC/B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IAAA,CACvB;IAED,wFAAwF;IACxF,iBAAiB,GAAa;QAC7B,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAAA,CAC3B;IAED,KAAK,CAAC,QAAQ,CAAC,KAAa,EAAmB;QAC9C,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAsB,CAAC;QAC3B,IAAI,CAAC;YACJ,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,EAAE,CAAC;QACX,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,iGAAiG;QACjG,gGAA8F;QAC9F,6DAA6D;QAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;QAChF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACjC,2FAA2F;QAC3F,iGAAiG;QACjG,8FAA8F;QAC9F,MAAM,IAAI,GAAG,IAAI;aACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,SAAS,IAAI,iBAAiB,KAAK,iBAAiB,CAAC,CAAC,CAAC,OAAO,EAAE,mBAAmB,CAAC,EAAE,CAAC;aAC1G,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,OAAO,8IAA8I,IAAI,qBAAqB,CAAC;IAAA,CAC/K;IAEO,WAAW,GAAoB;QACtC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACjB,IAAI,CAAC,KAAK,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC;IAAA,CAClB;IAEO,SAAS,GAAoB;QACpC,MAAM,IAAI,GAAoB,EAAE,CAAC;QACjC,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACJ,GAAG,GAAG,oBAAoB,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,IAAI,CAAC;QACb,CAAC;QAED,IAAI,KAA6C,CAAC;QAClD,IAAI,CAAC;YACJ,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC;iBACtB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;iBACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACX,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;gBAC1B,IAAI,KAAK,GAAG,CAAC,CAAC;gBACd,IAAI,IAAI,GAAG,CAAC,CAAC;gBACb,IAAI,CAAC;oBACJ,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;oBAC1B,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC;oBACnB,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC;gBAChB,CAAC;gBAAC,MAAM,CAAC,CAAA,CAAC;gBACV,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YAAA,CAC7B,CAAC;iBACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,cAAc,CAAC,CAAC,2CAA2C;iBACjG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,oBAAoB;iBACtD,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,IAAI,CAAC;QACb,CAAC;QAED,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;YAC9B,IAAI,OAAoB,CAAC;YACzB,IAAI,CAAC;gBACJ,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACR,SAAS;YACV,CAAC;YACD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAgD,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;YACvG,MAAM,SAAS,GAAG,MAAM,EAAE,EAAE,CAAC;YAC7B,IAAI,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC,gBAAgB,IAAI,oBAAoB,CAAC,SAAS,CAAC;gBAAE,SAAS;YACnG,4FAA4F;YAC5F,qFAAqF;YACrF,IAAI,MAAM,EAAE,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,SAAS;YAEvE,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YACxD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,SAAS;YAC3B,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7D,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC;YACrB,IAAI,KAAK,IAAI,eAAe;gBAAE,MAAM;QACrC,CAAC;QACD,OAAO,IAAI,CAAC;IAAA,CACZ;CACD;AAED,sFAAsF;AACtF,SAAS,kBAAkB,CAAC,OAAoB,EAAE,QAAgB,EAAU;IAC3E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,SAAS;QACvC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC9B,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW;YAAE,SAAS;QACtE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YACjC,IAAI,GAAG,OAAO,CAAC;QAChB,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,IAAI,GAAG,OAAO;iBACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;iBAClG,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,CAAC;QACD,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,2FAA2F;QAC3F,qCAAqC;QACrC,IAAI,IAAI,CAAC,QAAQ,CAAC,4CAA4C,CAAC;YAAE,SAAS;QAC1E,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC;QACnB,IAAI,GAAG,IAAI,QAAQ;YAAE,MAAM;IAC5B,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,CAC3C","sourcesContent":["/**\n * TranscriptRecallProvider — cross-session similarity recall (adaptive-agent design R3).\n *\n * A read-only CONTEXT memory provider: it indexes the most-recent past session transcripts (the JSONL\n * corpus) with a dependency-free token/Jaccard index ({@link TranscriptIndex}, reusing skill_audit's\n * tokenizer) and answers `prefetch(query)` with a small `<memory_context>` recall page of the most\n * relevant past snippets. The current session and auto-learn sessions are excluded. It never writes —\n * the file-store remains the write target; this is the recall corpus.\n */\n\nimport { readdirSync, statSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport {\n\ttype FileEntry,\n\tgetDefaultSessionDir,\n\tisAutoLearnSessionId,\n\tloadEntriesFromFile,\n} from \"@caupulican/pi-agent-core/node\";\nimport { wrapUntrustedText } from \"../../security/untrusted-boundary.ts\";\nimport type { MemoryCapabilities, MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\nimport { type TranscriptDoc, TranscriptIndex } from \"../transcript-index.ts\";\n\n/** Most-recent past sessions to consider. */\nconst MAX_SESSIONS = 60;\n/** Per-session text cap (keeps the index light and snippets relevant). */\nconst MAX_DOC_CHARS = 8_000;\n/** Overall corpus cap across all docs. */\nconst MAX_TOTAL_CHARS = 500_000;\n/** Skip transcript files larger than this before parsing them, so a huge log can't block/bloat the\n * first recalled turn (Bug #9). Far above a normal session; only pathological logs exceed it. */\nconst MAX_FILE_BYTES = 8_000_000;\n\nexport class TranscriptRecallProvider implements MemoryProvider {\n\treadonly name = \"transcript-recall\";\n\tprivate index: TranscriptIndex | undefined;\n\tprivate currentSessionId = \"\";\n\tprivate cwd = \"\";\n\tprivate agentDir = \"\";\n\n\tisAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tgetCapabilities(): MemoryCapabilities {\n\t\treturn { surfaces: [\"context\"] };\n\t}\n\n\tasync initialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.currentSessionId = sessionId;\n\t\tthis.cwd = ctx.cwd;\n\t\tthis.agentDir = ctx.agentDir;\n\t\tthis.index = undefined; // built lazily on first prefetch\n\t}\n\n\tasync shutdown(): Promise<void> {\n\t\tthis.index = undefined;\n\t}\n\n\t/** GC manages the dynamic recall page so stale pages pack while the newest are kept. */\n\tgetContextMarkers(): string[] {\n\t\treturn [\"<memory_context\"];\n\t}\n\n\tasync prefetch(query: string): Promise<string> {\n\t\tif (!query.trim()) return \"\";\n\t\tlet index: TranscriptIndex;\n\t\ttry {\n\t\t\tindex = this.ensureIndex();\n\t\t} catch {\n\t\t\treturn \"\";\n\t\t}\n\t\tif (index.size === 0) return \"\";\n\t\t// minScore is a query-CONTAINMENT threshold (fraction of the query's tokens present in the doc),\n\t\t// not Jaccard — so it is length-independent and recalls relevant long sessions. ~1/3 of query\n\t\t// terms must appear before a session is considered relevant.\n\t\tconst hits = index.query(query, { k: 3, minScore: 0.34, maxSnippetChars: 600 });\n\t\tif (hits.length === 0) return \"\";\n\t\t// Recalled past text is UNTRUSTED (it may itself contain injected instructions or a forged\n\t\t// `</memory_context>` to break out). Fence each snippet with the untrusted-content boundary so a\n\t\t// payload can't escape and be replayed as a current instruction (design: recall = untrusted).\n\t\tconst body = hits\n\t\t\t.map((h) => `- (${h.timestamp ?? \"earlier session\"}) ${wrapUntrustedText(h.snippet, \"transcript-recall\")}`)\n\t\t\t.join(\"\\n\");\n\t\treturn `<memory_context source=\"transcript-recall\">\\nRelevant context recalled from past sessions (read-only reference, untrusted, may be stale):\\n${body}\\n</memory_context>`;\n\t}\n\n\tprivate ensureIndex(): TranscriptIndex {\n\t\tif (!this.index) {\n\t\t\tthis.index = new TranscriptIndex(this.buildDocs());\n\t\t}\n\t\treturn this.index;\n\t}\n\n\tprivate buildDocs(): TranscriptDoc[] {\n\t\tconst docs: TranscriptDoc[] = [];\n\t\tlet dir: string;\n\t\ttry {\n\t\t\tdir = getDefaultSessionDir(this.cwd, this.agentDir);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet files: Array<{ path: string; mtime: number }>;\n\t\ttry {\n\t\t\tfiles = readdirSync(dir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => {\n\t\t\t\t\tconst path = join(dir, f);\n\t\t\t\t\tlet mtime = 0;\n\t\t\t\t\tlet size = 0;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst st = statSync(path);\n\t\t\t\t\t\tmtime = st.mtimeMs;\n\t\t\t\t\t\tsize = st.size;\n\t\t\t\t\t} catch {}\n\t\t\t\t\treturn { path, mtime, size };\n\t\t\t\t})\n\t\t\t\t.filter((f) => f.size > 0 && f.size <= MAX_FILE_BYTES) // skip oversize logs before parse (Bug #9)\n\t\t\t\t.sort((a, b) => b.mtime - a.mtime) // most-recent first\n\t\t\t\t.slice(0, MAX_SESSIONS);\n\t\t} catch {\n\t\t\treturn docs;\n\t\t}\n\n\t\tlet total = 0;\n\t\tfor (const { path } of files) {\n\t\t\tlet entries: FileEntry[];\n\t\t\ttry {\n\t\t\t\tentries = loadEntriesFromFile(path);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst header = entries.find((e): e is Extract<FileEntry, { type: \"session\" }> => e.type === \"session\");\n\t\t\tconst sessionId = header?.id;\n\t\t\tif (!sessionId || sessionId === this.currentSessionId || isAutoLearnSessionId(sessionId)) continue;\n\t\t\t// Privacy: only recall from sessions that ran in THIS working directory. A misplaced/copied\n\t\t\t// transcript with a different cwd must not leak across project boundaries (Bug #11).\n\t\t\tif (header?.cwd && resolve(header.cwd) !== resolve(this.cwd)) continue;\n\n\t\t\tconst text = extractSessionText(entries, MAX_DOC_CHARS);\n\t\t\tif (!text.trim()) continue;\n\t\t\tdocs.push({ sessionId, timestamp: header?.timestamp, text });\n\t\t\ttotal += text.length;\n\t\t\tif (total >= MAX_TOTAL_CHARS) break;\n\t\t}\n\t\treturn docs;\n\t}\n}\n\n/** Concatenate user+assistant text from a session's entries, capped to `maxChars`. */\nfunction extractSessionText(entries: FileEntry[], maxChars: number): string {\n\tconst parts: string[] = [];\n\tlet len = 0;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst message = entry.message;\n\t\tif (message.role !== \"user\" && message.role !== \"assistant\") continue;\n\t\tconst content = message.content;\n\t\tlet text = \"\";\n\t\tif (typeof content === \"string\") {\n\t\t\ttext = content;\n\t\t} else if (Array.isArray(content)) {\n\t\t\ttext = content\n\t\t\t\t.map((b) => (b && typeof b === \"object\" && \"type\" in b && b.type === \"text\" ? (b.text ?? \"\") : \"\"))\n\t\t\t\t.join(\" \");\n\t\t}\n\t\ttext = text.trim();\n\t\tif (!text) continue;\n\t\t// Skip our own previously-injected recall pages so recalled snippets don't recirculate and\n\t\t// amplify across sessions (Bug #10).\n\t\tif (text.includes('<memory_context source=\"transcript-recall\"')) continue;\n\t\tparts.push(text);\n\t\tlen += text.length;\n\t\tif (len >= maxChars) break;\n\t}\n\treturn parts.join(\"\\n\").slice(0, maxChars);\n}\n"]}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory controller: the session's plug-and-play memory subsystem — the read-only OKF retrieval
|
|
3
|
+
* provider, the bounded prompt-evidence surfacing pilot, cross-session recall effectiveness, and the
|
|
4
|
+
* live {@link MemoryManager} (bundled file-store + transcript-recall providers plus any extension
|
|
5
|
+
* contributions).
|
|
6
|
+
*
|
|
7
|
+
* Extracted verbatim from agent-session.ts (god-file decomposition). Owns the lazily-built OKF
|
|
8
|
+
* provider, the latest retrieval/prompt-inclusion reports, the recreated-on-reload MemoryManager, the
|
|
9
|
+
* recall {@link EffectivenessTracker}, and the extension-contributed pending providers. Everything
|
|
10
|
+
* else it needs — settings, the current turn index, agent/workspace dirs, the session id, the
|
|
11
|
+
* child-session flag, and the tool-registry refresh — is reached through narrow deps accessors rather
|
|
12
|
+
* than the whole AgentSession.
|
|
13
|
+
*
|
|
14
|
+
* Context-transform boundary (deliberate): {@link runMemoryRetrieval} and
|
|
15
|
+
* {@link maybeAppendMemoryEvidenceBlock} are invoked from the session's context transform as one-line
|
|
16
|
+
* delegations. This controller deliberately imports no compaction/context-pipeline internals — it only
|
|
17
|
+
* ever reads settings and builds the retrieval report + the bounded evidence block, so the transform
|
|
18
|
+
* stays the single owner of the pass ordering.
|
|
19
|
+
*/
|
|
20
|
+
import type { AgentMessage } from "@caupulican/pi-agent-core";
|
|
21
|
+
import { type MemoryPromptInclusionReport, type MemoryRetrievalDiagnostics } from "./context/memory-diagnostics.ts";
|
|
22
|
+
import { type MemoryRetrievalReport } from "./context/memory-retrieval.ts";
|
|
23
|
+
import { MemoryManager } from "./memory/memory-manager.ts";
|
|
24
|
+
import type { MemoryProvider } from "./memory/memory-provider.ts";
|
|
25
|
+
import type { SettingsManager } from "./settings-manager.ts";
|
|
26
|
+
export interface MemoryControllerDeps {
|
|
27
|
+
/** Memory-retrieval + prompt-inclusion settings (the opt-in gates for retrieval and surfacing). */
|
|
28
|
+
getSettingsManager(): SettingsManager;
|
|
29
|
+
/** Current turn index, stamped into a retrieval request's `createdAtTurn`. */
|
|
30
|
+
getTurnIndex(): number;
|
|
31
|
+
/** Agent root — the durable OKF memory docs live under `<agentDir>/okf-memory`. */
|
|
32
|
+
getAgentDir(): string;
|
|
33
|
+
/** Workspace root, passed to provider initialization. */
|
|
34
|
+
getCwd(): string;
|
|
35
|
+
/** This session's id, passed to provider initialization. */
|
|
36
|
+
getSessionId(): string;
|
|
37
|
+
/** Child sessions gate durable memory writes; passed to provider initialization. */
|
|
38
|
+
isChildSession(): boolean;
|
|
39
|
+
/** Re-derive the tool registry after (re)init so the newly-surfaced memory tools take effect. */
|
|
40
|
+
refreshToolRegistry(): void;
|
|
41
|
+
}
|
|
42
|
+
export declare class MemoryController {
|
|
43
|
+
private _memoryOkfProvider;
|
|
44
|
+
private _latestMemoryRetrievalReport;
|
|
45
|
+
private _latestMemoryPromptInclusionReport;
|
|
46
|
+
/** Plug-and-play memory subsystem. Recreated on each (re)initialize so reload is safe. */
|
|
47
|
+
private _memoryManager;
|
|
48
|
+
/** R4: tracks whether injected recall is actually used, to adapt the recall gate. */
|
|
49
|
+
private readonly _effectivenessTracker;
|
|
50
|
+
/** Memory providers registered by extensions via pi.registerMemoryProvider, applied on (re)init. */
|
|
51
|
+
private _pendingMemoryProviders;
|
|
52
|
+
private readonly deps;
|
|
53
|
+
constructor(deps: MemoryControllerDeps);
|
|
54
|
+
/** The live memory manager. Callers reach prefetch / tool-definitions / markers / shutdown through it. */
|
|
55
|
+
getMemoryManager(): MemoryManager;
|
|
56
|
+
/**
|
|
57
|
+
* Fixed path for this slice's local Pi OKF memory documents, shared across sessions
|
|
58
|
+
* under this agentDir (not session-scoped, unlike tool-artifacts/context-gc, since OKF
|
|
59
|
+
* memory represents durable cross-session knowledge, not a per-session capture). Not
|
|
60
|
+
* yet user-configurable -- see the memory-retrieval settings doc comment.
|
|
61
|
+
*/
|
|
62
|
+
private _memoryOkfDir;
|
|
63
|
+
/**
|
|
64
|
+
* Session-scoped, read-only local OKF memory provider. Lazily created ONLY when memory
|
|
65
|
+
* retrieval is enabled (see `runMemoryRetrieval`) -- never force-created, so a session
|
|
66
|
+
* with the setting off never touches `_memoryOkfDir()` at all (no directory access, no
|
|
67
|
+
* creation; `createOkfMemoryProvider` itself never writes/mkdirs either way).
|
|
68
|
+
*/
|
|
69
|
+
private _getMemoryOkfProvider;
|
|
70
|
+
/**
|
|
71
|
+
* Observe-only local memory retrieval (see context/memory-retrieval.ts and
|
|
72
|
+
* context/okf-memory-provider.ts): default disabled, opt-in setting. When disabled,
|
|
73
|
+
* never constructs the OKF provider (no directory access under `_memoryOkfDir()` at
|
|
74
|
+
* all) and returns an empty report -- fully fail-closed. When enabled, queries the
|
|
75
|
+
* local, read-only OKF provider with the latest user message text (empty if there is
|
|
76
|
+
* none, e.g. a goal-continuation turn -- degrades to zero results by construction, see
|
|
77
|
+
* `latestUserMessageText`'s doc comment) under `DEFAULT_LOCAL_MEMORY_EGRESS_POLICY`.
|
|
78
|
+
* Retrieved items are only ever stored in the report; nothing here touches `messages`,
|
|
79
|
+
* the transcript, or the provider-visible prompt. Never throws into a live turn: any
|
|
80
|
+
* failure (including a provider search error) degrades to an empty report.
|
|
81
|
+
*/
|
|
82
|
+
runMemoryRetrieval(messages: AgentMessage[]): Promise<MemoryRetrievalReport>;
|
|
83
|
+
/** Read-only inspection of the latest memory-retrieval report, for tests/debugging. */
|
|
84
|
+
getMemoryRetrievalReport(): MemoryRetrievalReport;
|
|
85
|
+
/**
|
|
86
|
+
* Bounded prompt-surfacing pilot for local memory evidence (see
|
|
87
|
+
* context/memory-prompt-block.ts): opt-in, default disabled, and gated on TWO settings
|
|
88
|
+
* (`enabled` AND `includeInPrompt`) plus a non-empty `report.contextItems` -- the first
|
|
89
|
+
* two are belt-and-suspenders on top of the fact that `runMemoryRetrieval` already
|
|
90
|
+
* leaves `contextItems` empty whenever `enabled` is false, regardless of
|
|
91
|
+
* `includeInPrompt`. Reuses the `report` this pass's `runMemoryRetrieval` call already
|
|
92
|
+
* computed -- never re-queries the provider here.
|
|
93
|
+
*
|
|
94
|
+
* Appends exactly one ephemeral `custom`/"memory_evidence" message wrapped by
|
|
95
|
+
* `wrapUntrustedText` (the same nonce-fenced boundary + always-on system-prompt rule
|
|
96
|
+
* used for other untrusted content) to the END of `messages`. This is purely additive
|
|
97
|
+
* (never mutates an existing message) and purely transient: `messages` here is the
|
|
98
|
+
* array about to be sent to the provider, not `this.agent.state.messages` or anything
|
|
99
|
+
* persisted via `sessionManager` -- so the injected message can never reach the
|
|
100
|
+
* transcript, regardless of how many times this pass runs.
|
|
101
|
+
*
|
|
102
|
+
* Also records a `MemoryPromptInclusionReport` (context/memory-diagnostics.ts) at each
|
|
103
|
+
* branch below, for context_audit's diagnostic surface only -- this is pure bookkeeping
|
|
104
|
+
* alongside the existing branches, not a new branch/condition: the messages returned
|
|
105
|
+
* are unchanged by this recording.
|
|
106
|
+
*/
|
|
107
|
+
maybeAppendMemoryEvidenceBlock(messages: AgentMessage[], report: MemoryRetrievalReport): AgentMessage[];
|
|
108
|
+
/** Read-only inspection of the latest memory-prompt-inclusion decision, for tests/debugging and context_audit. */
|
|
109
|
+
getMemoryPromptInclusionReport(): MemoryPromptInclusionReport;
|
|
110
|
+
/**
|
|
111
|
+
* Combines the already-stored, no-arg latest reports (never re-queries the provider or
|
|
112
|
+
* touches the OKF directory) into the safe, allow-list-projected shape context_audit
|
|
113
|
+
* exposes. See context/memory-diagnostics.ts for why this projection is allow-list
|
|
114
|
+
* based rather than a spread-then-delete of the raw report.
|
|
115
|
+
*/
|
|
116
|
+
getMemoryAuditDiagnostics(): {
|
|
117
|
+
retrieval: MemoryRetrievalDiagnostics;
|
|
118
|
+
promptInclusion: MemoryPromptInclusionReport;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Zero-I/O gate for cross-session recall (R3): skip trivial turns (short acks, slash commands) so
|
|
122
|
+
* recall only runs when it could plausibly help. The provider's similarity cutoff is the real
|
|
123
|
+
* filter — this just avoids the index query on turns that obviously don't warrant it.
|
|
124
|
+
*/
|
|
125
|
+
shouldAttemptRecall(text: string): boolean;
|
|
126
|
+
/** R4: score whether the agent actually used an injected recall page, so the recall gate can adapt. */
|
|
127
|
+
recordRecallOutcome(recallText: string, queryText: string, responseText: string): void;
|
|
128
|
+
/**
|
|
129
|
+
* (Re)build the memory subsystem: a fresh MemoryManager (reload-safe), register the bundled
|
|
130
|
+
* file-store + any extension-contributed providers, initialize, then surface the memory tools and
|
|
131
|
+
* the frozen system-prompt block. Best-effort: never throws into the session lifecycle.
|
|
132
|
+
*/
|
|
133
|
+
initialize(): Promise<void>;
|
|
134
|
+
/** Register a memory provider contributed by an extension; applied on the next memory (re)init. */
|
|
135
|
+
registerMemoryProvider(provider: MemoryProvider): void;
|
|
136
|
+
/** Reload starts memory providers fresh; loaded extensions re-register before the next `initialize()`. */
|
|
137
|
+
clearPendingProviders(): void;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=memory-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-controller.d.ts","sourceRoot":"","sources":["../../src/core/memory-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAEN,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAE/B,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EAAE,KAAK,qBAAqB,EAA4B,MAAM,+BAA+B,CAAC;AAGrG,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAIlE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA0B7D,MAAM,WAAW,oBAAoB;IACpC,mGAAmG;IACnG,kBAAkB,IAAI,eAAe,CAAC;IACtC,8EAA8E;IAC9E,YAAY,IAAI,MAAM,CAAC;IACvB,qFAAmF;IACnF,WAAW,IAAI,MAAM,CAAC;IACtB,yDAAyD;IACzD,MAAM,IAAI,MAAM,CAAC;IACjB,4DAA4D;IAC5D,YAAY,IAAI,MAAM,CAAC;IACvB,oFAAoF;IACpF,cAAc,IAAI,OAAO,CAAC;IAC1B,iGAAiG;IACjG,mBAAmB,IAAI,IAAI,CAAC;CAC5B;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,kBAAkB,CAAgD;IAC1E,OAAO,CAAC,4BAA4B,CAAgD;IACpF,OAAO,CAAC,kCAAkC,CAAsD;IAChG,0FAA0F;IAC1F,OAAO,CAAC,cAAc,CAAsC;IAC5D,qFAAqF;IACrF,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA8B;IACpE,oGAAoG;IACpG,OAAO,CAAC,uBAAuB,CAAwB;IAEvD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAE5C,YAAY,IAAI,EAAE,oBAAoB,EAErC;IAED,0GAA0G;IAC1G,gBAAgB,IAAI,aAAa,CAEhC;IAED;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAIrB;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IAK7B;;;;;;;;;;;OAWG;IACG,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAwBjF;IAED,uFAAuF;IACvF,wBAAwB,IAAI,qBAAqB,CAEhD;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,8BAA8B,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,qBAAqB,GAAG,YAAY,EAAE,CAoFtG;IAED,kHAAkH;IAClH,8BAA8B,IAAI,2BAA2B,CAE5D;IAED;;;;;OAKG;IACH,yBAAyB,IAAI;QAC5B,SAAS,EAAE,0BAA0B,CAAC;QACtC,eAAe,EAAE,2BAA2B,CAAC;KAC7C,CAMA;IAED;;;;OAIG;IACH,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUzC;IAED,uGAAuG;IACvG,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAErF;IAED;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CA8BhC;IAED,mGAAmG;IACnG,sBAAsB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAIrD;IAED,0GAA0G;IAC1G,qBAAqB,IAAI,IAAI,CAE5B;CACD","sourcesContent":["/**\n * Memory controller: the session's plug-and-play memory subsystem — the read-only OKF retrieval\n * provider, the bounded prompt-evidence surfacing pilot, cross-session recall effectiveness, and the\n * live {@link MemoryManager} (bundled file-store + transcript-recall providers plus any extension\n * contributions).\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the lazily-built OKF\n * provider, the latest retrieval/prompt-inclusion reports, the recreated-on-reload MemoryManager, the\n * recall {@link EffectivenessTracker}, and the extension-contributed pending providers. Everything\n * else it needs — settings, the current turn index, agent/workspace dirs, the session id, the\n * child-session flag, and the tool-registry refresh — is reached through narrow deps accessors rather\n * than the whole AgentSession.\n *\n * Context-transform boundary (deliberate): {@link runMemoryRetrieval} and\n * {@link maybeAppendMemoryEvidenceBlock} are invoked from the session's context transform as one-line\n * delegations. This controller deliberately imports no compaction/context-pipeline internals — it only\n * ever reads settings and builds the retrieval report + the bounded evidence block, so the transform\n * stays the single owner of the pass ordering.\n */\n\nimport { join } from \"node:path\";\nimport type { AgentMessage, CustomMessage } from \"@caupulican/pi-agent-core\";\nimport {\n\tdefaultMemoryPromptInclusionReport,\n\ttype MemoryPromptInclusionReport,\n\ttype MemoryRetrievalDiagnostics,\n\tsanitizeMemoryRetrievalReportForDiagnostics,\n} from \"./context/memory-diagnostics.ts\";\nimport { buildMemoryPromptBlock } from \"./context/memory-prompt-block.ts\";\nimport {\n\ttype MemoryProvider as ContextMemoryProvider,\n\tDEFAULT_LOCAL_MEMORY_EGRESS_POLICY,\n} from \"./context/memory-provider-contract.ts\";\nimport { type MemoryRetrievalReport, retrieveMemoryForContext } from \"./context/memory-retrieval.ts\";\nimport { createOkfMemoryProvider } from \"./context/okf-memory-provider.ts\";\nimport { EffectivenessTracker } from \"./memory/effectiveness-tracker.ts\";\nimport { MemoryManager } from \"./memory/memory-manager.ts\";\nimport type { MemoryProvider } from \"./memory/memory-provider.ts\";\nimport { FileStoreProvider } from \"./memory/providers/file-store.ts\";\nimport { TranscriptRecallProvider } from \"./memory/providers/transcript-recall.ts\";\nimport { wrapUntrustedText } from \"./security/untrusted-boundary.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\n/**\n * Text of the most recent user message, or \"\" if there is none (e.g. goal-continuation\n * turns with no new user input). An empty query degrades to zero memory-retrieval results\n * by construction (see memory-provider-contract.ts's score-on-empty-query-tokens rule) --\n * no special-casing needed here beyond returning \"\".\n */\nfunction latestUserMessageText(messages: AgentMessage[]): string {\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (message.role !== \"user\") continue;\n\t\tif (typeof message.content === \"string\") return message.content;\n\t\tconst parts: string[] = [];\n\t\tfor (const part of message.content) {\n\t\t\tif (part.type === \"text\") parts.push(part.text);\n\t\t}\n\t\treturn parts.join(\"\\n\");\n\t}\n\treturn \"\";\n}\n\nfunction emptyMemoryRetrievalReport(maxResults: number): MemoryRetrievalReport {\n\treturn { request: { query: \"\", maxResults }, providerReports: [], results: [], contextItems: [] };\n}\n\nexport interface MemoryControllerDeps {\n\t/** Memory-retrieval + prompt-inclusion settings (the opt-in gates for retrieval and surfacing). */\n\tgetSettingsManager(): SettingsManager;\n\t/** Current turn index, stamped into a retrieval request's `createdAtTurn`. */\n\tgetTurnIndex(): number;\n\t/** Agent root — the durable OKF memory docs live under `<agentDir>/okf-memory`. */\n\tgetAgentDir(): string;\n\t/** Workspace root, passed to provider initialization. */\n\tgetCwd(): string;\n\t/** This session's id, passed to provider initialization. */\n\tgetSessionId(): string;\n\t/** Child sessions gate durable memory writes; passed to provider initialization. */\n\tisChildSession(): boolean;\n\t/** Re-derive the tool registry after (re)init so the newly-surfaced memory tools take effect. */\n\trefreshToolRegistry(): void;\n}\n\nexport class MemoryController {\n\tprivate _memoryOkfProvider: ContextMemoryProvider | undefined = undefined;\n\tprivate _latestMemoryRetrievalReport: MemoryRetrievalReport | undefined = undefined;\n\tprivate _latestMemoryPromptInclusionReport: MemoryPromptInclusionReport | undefined = undefined;\n\t/** Plug-and-play memory subsystem. Recreated on each (re)initialize so reload is safe. */\n\tprivate _memoryManager: MemoryManager = new MemoryManager();\n\t/** R4: tracks whether injected recall is actually used, to adapt the recall gate. */\n\tprivate readonly _effectivenessTracker = new EffectivenessTracker();\n\t/** Memory providers registered by extensions via pi.registerMemoryProvider, applied on (re)init. */\n\tprivate _pendingMemoryProviders: MemoryProvider[] = [];\n\n\tprivate readonly deps: MemoryControllerDeps;\n\n\tconstructor(deps: MemoryControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/** The live memory manager. Callers reach prefetch / tool-definitions / markers / shutdown through it. */\n\tgetMemoryManager(): MemoryManager {\n\t\treturn this._memoryManager;\n\t}\n\n\t/**\n\t * Fixed path for this slice's local Pi OKF memory documents, shared across sessions\n\t * under this agentDir (not session-scoped, unlike tool-artifacts/context-gc, since OKF\n\t * memory represents durable cross-session knowledge, not a per-session capture). Not\n\t * yet user-configurable -- see the memory-retrieval settings doc comment.\n\t */\n\tprivate _memoryOkfDir(): string {\n\t\treturn join(this.deps.getAgentDir(), \"okf-memory\");\n\t}\n\n\t/**\n\t * Session-scoped, read-only local OKF memory provider. Lazily created ONLY when memory\n\t * retrieval is enabled (see `runMemoryRetrieval`) -- never force-created, so a session\n\t * with the setting off never touches `_memoryOkfDir()` at all (no directory access, no\n\t * creation; `createOkfMemoryProvider` itself never writes/mkdirs either way).\n\t */\n\tprivate _getMemoryOkfProvider(): ContextMemoryProvider {\n\t\tthis._memoryOkfProvider ??= createOkfMemoryProvider({ rootDir: this._memoryOkfDir() });\n\t\treturn this._memoryOkfProvider;\n\t}\n\n\t/**\n\t * Observe-only local memory retrieval (see context/memory-retrieval.ts and\n\t * context/okf-memory-provider.ts): default disabled, opt-in setting. When disabled,\n\t * never constructs the OKF provider (no directory access under `_memoryOkfDir()` at\n\t * all) and returns an empty report -- fully fail-closed. When enabled, queries the\n\t * local, read-only OKF provider with the latest user message text (empty if there is\n\t * none, e.g. a goal-continuation turn -- degrades to zero results by construction, see\n\t * `latestUserMessageText`'s doc comment) under `DEFAULT_LOCAL_MEMORY_EGRESS_POLICY`.\n\t * Retrieved items are only ever stored in the report; nothing here touches `messages`,\n\t * the transcript, or the provider-visible prompt. Never throws into a live turn: any\n\t * failure (including a provider search error) degrades to an empty report.\n\t */\n\tasync runMemoryRetrieval(messages: AgentMessage[]): Promise<MemoryRetrievalReport> {\n\t\ttry {\n\t\t\tconst settings = this.deps.getSettingsManager().getMemoryRetrievalSettings();\n\t\t\tif (!settings.enabled) {\n\t\t\t\tconst report = emptyMemoryRetrievalReport(settings.maxResults);\n\t\t\t\tthis._latestMemoryRetrievalReport = report;\n\t\t\t\treturn report;\n\t\t\t}\n\t\t\tconst report = await retrieveMemoryForContext(\n\t\t\t\t[this._getMemoryOkfProvider()],\n\t\t\t\t{ query: latestUserMessageText(messages), maxResults: settings.maxResults },\n\t\t\t\t{\n\t\t\t\t\tcreatedAtTurn: this.deps.getTurnIndex(),\n\t\t\t\t\tmaxResults: settings.maxResults,\n\t\t\t\t\tdefaultLocalPolicy: DEFAULT_LOCAL_MEMORY_EGRESS_POLICY,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis._latestMemoryRetrievalReport = report;\n\t\t\treturn report;\n\t\t} catch {\n\t\t\tconst report = emptyMemoryRetrievalReport(0);\n\t\t\tthis._latestMemoryRetrievalReport = report;\n\t\t\treturn report;\n\t\t}\n\t}\n\n\t/** Read-only inspection of the latest memory-retrieval report, for tests/debugging. */\n\tgetMemoryRetrievalReport(): MemoryRetrievalReport {\n\t\treturn this._latestMemoryRetrievalReport ?? emptyMemoryRetrievalReport(0);\n\t}\n\n\t/**\n\t * Bounded prompt-surfacing pilot for local memory evidence (see\n\t * context/memory-prompt-block.ts): opt-in, default disabled, and gated on TWO settings\n\t * (`enabled` AND `includeInPrompt`) plus a non-empty `report.contextItems` -- the first\n\t * two are belt-and-suspenders on top of the fact that `runMemoryRetrieval` already\n\t * leaves `contextItems` empty whenever `enabled` is false, regardless of\n\t * `includeInPrompt`. Reuses the `report` this pass's `runMemoryRetrieval` call already\n\t * computed -- never re-queries the provider here.\n\t *\n\t * Appends exactly one ephemeral `custom`/\"memory_evidence\" message wrapped by\n\t * `wrapUntrustedText` (the same nonce-fenced boundary + always-on system-prompt rule\n\t * used for other untrusted content) to the END of `messages`. This is purely additive\n\t * (never mutates an existing message) and purely transient: `messages` here is the\n\t * array about to be sent to the provider, not `this.agent.state.messages` or anything\n\t * persisted via `sessionManager` -- so the injected message can never reach the\n\t * transcript, regardless of how many times this pass runs.\n\t *\n\t * Also records a `MemoryPromptInclusionReport` (context/memory-diagnostics.ts) at each\n\t * branch below, for context_audit's diagnostic surface only -- this is pure bookkeeping\n\t * alongside the existing branches, not a new branch/condition: the messages returned\n\t * are unchanged by this recording.\n\t */\n\tmaybeAppendMemoryEvidenceBlock(messages: AgentMessage[], report: MemoryRetrievalReport): AgentMessage[] {\n\t\ttry {\n\t\t\tconst settings = this.deps.getSettingsManager().getMemoryRetrievalSettings();\n\t\t\tconst base = {\n\t\t\t\tenabled: settings.enabled,\n\t\t\t\tincludeInPrompt: settings.includeInPrompt,\n\t\t\t\tselectedItemCount: report.contextItems.length,\n\t\t\t};\n\t\t\tif (!settings.enabled) {\n\t\t\t\tthis._latestMemoryPromptInclusionReport = {\n\t\t\t\t\t...base,\n\t\t\t\t\tstatus: \"disabled\",\n\t\t\t\t\tincludedCount: 0,\n\t\t\t\t\tomittedCount: 0,\n\t\t\t\t\tblockChars: 0,\n\t\t\t\t};\n\t\t\t\treturn messages;\n\t\t\t}\n\t\t\tif (!settings.includeInPrompt) {\n\t\t\t\tthis._latestMemoryPromptInclusionReport = {\n\t\t\t\t\t...base,\n\t\t\t\t\tstatus: \"include_disabled\",\n\t\t\t\t\tincludedCount: 0,\n\t\t\t\t\tomittedCount: 0,\n\t\t\t\t\tblockChars: 0,\n\t\t\t\t};\n\t\t\t\treturn messages;\n\t\t\t}\n\t\t\tif (report.contextItems.length === 0) {\n\t\t\t\tthis._latestMemoryPromptInclusionReport = {\n\t\t\t\t\t...base,\n\t\t\t\t\tstatus: \"no_results\",\n\t\t\t\t\tincludedCount: 0,\n\t\t\t\t\tomittedCount: 0,\n\t\t\t\t\tblockChars: 0,\n\t\t\t\t};\n\t\t\t\treturn messages;\n\t\t\t}\n\n\t\t\tconst block = buildMemoryPromptBlock(report.contextItems);\n\t\t\tif (!block.text) {\n\t\t\t\tthis._latestMemoryPromptInclusionReport = {\n\t\t\t\t\t...base,\n\t\t\t\t\tstatus: \"empty_block\",\n\t\t\t\t\tincludedCount: block.includedCount,\n\t\t\t\t\tomittedCount: block.omittedCount,\n\t\t\t\t\tblockChars: 0,\n\t\t\t\t};\n\t\t\t\treturn messages;\n\t\t\t}\n\n\t\t\tconst wrapped = wrapUntrustedText(block.text, \"memory:pi-okf\");\n\t\t\tconst evidenceMessage: CustomMessage = {\n\t\t\t\trole: \"custom\",\n\t\t\t\tcustomType: \"memory_evidence\",\n\t\t\t\tcontent: [{ type: \"text\", text: wrapped }],\n\t\t\t\tdisplay: false,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tthis._latestMemoryPromptInclusionReport = {\n\t\t\t\t...base,\n\t\t\t\tstatus: \"included\",\n\t\t\t\tincludedCount: block.includedCount,\n\t\t\t\tomittedCount: block.omittedCount,\n\t\t\t\tblockChars: wrapped.length,\n\t\t\t\tsourceLabel: \"memory:pi-okf\",\n\t\t\t};\n\t\t\treturn [...messages, evidenceMessage];\n\t\t} catch {\n\t\t\t// `base` may not exist yet if the throw happened before it was computed (e.g.\n\t\t\t// settings access or `report.contextItems` itself threw), so this branch cannot\n\t\t\t// rely on it -- fall back to safe, fixed defaults rather than risk referencing\n\t\t\t// a partially-evaluated value.\n\t\t\tthis._latestMemoryPromptInclusionReport = {\n\t\t\t\tenabled: false,\n\t\t\t\tincludeInPrompt: false,\n\t\t\t\tselectedItemCount: 0,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tincludedCount: 0,\n\t\t\t\tomittedCount: 0,\n\t\t\t\tblockChars: 0,\n\t\t\t};\n\t\t\treturn messages;\n\t\t}\n\t}\n\n\t/** Read-only inspection of the latest memory-prompt-inclusion decision, for tests/debugging and context_audit. */\n\tgetMemoryPromptInclusionReport(): MemoryPromptInclusionReport {\n\t\treturn this._latestMemoryPromptInclusionReport ?? defaultMemoryPromptInclusionReport();\n\t}\n\n\t/**\n\t * Combines the already-stored, no-arg latest reports (never re-queries the provider or\n\t * touches the OKF directory) into the safe, allow-list-projected shape context_audit\n\t * exposes. See context/memory-diagnostics.ts for why this projection is allow-list\n\t * based rather than a spread-then-delete of the raw report.\n\t */\n\tgetMemoryAuditDiagnostics(): {\n\t\tretrieval: MemoryRetrievalDiagnostics;\n\t\tpromptInclusion: MemoryPromptInclusionReport;\n\t} {\n\t\tconst settings = this.deps.getSettingsManager().getMemoryRetrievalSettings();\n\t\treturn {\n\t\t\tretrieval: sanitizeMemoryRetrievalReportForDiagnostics(this.getMemoryRetrievalReport(), settings),\n\t\t\tpromptInclusion: this.getMemoryPromptInclusionReport(),\n\t\t};\n\t}\n\n\t/**\n\t * Zero-I/O gate for cross-session recall (R3): skip trivial turns (short acks, slash commands) so\n\t * recall only runs when it could plausibly help. The provider's similarity cutoff is the real\n\t * filter — this just avoids the index query on turns that obviously don't warrant it.\n\t */\n\tshouldAttemptRecall(text: string): boolean {\n\t\tconst t = text.trim();\n\t\tif (t.length < 12 || t.startsWith(\"/\")) return false;\n\t\tconst words = t.split(/\\s+/).filter((w) => w.length >= 3);\n\t\t// R4 adaptive gate: if recall has rarely been used lately (enough samples to trust the signal),\n\t\t// raise the bar so we only recall on clearly substantial turns — and relax it again once recall\n\t\t// starts paying off. Never fully disabled, so the loop can recover.\n\t\tconst recallRarelyUseful =\n\t\t\tthis._effectivenessTracker.sampleCount >= 5 && this._effectivenessTracker.usefulLately() < 0.15;\n\t\treturn words.length >= (recallRarelyUseful ? 6 : 3);\n\t}\n\n\t/** R4: score whether the agent actually used an injected recall page, so the recall gate can adapt. */\n\trecordRecallOutcome(recallText: string, queryText: string, responseText: string): void {\n\t\tthis._effectivenessTracker.recordRecallOutcome(recallText, queryText, responseText);\n\t}\n\n\t/**\n\t * (Re)build the memory subsystem: a fresh MemoryManager (reload-safe), register the bundled\n\t * file-store + any extension-contributed providers, initialize, then surface the memory tools and\n\t * the frozen system-prompt block. Best-effort: never throws into the session lifecycle.\n\t */\n\tasync initialize(): Promise<void> {\n\t\ttry {\n\t\t\t// Release the previous generation's providers (locks/handles) before recreating, so a\n\t\t\t// reload does not orphan the old MemoryManager. No-op on first init / for file-store.\n\t\t\tawait this._memoryManager.shutdownAll().catch(() => {});\n\t\t\tconst manager = new MemoryManager();\n\t\t\tmanager.registerProvider(new FileStoreProvider());\n\t\t\t// Bundled read-only cross-session recall (R3): indexes past-session transcripts and answers\n\t\t\t// prefetch() with a <memory_context> page. Never writes.\n\t\t\tmanager.registerProvider(new TranscriptRecallProvider());\n\t\t\tfor (const provider of this._pendingMemoryProviders) {\n\t\t\t\ttry {\n\t\t\t\t\tmanager.registerProvider(provider);\n\t\t\t\t} catch {\n\t\t\t\t\t// Duplicate name or reserved-tool collision — skip this provider, keep the rest.\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._memoryManager = manager;\n\t\t\tawait manager.initializeAll(this.deps.getSessionId(), {\n\t\t\t\tagentDir: this.deps.getAgentDir(),\n\t\t\t\tcwd: this.deps.getCwd(),\n\t\t\t\tisChildSession: this.deps.isChildSession(),\n\t\t\t});\n\t\t\t// Surface memory tools + the frozen memory block now that providers are initialized.\n\t\t\t// refreshToolRegistry() ends in setActiveToolsByName(), which rebuilds AND assigns the\n\t\t\t// system prompt (including the memory block), so no explicit _rebuildSystemPrompt is needed.\n\t\t\tthis.deps.refreshToolRegistry();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Memory subsystem init failed:\", error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\t/** Register a memory provider contributed by an extension; applied on the next memory (re)init. */\n\tregisterMemoryProvider(provider: MemoryProvider): void {\n\t\tif (!this._pendingMemoryProviders.some((p) => p.name === provider.name)) {\n\t\t\tthis._pendingMemoryProviders.push(provider);\n\t\t}\n\t}\n\n\t/** Reload starts memory providers fresh; loaded extensions re-register before the next `initialize()`. */\n\tclearPendingProviders(): void {\n\t\tthis._pendingMemoryProviders = [];\n\t}\n}\n"]}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory controller: the session's plug-and-play memory subsystem — the read-only OKF retrieval
|
|
3
|
+
* provider, the bounded prompt-evidence surfacing pilot, cross-session recall effectiveness, and the
|
|
4
|
+
* live {@link MemoryManager} (bundled file-store + transcript-recall providers plus any extension
|
|
5
|
+
* contributions).
|
|
6
|
+
*
|
|
7
|
+
* Extracted verbatim from agent-session.ts (god-file decomposition). Owns the lazily-built OKF
|
|
8
|
+
* provider, the latest retrieval/prompt-inclusion reports, the recreated-on-reload MemoryManager, the
|
|
9
|
+
* recall {@link EffectivenessTracker}, and the extension-contributed pending providers. Everything
|
|
10
|
+
* else it needs — settings, the current turn index, agent/workspace dirs, the session id, the
|
|
11
|
+
* child-session flag, and the tool-registry refresh — is reached through narrow deps accessors rather
|
|
12
|
+
* than the whole AgentSession.
|
|
13
|
+
*
|
|
14
|
+
* Context-transform boundary (deliberate): {@link runMemoryRetrieval} and
|
|
15
|
+
* {@link maybeAppendMemoryEvidenceBlock} are invoked from the session's context transform as one-line
|
|
16
|
+
* delegations. This controller deliberately imports no compaction/context-pipeline internals — it only
|
|
17
|
+
* ever reads settings and builds the retrieval report + the bounded evidence block, so the transform
|
|
18
|
+
* stays the single owner of the pass ordering.
|
|
19
|
+
*/
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { defaultMemoryPromptInclusionReport, sanitizeMemoryRetrievalReportForDiagnostics, } from "./context/memory-diagnostics.js";
|
|
22
|
+
import { buildMemoryPromptBlock } from "./context/memory-prompt-block.js";
|
|
23
|
+
import { DEFAULT_LOCAL_MEMORY_EGRESS_POLICY, } from "./context/memory-provider-contract.js";
|
|
24
|
+
import { retrieveMemoryForContext } from "./context/memory-retrieval.js";
|
|
25
|
+
import { createOkfMemoryProvider } from "./context/okf-memory-provider.js";
|
|
26
|
+
import { EffectivenessTracker } from "./memory/effectiveness-tracker.js";
|
|
27
|
+
import { MemoryManager } from "./memory/memory-manager.js";
|
|
28
|
+
import { FileStoreProvider } from "./memory/providers/file-store.js";
|
|
29
|
+
import { TranscriptRecallProvider } from "./memory/providers/transcript-recall.js";
|
|
30
|
+
import { wrapUntrustedText } from "./security/untrusted-boundary.js";
|
|
31
|
+
/**
|
|
32
|
+
* Text of the most recent user message, or "" if there is none (e.g. goal-continuation
|
|
33
|
+
* turns with no new user input). An empty query degrades to zero memory-retrieval results
|
|
34
|
+
* by construction (see memory-provider-contract.ts's score-on-empty-query-tokens rule) --
|
|
35
|
+
* no special-casing needed here beyond returning "".
|
|
36
|
+
*/
|
|
37
|
+
function latestUserMessageText(messages) {
|
|
38
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
39
|
+
const message = messages[index];
|
|
40
|
+
if (message.role !== "user")
|
|
41
|
+
continue;
|
|
42
|
+
if (typeof message.content === "string")
|
|
43
|
+
return message.content;
|
|
44
|
+
const parts = [];
|
|
45
|
+
for (const part of message.content) {
|
|
46
|
+
if (part.type === "text")
|
|
47
|
+
parts.push(part.text);
|
|
48
|
+
}
|
|
49
|
+
return parts.join("\n");
|
|
50
|
+
}
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
function emptyMemoryRetrievalReport(maxResults) {
|
|
54
|
+
return { request: { query: "", maxResults }, providerReports: [], results: [], contextItems: [] };
|
|
55
|
+
}
|
|
56
|
+
export class MemoryController {
|
|
57
|
+
_memoryOkfProvider = undefined;
|
|
58
|
+
_latestMemoryRetrievalReport = undefined;
|
|
59
|
+
_latestMemoryPromptInclusionReport = undefined;
|
|
60
|
+
/** Plug-and-play memory subsystem. Recreated on each (re)initialize so reload is safe. */
|
|
61
|
+
_memoryManager = new MemoryManager();
|
|
62
|
+
/** R4: tracks whether injected recall is actually used, to adapt the recall gate. */
|
|
63
|
+
_effectivenessTracker = new EffectivenessTracker();
|
|
64
|
+
/** Memory providers registered by extensions via pi.registerMemoryProvider, applied on (re)init. */
|
|
65
|
+
_pendingMemoryProviders = [];
|
|
66
|
+
deps;
|
|
67
|
+
constructor(deps) {
|
|
68
|
+
this.deps = deps;
|
|
69
|
+
}
|
|
70
|
+
/** The live memory manager. Callers reach prefetch / tool-definitions / markers / shutdown through it. */
|
|
71
|
+
getMemoryManager() {
|
|
72
|
+
return this._memoryManager;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Fixed path for this slice's local Pi OKF memory documents, shared across sessions
|
|
76
|
+
* under this agentDir (not session-scoped, unlike tool-artifacts/context-gc, since OKF
|
|
77
|
+
* memory represents durable cross-session knowledge, not a per-session capture). Not
|
|
78
|
+
* yet user-configurable -- see the memory-retrieval settings doc comment.
|
|
79
|
+
*/
|
|
80
|
+
_memoryOkfDir() {
|
|
81
|
+
return join(this.deps.getAgentDir(), "okf-memory");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Session-scoped, read-only local OKF memory provider. Lazily created ONLY when memory
|
|
85
|
+
* retrieval is enabled (see `runMemoryRetrieval`) -- never force-created, so a session
|
|
86
|
+
* with the setting off never touches `_memoryOkfDir()` at all (no directory access, no
|
|
87
|
+
* creation; `createOkfMemoryProvider` itself never writes/mkdirs either way).
|
|
88
|
+
*/
|
|
89
|
+
_getMemoryOkfProvider() {
|
|
90
|
+
this._memoryOkfProvider ??= createOkfMemoryProvider({ rootDir: this._memoryOkfDir() });
|
|
91
|
+
return this._memoryOkfProvider;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Observe-only local memory retrieval (see context/memory-retrieval.ts and
|
|
95
|
+
* context/okf-memory-provider.ts): default disabled, opt-in setting. When disabled,
|
|
96
|
+
* never constructs the OKF provider (no directory access under `_memoryOkfDir()` at
|
|
97
|
+
* all) and returns an empty report -- fully fail-closed. When enabled, queries the
|
|
98
|
+
* local, read-only OKF provider with the latest user message text (empty if there is
|
|
99
|
+
* none, e.g. a goal-continuation turn -- degrades to zero results by construction, see
|
|
100
|
+
* `latestUserMessageText`'s doc comment) under `DEFAULT_LOCAL_MEMORY_EGRESS_POLICY`.
|
|
101
|
+
* Retrieved items are only ever stored in the report; nothing here touches `messages`,
|
|
102
|
+
* the transcript, or the provider-visible prompt. Never throws into a live turn: any
|
|
103
|
+
* failure (including a provider search error) degrades to an empty report.
|
|
104
|
+
*/
|
|
105
|
+
async runMemoryRetrieval(messages) {
|
|
106
|
+
try {
|
|
107
|
+
const settings = this.deps.getSettingsManager().getMemoryRetrievalSettings();
|
|
108
|
+
if (!settings.enabled) {
|
|
109
|
+
const report = emptyMemoryRetrievalReport(settings.maxResults);
|
|
110
|
+
this._latestMemoryRetrievalReport = report;
|
|
111
|
+
return report;
|
|
112
|
+
}
|
|
113
|
+
const report = await retrieveMemoryForContext([this._getMemoryOkfProvider()], { query: latestUserMessageText(messages), maxResults: settings.maxResults }, {
|
|
114
|
+
createdAtTurn: this.deps.getTurnIndex(),
|
|
115
|
+
maxResults: settings.maxResults,
|
|
116
|
+
defaultLocalPolicy: DEFAULT_LOCAL_MEMORY_EGRESS_POLICY,
|
|
117
|
+
});
|
|
118
|
+
this._latestMemoryRetrievalReport = report;
|
|
119
|
+
return report;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
const report = emptyMemoryRetrievalReport(0);
|
|
123
|
+
this._latestMemoryRetrievalReport = report;
|
|
124
|
+
return report;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Read-only inspection of the latest memory-retrieval report, for tests/debugging. */
|
|
128
|
+
getMemoryRetrievalReport() {
|
|
129
|
+
return this._latestMemoryRetrievalReport ?? emptyMemoryRetrievalReport(0);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Bounded prompt-surfacing pilot for local memory evidence (see
|
|
133
|
+
* context/memory-prompt-block.ts): opt-in, default disabled, and gated on TWO settings
|
|
134
|
+
* (`enabled` AND `includeInPrompt`) plus a non-empty `report.contextItems` -- the first
|
|
135
|
+
* two are belt-and-suspenders on top of the fact that `runMemoryRetrieval` already
|
|
136
|
+
* leaves `contextItems` empty whenever `enabled` is false, regardless of
|
|
137
|
+
* `includeInPrompt`. Reuses the `report` this pass's `runMemoryRetrieval` call already
|
|
138
|
+
* computed -- never re-queries the provider here.
|
|
139
|
+
*
|
|
140
|
+
* Appends exactly one ephemeral `custom`/"memory_evidence" message wrapped by
|
|
141
|
+
* `wrapUntrustedText` (the same nonce-fenced boundary + always-on system-prompt rule
|
|
142
|
+
* used for other untrusted content) to the END of `messages`. This is purely additive
|
|
143
|
+
* (never mutates an existing message) and purely transient: `messages` here is the
|
|
144
|
+
* array about to be sent to the provider, not `this.agent.state.messages` or anything
|
|
145
|
+
* persisted via `sessionManager` -- so the injected message can never reach the
|
|
146
|
+
* transcript, regardless of how many times this pass runs.
|
|
147
|
+
*
|
|
148
|
+
* Also records a `MemoryPromptInclusionReport` (context/memory-diagnostics.ts) at each
|
|
149
|
+
* branch below, for context_audit's diagnostic surface only -- this is pure bookkeeping
|
|
150
|
+
* alongside the existing branches, not a new branch/condition: the messages returned
|
|
151
|
+
* are unchanged by this recording.
|
|
152
|
+
*/
|
|
153
|
+
maybeAppendMemoryEvidenceBlock(messages, report) {
|
|
154
|
+
try {
|
|
155
|
+
const settings = this.deps.getSettingsManager().getMemoryRetrievalSettings();
|
|
156
|
+
const base = {
|
|
157
|
+
enabled: settings.enabled,
|
|
158
|
+
includeInPrompt: settings.includeInPrompt,
|
|
159
|
+
selectedItemCount: report.contextItems.length,
|
|
160
|
+
};
|
|
161
|
+
if (!settings.enabled) {
|
|
162
|
+
this._latestMemoryPromptInclusionReport = {
|
|
163
|
+
...base,
|
|
164
|
+
status: "disabled",
|
|
165
|
+
includedCount: 0,
|
|
166
|
+
omittedCount: 0,
|
|
167
|
+
blockChars: 0,
|
|
168
|
+
};
|
|
169
|
+
return messages;
|
|
170
|
+
}
|
|
171
|
+
if (!settings.includeInPrompt) {
|
|
172
|
+
this._latestMemoryPromptInclusionReport = {
|
|
173
|
+
...base,
|
|
174
|
+
status: "include_disabled",
|
|
175
|
+
includedCount: 0,
|
|
176
|
+
omittedCount: 0,
|
|
177
|
+
blockChars: 0,
|
|
178
|
+
};
|
|
179
|
+
return messages;
|
|
180
|
+
}
|
|
181
|
+
if (report.contextItems.length === 0) {
|
|
182
|
+
this._latestMemoryPromptInclusionReport = {
|
|
183
|
+
...base,
|
|
184
|
+
status: "no_results",
|
|
185
|
+
includedCount: 0,
|
|
186
|
+
omittedCount: 0,
|
|
187
|
+
blockChars: 0,
|
|
188
|
+
};
|
|
189
|
+
return messages;
|
|
190
|
+
}
|
|
191
|
+
const block = buildMemoryPromptBlock(report.contextItems);
|
|
192
|
+
if (!block.text) {
|
|
193
|
+
this._latestMemoryPromptInclusionReport = {
|
|
194
|
+
...base,
|
|
195
|
+
status: "empty_block",
|
|
196
|
+
includedCount: block.includedCount,
|
|
197
|
+
omittedCount: block.omittedCount,
|
|
198
|
+
blockChars: 0,
|
|
199
|
+
};
|
|
200
|
+
return messages;
|
|
201
|
+
}
|
|
202
|
+
const wrapped = wrapUntrustedText(block.text, "memory:pi-okf");
|
|
203
|
+
const evidenceMessage = {
|
|
204
|
+
role: "custom",
|
|
205
|
+
customType: "memory_evidence",
|
|
206
|
+
content: [{ type: "text", text: wrapped }],
|
|
207
|
+
display: false,
|
|
208
|
+
timestamp: Date.now(),
|
|
209
|
+
};
|
|
210
|
+
this._latestMemoryPromptInclusionReport = {
|
|
211
|
+
...base,
|
|
212
|
+
status: "included",
|
|
213
|
+
includedCount: block.includedCount,
|
|
214
|
+
omittedCount: block.omittedCount,
|
|
215
|
+
blockChars: wrapped.length,
|
|
216
|
+
sourceLabel: "memory:pi-okf",
|
|
217
|
+
};
|
|
218
|
+
return [...messages, evidenceMessage];
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// `base` may not exist yet if the throw happened before it was computed (e.g.
|
|
222
|
+
// settings access or `report.contextItems` itself threw), so this branch cannot
|
|
223
|
+
// rely on it -- fall back to safe, fixed defaults rather than risk referencing
|
|
224
|
+
// a partially-evaluated value.
|
|
225
|
+
this._latestMemoryPromptInclusionReport = {
|
|
226
|
+
enabled: false,
|
|
227
|
+
includeInPrompt: false,
|
|
228
|
+
selectedItemCount: 0,
|
|
229
|
+
status: "failed",
|
|
230
|
+
includedCount: 0,
|
|
231
|
+
omittedCount: 0,
|
|
232
|
+
blockChars: 0,
|
|
233
|
+
};
|
|
234
|
+
return messages;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** Read-only inspection of the latest memory-prompt-inclusion decision, for tests/debugging and context_audit. */
|
|
238
|
+
getMemoryPromptInclusionReport() {
|
|
239
|
+
return this._latestMemoryPromptInclusionReport ?? defaultMemoryPromptInclusionReport();
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Combines the already-stored, no-arg latest reports (never re-queries the provider or
|
|
243
|
+
* touches the OKF directory) into the safe, allow-list-projected shape context_audit
|
|
244
|
+
* exposes. See context/memory-diagnostics.ts for why this projection is allow-list
|
|
245
|
+
* based rather than a spread-then-delete of the raw report.
|
|
246
|
+
*/
|
|
247
|
+
getMemoryAuditDiagnostics() {
|
|
248
|
+
const settings = this.deps.getSettingsManager().getMemoryRetrievalSettings();
|
|
249
|
+
return {
|
|
250
|
+
retrieval: sanitizeMemoryRetrievalReportForDiagnostics(this.getMemoryRetrievalReport(), settings),
|
|
251
|
+
promptInclusion: this.getMemoryPromptInclusionReport(),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Zero-I/O gate for cross-session recall (R3): skip trivial turns (short acks, slash commands) so
|
|
256
|
+
* recall only runs when it could plausibly help. The provider's similarity cutoff is the real
|
|
257
|
+
* filter — this just avoids the index query on turns that obviously don't warrant it.
|
|
258
|
+
*/
|
|
259
|
+
shouldAttemptRecall(text) {
|
|
260
|
+
const t = text.trim();
|
|
261
|
+
if (t.length < 12 || t.startsWith("/"))
|
|
262
|
+
return false;
|
|
263
|
+
const words = t.split(/\s+/).filter((w) => w.length >= 3);
|
|
264
|
+
// R4 adaptive gate: if recall has rarely been used lately (enough samples to trust the signal),
|
|
265
|
+
// raise the bar so we only recall on clearly substantial turns — and relax it again once recall
|
|
266
|
+
// starts paying off. Never fully disabled, so the loop can recover.
|
|
267
|
+
const recallRarelyUseful = this._effectivenessTracker.sampleCount >= 5 && this._effectivenessTracker.usefulLately() < 0.15;
|
|
268
|
+
return words.length >= (recallRarelyUseful ? 6 : 3);
|
|
269
|
+
}
|
|
270
|
+
/** R4: score whether the agent actually used an injected recall page, so the recall gate can adapt. */
|
|
271
|
+
recordRecallOutcome(recallText, queryText, responseText) {
|
|
272
|
+
this._effectivenessTracker.recordRecallOutcome(recallText, queryText, responseText);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* (Re)build the memory subsystem: a fresh MemoryManager (reload-safe), register the bundled
|
|
276
|
+
* file-store + any extension-contributed providers, initialize, then surface the memory tools and
|
|
277
|
+
* the frozen system-prompt block. Best-effort: never throws into the session lifecycle.
|
|
278
|
+
*/
|
|
279
|
+
async initialize() {
|
|
280
|
+
try {
|
|
281
|
+
// Release the previous generation's providers (locks/handles) before recreating, so a
|
|
282
|
+
// reload does not orphan the old MemoryManager. No-op on first init / for file-store.
|
|
283
|
+
await this._memoryManager.shutdownAll().catch(() => { });
|
|
284
|
+
const manager = new MemoryManager();
|
|
285
|
+
manager.registerProvider(new FileStoreProvider());
|
|
286
|
+
// Bundled read-only cross-session recall (R3): indexes past-session transcripts and answers
|
|
287
|
+
// prefetch() with a <memory_context> page. Never writes.
|
|
288
|
+
manager.registerProvider(new TranscriptRecallProvider());
|
|
289
|
+
for (const provider of this._pendingMemoryProviders) {
|
|
290
|
+
try {
|
|
291
|
+
manager.registerProvider(provider);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// Duplicate name or reserved-tool collision — skip this provider, keep the rest.
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
this._memoryManager = manager;
|
|
298
|
+
await manager.initializeAll(this.deps.getSessionId(), {
|
|
299
|
+
agentDir: this.deps.getAgentDir(),
|
|
300
|
+
cwd: this.deps.getCwd(),
|
|
301
|
+
isChildSession: this.deps.isChildSession(),
|
|
302
|
+
});
|
|
303
|
+
// Surface memory tools + the frozen memory block now that providers are initialized.
|
|
304
|
+
// refreshToolRegistry() ends in setActiveToolsByName(), which rebuilds AND assigns the
|
|
305
|
+
// system prompt (including the memory block), so no explicit _rebuildSystemPrompt is needed.
|
|
306
|
+
this.deps.refreshToolRegistry();
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
console.error("Memory subsystem init failed:", error instanceof Error ? error.message : String(error));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/** Register a memory provider contributed by an extension; applied on the next memory (re)init. */
|
|
313
|
+
registerMemoryProvider(provider) {
|
|
314
|
+
if (!this._pendingMemoryProviders.some((p) => p.name === provider.name)) {
|
|
315
|
+
this._pendingMemoryProviders.push(provider);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** Reload starts memory providers fresh; loaded extensions re-register before the next `initialize()`. */
|
|
319
|
+
clearPendingProviders() {
|
|
320
|
+
this._pendingMemoryProviders = [];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=memory-controller.js.map
|