@caupulican/pi-adaptative 0.81.26 → 0.81.27
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 +31 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +3 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +4 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +19 -2
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +21 -10
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +71 -33
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
- package/dist/core/autonomy/bounded-completion.js +59 -19
- package/dist/core/autonomy/bounded-completion.js.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.d.ts +31 -0
- package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -0
- package/dist/core/autonomy/lane-tool-surface.js +96 -0
- package/dist/core/autonomy/lane-tool-surface.js.map +1 -0
- package/dist/core/background-lane-controller.d.ts +6 -3
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +134 -25
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/billing-failover-controller.d.ts.map +1 -1
- package/dist/core/billing-failover-controller.js +1 -1
- package/dist/core/billing-failover-controller.js.map +1 -1
- package/dist/core/context/current-work-memory.d.ts +9 -0
- package/dist/core/context/current-work-memory.d.ts.map +1 -0
- package/dist/core/context/current-work-memory.js +61 -0
- package/dist/core/context/current-work-memory.js.map +1 -0
- package/dist/core/context/file-store-memory-provider.d.ts +9 -0
- package/dist/core/context/file-store-memory-provider.d.ts.map +1 -0
- package/dist/core/context/file-store-memory-provider.js +106 -0
- package/dist/core/context/file-store-memory-provider.js.map +1 -0
- package/dist/core/context/long-term-memory-trigger.d.ts +15 -0
- package/dist/core/context/long-term-memory-trigger.d.ts.map +1 -0
- package/dist/core/context/long-term-memory-trigger.js +39 -0
- package/dist/core/context/long-term-memory-trigger.js.map +1 -0
- package/dist/core/context/memory-prompt-block.d.ts +3 -1
- package/dist/core/context/memory-prompt-block.d.ts.map +1 -1
- package/dist/core/context/memory-prompt-block.js +20 -3
- package/dist/core/context/memory-prompt-block.js.map +1 -1
- package/dist/core/context/memory-prompt-budget.d.ts +17 -0
- package/dist/core/context/memory-prompt-budget.d.ts.map +1 -0
- package/dist/core/context/memory-prompt-budget.js +64 -0
- package/dist/core/context/memory-prompt-budget.js.map +1 -0
- package/dist/core/context/memory-provider-contract.d.ts.map +1 -1
- package/dist/core/context/memory-provider-contract.js +2 -5
- package/dist/core/context/memory-provider-contract.js.map +1 -1
- package/dist/core/context/memory-tier-composer.d.ts +27 -0
- package/dist/core/context/memory-tier-composer.d.ts.map +1 -0
- package/dist/core/context/memory-tier-composer.js +79 -0
- package/dist/core/context/memory-tier-composer.js.map +1 -0
- package/dist/core/context-gc.d.ts.map +1 -1
- package/dist/core/context-gc.js +1 -0
- package/dist/core/context-gc.js.map +1 -1
- package/dist/core/cost/cost-summary.d.ts +3 -1
- package/dist/core/cost/cost-summary.d.ts.map +1 -1
- package/dist/core/cost/cost-summary.js +3 -3
- package/dist/core/cost/cost-summary.js.map +1 -1
- package/dist/core/cost-guard.d.ts +17 -7
- package/dist/core/cost-guard.d.ts.map +1 -1
- package/dist/core/cost-guard.js +33 -10
- package/dist/core/cost-guard.js.map +1 -1
- package/dist/core/default-tool-surface.d.ts +9 -0
- package/dist/core/default-tool-surface.d.ts.map +1 -0
- package/dist/core/default-tool-surface.js +18 -0
- package/dist/core/default-tool-surface.js.map +1 -0
- package/dist/core/delegation/worker-runner.d.ts +11 -5
- package/dist/core/delegation/worker-runner.d.ts.map +1 -1
- package/dist/core/delegation/worker-runner.js +24 -12
- package/dist/core/delegation/worker-runner.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +15 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +2 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +7 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +9 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/failure-corpus.d.ts.map +1 -1
- package/dist/core/failure-corpus.js +2 -4
- package/dist/core/failure-corpus.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +9 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.js +4 -3
- package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts +12 -0
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +42 -4
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/memory/memory-manager.d.ts +8 -3
- package/dist/core/memory/memory-manager.d.ts.map +1 -1
- package/dist/core/memory/memory-manager.js +27 -11
- package/dist/core/memory/memory-manager.js.map +1 -1
- package/dist/core/memory/memory-provider.d.ts +5 -1
- package/dist/core/memory/memory-provider.d.ts.map +1 -1
- package/dist/core/memory/memory-provider.js.map +1 -1
- package/dist/core/memory/providers/file-store.d.ts +3 -1
- package/dist/core/memory/providers/file-store.d.ts.map +1 -1
- package/dist/core/memory/providers/file-store.js +7 -2
- package/dist/core/memory/providers/file-store.js.map +1 -1
- package/dist/core/memory/providers/transcript-recall.d.ts +1 -0
- package/dist/core/memory/providers/transcript-recall.d.ts.map +1 -1
- package/dist/core/memory/providers/transcript-recall.js +4 -7
- package/dist/core/memory/providers/transcript-recall.js.map +1 -1
- package/dist/core/memory-controller.d.ts +36 -10
- package/dist/core/memory-controller.d.ts.map +1 -1
- package/dist/core/memory-controller.js +137 -21
- package/dist/core/memory-controller.js.map +1 -1
- package/dist/core/model-registry.d.ts +23 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +87 -8
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +30 -11
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-router-controller.d.ts.map +1 -1
- package/dist/core/model-router-controller.js +33 -29
- package/dist/core/model-router-controller.js.map +1 -1
- package/dist/core/model-selection-controller.d.ts +3 -0
- package/dist/core/model-selection-controller.d.ts.map +1 -1
- package/dist/core/model-selection-controller.js +8 -1
- package/dist/core/model-selection-controller.js.map +1 -1
- package/dist/core/models/perf-profile.d.ts.map +1 -1
- package/dist/core/models/perf-profile.js +95 -33
- package/dist/core/models/perf-profile.js.map +1 -1
- package/dist/core/profile-filter-controller.d.ts +11 -0
- package/dist/core/profile-filter-controller.d.ts.map +1 -1
- package/dist/core/profile-filter-controller.js +56 -11
- package/dist/core/profile-filter-controller.js.map +1 -1
- package/dist/core/profile-registry.d.ts +2 -2
- package/dist/core/profile-registry.d.ts.map +1 -1
- package/dist/core/profile-registry.js +58 -7
- package/dist/core/profile-registry.js.map +1 -1
- package/dist/core/reflection-controller.d.ts +7 -7
- package/dist/core/reflection-controller.d.ts.map +1 -1
- package/dist/core/reflection-controller.js +78 -12
- package/dist/core/reflection-controller.js.map +1 -1
- package/dist/core/research/research-runner.d.ts +2 -2
- package/dist/core/research/research-runner.d.ts.map +1 -1
- package/dist/core/research/research-runner.js +8 -6
- package/dist/core/research/research-runner.js.map +1 -1
- package/dist/core/resource-loader.d.ts +4 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +11 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +13 -1
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +127 -35
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/sdk.d.ts +8 -7
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +35 -5
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/security/secret-text.d.ts +3 -0
- package/dist/core/security/secret-text.d.ts.map +1 -0
- package/dist/core/security/secret-text.js +26 -0
- package/dist/core/security/secret-text.js.map +1 -0
- package/dist/core/settings-manager.d.ts +59 -17
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +300 -51
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +1 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +7 -0
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.js +23 -1
- package/dist/core/system-prompt-builder.js.map +1 -1
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +15 -11
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +69 -29
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +2 -0
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/config-backup.d.ts +7 -1
- package/dist/modes/interactive/config-backup.d.ts.map +1 -1
- package/dist/modes/interactive/config-backup.js +196 -32
- package/dist/modes/interactive/config-backup.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +10 -28
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
- package/dist/modes/interactive/local-model-commands.js +2 -0
- package/dist/modes/interactive/local-model-commands.js.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.d.ts +6 -4
- package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.js +248 -65
- package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
- package/dist/modes/interactive/report-commands.d.ts.map +1 -1
- package/dist/modes/interactive/report-commands.js +8 -2
- package/dist/modes/interactive/report-commands.js.map +1 -1
- package/dist/modes/interactive/settings-selector-flow.d.ts.map +1 -1
- package/dist/modes/interactive/settings-selector-flow.js +8 -0
- package/dist/modes/interactive/settings-selector-flow.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +3 -1
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/docs/extensions.md +32 -0
- package/docs/resources.md +15 -7
- package/docs/settings.md +54 -5
- 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/preset.ts +2 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +44 -35
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-manager.js","sourceRoot":"","sources":["../../../src/core/memory/memory-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ToolDefinition } from \"../extensions/types.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"./memory-provider.ts\";\n\nexport class MemoryManager {\n\tprivate readonly providers: MemoryProvider[] = [];\n\tprivate readonly activeProviders: Set<string> = new Set();\n\tprivate readonly registeredToolNames: Set<string> = new Set();\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate systemPromptBlockCache?: string;\n\n\t// Core reserved tool names to prevent hijacking or schema corruption.\n\tprivate static readonly RESERVED_CORE_TOOL_NAMES = new Set([\n\t\t\"read\",\n\t\t\"write\",\n\t\t\"grep\",\n\t\t\"find\",\n\t\t\"ls\",\n\t\t\"bash\",\n\t\t\"ask_user\",\n\t\t\"skillify\",\n\t\t\"skill_audit\",\n\t\t\"skill_search\",\n\t\t\"skill_open\",\n\t]);\n\n\tpublic registerProvider(p: MemoryProvider): void {\n\t\tif (this.providers.some((prov) => prov.name === p.name)) {\n\t\t\tthrow new Error(`Memory provider ${p.name} is already registered.`);\n\t\t}\n\n\t\tif (p.getToolDefinitions) {\n\t\t\tlet tools: ToolDefinition[] = [];\n\t\t\ttry {\n\t\t\t\ttools = p.getToolDefinitions() ?? [];\n\t\t\t} catch (err) {\n\t\t\t\tthrow new Error(`Failed to get tool definitions from provider ${p.name}: ${String(err)}`);\n\t\t\t}\n\n\t\t\tfor (const tool of tools) {\n\t\t\t\tif (MemoryManager.RESERVED_CORE_TOOL_NAMES.has(tool.name)) {\n\t\t\t\t\tthrow new Error(`Memory provider ${p.name} tried to register reserved core tool: ${tool.name}`);\n\t\t\t\t}\n\t\t\t\tif (this.registeredToolNames.has(tool.name)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Memory provider tool name collision: ${tool.name} is already registered. First-registration-wins.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const tool of tools) {\n\t\t\t\tthis.registeredToolNames.add(tool.name);\n\t\t\t}\n\t\t}\n\n\t\tthis.providers.push(p);\n\t}\n\n\tpublic async initializeAll(sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.activeProviders.clear();\n\t\tthis.systemPromptBlockCache = undefined;\n\n\t\tfor (const p of this.providers) {\n\t\t\ttry {\n\t\t\t\tconst available = await p.isAvailable();\n\t\t\t\tif (!available) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait p.initialize(sessionId, ctx);\n\t\t\t\tthis.activeProviders.add(p.name);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed to initialize and was deactivated:`, err);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic buildSystemPromptBlock(): string {\n\t\tif (this.systemPromptBlockCache !== undefined) {\n\t\t\treturn this.systemPromptBlockCache;\n\t\t}\n\n\t\tthis.systemPromptBlockCache = this._composeSystemPromptBlock();\n\t\treturn this.systemPromptBlockCache;\n\t}\n\n\t/**\n\t * Compose the memory block freshly from the providers, BYPASSING the frozen cache used by the\n\t * system prompt. Used by end-of-loop reflection so its confront-before-write sees the live memory\n\t * (including writes made earlier in the same session) without churning the prefix-cache-stable\n\t * system prompt block.\n\t */\n\tpublic buildSystemPromptBlockFresh(): string {\n\t\treturn this._composeSystemPromptBlock();\n\t}\n\n\tprivate _composeSystemPromptBlock(): string {\n\t\tconst blocks: string[] = [];\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.systemPromptBlock) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst block = p.systemPromptBlock();\n\t\t\t\tif (block) {\n\t\t\t\t\tblocks.push(block);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed to generate system prompt block:`, err);\n\t\t\t}\n\t\t}\n\t\treturn blocks.join(\"\\n\\n\");\n\t}\n\n\tpublic async prefetch(query: string): Promise<string> {\n\t\tconst results: string[] = [];\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.prefetch) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst text = await p.prefetch(query);\n\t\t\t\tif (text) {\n\t\t\t\t\tresults.push(text);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed during prefetch:`, err);\n\t\t\t}\n\t\t}\n\t\treturn results.join(\"\\n\\n\");\n\t}\n\n\tpublic async syncTurn(user: string, assistant: string): Promise<void> {\n\t\tif (this.ctx?.isChildSession) {\n\t\t\treturn; // Write-gated: skip writes in child sessions\n\t\t}\n\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.syncTurn) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait p.syncTurn(user, assistant);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed during syncTurn:`, err);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic async onPreCompress(): Promise<string> {\n\t\tconst insights: string[] = [];\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.onPreCompress) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst insight = await p.onPreCompress();\n\t\t\t\tif (insight) {\n\t\t\t\t\tinsights.push(insight);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed during onPreCompress:`, err);\n\t\t\t}\n\t\t}\n\t\treturn insights.join(\"\\n\\n\");\n\t}\n\n\tpublic async onSessionEnd(): Promise<void> {\n\t\tif (this.ctx?.isChildSession) {\n\t\t\treturn; // Write-gated: skip writes in child sessions\n\t\t}\n\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.onSessionEnd) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait p.onSessionEnd();\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed during onSessionEnd:`, err);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic async shutdownAll(): Promise<void> {\n\t\t// Shutdown in reverse registration order\n\t\tconst reversed = [...this.providers].reverse();\n\t\tfor (const p of reversed) {\n\t\t\tif (!this.activeProviders.has(p.name)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait p.shutdown();\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed to shutdown cleanly:`, err);\n\t\t\t}\n\t\t}\n\t\tthis.activeProviders.clear();\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\tconst tools: ToolDefinition[] = [];\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.getToolDefinitions) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\ttools.push(...p.getToolDefinitions());\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Failed to get tool definitions from provider ${p.name}:`, err);\n\t\t\t}\n\t\t}\n\t\treturn tools;\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\tconst markers = new Set<string>();\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.getContextMarkers) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst list = p.getContextMarkers() ?? [];\n\t\t\t\tfor (const m of list) {\n\t\t\t\t\tmarkers.add(m);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Failed to get context markers from provider ${p.name}:`, err);\n\t\t\t}\n\t\t}\n\t\treturn [...markers];\n\t}\n\n\tpublic reset(): void {\n\t\tthis.providers.length = 0;\n\t\tthis.activeProviders.clear();\n\t\tthis.registeredToolNames.clear();\n\t\tthis.systemPromptBlockCache = undefined;\n\t\tthis.ctx = undefined;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"memory-manager.js","sourceRoot":"","sources":["../../../src/core/memory/memory-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MemoryPromptBudget } from \"../context/memory-prompt-budget.ts\";\nimport {\n\tDEFAULT_EXTERNAL_MEMORY_EGRESS_POLICY,\n\thasSecretLikeMemoryText,\n\ttype MemoryEgressPolicy,\n} from \"../context/memory-provider-contract.ts\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { wrapUntrustedText } from \"../security/untrusted-boundary.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"./memory-provider.ts\";\n\nexport interface MemoryPrefetchOptions {\n\texternalEgressPolicy?: MemoryEgressPolicy;\n}\n\nexport class MemoryManager {\n\tprivate readonly providers: MemoryProvider[] = [];\n\tprivate readonly activeProviders: Set<string> = new Set();\n\tprivate readonly registeredToolNames: Set<string> = new Set();\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate systemPromptBlockCache?: { key: string; text: string };\n\n\t// Core reserved tool names to prevent hijacking or schema corruption.\n\tprivate static readonly RESERVED_CORE_TOOL_NAMES = new Set([\n\t\t\"read\",\n\t\t\"write\",\n\t\t\"grep\",\n\t\t\"find\",\n\t\t\"ls\",\n\t\t\"bash\",\n\t\t\"ask_user\",\n\t\t\"skillify\",\n\t\t\"skill_audit\",\n\t\t\"skill_search\",\n\t\t\"skill_open\",\n\t]);\n\n\tpublic registerProvider(p: MemoryProvider): void {\n\t\tif (this.providers.some((prov) => prov.name === p.name)) {\n\t\t\tthrow new Error(`Memory provider ${p.name} is already registered.`);\n\t\t}\n\n\t\tif (p.getToolDefinitions) {\n\t\t\tlet tools: ToolDefinition[] = [];\n\t\t\ttry {\n\t\t\t\ttools = p.getToolDefinitions() ?? [];\n\t\t\t} catch (err) {\n\t\t\t\tthrow new Error(`Failed to get tool definitions from provider ${p.name}: ${String(err)}`);\n\t\t\t}\n\n\t\t\tfor (const tool of tools) {\n\t\t\t\tif (MemoryManager.RESERVED_CORE_TOOL_NAMES.has(tool.name)) {\n\t\t\t\t\tthrow new Error(`Memory provider ${p.name} tried to register reserved core tool: ${tool.name}`);\n\t\t\t\t}\n\t\t\t\tif (this.registeredToolNames.has(tool.name)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Memory provider tool name collision: ${tool.name} is already registered. First-registration-wins.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const tool of tools) {\n\t\t\t\tthis.registeredToolNames.add(tool.name);\n\t\t\t}\n\t\t}\n\n\t\tthis.providers.push(p);\n\t}\n\n\tpublic async initializeAll(sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.activeProviders.clear();\n\t\tthis.systemPromptBlockCache = undefined;\n\n\t\tfor (const p of this.providers) {\n\t\t\ttry {\n\t\t\t\tconst available = await p.isAvailable();\n\t\t\t\tif (!available) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait p.initialize(sessionId, ctx);\n\t\t\t\tthis.activeProviders.add(p.name);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed to initialize and was deactivated:`, err);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic buildSystemPromptBlock(budget?: MemoryPromptBudget): string {\n\t\tconst key =\n\t\t\tbudget === undefined\n\t\t\t\t? \"none\"\n\t\t\t\t: `${budget.enabled}:${budget.compact}:${budget.maxLines}:${budget.maxEstimatedTokens}:${budget.maxChars}`;\n\t\tif (this.systemPromptBlockCache !== undefined && this.systemPromptBlockCache.key === key) {\n\t\t\treturn this.systemPromptBlockCache.text;\n\t\t}\n\n\t\tconst text = this._composeSystemPromptBlock(budget);\n\t\tthis.systemPromptBlockCache = { key, text };\n\t\treturn text;\n\t}\n\n\t/**\n\t * Compose the memory block freshly from the providers, BYPASSING the frozen cache used by the\n\t * system prompt. Used by end-of-loop reflection so its confront-before-write sees the live memory\n\t * (including writes made earlier in the same session) without churning the prefix-cache-stable\n\t * system prompt block.\n\t */\n\tpublic buildSystemPromptBlockFresh(budget?: MemoryPromptBudget): string {\n\t\treturn this._composeSystemPromptBlock(budget);\n\t}\n\n\tprivate _composeSystemPromptBlock(budget?: MemoryPromptBudget): string {\n\t\tconst blocks: string[] = [];\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.systemPromptBlock) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst block = p.systemPromptBlock(budget);\n\t\t\t\tif (block) {\n\t\t\t\t\tblocks.push(block);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed to generate system prompt block:`, err);\n\t\t\t}\n\t\t}\n\t\treturn blocks.join(\"\\n\\n\");\n\t}\n\n\tpublic async prefetch(query: string, options: MemoryPrefetchOptions = {}): Promise<string> {\n\t\tconst results: string[] = [];\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.prefetch) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (p.egress !== \"local\") {\n\t\t\t\tconst policy = options.externalEgressPolicy ?? DEFAULT_EXTERNAL_MEMORY_EGRESS_POLICY;\n\t\t\t\tif (\n\t\t\t\t\t!policy.enabled ||\n\t\t\t\t\t!policy.allowExternalEgress ||\n\t\t\t\t\t!policy.allowQueryText ||\n\t\t\t\t\tquery.length > policy.maxOutboundChars ||\n\t\t\t\t\t(policy.redactSecretLikeText && hasSecretLikeMemoryText(query))\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst text = await p.prefetch(query);\n\t\t\t\tif (text) {\n\t\t\t\t\tresults.push(wrapUntrustedText(text, `memory:${p.name}`));\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed during prefetch:`, err);\n\t\t\t}\n\t\t}\n\t\treturn results.join(\"\\n\\n\");\n\t}\n\n\tpublic async syncTurn(user: string, assistant: string): Promise<void> {\n\t\tif (this.ctx?.isChildSession) {\n\t\t\treturn; // Write-gated: skip writes in child sessions\n\t\t}\n\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.syncTurn) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait p.syncTurn(user, assistant);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed during syncTurn:`, err);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic async onPreCompress(): Promise<string> {\n\t\tconst insights: string[] = [];\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.onPreCompress) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst insight = await p.onPreCompress();\n\t\t\t\tif (insight) {\n\t\t\t\t\tinsights.push(insight);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed during onPreCompress:`, err);\n\t\t\t}\n\t\t}\n\t\treturn insights.join(\"\\n\\n\");\n\t}\n\n\tpublic async onSessionEnd(): Promise<void> {\n\t\tif (this.ctx?.isChildSession) {\n\t\t\treturn; // Write-gated: skip writes in child sessions\n\t\t}\n\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.onSessionEnd) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait p.onSessionEnd();\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed during onSessionEnd:`, err);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic async shutdownAll(): Promise<void> {\n\t\t// Shutdown in reverse registration order\n\t\tconst reversed = [...this.providers].reverse();\n\t\tfor (const p of reversed) {\n\t\t\tif (!this.activeProviders.has(p.name)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait p.shutdown();\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Memory provider ${p.name} failed to shutdown cleanly:`, err);\n\t\t\t}\n\t\t}\n\t\tthis.activeProviders.clear();\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\tconst tools: ToolDefinition[] = [];\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.getToolDefinitions) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\ttools.push(...p.getToolDefinitions());\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Failed to get tool definitions from provider ${p.name}:`, err);\n\t\t\t}\n\t\t}\n\t\treturn tools;\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\tconst markers = new Set<string>();\n\t\tfor (const p of this.providers) {\n\t\t\tif (!this.activeProviders.has(p.name) || !p.getContextMarkers) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst list = p.getContextMarkers() ?? [];\n\t\t\t\tfor (const m of list) {\n\t\t\t\t\tmarkers.add(m);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(`Failed to get context markers from provider ${p.name}:`, err);\n\t\t\t}\n\t\t}\n\t\treturn [...markers];\n\t}\n\n\tpublic reset(): void {\n\t\tthis.providers.length = 0;\n\t\tthis.activeProviders.clear();\n\t\tthis.registeredToolNames.clear();\n\t\tthis.systemPromptBlockCache = undefined;\n\t\tthis.ctx = undefined;\n\t}\n}\n"]}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { MemoryPromptBudget } from "../context/memory-prompt-budget.ts";
|
|
1
2
|
import type { ToolDefinition } from "../extensions/types.ts";
|
|
2
3
|
export type MemorySurface = "context" | "routing" | "tooling" | "parametric";
|
|
4
|
+
/** Where raw memory queries are processed. Omitted classifications fail closed as external. */
|
|
5
|
+
export type MemoryProviderEgress = "local" | "external";
|
|
3
6
|
export interface MemoryCapabilities {
|
|
4
7
|
surfaces: MemorySurface[];
|
|
5
8
|
}
|
|
@@ -10,11 +13,12 @@ export interface MemoryLifecycleContext {
|
|
|
10
13
|
}
|
|
11
14
|
export interface MemoryProvider {
|
|
12
15
|
readonly name: string;
|
|
16
|
+
readonly egress?: MemoryProviderEgress;
|
|
13
17
|
isAvailable(): boolean | Promise<boolean>;
|
|
14
18
|
getCapabilities(): MemoryCapabilities;
|
|
15
19
|
initialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void>;
|
|
16
20
|
shutdown(): Promise<void>;
|
|
17
|
-
systemPromptBlock?(): string;
|
|
21
|
+
systemPromptBlock?(budget?: MemoryPromptBudget): string;
|
|
18
22
|
prefetch?(query: string): Promise<string>;
|
|
19
23
|
syncTurn?(user: string, assistant: string): Promise<void>;
|
|
20
24
|
onPreCompress?(): Promise<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-provider.d.ts","sourceRoot":"","sources":["../../../src/core/memory/memory-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAE7E,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,WAAW,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,eAAe,IAAI,kBAAkB,CAAC;IACtC,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1B,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"memory-provider.d.ts","sourceRoot":"","sources":["../../../src/core/memory/memory-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAE7E,+FAA+F;AAC/F,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,UAAU,CAAC;AAExD,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;IACvC,WAAW,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,eAAe,IAAI,kBAAkB,CAAC;IACtC,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1B,iBAAiB,CAAC,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACxD,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,aAAa,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,kBAAkB,CAAC,IAAI,cAAc,EAAE,CAAC;IACxC,iBAAiB,CAAC,IAAI,MAAM,EAAE,CAAC;CAC/B","sourcesContent":["import type { MemoryPromptBudget } from \"../context/memory-prompt-budget.ts\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\n\nexport type MemorySurface = \"context\" | \"routing\" | \"tooling\" | \"parametric\";\n\n/** Where raw memory queries are processed. Omitted classifications fail closed as external. */\nexport type MemoryProviderEgress = \"local\" | \"external\";\n\nexport interface MemoryCapabilities {\n\tsurfaces: MemorySurface[];\n}\n\nexport interface MemoryLifecycleContext {\n\tagentDir: string;\n\tcwd: string;\n\tisChildSession: boolean;\n}\n\nexport interface MemoryProvider {\n\treadonly name: string;\n\treadonly egress?: MemoryProviderEgress;\n\tisAvailable(): boolean | Promise<boolean>;\n\tgetCapabilities(): MemoryCapabilities;\n\tinitialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void>;\n\tshutdown(): Promise<void>;\n\t// context surface:\n\tsystemPromptBlock?(budget?: MemoryPromptBudget): string;\n\tprefetch?(query: string): Promise<string>;\n\tsyncTurn?(user: string, assistant: string): Promise<void>;\n\tonPreCompress?(): Promise<string>;\n\tonSessionEnd?(): Promise<void>;\n\tgetToolDefinitions?(): ToolDefinition[];\n\tgetContextMarkers?(): string[];\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-provider.js","sourceRoot":"","sources":["../../../src/core/memory/memory-provider.ts"],"names":[],"mappings":"","sourcesContent":["import type { ToolDefinition } from \"../extensions/types.ts\";\n\nexport type MemorySurface = \"context\" | \"routing\" | \"tooling\" | \"parametric\";\n\nexport interface MemoryCapabilities {\n\tsurfaces: MemorySurface[];\n}\n\nexport interface MemoryLifecycleContext {\n\tagentDir: string;\n\tcwd: string;\n\tisChildSession: boolean;\n}\n\nexport interface MemoryProvider {\n\treadonly name: string;\n\tisAvailable(): boolean | Promise<boolean>;\n\tgetCapabilities(): MemoryCapabilities;\n\tinitialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void>;\n\tshutdown(): Promise<void>;\n\t// context surface:\n\tsystemPromptBlock?(): string;\n\tprefetch?(query: string): Promise<string>;\n\tsyncTurn?(user: string, assistant: string): Promise<void>;\n\tonPreCompress?(): Promise<string>;\n\tonSessionEnd?(): Promise<void>;\n\tgetToolDefinitions?(): ToolDefinition[];\n\tgetContextMarkers?(): string[];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"memory-provider.js","sourceRoot":"","sources":["../../../src/core/memory/memory-provider.ts"],"names":[],"mappings":"","sourcesContent":["import type { MemoryPromptBudget } from \"../context/memory-prompt-budget.ts\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\n\nexport type MemorySurface = \"context\" | \"routing\" | \"tooling\" | \"parametric\";\n\n/** Where raw memory queries are processed. Omitted classifications fail closed as external. */\nexport type MemoryProviderEgress = \"local\" | \"external\";\n\nexport interface MemoryCapabilities {\n\tsurfaces: MemorySurface[];\n}\n\nexport interface MemoryLifecycleContext {\n\tagentDir: string;\n\tcwd: string;\n\tisChildSession: boolean;\n}\n\nexport interface MemoryProvider {\n\treadonly name: string;\n\treadonly egress?: MemoryProviderEgress;\n\tisAvailable(): boolean | Promise<boolean>;\n\tgetCapabilities(): MemoryCapabilities;\n\tinitialize(sessionId: string, ctx: MemoryLifecycleContext): Promise<void>;\n\tshutdown(): Promise<void>;\n\t// context surface:\n\tsystemPromptBlock?(budget?: MemoryPromptBudget): string;\n\tprefetch?(query: string): Promise<string>;\n\tsyncTurn?(user: string, assistant: string): Promise<void>;\n\tonPreCompress?(): Promise<string>;\n\tonSessionEnd?(): Promise<void>;\n\tgetToolDefinitions?(): ToolDefinition[];\n\tgetContextMarkers?(): string[];\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MemoryPromptBudget } from "../../context/memory-prompt-budget.ts";
|
|
1
2
|
import type { ToolDefinition } from "../../extensions/types.ts";
|
|
2
3
|
import type { MemoryLifecycleContext, MemoryProvider } from "../memory-provider.ts";
|
|
3
4
|
/**
|
|
@@ -8,6 +9,7 @@ import type { MemoryLifecycleContext, MemoryProvider } from "../memory-provider.
|
|
|
8
9
|
export declare function supersedeNearDuplicateLine(existing: string, content: string): string | null;
|
|
9
10
|
export declare class FileStoreProvider implements MemoryProvider {
|
|
10
11
|
readonly name = "file-store";
|
|
12
|
+
readonly egress = "local";
|
|
11
13
|
private ctx?;
|
|
12
14
|
private memoryFilePath;
|
|
13
15
|
private userFilePath;
|
|
@@ -20,7 +22,7 @@ export declare class FileStoreProvider implements MemoryProvider {
|
|
|
20
22
|
surfaces: "context"[];
|
|
21
23
|
};
|
|
22
24
|
initialize(_sessionId: string, ctx: MemoryLifecycleContext): Promise<void>;
|
|
23
|
-
systemPromptBlock(): string;
|
|
25
|
+
systemPromptBlock(budget?: MemoryPromptBudget): string;
|
|
24
26
|
prefetch(_query: string): Promise<string>;
|
|
25
27
|
shutdown(): Promise<void>;
|
|
26
28
|
getContextMarkers(): string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../../../src/core/memory/providers/file-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsB3F;AAiBD,qBAAa,iBAAkB,YAAW,cAAc;IACvD,SAAgB,IAAI,gBAAgB;IAEpC,OAAO,CAAC,GAAG,CAAC,CAAyB;IACrC,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,YAAY,CAAM;IAE1B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,eAAe,CAAM;IAG7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAQ;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC,WAAW,IAAI,OAAO,CAE5B;IAEM,eAAe;;MAErB;IAEY,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBtF;IAEM,iBAAiB,IAAI,MAAM,CAwCjC;IAEY,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrD;IAEY,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAErC;IAEM,iBAAiB,IAAI,MAAM,EAAE,CAEnC;IAEM,kBAAkB,IAAI,cAAc,EAAE,CAoK5C;CACD","sourcesContent":["import { existsSync, promises as fs, mkdirSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { type Static, Type } from \"typebox\";\nimport type { ToolDefinition } from \"../../extensions/types.ts\";\nimport { hasInvisibleUnicode, scanContextFileThreats, stripInvisibleUnicode } from \"../../resource-loader.ts\";\nimport { jaccard, tokenize } from \"../../tools/skill-audit.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\n\n/**\n * R5 confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing\n * non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in\n * place and return the rewritten file; otherwise return null (the caller appends normally).\n */\nexport function supersedeNearDuplicateLine(existing: string, content: string): string | null {\n\tconst NEAR_DUP_THRESHOLD = 0.6;\n\tconst contentTokens = tokenize(content);\n\tif (contentTokens.length === 0) return null;\n\tconst lines = existing.split(\"\\n\");\n\tlet bestIdx = -1;\n\tlet bestScore = NEAR_DUP_THRESHOLD;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i].trim();\n\t\tif (!line) continue;\n\t\t// Never supersede structural Markdown (headings, list markers as headings) — a fact must not\n\t\t// silently overwrite section structure (Bug #15).\n\t\tif (line.startsWith(\"#\")) continue;\n\t\tconst score = jaccard(contentTokens, tokenize(line));\n\t\tif (score >= bestScore) {\n\t\t\tbestScore = score;\n\t\t\tbestIdx = i;\n\t\t}\n\t}\n\tif (bestIdx === -1) return null;\n\tlines[bestIdx] = content;\n\treturn lines.join(\"\\n\");\n}\n\nconst memorySchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"add\"), Type.Literal(\"replace\"), Type.Literal(\"remove\")], {\n\t\tdescription: \"Action to perform: add new content, replace existing content, or remove content\",\n\t}),\n\ttarget: Type.Union([Type.Literal(\"memory\"), Type.Literal(\"user\")], {\n\t\tdescription: \"Target file: 'memory' for MEMORY.md, 'user' for USER.md\",\n\t}),\n\tcontent: Type.Optional(Type.String({ description: \"Content to write (required for 'add' or 'replace')\" })),\n\toldContent: Type.Optional(\n\t\tType.String({ description: \"Exact substring to replace or remove (required for 'replace' or 'remove')\" }),\n\t),\n});\n\ntype MemoryParams = Static<typeof memorySchema>;\n\nexport class FileStoreProvider implements MemoryProvider {\n\tpublic readonly name = \"file-store\";\n\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate memoryFilePath = \"\";\n\tprivate userFilePath = \"\";\n\n\tprivate lastWrittenMemory = \"\";\n\tprivate lastWrittenUser = \"\";\n\n\t// Character budgets\n\tprivate static readonly BUDGET_MEMORY = 2200;\n\tprivate static readonly BUDGET_USER = 1375;\n\n\tpublic isAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tpublic getCapabilities() {\n\t\treturn { surfaces: [\"context\" as const] };\n\t}\n\n\tpublic async initialize(_sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.memoryFilePath = join(ctx.agentDir, \"MEMORY.md\");\n\t\tthis.userFilePath = join(ctx.agentDir, \"USER.md\");\n\n\t\t// Ensure agentDir exists\n\t\tif (!existsSync(ctx.agentDir)) {\n\t\t\tmkdirSync(ctx.agentDir, { recursive: true });\n\t\t}\n\n\t\t// Initialize files if they do not exist\n\t\tif (!existsSync(this.memoryFilePath)) {\n\t\t\twriteFileSync(this.memoryFilePath, \"\", \"utf-8\");\n\t\t}\n\t\tif (!existsSync(this.userFilePath)) {\n\t\t\twriteFileSync(this.userFilePath, \"\", \"utf-8\");\n\t\t}\n\n\t\t// Load initial contents\n\t\tthis.lastWrittenMemory = await fs.readFile(this.memoryFilePath, \"utf-8\");\n\t\tthis.lastWrittenUser = await fs.readFile(this.userFilePath, \"utf-8\");\n\t}\n\n\tpublic systemPromptBlock(): string {\n\t\tconst sanitize = (content: string) => {\n\t\t\t// Strip hidden/bidi-control chars before injecting memory into the prompt (defence in depth: the\n\t\t\t// write path already blocks them, but a file edited out-of-band could carry them). Strip #31.\n\t\t\tconst lines = stripInvisibleUnicode(content).cleaned.split(\"\\n\");\n\t\t\tconst sanitizedLines = lines.map((line) => {\n\t\t\t\tconst threats = scanContextFileThreats(line);\n\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\treturn `[BLOCKED: potential threat detected (${threats.join(\", \")})]`;\n\t\t\t\t}\n\t\t\t\treturn line;\n\t\t\t});\n\t\t\treturn sanitizedLines.join(\"\\n\");\n\t\t};\n\n\t\t// Read-time budget guard (cost): the memory tool already caps writes at BUDGET_*, but a file edited\n\t\t// externally (or by any path that bypasses the tool) could be arbitrarily large and would then\n\t\t// bloat the system prompt on EVERY turn. Cap the injected view to the same budget so the per-turn\n\t\t// cost stays bounded; the file on disk is untouched and the model is told it was truncated.\n\t\tconst cap = (content: string, limit: number) => {\n\t\t\tif (content.length <= limit) return content;\n\t\t\treturn `${content.slice(0, limit)}\\n[…truncated to ${limit} chars for the prompt; full file is on disk]`;\n\t\t};\n\n\t\tconst mem = cap(sanitize(this.lastWrittenMemory), FileStoreProvider.BUDGET_MEMORY);\n\t\tconst usr = cap(sanitize(this.lastWrittenUser), FileStoreProvider.BUDGET_USER);\n\n\t\tconst blocks: string[] = [];\n\t\tif (mem.trim()) {\n\t\t\tblocks.push(`## MEMORY.md:\\n${mem}`);\n\t\t}\n\t\tif (usr.trim()) {\n\t\t\tblocks.push(`## USER.md:\\n${usr}`);\n\t\t}\n\n\t\tif (blocks.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\treturn `=== Persistent Memory (file-store) ===\\n[System Note: Below is a snapshot of your persistent memory. You can update these using the 'memory' tool.]\\n\\n${blocks.join(\"\\n\\n\")}`;\n\t}\n\n\tpublic async prefetch(_query: string): Promise<string> {\n\t\t// static system prompt block is sufficient for file-store default; no-op prefetch\n\t\treturn \"\";\n\t}\n\n\tpublic async shutdown(): Promise<void> {\n\t\t// no-op\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\treturn [];\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tname: \"memory\",\n\t\t\t\tlabel: \"Persistent Memory Manager\",\n\t\t\t\tdescription: \"Add, replace, or remove contents in persistent memory files (MEMORY.md/USER.md).\",\n\t\t\t\tparameters: memorySchema,\n\t\t\t\texecute: async (_toolCallId, params: MemoryParams, _signal, _onUpdate, _execCtx) => {\n\t\t\t\t\tif (this.ctx?.isChildSession) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: \"Error: Writes to persistent memory are not allowed in child sessions (subagents).\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: \"Child session write-gated\" },\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { action, target, content, oldContent } = params;\n\n\t\t\t\t\t// Strict-scope injection guard on the high-privilege WRITE path (agy #31): a poisoned\n\t\t\t\t\t// memory entry persists across sessions and is injected into every future system prompt,\n\t\t\t\t\t// so block outright rather than strip. Hidden/bidi-control chars have no legitimate place\n\t\t\t\t\t// in a memory note, so reject those too.\n\t\t\t\t\tif ((action === \"add\" || action === \"replace\") && content !== undefined) {\n\t\t\t\t\t\tif (hasInvisibleUnicode(content)) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: \"Error: memory write rejected — contains hidden/bidirectional control characters.\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Invisible unicode in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst threats = scanContextFileThreats(content, \"strict\");\n\t\t\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: memory write rejected — potential injection/exfiltration detected (${threats.join(\", \")}).`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Threat in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst filePath = target === \"memory\" ? this.memoryFilePath : this.userFilePath;\n\t\t\t\t\tconst budget = target === \"memory\" ? FileStoreProvider.BUDGET_MEMORY : FileStoreProvider.BUDGET_USER;\n\n\t\t\t\t\tlet release: (() => Promise<void>) | undefined;\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// File lock\n\t\t\t\t\t\trelease = await lockfile.lock(filePath, { realpath: false, retries: 5 });\n\n\t\t\t\t\t\tconst lastWritten = target === \"memory\" ? this.lastWrittenMemory : this.lastWrittenUser;\n\t\t\t\t\t\t// Read current file content on disk for drift detection\n\t\t\t\t\t\tconst currentOnDisk = await fs.readFile(filePath, \"utf-8\");\n\t\t\t\t\t\tif (currentOnDisk !== lastWritten) {\n\t\t\t\t\t\t\t// Drift detected. Backup current file and refuse write.\n\t\t\t\t\t\t\tconst backupPath = `${filePath}.bak.${Date.now()}`;\n\t\t\t\t\t\t\tawait fs.writeFile(backupPath, currentOnDisk, \"utf-8\");\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Drift detected. The memory file has been modified out-of-band by an external process. A backup was created at ${backupPath}. Operation aborted.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Drift detected\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet newContent = currentOnDisk;\n\t\t\t\t\t\tif (action === \"add\") {\n\t\t\t\t\t\t\tif (content === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'content' is required for action 'add'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// R5: confront before write. If this fact is a near-duplicate of an existing line,\n\t\t\t\t\t\t\t// supersede it in place instead of appending a redundant copy (prevents append-rot).\n\t\t\t\t\t\t\tconst superseded = supersedeNearDuplicateLine(currentOnDisk, content);\n\t\t\t\t\t\t\tif (superseded !== null) {\n\t\t\t\t\t\t\t\tnewContent = superseded;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnewContent =\n\t\t\t\t\t\t\t\t\tnewContent.endsWith(\"\\n\") || newContent === \"\"\n\t\t\t\t\t\t\t\t\t\t? `${newContent}${content}\\n`\n\t\t\t\t\t\t\t\t\t\t: `${newContent}\\n${content}\\n`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (action === \"replace\") {\n\t\t\t\t\t\t\tif (content === undefined || oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameters 'content' and 'oldContent' are required for action 'replace'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to replace ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, content);\n\t\t\t\t\t\t} else if (action === \"remove\") {\n\t\t\t\t\t\t\tif (oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'oldContent' is required for action 'remove'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to remove ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Budget check\n\t\t\t\t\t\tif (newContent.length > budget) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Memory budget exceeded. ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"} limit is ${budget} characters. Current operation would result in ${newContent.length} characters.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Memory budget exceeded\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Atomic write\n\t\t\t\t\t\tconst tmpPath = `${filePath}.tmp`;\n\t\t\t\t\t\tawait fs.writeFile(tmpPath, newContent, \"utf-8\");\n\t\t\t\t\t\tawait fs.rename(tmpPath, filePath);\n\n\t\t\t\t\t\t// Update in-memory tracker\n\t\t\t\t\t\tif (target === \"memory\") {\n\t\t\t\t\t\t\tthis.lastWrittenMemory = newContent;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.lastWrittenUser = newContent;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Successfully updated ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"}.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: true },\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Error: Failed to perform memory operation: ${String(err)}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: String(err) },\n\t\t\t\t\t\t};\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (release) {\n\t\t\t\t\t\t\tawait release();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../../../src/core/memory/providers/file-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsB3F;AAiBD,qBAAa,iBAAkB,YAAW,cAAc;IACvD,SAAgB,IAAI,gBAAgB;IACpC,SAAgB,MAAM,WAAW;IAEjC,OAAO,CAAC,GAAG,CAAC,CAAyB;IACrC,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,YAAY,CAAM;IAE1B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,eAAe,CAAM;IAG7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAQ;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC,WAAW,IAAI,OAAO,CAE5B;IAEM,eAAe;;MAErB;IAEY,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBtF;IAEM,iBAAiB,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,MAAM,CA4C5D;IAEY,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrD;IAEY,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAErC;IAEM,iBAAiB,IAAI,MAAM,EAAE,CAEnC;IAEM,kBAAkB,IAAI,cAAc,EAAE,CAoK5C;CACD","sourcesContent":["import { existsSync, promises as fs, mkdirSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { type Static, Type } from \"typebox\";\nimport type { MemoryPromptBudget } from \"../../context/memory-prompt-budget.ts\";\nimport type { ToolDefinition } from \"../../extensions/types.ts\";\nimport { hasInvisibleUnicode, scanContextFileThreats, stripInvisibleUnicode } from \"../../resource-loader.ts\";\nimport { jaccard, tokenize } from \"../../tools/skill-audit.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\n\n/**\n * R5 confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing\n * non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in\n * place and return the rewritten file; otherwise return null (the caller appends normally).\n */\nexport function supersedeNearDuplicateLine(existing: string, content: string): string | null {\n\tconst NEAR_DUP_THRESHOLD = 0.6;\n\tconst contentTokens = tokenize(content);\n\tif (contentTokens.length === 0) return null;\n\tconst lines = existing.split(\"\\n\");\n\tlet bestIdx = -1;\n\tlet bestScore = NEAR_DUP_THRESHOLD;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i].trim();\n\t\tif (!line) continue;\n\t\t// Never supersede structural Markdown (headings, list markers as headings) — a fact must not\n\t\t// silently overwrite section structure (Bug #15).\n\t\tif (line.startsWith(\"#\")) continue;\n\t\tconst score = jaccard(contentTokens, tokenize(line));\n\t\tif (score >= bestScore) {\n\t\t\tbestScore = score;\n\t\t\tbestIdx = i;\n\t\t}\n\t}\n\tif (bestIdx === -1) return null;\n\tlines[bestIdx] = content;\n\treturn lines.join(\"\\n\");\n}\n\nconst memorySchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"add\"), Type.Literal(\"replace\"), Type.Literal(\"remove\")], {\n\t\tdescription: \"Action to perform: add new content, replace existing content, or remove content\",\n\t}),\n\ttarget: Type.Union([Type.Literal(\"memory\"), Type.Literal(\"user\")], {\n\t\tdescription: \"Target file: 'memory' for MEMORY.md, 'user' for USER.md\",\n\t}),\n\tcontent: Type.Optional(Type.String({ description: \"Content to write (required for 'add' or 'replace')\" })),\n\toldContent: Type.Optional(\n\t\tType.String({ description: \"Exact substring to replace or remove (required for 'replace' or 'remove')\" }),\n\t),\n});\n\ntype MemoryParams = Static<typeof memorySchema>;\n\nexport class FileStoreProvider implements MemoryProvider {\n\tpublic readonly name = \"file-store\";\n\tpublic readonly egress = \"local\";\n\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate memoryFilePath = \"\";\n\tprivate userFilePath = \"\";\n\n\tprivate lastWrittenMemory = \"\";\n\tprivate lastWrittenUser = \"\";\n\n\t// Character budgets\n\tprivate static readonly BUDGET_MEMORY = 2200;\n\tprivate static readonly BUDGET_USER = 1375;\n\n\tpublic isAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tpublic getCapabilities() {\n\t\treturn { surfaces: [\"context\" as const] };\n\t}\n\n\tpublic async initialize(_sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.memoryFilePath = join(ctx.agentDir, \"MEMORY.md\");\n\t\tthis.userFilePath = join(ctx.agentDir, \"USER.md\");\n\n\t\t// Ensure agentDir exists\n\t\tif (!existsSync(ctx.agentDir)) {\n\t\t\tmkdirSync(ctx.agentDir, { recursive: true });\n\t\t}\n\n\t\t// Initialize files if they do not exist\n\t\tif (!existsSync(this.memoryFilePath)) {\n\t\t\twriteFileSync(this.memoryFilePath, \"\", \"utf-8\");\n\t\t}\n\t\tif (!existsSync(this.userFilePath)) {\n\t\t\twriteFileSync(this.userFilePath, \"\", \"utf-8\");\n\t\t}\n\n\t\t// Load initial contents\n\t\tthis.lastWrittenMemory = await fs.readFile(this.memoryFilePath, \"utf-8\");\n\t\tthis.lastWrittenUser = await fs.readFile(this.userFilePath, \"utf-8\");\n\t}\n\n\tpublic systemPromptBlock(budget?: MemoryPromptBudget): string {\n\t\tconst sanitize = (content: string) => {\n\t\t\t// Strip hidden/bidi-control chars before injecting memory into the prompt (defence in depth: the\n\t\t\t// write path already blocks them, but a file edited out-of-band could carry them). Strip #31.\n\t\t\tconst lines = stripInvisibleUnicode(content).cleaned.split(\"\\n\");\n\t\t\tconst sanitizedLines = lines.map((line) => {\n\t\t\t\tconst threats = scanContextFileThreats(line);\n\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\treturn `[BLOCKED: potential threat detected (${threats.join(\", \")})]`;\n\t\t\t\t}\n\t\t\t\treturn line;\n\t\t\t});\n\t\t\treturn sanitizedLines.join(\"\\n\");\n\t\t};\n\n\t\t// Read-time budget guard (cost): the memory tool already caps writes at BUDGET_*, but a file edited\n\t\t// externally (or by any path that bypasses the tool) could be arbitrarily large and would then\n\t\t// bloat the system prompt on EVERY turn. Cap the injected view to the same budget so the per-turn\n\t\t// cost stays bounded; the file on disk is untouched and the model is told it was truncated.\n\t\tconst cap = (content: string, limit: number) => {\n\t\t\tif (content.length <= limit) return content;\n\t\t\treturn `${content.slice(0, limit)}\\n[…truncated to ${limit} chars for the prompt; full file is on disk]`;\n\t\t};\n\n\t\tconst mem = cap(sanitize(this.lastWrittenMemory), FileStoreProvider.BUDGET_MEMORY);\n\t\tconst usr = cap(sanitize(this.lastWrittenUser), FileStoreProvider.BUDGET_USER);\n\n\t\tconst blocks: string[] = [];\n\t\tif (mem.trim()) {\n\t\t\tblocks.push(`## MEMORY.md:\\n${mem}`);\n\t\t}\n\t\tif (usr.trim()) {\n\t\t\tblocks.push(`## USER.md:\\n${usr}`);\n\t\t}\n\n\t\tif (blocks.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tconst block = `=== Persistent Memory (file-store) ===\\n[System Note: Below is a snapshot of your persistent memory. You can update these using the 'memory' tool.]\\n\\n${blocks.join(\"\\n\\n\")}`;\n\t\tif (budget?.compact && (block.split(\"\\n\").length > budget.maxLines || block.length > budget.maxChars)) {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn block;\n\t}\n\n\tpublic async prefetch(_query: string): Promise<string> {\n\t\t// static system prompt block is sufficient for file-store default; no-op prefetch\n\t\treturn \"\";\n\t}\n\n\tpublic async shutdown(): Promise<void> {\n\t\t// no-op\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\treturn [];\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tname: \"memory\",\n\t\t\t\tlabel: \"Persistent Memory Manager\",\n\t\t\t\tdescription: \"Add, replace, or remove contents in persistent memory files (MEMORY.md/USER.md).\",\n\t\t\t\tparameters: memorySchema,\n\t\t\t\texecute: async (_toolCallId, params: MemoryParams, _signal, _onUpdate, _execCtx) => {\n\t\t\t\t\tif (this.ctx?.isChildSession) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: \"Error: Writes to persistent memory are not allowed in child sessions (subagents).\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: \"Child session write-gated\" },\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { action, target, content, oldContent } = params;\n\n\t\t\t\t\t// Strict-scope injection guard on the high-privilege WRITE path (agy #31): a poisoned\n\t\t\t\t\t// memory entry persists across sessions and is injected into every future system prompt,\n\t\t\t\t\t// so block outright rather than strip. Hidden/bidi-control chars have no legitimate place\n\t\t\t\t\t// in a memory note, so reject those too.\n\t\t\t\t\tif ((action === \"add\" || action === \"replace\") && content !== undefined) {\n\t\t\t\t\t\tif (hasInvisibleUnicode(content)) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: \"Error: memory write rejected — contains hidden/bidirectional control characters.\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Invisible unicode in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst threats = scanContextFileThreats(content, \"strict\");\n\t\t\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: memory write rejected — potential injection/exfiltration detected (${threats.join(\", \")}).`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Threat in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst filePath = target === \"memory\" ? this.memoryFilePath : this.userFilePath;\n\t\t\t\t\tconst budget = target === \"memory\" ? FileStoreProvider.BUDGET_MEMORY : FileStoreProvider.BUDGET_USER;\n\n\t\t\t\t\tlet release: (() => Promise<void>) | undefined;\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// File lock\n\t\t\t\t\t\trelease = await lockfile.lock(filePath, { realpath: false, retries: 5 });\n\n\t\t\t\t\t\tconst lastWritten = target === \"memory\" ? this.lastWrittenMemory : this.lastWrittenUser;\n\t\t\t\t\t\t// Read current file content on disk for drift detection\n\t\t\t\t\t\tconst currentOnDisk = await fs.readFile(filePath, \"utf-8\");\n\t\t\t\t\t\tif (currentOnDisk !== lastWritten) {\n\t\t\t\t\t\t\t// Drift detected. Backup current file and refuse write.\n\t\t\t\t\t\t\tconst backupPath = `${filePath}.bak.${Date.now()}`;\n\t\t\t\t\t\t\tawait fs.writeFile(backupPath, currentOnDisk, \"utf-8\");\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Drift detected. The memory file has been modified out-of-band by an external process. A backup was created at ${backupPath}. Operation aborted.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Drift detected\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet newContent = currentOnDisk;\n\t\t\t\t\t\tif (action === \"add\") {\n\t\t\t\t\t\t\tif (content === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'content' is required for action 'add'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// R5: confront before write. If this fact is a near-duplicate of an existing line,\n\t\t\t\t\t\t\t// supersede it in place instead of appending a redundant copy (prevents append-rot).\n\t\t\t\t\t\t\tconst superseded = supersedeNearDuplicateLine(currentOnDisk, content);\n\t\t\t\t\t\t\tif (superseded !== null) {\n\t\t\t\t\t\t\t\tnewContent = superseded;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnewContent =\n\t\t\t\t\t\t\t\t\tnewContent.endsWith(\"\\n\") || newContent === \"\"\n\t\t\t\t\t\t\t\t\t\t? `${newContent}${content}\\n`\n\t\t\t\t\t\t\t\t\t\t: `${newContent}\\n${content}\\n`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (action === \"replace\") {\n\t\t\t\t\t\t\tif (content === undefined || oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameters 'content' and 'oldContent' are required for action 'replace'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to replace ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, content);\n\t\t\t\t\t\t} else if (action === \"remove\") {\n\t\t\t\t\t\t\tif (oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'oldContent' is required for action 'remove'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to remove ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Budget check\n\t\t\t\t\t\tif (newContent.length > budget) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Memory budget exceeded. ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"} limit is ${budget} characters. Current operation would result in ${newContent.length} characters.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Memory budget exceeded\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Atomic write\n\t\t\t\t\t\tconst tmpPath = `${filePath}.tmp`;\n\t\t\t\t\t\tawait fs.writeFile(tmpPath, newContent, \"utf-8\");\n\t\t\t\t\t\tawait fs.rename(tmpPath, filePath);\n\n\t\t\t\t\t\t// Update in-memory tracker\n\t\t\t\t\t\tif (target === \"memory\") {\n\t\t\t\t\t\t\tthis.lastWrittenMemory = newContent;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.lastWrittenUser = newContent;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Successfully updated ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"}.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: true },\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Error: Failed to perform memory operation: ${String(err)}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: String(err) },\n\t\t\t\t\t\t};\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (release) {\n\t\t\t\t\t\t\tawait release();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n}\n"]}
|
|
@@ -48,6 +48,7 @@ const memorySchema = Type.Object({
|
|
|
48
48
|
});
|
|
49
49
|
export class FileStoreProvider {
|
|
50
50
|
name = "file-store";
|
|
51
|
+
egress = "local";
|
|
51
52
|
ctx;
|
|
52
53
|
memoryFilePath = "";
|
|
53
54
|
userFilePath = "";
|
|
@@ -81,7 +82,7 @@ export class FileStoreProvider {
|
|
|
81
82
|
this.lastWrittenMemory = await fs.readFile(this.memoryFilePath, "utf-8");
|
|
82
83
|
this.lastWrittenUser = await fs.readFile(this.userFilePath, "utf-8");
|
|
83
84
|
}
|
|
84
|
-
systemPromptBlock() {
|
|
85
|
+
systemPromptBlock(budget) {
|
|
85
86
|
const sanitize = (content) => {
|
|
86
87
|
// Strip hidden/bidi-control chars before injecting memory into the prompt (defence in depth: the
|
|
87
88
|
// write path already blocks them, but a file edited out-of-band could carry them). Strip #31.
|
|
@@ -116,7 +117,11 @@ export class FileStoreProvider {
|
|
|
116
117
|
if (blocks.length === 0) {
|
|
117
118
|
return "";
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
+
const block = `=== Persistent Memory (file-store) ===\n[System Note: Below is a snapshot of your persistent memory. You can update these using the 'memory' tool.]\n\n${blocks.join("\n\n")}`;
|
|
121
|
+
if (budget?.compact && (block.split("\n").length > budget.maxLines || block.length > budget.maxChars)) {
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
return block;
|
|
120
125
|
}
|
|
121
126
|
async prefetch(_query) {
|
|
122
127
|
// static system prompt block is sufficient for file-store default; no-op prefetch
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.js","sourceRoot":"","sources":["../../../../src/core/memory/providers/file-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, promises as fs, mkdirSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { type Static, Type } from \"typebox\";\nimport type { ToolDefinition } from \"../../extensions/types.ts\";\nimport { hasInvisibleUnicode, scanContextFileThreats, stripInvisibleUnicode } from \"../../resource-loader.ts\";\nimport { jaccard, tokenize } from \"../../tools/skill-audit.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\n\n/**\n * R5 confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing\n * non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in\n * place and return the rewritten file; otherwise return null (the caller appends normally).\n */\nexport function supersedeNearDuplicateLine(existing: string, content: string): string | null {\n\tconst NEAR_DUP_THRESHOLD = 0.6;\n\tconst contentTokens = tokenize(content);\n\tif (contentTokens.length === 0) return null;\n\tconst lines = existing.split(\"\\n\");\n\tlet bestIdx = -1;\n\tlet bestScore = NEAR_DUP_THRESHOLD;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i].trim();\n\t\tif (!line) continue;\n\t\t// Never supersede structural Markdown (headings, list markers as headings) — a fact must not\n\t\t// silently overwrite section structure (Bug #15).\n\t\tif (line.startsWith(\"#\")) continue;\n\t\tconst score = jaccard(contentTokens, tokenize(line));\n\t\tif (score >= bestScore) {\n\t\t\tbestScore = score;\n\t\t\tbestIdx = i;\n\t\t}\n\t}\n\tif (bestIdx === -1) return null;\n\tlines[bestIdx] = content;\n\treturn lines.join(\"\\n\");\n}\n\nconst memorySchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"add\"), Type.Literal(\"replace\"), Type.Literal(\"remove\")], {\n\t\tdescription: \"Action to perform: add new content, replace existing content, or remove content\",\n\t}),\n\ttarget: Type.Union([Type.Literal(\"memory\"), Type.Literal(\"user\")], {\n\t\tdescription: \"Target file: 'memory' for MEMORY.md, 'user' for USER.md\",\n\t}),\n\tcontent: Type.Optional(Type.String({ description: \"Content to write (required for 'add' or 'replace')\" })),\n\toldContent: Type.Optional(\n\t\tType.String({ description: \"Exact substring to replace or remove (required for 'replace' or 'remove')\" }),\n\t),\n});\n\ntype MemoryParams = Static<typeof memorySchema>;\n\nexport class FileStoreProvider implements MemoryProvider {\n\tpublic readonly name = \"file-store\";\n\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate memoryFilePath = \"\";\n\tprivate userFilePath = \"\";\n\n\tprivate lastWrittenMemory = \"\";\n\tprivate lastWrittenUser = \"\";\n\n\t// Character budgets\n\tprivate static readonly BUDGET_MEMORY = 2200;\n\tprivate static readonly BUDGET_USER = 1375;\n\n\tpublic isAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tpublic getCapabilities() {\n\t\treturn { surfaces: [\"context\" as const] };\n\t}\n\n\tpublic async initialize(_sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.memoryFilePath = join(ctx.agentDir, \"MEMORY.md\");\n\t\tthis.userFilePath = join(ctx.agentDir, \"USER.md\");\n\n\t\t// Ensure agentDir exists\n\t\tif (!existsSync(ctx.agentDir)) {\n\t\t\tmkdirSync(ctx.agentDir, { recursive: true });\n\t\t}\n\n\t\t// Initialize files if they do not exist\n\t\tif (!existsSync(this.memoryFilePath)) {\n\t\t\twriteFileSync(this.memoryFilePath, \"\", \"utf-8\");\n\t\t}\n\t\tif (!existsSync(this.userFilePath)) {\n\t\t\twriteFileSync(this.userFilePath, \"\", \"utf-8\");\n\t\t}\n\n\t\t// Load initial contents\n\t\tthis.lastWrittenMemory = await fs.readFile(this.memoryFilePath, \"utf-8\");\n\t\tthis.lastWrittenUser = await fs.readFile(this.userFilePath, \"utf-8\");\n\t}\n\n\tpublic systemPromptBlock(): string {\n\t\tconst sanitize = (content: string) => {\n\t\t\t// Strip hidden/bidi-control chars before injecting memory into the prompt (defence in depth: the\n\t\t\t// write path already blocks them, but a file edited out-of-band could carry them). Strip #31.\n\t\t\tconst lines = stripInvisibleUnicode(content).cleaned.split(\"\\n\");\n\t\t\tconst sanitizedLines = lines.map((line) => {\n\t\t\t\tconst threats = scanContextFileThreats(line);\n\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\treturn `[BLOCKED: potential threat detected (${threats.join(\", \")})]`;\n\t\t\t\t}\n\t\t\t\treturn line;\n\t\t\t});\n\t\t\treturn sanitizedLines.join(\"\\n\");\n\t\t};\n\n\t\t// Read-time budget guard (cost): the memory tool already caps writes at BUDGET_*, but a file edited\n\t\t// externally (or by any path that bypasses the tool) could be arbitrarily large and would then\n\t\t// bloat the system prompt on EVERY turn. Cap the injected view to the same budget so the per-turn\n\t\t// cost stays bounded; the file on disk is untouched and the model is told it was truncated.\n\t\tconst cap = (content: string, limit: number) => {\n\t\t\tif (content.length <= limit) return content;\n\t\t\treturn `${content.slice(0, limit)}\\n[…truncated to ${limit} chars for the prompt; full file is on disk]`;\n\t\t};\n\n\t\tconst mem = cap(sanitize(this.lastWrittenMemory), FileStoreProvider.BUDGET_MEMORY);\n\t\tconst usr = cap(sanitize(this.lastWrittenUser), FileStoreProvider.BUDGET_USER);\n\n\t\tconst blocks: string[] = [];\n\t\tif (mem.trim()) {\n\t\t\tblocks.push(`## MEMORY.md:\\n${mem}`);\n\t\t}\n\t\tif (usr.trim()) {\n\t\t\tblocks.push(`## USER.md:\\n${usr}`);\n\t\t}\n\n\t\tif (blocks.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\treturn `=== Persistent Memory (file-store) ===\\n[System Note: Below is a snapshot of your persistent memory. You can update these using the 'memory' tool.]\\n\\n${blocks.join(\"\\n\\n\")}`;\n\t}\n\n\tpublic async prefetch(_query: string): Promise<string> {\n\t\t// static system prompt block is sufficient for file-store default; no-op prefetch\n\t\treturn \"\";\n\t}\n\n\tpublic async shutdown(): Promise<void> {\n\t\t// no-op\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\treturn [];\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tname: \"memory\",\n\t\t\t\tlabel: \"Persistent Memory Manager\",\n\t\t\t\tdescription: \"Add, replace, or remove contents in persistent memory files (MEMORY.md/USER.md).\",\n\t\t\t\tparameters: memorySchema,\n\t\t\t\texecute: async (_toolCallId, params: MemoryParams, _signal, _onUpdate, _execCtx) => {\n\t\t\t\t\tif (this.ctx?.isChildSession) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: \"Error: Writes to persistent memory are not allowed in child sessions (subagents).\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: \"Child session write-gated\" },\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { action, target, content, oldContent } = params;\n\n\t\t\t\t\t// Strict-scope injection guard on the high-privilege WRITE path (agy #31): a poisoned\n\t\t\t\t\t// memory entry persists across sessions and is injected into every future system prompt,\n\t\t\t\t\t// so block outright rather than strip. Hidden/bidi-control chars have no legitimate place\n\t\t\t\t\t// in a memory note, so reject those too.\n\t\t\t\t\tif ((action === \"add\" || action === \"replace\") && content !== undefined) {\n\t\t\t\t\t\tif (hasInvisibleUnicode(content)) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: \"Error: memory write rejected — contains hidden/bidirectional control characters.\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Invisible unicode in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst threats = scanContextFileThreats(content, \"strict\");\n\t\t\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: memory write rejected — potential injection/exfiltration detected (${threats.join(\", \")}).`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Threat in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst filePath = target === \"memory\" ? this.memoryFilePath : this.userFilePath;\n\t\t\t\t\tconst budget = target === \"memory\" ? FileStoreProvider.BUDGET_MEMORY : FileStoreProvider.BUDGET_USER;\n\n\t\t\t\t\tlet release: (() => Promise<void>) | undefined;\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// File lock\n\t\t\t\t\t\trelease = await lockfile.lock(filePath, { realpath: false, retries: 5 });\n\n\t\t\t\t\t\tconst lastWritten = target === \"memory\" ? this.lastWrittenMemory : this.lastWrittenUser;\n\t\t\t\t\t\t// Read current file content on disk for drift detection\n\t\t\t\t\t\tconst currentOnDisk = await fs.readFile(filePath, \"utf-8\");\n\t\t\t\t\t\tif (currentOnDisk !== lastWritten) {\n\t\t\t\t\t\t\t// Drift detected. Backup current file and refuse write.\n\t\t\t\t\t\t\tconst backupPath = `${filePath}.bak.${Date.now()}`;\n\t\t\t\t\t\t\tawait fs.writeFile(backupPath, currentOnDisk, \"utf-8\");\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Drift detected. The memory file has been modified out-of-band by an external process. A backup was created at ${backupPath}. Operation aborted.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Drift detected\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet newContent = currentOnDisk;\n\t\t\t\t\t\tif (action === \"add\") {\n\t\t\t\t\t\t\tif (content === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'content' is required for action 'add'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// R5: confront before write. If this fact is a near-duplicate of an existing line,\n\t\t\t\t\t\t\t// supersede it in place instead of appending a redundant copy (prevents append-rot).\n\t\t\t\t\t\t\tconst superseded = supersedeNearDuplicateLine(currentOnDisk, content);\n\t\t\t\t\t\t\tif (superseded !== null) {\n\t\t\t\t\t\t\t\tnewContent = superseded;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnewContent =\n\t\t\t\t\t\t\t\t\tnewContent.endsWith(\"\\n\") || newContent === \"\"\n\t\t\t\t\t\t\t\t\t\t? `${newContent}${content}\\n`\n\t\t\t\t\t\t\t\t\t\t: `${newContent}\\n${content}\\n`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (action === \"replace\") {\n\t\t\t\t\t\t\tif (content === undefined || oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameters 'content' and 'oldContent' are required for action 'replace'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to replace ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, content);\n\t\t\t\t\t\t} else if (action === \"remove\") {\n\t\t\t\t\t\t\tif (oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'oldContent' is required for action 'remove'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to remove ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Budget check\n\t\t\t\t\t\tif (newContent.length > budget) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Memory budget exceeded. ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"} limit is ${budget} characters. Current operation would result in ${newContent.length} characters.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Memory budget exceeded\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Atomic write\n\t\t\t\t\t\tconst tmpPath = `${filePath}.tmp`;\n\t\t\t\t\t\tawait fs.writeFile(tmpPath, newContent, \"utf-8\");\n\t\t\t\t\t\tawait fs.rename(tmpPath, filePath);\n\n\t\t\t\t\t\t// Update in-memory tracker\n\t\t\t\t\t\tif (target === \"memory\") {\n\t\t\t\t\t\t\tthis.lastWrittenMemory = newContent;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.lastWrittenUser = newContent;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Successfully updated ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"}.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: true },\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Error: Failed to perform memory operation: ${String(err)}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: String(err) },\n\t\t\t\t\t\t};\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (release) {\n\t\t\t\t\t\t\tawait release();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"file-store.js","sourceRoot":"","sources":["../../../../src/core/memory/providers/file-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, promises as fs, mkdirSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { type Static, Type } from \"typebox\";\nimport type { MemoryPromptBudget } from \"../../context/memory-prompt-budget.ts\";\nimport type { ToolDefinition } from \"../../extensions/types.ts\";\nimport { hasInvisibleUnicode, scanContextFileThreats, stripInvisibleUnicode } from \"../../resource-loader.ts\";\nimport { jaccard, tokenize } from \"../../tools/skill-audit.ts\";\nimport type { MemoryLifecycleContext, MemoryProvider } from \"../memory-provider.ts\";\n\n/**\n * R5 confront-before-write (anti append-rot): if `content` is a near-duplicate of an existing\n * non-empty line (token Jaccard ≥ threshold — i.e. the same fact reworded), supersede that line in\n * place and return the rewritten file; otherwise return null (the caller appends normally).\n */\nexport function supersedeNearDuplicateLine(existing: string, content: string): string | null {\n\tconst NEAR_DUP_THRESHOLD = 0.6;\n\tconst contentTokens = tokenize(content);\n\tif (contentTokens.length === 0) return null;\n\tconst lines = existing.split(\"\\n\");\n\tlet bestIdx = -1;\n\tlet bestScore = NEAR_DUP_THRESHOLD;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i].trim();\n\t\tif (!line) continue;\n\t\t// Never supersede structural Markdown (headings, list markers as headings) — a fact must not\n\t\t// silently overwrite section structure (Bug #15).\n\t\tif (line.startsWith(\"#\")) continue;\n\t\tconst score = jaccard(contentTokens, tokenize(line));\n\t\tif (score >= bestScore) {\n\t\t\tbestScore = score;\n\t\t\tbestIdx = i;\n\t\t}\n\t}\n\tif (bestIdx === -1) return null;\n\tlines[bestIdx] = content;\n\treturn lines.join(\"\\n\");\n}\n\nconst memorySchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"add\"), Type.Literal(\"replace\"), Type.Literal(\"remove\")], {\n\t\tdescription: \"Action to perform: add new content, replace existing content, or remove content\",\n\t}),\n\ttarget: Type.Union([Type.Literal(\"memory\"), Type.Literal(\"user\")], {\n\t\tdescription: \"Target file: 'memory' for MEMORY.md, 'user' for USER.md\",\n\t}),\n\tcontent: Type.Optional(Type.String({ description: \"Content to write (required for 'add' or 'replace')\" })),\n\toldContent: Type.Optional(\n\t\tType.String({ description: \"Exact substring to replace or remove (required for 'replace' or 'remove')\" }),\n\t),\n});\n\ntype MemoryParams = Static<typeof memorySchema>;\n\nexport class FileStoreProvider implements MemoryProvider {\n\tpublic readonly name = \"file-store\";\n\tpublic readonly egress = \"local\";\n\n\tprivate ctx?: MemoryLifecycleContext;\n\tprivate memoryFilePath = \"\";\n\tprivate userFilePath = \"\";\n\n\tprivate lastWrittenMemory = \"\";\n\tprivate lastWrittenUser = \"\";\n\n\t// Character budgets\n\tprivate static readonly BUDGET_MEMORY = 2200;\n\tprivate static readonly BUDGET_USER = 1375;\n\n\tpublic isAvailable(): boolean {\n\t\treturn true;\n\t}\n\n\tpublic getCapabilities() {\n\t\treturn { surfaces: [\"context\" as const] };\n\t}\n\n\tpublic async initialize(_sessionId: string, ctx: MemoryLifecycleContext): Promise<void> {\n\t\tthis.ctx = ctx;\n\t\tthis.memoryFilePath = join(ctx.agentDir, \"MEMORY.md\");\n\t\tthis.userFilePath = join(ctx.agentDir, \"USER.md\");\n\n\t\t// Ensure agentDir exists\n\t\tif (!existsSync(ctx.agentDir)) {\n\t\t\tmkdirSync(ctx.agentDir, { recursive: true });\n\t\t}\n\n\t\t// Initialize files if they do not exist\n\t\tif (!existsSync(this.memoryFilePath)) {\n\t\t\twriteFileSync(this.memoryFilePath, \"\", \"utf-8\");\n\t\t}\n\t\tif (!existsSync(this.userFilePath)) {\n\t\t\twriteFileSync(this.userFilePath, \"\", \"utf-8\");\n\t\t}\n\n\t\t// Load initial contents\n\t\tthis.lastWrittenMemory = await fs.readFile(this.memoryFilePath, \"utf-8\");\n\t\tthis.lastWrittenUser = await fs.readFile(this.userFilePath, \"utf-8\");\n\t}\n\n\tpublic systemPromptBlock(budget?: MemoryPromptBudget): string {\n\t\tconst sanitize = (content: string) => {\n\t\t\t// Strip hidden/bidi-control chars before injecting memory into the prompt (defence in depth: the\n\t\t\t// write path already blocks them, but a file edited out-of-band could carry them). Strip #31.\n\t\t\tconst lines = stripInvisibleUnicode(content).cleaned.split(\"\\n\");\n\t\t\tconst sanitizedLines = lines.map((line) => {\n\t\t\t\tconst threats = scanContextFileThreats(line);\n\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\treturn `[BLOCKED: potential threat detected (${threats.join(\", \")})]`;\n\t\t\t\t}\n\t\t\t\treturn line;\n\t\t\t});\n\t\t\treturn sanitizedLines.join(\"\\n\");\n\t\t};\n\n\t\t// Read-time budget guard (cost): the memory tool already caps writes at BUDGET_*, but a file edited\n\t\t// externally (or by any path that bypasses the tool) could be arbitrarily large and would then\n\t\t// bloat the system prompt on EVERY turn. Cap the injected view to the same budget so the per-turn\n\t\t// cost stays bounded; the file on disk is untouched and the model is told it was truncated.\n\t\tconst cap = (content: string, limit: number) => {\n\t\t\tif (content.length <= limit) return content;\n\t\t\treturn `${content.slice(0, limit)}\\n[…truncated to ${limit} chars for the prompt; full file is on disk]`;\n\t\t};\n\n\t\tconst mem = cap(sanitize(this.lastWrittenMemory), FileStoreProvider.BUDGET_MEMORY);\n\t\tconst usr = cap(sanitize(this.lastWrittenUser), FileStoreProvider.BUDGET_USER);\n\n\t\tconst blocks: string[] = [];\n\t\tif (mem.trim()) {\n\t\t\tblocks.push(`## MEMORY.md:\\n${mem}`);\n\t\t}\n\t\tif (usr.trim()) {\n\t\t\tblocks.push(`## USER.md:\\n${usr}`);\n\t\t}\n\n\t\tif (blocks.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tconst block = `=== Persistent Memory (file-store) ===\\n[System Note: Below is a snapshot of your persistent memory. You can update these using the 'memory' tool.]\\n\\n${blocks.join(\"\\n\\n\")}`;\n\t\tif (budget?.compact && (block.split(\"\\n\").length > budget.maxLines || block.length > budget.maxChars)) {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn block;\n\t}\n\n\tpublic async prefetch(_query: string): Promise<string> {\n\t\t// static system prompt block is sufficient for file-store default; no-op prefetch\n\t\treturn \"\";\n\t}\n\n\tpublic async shutdown(): Promise<void> {\n\t\t// no-op\n\t}\n\n\tpublic getContextMarkers(): string[] {\n\t\treturn [];\n\t}\n\n\tpublic getToolDefinitions(): ToolDefinition[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tname: \"memory\",\n\t\t\t\tlabel: \"Persistent Memory Manager\",\n\t\t\t\tdescription: \"Add, replace, or remove contents in persistent memory files (MEMORY.md/USER.md).\",\n\t\t\t\tparameters: memorySchema,\n\t\t\t\texecute: async (_toolCallId, params: MemoryParams, _signal, _onUpdate, _execCtx) => {\n\t\t\t\t\tif (this.ctx?.isChildSession) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: \"Error: Writes to persistent memory are not allowed in child sessions (subagents).\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: \"Child session write-gated\" },\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { action, target, content, oldContent } = params;\n\n\t\t\t\t\t// Strict-scope injection guard on the high-privilege WRITE path (agy #31): a poisoned\n\t\t\t\t\t// memory entry persists across sessions and is injected into every future system prompt,\n\t\t\t\t\t// so block outright rather than strip. Hidden/bidi-control chars have no legitimate place\n\t\t\t\t\t// in a memory note, so reject those too.\n\t\t\t\t\tif ((action === \"add\" || action === \"replace\") && content !== undefined) {\n\t\t\t\t\t\tif (hasInvisibleUnicode(content)) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: \"Error: memory write rejected — contains hidden/bidirectional control characters.\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Invisible unicode in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst threats = scanContextFileThreats(content, \"strict\");\n\t\t\t\t\t\tif (threats.length > 0) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: memory write rejected — potential injection/exfiltration detected (${threats.join(\", \")}).`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Threat in memory write\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst filePath = target === \"memory\" ? this.memoryFilePath : this.userFilePath;\n\t\t\t\t\tconst budget = target === \"memory\" ? FileStoreProvider.BUDGET_MEMORY : FileStoreProvider.BUDGET_USER;\n\n\t\t\t\t\tlet release: (() => Promise<void>) | undefined;\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// File lock\n\t\t\t\t\t\trelease = await lockfile.lock(filePath, { realpath: false, retries: 5 });\n\n\t\t\t\t\t\tconst lastWritten = target === \"memory\" ? this.lastWrittenMemory : this.lastWrittenUser;\n\t\t\t\t\t\t// Read current file content on disk for drift detection\n\t\t\t\t\t\tconst currentOnDisk = await fs.readFile(filePath, \"utf-8\");\n\t\t\t\t\t\tif (currentOnDisk !== lastWritten) {\n\t\t\t\t\t\t\t// Drift detected. Backup current file and refuse write.\n\t\t\t\t\t\t\tconst backupPath = `${filePath}.bak.${Date.now()}`;\n\t\t\t\t\t\t\tawait fs.writeFile(backupPath, currentOnDisk, \"utf-8\");\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Drift detected. The memory file has been modified out-of-band by an external process. A backup was created at ${backupPath}. Operation aborted.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Drift detected\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet newContent = currentOnDisk;\n\t\t\t\t\t\tif (action === \"add\") {\n\t\t\t\t\t\t\tif (content === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'content' is required for action 'add'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// R5: confront before write. If this fact is a near-duplicate of an existing line,\n\t\t\t\t\t\t\t// supersede it in place instead of appending a redundant copy (prevents append-rot).\n\t\t\t\t\t\t\tconst superseded = supersedeNearDuplicateLine(currentOnDisk, content);\n\t\t\t\t\t\t\tif (superseded !== null) {\n\t\t\t\t\t\t\t\tnewContent = superseded;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tnewContent =\n\t\t\t\t\t\t\t\t\tnewContent.endsWith(\"\\n\") || newContent === \"\"\n\t\t\t\t\t\t\t\t\t\t? `${newContent}${content}\\n`\n\t\t\t\t\t\t\t\t\t\t: `${newContent}\\n${content}\\n`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (action === \"replace\") {\n\t\t\t\t\t\t\tif (content === undefined || oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameters 'content' and 'oldContent' are required for action 'replace'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to replace ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, content);\n\t\t\t\t\t\t} else if (action === \"remove\") {\n\t\t\t\t\t\t\tif (oldContent === undefined) {\n\t\t\t\t\t\t\t\tthrow new Error(\"Parameter 'oldContent' is required for action 'remove'.\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!currentOnDisk.includes(oldContent)) {\n\t\t\t\t\t\t\t\tthrow new Error(`The content to remove ('oldContent') was not found in the file.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewContent = currentOnDisk.replace(oldContent, \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Budget check\n\t\t\t\t\t\tif (newContent.length > budget) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `Error: Memory budget exceeded. ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"} limit is ${budget} characters. Current operation would result in ${newContent.length} characters.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdetails: { success: false, error: \"Memory budget exceeded\" },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Atomic write\n\t\t\t\t\t\tconst tmpPath = `${filePath}.tmp`;\n\t\t\t\t\t\tawait fs.writeFile(tmpPath, newContent, \"utf-8\");\n\t\t\t\t\t\tawait fs.rename(tmpPath, filePath);\n\n\t\t\t\t\t\t// Update in-memory tracker\n\t\t\t\t\t\tif (target === \"memory\") {\n\t\t\t\t\t\t\tthis.lastWrittenMemory = newContent;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.lastWrittenUser = newContent;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Successfully updated ${target === \"memory\" ? \"MEMORY.md\" : \"USER.md\"}.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: true },\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Error: Failed to perform memory operation: ${String(err)}`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { success: false, error: String(err) },\n\t\t\t\t\t\t};\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tif (release) {\n\t\t\t\t\t\t\tawait release();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n}\n"]}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import type { MemoryCapabilities, MemoryLifecycleContext, MemoryProvider } from "../memory-provider.ts";
|
|
11
11
|
export declare class TranscriptRecallProvider implements MemoryProvider {
|
|
12
12
|
readonly name = "transcript-recall";
|
|
13
|
+
readonly egress = "local";
|
|
13
14
|
private index;
|
|
14
15
|
private currentSessionId;
|
|
15
16
|
private cwd;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript-recall.d.ts","sourceRoot":"","sources":["../../../../src/core/memory/providers/transcript-recall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"transcript-recall.d.ts","sourceRoot":"","sources":["../../../../src/core/memory/providers/transcript-recall.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAaxG,qBAAa,wBAAyB,YAAW,cAAc;IAC9D,QAAQ,CAAC,IAAI,uBAAuB;IACpC,QAAQ,CAAC,MAAM,WAAW;IAC1B,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,CAkB7C;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 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\treadonly egress = \"local\";\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// MemoryManager centrally source-labels and fences every provider's complete recall page. Keep\n\t\t// this provider output raw here so the built-in path is wrapped exactly once like extensions.\n\t\tconst body = hits.map((h) => `- (${h.timestamp ?? \"earlier session\"}) ${h.snippet}`).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"]}
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
import { readdirSync, statSync } from "node:fs";
|
|
11
11
|
import { join, resolve } from "node:path";
|
|
12
12
|
import { getDefaultSessionDir, isAutoLearnSessionId, loadEntriesFromFile, } from "@caupulican/pi-agent-core/node";
|
|
13
|
-
import { wrapUntrustedText } from "../../security/untrusted-boundary.js";
|
|
14
13
|
import { TranscriptIndex } from "../transcript-index.js";
|
|
15
14
|
/** Most-recent past sessions to consider. */
|
|
16
15
|
const MAX_SESSIONS = 60;
|
|
@@ -23,6 +22,7 @@ const MAX_TOTAL_CHARS = 500_000;
|
|
|
23
22
|
const MAX_FILE_BYTES = 8_000_000;
|
|
24
23
|
export class TranscriptRecallProvider {
|
|
25
24
|
name = "transcript-recall";
|
|
25
|
+
egress = "local";
|
|
26
26
|
index;
|
|
27
27
|
currentSessionId = "";
|
|
28
28
|
cwd = "";
|
|
@@ -64,12 +64,9 @@ export class TranscriptRecallProvider {
|
|
|
64
64
|
const hits = index.query(query, { k: 3, minScore: 0.34, maxSnippetChars: 600 });
|
|
65
65
|
if (hits.length === 0)
|
|
66
66
|
return "";
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
const body = hits
|
|
71
|
-
.map((h) => `- (${h.timestamp ?? "earlier session"}) ${wrapUntrustedText(h.snippet, "transcript-recall")}`)
|
|
72
|
-
.join("\n");
|
|
67
|
+
// MemoryManager centrally source-labels and fences every provider's complete recall page. Keep
|
|
68
|
+
// this provider output raw here so the built-in path is wrapped exactly once like extensions.
|
|
69
|
+
const body = hits.map((h) => `- (${h.timestamp ?? "earlier session"}) ${h.snippet}`).join("\n");
|
|
73
70
|
return `<memory_context source="transcript-recall">\nRelevant context recalled from past sessions (read-only reference, untrusted, may be stale):\n${body}\n</memory_context>`;
|
|
74
71
|
}
|
|
75
72
|
ensureIndex() {
|
|
@@ -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 {\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"]}
|
|
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 {\n\ttype FileEntry,\n\tgetDefaultSessionDir,\n\tisAutoLearnSessionId,\n\tloadEntriesFromFile,\n} from \"@caupulican/pi-agent-core/node\";\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\treadonly egress = \"local\";\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// MemoryManager centrally source-labels and fences every provider's complete recall page. Keep\n\t\t// this provider output raw here so the built-in path is wrapped exactly once like extensions.\n\t\tconst body = hits.map((h) => `- (${h.timestamp ?? \"earlier session\"}) ${h.snippet}`).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"]}
|