open-multi-agent-kit 0.95.0 → 0.95.2
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 +33 -0
- package/README.md +1 -1
- package/dist/approvals/approval-receipt-store.d.ts +18 -0
- package/dist/approvals/approval-receipt-store.d.ts.map +1 -0
- package/dist/approvals/approval-receipt-store.js +169 -0
- package/dist/approvals/approval-receipt-store.js.map +1 -0
- package/dist/approvals/approval-receipt.d.ts +43 -0
- package/dist/approvals/approval-receipt.d.ts.map +1 -0
- package/dist/approvals/approval-receipt.js +208 -0
- package/dist/approvals/approval-receipt.js.map +1 -0
- package/dist/approvals/approval-verifier.d.ts +17 -0
- package/dist/approvals/approval-verifier.d.ts.map +1 -0
- package/dist/approvals/approval-verifier.js +23 -0
- package/dist/approvals/approval-verifier.js.map +1 -0
- package/dist/approvals/plannotator-approval-bridge.d.ts +58 -0
- package/dist/approvals/plannotator-approval-bridge.d.ts.map +1 -0
- package/dist/approvals/plannotator-approval-bridge.js +169 -0
- package/dist/approvals/plannotator-approval-bridge.js.map +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/package-doctor-cli.d.ts +13 -0
- package/dist/commands/package-doctor-cli.d.ts.map +1 -0
- package/dist/commands/package-doctor-cli.js +80 -0
- package/dist/commands/package-doctor-cli.js.map +1 -0
- package/dist/commands/router-feedback-cli.d.ts +11 -0
- package/dist/commands/router-feedback-cli.d.ts.map +1 -0
- package/dist/commands/router-feedback-cli.js +99 -0
- package/dist/commands/router-feedback-cli.js.map +1 -0
- package/dist/commands/stats-cli.d.ts +20 -0
- package/dist/commands/stats-cli.d.ts.map +1 -0
- package/dist/commands/stats-cli.js +99 -0
- package/dist/commands/stats-cli.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +4 -1
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +97 -29
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +583 -199
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +8 -2
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/transaction.d.ts +9 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -1
- package/dist/core/compaction/transaction.js +17 -2
- package/dist/core/compaction/transaction.js.map +1 -1
- package/dist/core/context-budget-governor-v2.d.ts +1 -1
- package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
- package/dist/core/context-budget-governor-v2.js +1 -1
- package/dist/core/context-budget-governor-v2.js.map +1 -1
- package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt.js +18 -2
- package/dist/core/context-budget-system-prompt.js.map +1 -1
- package/dist/core/context-budget-v2-cache-disk.d.ts +109 -0
- package/dist/core/context-budget-v2-cache-disk.d.ts.map +1 -0
- package/dist/core/context-budget-v2-cache-disk.js +343 -0
- package/dist/core/context-budget-v2-cache-disk.js.map +1 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts +22 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-keys.js +4 -4
- package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-cache.d.ts +1 -0
- package/dist/core/context-budget-v2-cache.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache.js +1 -0
- package/dist/core/context-budget-v2-cache.js.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.js +39 -5
- package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +17 -14
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +1 -0
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +45 -22
- package/dist/core/extensions/loader.js.bak-sharedjiti-20260812-111246 +553 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +4 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/headroom.d.ts.map +1 -1
- package/dist/core/headroom.js +7 -2
- package/dist/core/headroom.js.map +1 -1
- package/dist/core/mcp/client.d.ts +105 -0
- package/dist/core/mcp/client.d.ts.map +1 -0
- package/dist/core/mcp/client.js +198 -0
- package/dist/core/mcp/client.js.map +1 -0
- package/dist/core/mcp/config.d.ts +23 -0
- package/dist/core/mcp/config.d.ts.map +1 -0
- package/dist/core/mcp/config.js +89 -0
- package/dist/core/mcp/config.js.map +1 -0
- package/dist/core/mcp/index.d.ts +15 -0
- package/dist/core/mcp/index.d.ts.map +1 -0
- package/dist/core/mcp/index.js +15 -0
- package/dist/core/mcp/index.js.map +1 -0
- package/dist/core/mcp/manager.d.ts +84 -0
- package/dist/core/mcp/manager.d.ts.map +1 -0
- package/dist/core/mcp/manager.js +173 -0
- package/dist/core/mcp/manager.js.map +1 -0
- package/dist/core/mcp/protocol.d.ts +59 -0
- package/dist/core/mcp/protocol.d.ts.map +1 -0
- package/dist/core/mcp/protocol.js +103 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/stdio-transport.d.ts +63 -0
- package/dist/core/mcp/stdio-transport.d.ts.map +1 -0
- package/dist/core/mcp/stdio-transport.js +132 -0
- package/dist/core/mcp/stdio-transport.js.map +1 -0
- package/dist/core/mcp/tools.d.ts +47 -0
- package/dist/core/mcp/tools.d.ts.map +1 -0
- package/dist/core/mcp/tools.js +102 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/package-archive.d.ts +3 -0
- package/dist/core/package-archive.d.ts.map +1 -0
- package/dist/core/package-archive.js +161 -0
- package/dist/core/package-archive.js.map +1 -0
- package/dist/core/package-doctor-source-scan.d.ts +13 -0
- package/dist/core/package-doctor-source-scan.d.ts.map +1 -0
- package/dist/core/package-doctor-source-scan.js +124 -0
- package/dist/core/package-doctor-source-scan.js.map +1 -0
- package/dist/core/package-doctor.d.ts +41 -0
- package/dist/core/package-doctor.d.ts.map +1 -0
- package/dist/core/package-doctor.js +145 -0
- package/dist/core/package-doctor.js.map +1 -0
- package/dist/core/package-manager.d.ts +10 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +175 -42
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-manifest.d.ts +26 -0
- package/dist/core/package-manifest.d.ts.map +1 -0
- package/dist/core/package-manifest.js +85 -0
- package/dist/core/package-manifest.js.map +1 -0
- package/dist/core/pi-compat.d.ts +5 -0
- package/dist/core/pi-compat.d.ts.map +1 -0
- package/dist/core/pi-compat.js +18 -0
- package/dist/core/pi-compat.js.map +1 -0
- package/dist/core/reasoning-router-bias.d.ts +10 -14
- package/dist/core/reasoning-router-bias.d.ts.map +1 -1
- package/dist/core/reasoning-router-bias.js +22 -15
- package/dist/core/reasoning-router-bias.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts +9 -1
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js +20 -5
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/redaction.d.ts +2 -5
- package/dist/core/redaction.d.ts.map +1 -1
- package/dist/core/redaction.js +14 -1
- package/dist/core/redaction.js.map +1 -1
- package/dist/core/repository-learning-scope.d.ts +10 -0
- package/dist/core/repository-learning-scope.d.ts.map +1 -0
- package/dist/core/repository-learning-scope.js +42 -0
- package/dist/core/repository-learning-scope.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +16 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/router-feedback-collector.d.ts +2 -1
- package/dist/core/router-feedback-collector.d.ts.map +1 -1
- package/dist/core/router-feedback-collector.js +2 -2
- package/dist/core/router-feedback-collector.js.map +1 -1
- package/dist/core/runtime-provenance.d.ts +54 -0
- package/dist/core/runtime-provenance.d.ts.map +1 -0
- package/dist/core/runtime-provenance.js +115 -0
- package/dist/core/runtime-provenance.js.map +1 -0
- package/dist/core/sandbox/backend.d.ts.map +1 -1
- package/dist/core/sandbox/backend.js +39 -21
- package/dist/core/sandbox/backend.js.map +1 -1
- package/dist/core/sandbox/default-policy.d.ts +4 -5
- package/dist/core/sandbox/default-policy.d.ts.map +1 -1
- package/dist/core/sandbox/default-policy.js +9 -11
- package/dist/core/sandbox/default-policy.js.map +1 -1
- package/dist/core/session-bash-runtime.d.ts +4 -4
- package/dist/core/session-bash-runtime.d.ts.map +1 -1
- package/dist/core/session-bash-runtime.js +6 -5
- package/dist/core/session-bash-runtime.js.map +1 -1
- package/dist/core/session-compaction-service.d.ts.map +1 -1
- package/dist/core/session-compaction-service.js +7 -7
- package/dist/core/session-compaction-service.js.map +1 -1
- package/dist/core/session-manager.d.ts +4 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +47 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +7 -4
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +9 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +10 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +2 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +15 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/turn-metrics.d.ts +135 -0
- package/dist/core/turn-metrics.d.ts.map +1 -0
- package/dist/core/turn-metrics.js +261 -0
- package/dist/core/turn-metrics.js.map +1 -0
- package/dist/core/verified-bash-runtime.d.ts.map +1 -1
- package/dist/core/verified-bash-runtime.js +10 -4
- package/dist/core/verified-bash-runtime.js.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.d.ts +7 -0
- package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.js +39 -0
- package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +18 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.d.ts.map +1 -1
- package/dist/modes/interactive/components/control-panel-layout.js +2 -2
- package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +3 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +40 -12
- 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 +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.d.ts +14 -2
- package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.js +106 -19
- package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +10 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +104 -17
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +1 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/compaction.md +25 -3
- package/docs/containerization.md +14 -2
- package/docs/docs.json +8 -0
- package/docs/environment-variables.md +1 -1
- package/docs/extensions.md +75 -16
- package/docs/mcp.md +97 -0
- package/docs/metrics.md +87 -0
- package/docs/packages.md +25 -4
- package/docs/providers.md +1 -1
- package/docs/sdk.md +25 -4
- package/docs/settings.md +8 -7
- package/docs/usage.md +13 -3
- 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/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/plannotator-approval-bridge.ts +83 -0
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +21 -11
- package/examples/extensions/subagent/adaptive-agent-runtime.ts +9 -3
- package/examples/extensions/subagent/agents/planner.md +1 -3
- package/examples/extensions/subagent/agents/reviewer.md +1 -3
- package/examples/extensions/subagent/agents/scout.md +1 -3
- package/examples/extensions/subagent/agents/worker.md +0 -1
- package/examples/extensions/subagent/agents.test.ts +9 -0
- package/examples/extensions/subagent/agents.ts +11 -4
- package/examples/extensions/subagent/deadline-budget.test.ts +32 -0
- package/examples/extensions/subagent/deadline-budget.ts +25 -1
- package/examples/extensions/subagent/fixtures/fake-omk-json.mjs +10 -0
- package/examples/extensions/subagent/index.ts +234 -55
- package/examples/extensions/subagent/subagent-execution-policy.test.ts +27 -0
- package/examples/extensions/subagent/subagent-extension-smoke.test.ts +167 -2
- package/examples/extensions/subagent/workflow-graph.test.ts +80 -0
- package/examples/extensions/subagent/workflow-graph.ts +89 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +253 -439
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-budget-system-prompt.js","sourceRoot":"","sources":["../../src/core/context-budget-system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCONTEXT_BUDGET_POLICY_VERSION_V2,\n\ttype ContextBudgetCacheProviderV2,\n\ttype ContextBudgetItemV2,\n\ttype PromptContextBudgetPlanV2,\n\tplanPromptContextBudgetV2,\n} from \"./context-budget-governor-v2.ts\";\nimport { createSystemPromptBudgetItems, escapeXml } from \"./context-budget-system-prompt-items.ts\";\nimport {\n\ttype ContextBudgetTokenizerMode,\n\tcreateTokenCounterForMode,\n\ttype TokenCounterAdapter,\n} from \"./context-budget-token-counter.ts\";\nimport type { ContextFile } from \"./resource-loader.ts\";\nimport type { Skill } from \"./skills.ts\";\n\nexport interface SystemPromptContextBudgetOptions {\n\treadonly maxPromptTokens: number;\n\treadonly responseReserveTokens?: number;\n\treadonly modelId?: string;\n\treadonly tokenizerMode?: ContextBudgetTokenizerMode;\n\treadonly activeSkillNames?: readonly string[];\n\treadonly includeSkillInventory?: boolean;\n\treadonly includeFullContextFiles?: boolean;\n\treadonly tokenCounter?: TokenCounterAdapter;\n\treadonly cacheProvider?: ContextBudgetCacheProviderV2;\n\t/** Maximum number of inactive (non-active) skills to include as items. Default: 15. */\n\treadonly maxInactiveSkills?: number;\n\t/** Current user query text for relevance-aware skill ranking. */\n\treadonly queryContext?: string;\n}\n\nexport interface SystemPromptBudgetedResourcesInput {\n\treadonly basePrompt: string;\n\treadonly contextFiles: readonly ContextFile[];\n\treadonly skills: readonly Skill[];\n\treadonly includeSkills: boolean;\n\treadonly options: SystemPromptContextBudgetOptions;\n}\n\nexport interface SystemPromptBudgetedResources {\n\treadonly text: string;\n\treadonly plan: PromptContextBudgetPlanV2;\n}\n\nexport function renderSystemPromptBudgetedResources(\n\tinput: SystemPromptBudgetedResourcesInput,\n): SystemPromptBudgetedResources {\n\tconst tokenCounter =\n\t\tinput.options.tokenCounter ?? createTokenCounterForMode(input.options.tokenizerMode ?? \"fallback\");\n\tconst modelId = input.options.modelId ?? \"unknown\";\n\tconst baseTokens = tokenCounter.countText(input.basePrompt, modelId).tokens;\n\tconst resourceBudget = Math.max(0, input.options.maxPromptTokens - baseTokens);\n\tconst items = createSystemPromptBudgetItems(input, resourceBudget);\n\tconst plan = planPromptContextBudgetV2({\n\t\tmaxTokens: resourceBudget,\n\t\tresponseReserveTokens: input.options.responseReserveTokens ?? 0,\n\t\tmodelId,\n\t\tpolicyVersion: CONTEXT_BUDGET_POLICY_VERSION_V2,\n\t\tquery: input.options.queryContext,\n\t\titems,\n\t\ttokenCounter,\n\t\tcacheProvider: input.options.cacheProvider,\n\t});\n\tconst included = new Set(plan.includedItemIds);\n\tconst filtered = deduplicatePointerFull(items, included);\n\tconst text = filtered\n\t\t.filter((item) => included.has(item.id))\n\t\t.map(\n\t\t\t(item) =>\n\t\t\t\tplan.selectedRepresentations.find((representation) => representation.itemId === item.id)?.text ?? item.text,\n\t\t)\n\t\t.join(\"\\n\")\n\t\t.trimEnd();\n\tconst note = renderBudgetNote(plan, baseTokens);\n\treturn { text: text ? `${text}\\n${note}` : note, plan };\n}\n\nfunction renderBudgetNote(plan: PromptContextBudgetPlanV2, baseTokens: number): string {\n\tconst omitted = plan.omittedItemIds.length;\n\tconst { cache, counts, diagnosticReasons, tokenOptimizer, tokens } = plan.observability;\n\tconst selectedCacheHits =\n\t\tcache.representationCache.exactHits +\n\t\tcache.representationCache.semanticHits +\n\t\tcache.representationCache.pointerHits;\n\tconst renderedDiagnosticReasons =\n\t\tdiagnosticReasons.length === 0\n\t\t\t? ' <diagnostic_reasons count=\"0\" />'\n\t\t\t: [\n\t\t\t\t\t` <diagnostic_reasons count=\"${formatObservableInteger(diagnosticReasons.length)}\">`,\n\t\t\t\t\t...diagnosticReasons.map((reason) => ` <reason>${escapeXml(reason)}</reason>`),\n\t\t\t\t\t\" </diagnostic_reasons>\",\n\t\t\t\t].join(\"\\n\");\n\treturn [\n\t\t\"<context_budget>\",\n\t\t` <policy>${escapeXml(plan.policyVersion)}</policy>`,\n\t\t` <plan_hash>${escapeXml(plan.planHash)}</plan_hash>`,\n\t\t` <base_prompt_tokens>${formatObservableInteger(baseTokens)}</base_prompt_tokens>`,\n\t\t` <resource_tokens_used>${formatObservableInteger(plan.usedTokens)}</resource_tokens_used>`,\n\t\t` <resource_tokens_omitted>${formatObservableInteger(plan.omittedTokens)}</resource_tokens_omitted>`,\n\t\t` <omitted_items>${formatObservableInteger(omitted)}</omitted_items>`,\n\t\t` <emergency>${plan.emergency ? \"true\" : \"false\"}</emergency>`,\n\t\t\" <decision_observability>\",\n\t\t` <counts selected=\"${formatObservableInteger(counts.selected)}\" omitted=\"${formatObservableInteger(counts.omitted)}\" pointer=\"${formatObservableInteger(counts.pointer)}\" compressed=\"${formatObservableInteger(counts.compressed)}\" full=\"${formatObservableInteger(counts.full)}\" retrieval_fallbacks=\"${formatObservableInteger(counts.retrievalFallback)}\" />`,\n\t\t` <tokens available=\"${formatObservableInteger(tokens.available)}\" used=\"${formatObservableInteger(tokens.used)}\" raw=\"${formatObservableInteger(tokens.raw)}\" omitted=\"${formatObservableInteger(tokens.omitted)}\" token_savings=\"${formatObservableInteger(tokens.tokenSavings)}\" />`,\n\t\t` <cache_decision plan_hit=\"${cache.planCache.hit ? \"true\" : \"false\"}\" selected_hits=\"${formatObservableInteger(selectedCacheHits)}\" misses=\"${formatObservableInteger(cache.representationCache.misses)}\" stale_rejects=\"${formatObservableInteger(cache.representationCache.staleRejects)}\" negative_hits=\"${formatObservableInteger(cache.representationCache.negativeHits)}\" writes=\"${formatObservableInteger(cache.representationCache.writes)}\" />`,\n\t\trenderedDiagnosticReasons,\n\t\t` <token_optimizer optimizer_id=\"${escapeXml(tokenOptimizer.optimizerId)}\" status=\"${escapeXml(tokenOptimizer.status)}\" active=\"${tokenOptimizer.active ? \"true\" : \"false\"}\" active_context_budget_optimizer=\"${escapeXml(tokenOptimizer.activeContextBudgetOptimizer)}\" compatibility_only=\"${tokenOptimizer.compatibilityOnly ? \"true\" : \"false\"}\" />`,\n\t\t\" </decision_observability>\",\n\t\t\" <note>Some low-priority resource inventory may be represented by pointers. Use read on referenced paths when needed.</note>\",\n\t\t\"</context_budget>\",\n\t].join(\"\\n\");\n}\n\nfunction formatObservableInteger(value: number): string {\n\tif (!Number.isFinite(value)) {\n\t\treturn \"0\";\n\t}\n\treturn String(Math.max(0, Math.floor(value)));\n}\n\n/**\n * R3: When a context-full item is included, exclude the matching pointer\n * so the same file does not consume tokens twice.\n */\nfunction deduplicatePointerFull(\n\titems: readonly ContextBudgetItemV2[],\n\tincluded: ReadonlySet<string>,\n): readonly ContextBudgetItemV2[] {\n\tconst includedFullPaths = new Set<string>();\n\tfor (const item of items) {\n\t\tif (item.id.startsWith(\"context-full:\") && included.has(item.id)) {\n\t\t\tconst path = item.id.slice(\"context-full:\".length);\n\t\t\tincludedFullPaths.add(path);\n\t\t}\n\t}\n\tif (includedFullPaths.size === 0) {\n\t\treturn items;\n\t}\n\treturn items.filter((item) => {\n\t\tif (!item.id.startsWith(\"context-pointer:\")) {\n\t\t\treturn true;\n\t\t}\n\t\tconst path = item.id.slice(\"context-pointer:\".length);\n\t\treturn !includedFullPaths.has(path);\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context-budget-system-prompt.js","sourceRoot":"","sources":["../../src/core/context-budget-system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCONTEXT_BUDGET_POLICY_VERSION_V2,\n\ttype ContextBudgetCacheProviderV2,\n\ttype ContextBudgetItemV2,\n\ttype PromptContextBudgetPlanV2,\n\tplanPromptContextBudgetV2,\n} from \"./context-budget-governor-v2.ts\";\nimport { createSystemPromptBudgetItems, escapeXml } from \"./context-budget-system-prompt-items.ts\";\nimport {\n\ttype ContextBudgetTokenizerMode,\n\tcreateTokenCounterForMode,\n\ttype TokenCounterAdapter,\n} from \"./context-budget-token-counter.ts\";\nimport type { ContextFile } from \"./resource-loader.ts\";\nimport type { Skill } from \"./skills.ts\";\n\nexport interface SystemPromptContextBudgetOptions {\n\treadonly maxPromptTokens: number;\n\treadonly responseReserveTokens?: number;\n\treadonly modelId?: string;\n\treadonly tokenizerMode?: ContextBudgetTokenizerMode;\n\treadonly activeSkillNames?: readonly string[];\n\treadonly includeSkillInventory?: boolean;\n\treadonly includeFullContextFiles?: boolean;\n\treadonly tokenCounter?: TokenCounterAdapter;\n\treadonly cacheProvider?: ContextBudgetCacheProviderV2;\n\t/** Maximum number of inactive (non-active) skills to include as items. Default: 15. */\n\treadonly maxInactiveSkills?: number;\n\t/** Current user query text for relevance-aware skill ranking. */\n\treadonly queryContext?: string;\n}\n\nexport interface SystemPromptBudgetedResourcesInput {\n\treadonly basePrompt: string;\n\treadonly contextFiles: readonly ContextFile[];\n\treadonly skills: readonly Skill[];\n\treadonly includeSkills: boolean;\n\treadonly options: SystemPromptContextBudgetOptions;\n}\n\nexport interface SystemPromptBudgetedResources {\n\treadonly text: string;\n\treadonly plan: PromptContextBudgetPlanV2;\n}\n\nexport function renderSystemPromptBudgetedResources(\n\tinput: SystemPromptBudgetedResourcesInput,\n): SystemPromptBudgetedResources {\n\tconst tokenCounter =\n\t\tinput.options.tokenCounter ?? createTokenCounterForMode(input.options.tokenizerMode ?? \"fallback\");\n\tconst modelId = input.options.modelId ?? \"unknown\";\n\tconst baseTokens = tokenCounter.countText(input.basePrompt, modelId).tokens;\n\tconst resourceBudget = Math.max(0, input.options.maxPromptTokens - baseTokens);\n\tconst items = createSystemPromptBudgetItems(input, resourceBudget);\n\tconst plan = planPromptContextBudgetV2({\n\t\tmaxTokens: resourceBudget,\n\t\tresponseReserveTokens: input.options.responseReserveTokens ?? 0,\n\t\tmodelId,\n\t\tpolicyVersion: CONTEXT_BUDGET_POLICY_VERSION_V2,\n\t\tquery: input.options.queryContext,\n\t\titems,\n\t\ttokenCounter,\n\t\tcacheProvider: input.options.cacheProvider,\n\t});\n\tif (items.length === 0 && !plan.emergency && plan.observability.diagnosticReasons.length === 0) {\n\t\treturn { text: renderEmptyBudgetNote(plan), plan };\n\t}\n\n\tconst included = new Set(plan.includedItemIds);\n\tconst filtered = deduplicatePointerFull(items, included);\n\tconst text = filtered\n\t\t.filter((item) => included.has(item.id))\n\t\t.map(\n\t\t\t(item) =>\n\t\t\t\tplan.selectedRepresentations.find((representation) => representation.itemId === item.id)?.text ?? item.text,\n\t\t)\n\t\t.join(\"\\n\")\n\t\t.trimEnd();\n\tconst note = renderBudgetNote(plan, baseTokens);\n\treturn { text: text ? `${text}\\n${note}` : note, plan };\n}\n\nfunction renderEmptyBudgetNote(plan: PromptContextBudgetPlanV2): string {\n\treturn [\n\t\t\"<context_budget>\",\n\t\t` <policy>${escapeXml(plan.policyVersion)}</policy>`,\n\t\t` <cache_decision plan_hit=\"${plan.observability.cache.planCache.hit ? \"true\" : \"false\"}\" />`,\n\t\trenderTokenOptimizerStatus(plan, \" \"),\n\t\t\"</context_budget>\",\n\t].join(\"\\n\");\n}\n\nfunction renderBudgetNote(plan: PromptContextBudgetPlanV2, baseTokens: number): string {\n\tconst omitted = plan.omittedItemIds.length;\n\tconst { cache, counts, diagnosticReasons, tokens } = plan.observability;\n\tconst selectedCacheHits =\n\t\tcache.representationCache.exactHits +\n\t\tcache.representationCache.semanticHits +\n\t\tcache.representationCache.pointerHits;\n\tconst renderedDiagnosticReasons =\n\t\tdiagnosticReasons.length === 0\n\t\t\t? ' <diagnostic_reasons count=\"0\" />'\n\t\t\t: [\n\t\t\t\t\t` <diagnostic_reasons count=\"${formatObservableInteger(diagnosticReasons.length)}\">`,\n\t\t\t\t\t...diagnosticReasons.map((reason) => ` <reason>${escapeXml(reason)}</reason>`),\n\t\t\t\t\t\" </diagnostic_reasons>\",\n\t\t\t\t].join(\"\\n\");\n\treturn [\n\t\t\"<context_budget>\",\n\t\t` <policy>${escapeXml(plan.policyVersion)}</policy>`,\n\t\t` <plan_hash>${escapeXml(plan.planHash)}</plan_hash>`,\n\t\t` <base_prompt_tokens>${formatObservableInteger(baseTokens)}</base_prompt_tokens>`,\n\t\t` <resource_tokens_used>${formatObservableInteger(plan.usedTokens)}</resource_tokens_used>`,\n\t\t` <resource_tokens_omitted>${formatObservableInteger(plan.omittedTokens)}</resource_tokens_omitted>`,\n\t\t` <omitted_items>${formatObservableInteger(omitted)}</omitted_items>`,\n\t\t` <emergency>${plan.emergency ? \"true\" : \"false\"}</emergency>`,\n\t\t\" <decision_observability>\",\n\t\t` <counts selected=\"${formatObservableInteger(counts.selected)}\" omitted=\"${formatObservableInteger(counts.omitted)}\" pointer=\"${formatObservableInteger(counts.pointer)}\" compressed=\"${formatObservableInteger(counts.compressed)}\" full=\"${formatObservableInteger(counts.full)}\" retrieval_fallbacks=\"${formatObservableInteger(counts.retrievalFallback)}\" />`,\n\t\t` <tokens available=\"${formatObservableInteger(tokens.available)}\" used=\"${formatObservableInteger(tokens.used)}\" raw=\"${formatObservableInteger(tokens.raw)}\" omitted=\"${formatObservableInteger(tokens.omitted)}\" token_savings=\"${formatObservableInteger(tokens.tokenSavings)}\" />`,\n\t\t` <cache_decision plan_hit=\"${cache.planCache.hit ? \"true\" : \"false\"}\" selected_hits=\"${formatObservableInteger(selectedCacheHits)}\" misses=\"${formatObservableInteger(cache.representationCache.misses)}\" stale_rejects=\"${formatObservableInteger(cache.representationCache.staleRejects)}\" negative_hits=\"${formatObservableInteger(cache.representationCache.negativeHits)}\" writes=\"${formatObservableInteger(cache.representationCache.writes)}\" />`,\n\t\trenderedDiagnosticReasons,\n\t\trenderTokenOptimizerStatus(plan, \" \"),\n\t\t\" </decision_observability>\",\n\t\t\" <note>Some low-priority resource inventory may be represented by pointers. Use read on referenced paths when needed.</note>\",\n\t\t\"</context_budget>\",\n\t].join(\"\\n\");\n}\n\nfunction renderTokenOptimizerStatus(plan: PromptContextBudgetPlanV2, indent: string): string {\n\tconst { tokenOptimizer } = plan.observability;\n\treturn `${indent}<token_optimizer optimizer_id=\"${escapeXml(tokenOptimizer.optimizerId)}\" status=\"${escapeXml(tokenOptimizer.status)}\" active=\"${tokenOptimizer.active ? \"true\" : \"false\"}\" active_context_budget_optimizer=\"${escapeXml(tokenOptimizer.activeContextBudgetOptimizer)}\" compatibility_only=\"${tokenOptimizer.compatibilityOnly ? \"true\" : \"false\"}\" />`;\n}\n\nfunction formatObservableInteger(value: number): string {\n\tif (!Number.isFinite(value)) {\n\t\treturn \"0\";\n\t}\n\treturn String(Math.max(0, Math.floor(value)));\n}\n\n/**\n * R3: When a context-full item is included, exclude the matching pointer\n * so the same file does not consume tokens twice.\n */\nfunction deduplicatePointerFull(\n\titems: readonly ContextBudgetItemV2[],\n\tincluded: ReadonlySet<string>,\n): readonly ContextBudgetItemV2[] {\n\tconst includedFullPaths = new Set<string>();\n\tfor (const item of items) {\n\t\tif (item.id.startsWith(\"context-full:\") && included.has(item.id)) {\n\t\t\tconst path = item.id.slice(\"context-full:\".length);\n\t\t\tincludedFullPaths.add(path);\n\t\t}\n\t}\n\tif (includedFullPaths.size === 0) {\n\t\treturn items;\n\t}\n\treturn items.filter((item) => {\n\t\tif (!item.id.startsWith(\"context-pointer:\")) {\n\t\t\treturn true;\n\t\t}\n\t\tconst path = item.id.slice(\"context-pointer:\".length);\n\t\treturn !includedFullPaths.has(path);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-layer disk persistence for the context-budget-v2 representation
|
|
3
|
+
* cache.
|
|
4
|
+
*
|
|
5
|
+
* The in-memory provider (`context-budget-v2-cache-provider.ts`) dies with the
|
|
6
|
+
* process, so every new session re-renders and re-compresses context it already
|
|
7
|
+
* paid for. This provider keeps the same synchronous
|
|
8
|
+
* `ContextBudgetCacheProviderV2` contract and adds a bounded, atomically
|
|
9
|
+
* written JSON snapshot on disk.
|
|
10
|
+
*
|
|
11
|
+
* Scope: representation entries and negative entries only. Plan entries stay in
|
|
12
|
+
* memory on purpose — a plan is session-scoped (its key folds in the
|
|
13
|
+
* invalidation snapshot), so persisting it would only ever store misses.
|
|
14
|
+
*
|
|
15
|
+
* Safety: reads go through the same validator as the memory provider
|
|
16
|
+
* (`validateContextBudgetRepresentationCacheEntryV2`), which re-checks source
|
|
17
|
+
* hash, representation fingerprint, model, tokenizer, policy version and TTL.
|
|
18
|
+
* A corrupt, truncated, oversized, or schema-mismatched snapshot is discarded
|
|
19
|
+
* and the cache simply starts empty; a cache is never load-bearing for
|
|
20
|
+
* correctness.
|
|
21
|
+
*/
|
|
22
|
+
import { type ContextCacheInvalidationSnapshot } from "./context-budget-v2-cache-invalidation.ts";
|
|
23
|
+
import type { ContextBudgetCacheLayerV2, ContextBudgetCacheProviderV2, ContextBudgetNegativeCacheEntryV2, ContextBudgetPlanCacheEntryV2, ContextBudgetPlanCacheReadV2, ContextBudgetRepresentationCacheEntryV2, ContextBudgetRepresentationCacheReadV2 } from "./context-budget-v2-types.ts";
|
|
24
|
+
export declare const CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION: "context-budget-v2-diskcache-1";
|
|
25
|
+
/** Default cap on persisted representation entries. */
|
|
26
|
+
export declare const DEFAULT_DISK_CACHE_MAX_ENTRIES = 2048;
|
|
27
|
+
/** Default cap on the persisted snapshot size. Oversized snapshots are dropped on load. */
|
|
28
|
+
export declare const DEFAULT_DISK_CACHE_MAX_BYTES: number;
|
|
29
|
+
/** Default write-behind debounce. Collapses a burst of writes into a single flush. */
|
|
30
|
+
export declare const DEFAULT_DISK_CACHE_FLUSH_DEBOUNCE_MS = 750;
|
|
31
|
+
/** Default cap on a single persisted representation's text length. */
|
|
32
|
+
export declare const DEFAULT_DISK_CACHE_MAX_ENTRY_TEXT_LENGTH: number;
|
|
33
|
+
export interface DiskContextBudgetCacheOptionsV2 {
|
|
34
|
+
/** Directory holding the snapshot file. Created on first flush. */
|
|
35
|
+
readonly dir: string;
|
|
36
|
+
/** Cache layer reported on reads. Defaults to `workspace`. */
|
|
37
|
+
readonly layer?: ContextBudgetCacheLayerV2;
|
|
38
|
+
/** Max persisted representation entries (LRU). */
|
|
39
|
+
readonly maxEntries?: number;
|
|
40
|
+
/** Max accepted snapshot size in bytes. */
|
|
41
|
+
readonly maxBytes?: number;
|
|
42
|
+
/** Max persisted text length for a single entry. */
|
|
43
|
+
readonly maxEntryTextLength?: number;
|
|
44
|
+
/** Write-behind debounce in ms. `0` flushes synchronously on every write. */
|
|
45
|
+
readonly flushDebounceMs?: number;
|
|
46
|
+
/** Injectable clock, for tests. */
|
|
47
|
+
readonly now?: () => number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a disk backed workspace cache provider. Loading is synchronous and
|
|
51
|
+
* best-effort: any failure yields an empty in-memory cache rather than throwing,
|
|
52
|
+
* because a cache miss is always a legal outcome.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createDiskContextBudgetCacheProviderV2(options: DiskContextBudgetCacheOptionsV2): DiskContextBudgetCacheProviderV2;
|
|
55
|
+
export declare class DiskContextBudgetCacheProviderV2 implements ContextBudgetCacheProviderV2 {
|
|
56
|
+
private readonly representations;
|
|
57
|
+
private readonly negatives;
|
|
58
|
+
/** Plans stay in memory: their keys are session-scoped, so persisting them stores only misses. */
|
|
59
|
+
private readonly plans;
|
|
60
|
+
private readonly layer;
|
|
61
|
+
private readonly maxEntries;
|
|
62
|
+
private readonly maxBytes;
|
|
63
|
+
private readonly maxEntryTextLength;
|
|
64
|
+
private readonly flushDebounceMs;
|
|
65
|
+
private readonly now;
|
|
66
|
+
private readonly snapshotPath;
|
|
67
|
+
private invalidationSnapshot;
|
|
68
|
+
private dirty;
|
|
69
|
+
private flushTimer;
|
|
70
|
+
private loadError;
|
|
71
|
+
constructor(options: DiskContextBudgetCacheOptionsV2);
|
|
72
|
+
/** Absolute path of the persisted snapshot. */
|
|
73
|
+
get snapshotFilePath(): string;
|
|
74
|
+
/** Reason the on-disk snapshot was rejected, if it was. `undefined` on a clean load or a cold start. */
|
|
75
|
+
get lastLoadError(): string | undefined;
|
|
76
|
+
/** Number of representation entries currently held. */
|
|
77
|
+
get size(): number;
|
|
78
|
+
private load;
|
|
79
|
+
private markDirty;
|
|
80
|
+
/**
|
|
81
|
+
* Write the snapshot to disk atomically (temp file + rename). Best-effort:
|
|
82
|
+
* a failed flush leaves the previous snapshot intact and keeps the entries
|
|
83
|
+
* in memory. Returns `true` when a snapshot was written.
|
|
84
|
+
*/
|
|
85
|
+
flush(): boolean;
|
|
86
|
+
/** Cancel any pending flush and persist immediately. Safe to call more than once. */
|
|
87
|
+
close(): void;
|
|
88
|
+
getInvalidationSnapshot(): ContextCacheInvalidationSnapshot | undefined;
|
|
89
|
+
setInvalidationSnapshot(snapshot: ContextCacheInvalidationSnapshot): void;
|
|
90
|
+
readRepresentation(key: string): ContextBudgetRepresentationCacheReadV2 | undefined;
|
|
91
|
+
writeRepresentation(input: {
|
|
92
|
+
readonly key: string;
|
|
93
|
+
readonly entry: ContextBudgetRepresentationCacheEntryV2;
|
|
94
|
+
}): void;
|
|
95
|
+
deleteRepresentation(key: string): void;
|
|
96
|
+
readNegativeRepresentation(key: string): ContextBudgetNegativeCacheEntryV2 | undefined;
|
|
97
|
+
writeNegativeRepresentation(input: {
|
|
98
|
+
readonly key: string;
|
|
99
|
+
readonly reason: string;
|
|
100
|
+
}): void;
|
|
101
|
+
deleteNegativeRepresentation(key: string): void;
|
|
102
|
+
readPlan(key: string): ContextBudgetPlanCacheReadV2 | undefined;
|
|
103
|
+
writePlan(input: {
|
|
104
|
+
readonly key: string;
|
|
105
|
+
readonly entry: ContextBudgetPlanCacheEntryV2;
|
|
106
|
+
}): void;
|
|
107
|
+
deletePlan(key: string): void;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=context-budget-v2-cache-disk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-budget-v2-cache-disk.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-v2-cache-disk.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Workspace-layer disk persistence for the context-budget-v2 representation\n * cache.\n *\n * The in-memory provider (`context-budget-v2-cache-provider.ts`) dies with the\n * process, so every new session re-renders and re-compresses context it already\n * paid for. This provider keeps the same synchronous\n * `ContextBudgetCacheProviderV2` contract and adds a bounded, atomically\n * written JSON snapshot on disk.\n *\n * Scope: representation entries and negative entries only. Plan entries stay in\n * memory on purpose — a plan is session-scoped (its key folds in the\n * invalidation snapshot), so persisting it would only ever store misses.\n *\n * Safety: reads go through the same validator as the memory provider\n * (`validateContextBudgetRepresentationCacheEntryV2`), which re-checks source\n * hash, representation fingerprint, model, tokenizer, policy version and TTL.\n * A corrupt, truncated, oversized, or schema-mismatched snapshot is discarded\n * and the cache simply starts empty; a cache is never load-bearing for\n * correctness.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { containsCredentialShape } from \"./compaction/transaction.ts\";\nimport {\n\ttype ContextCacheInvalidationSnapshot,\n\tcreateContextCacheInvalidationSnapshot,\n} from \"./context-budget-v2-cache-invalidation.ts\";\nimport type {\n\tContextBudgetCacheLayerV2,\n\tContextBudgetCacheProviderV2,\n\tContextBudgetNegativeCacheEntryV2,\n\tContextBudgetPlanCacheEntryV2,\n\tContextBudgetPlanCacheReadV2,\n\tContextBudgetRepresentationCacheEntryV2,\n\tContextBudgetRepresentationCacheReadV2,\n} from \"./context-budget-v2-types.ts\";\n\nexport const CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION = \"context-budget-v2-diskcache-1\" as const;\n\n/** Default cap on persisted representation entries. */\nexport const DEFAULT_DISK_CACHE_MAX_ENTRIES = 2048;\n/** Default cap on the persisted snapshot size. Oversized snapshots are dropped on load. */\nexport const DEFAULT_DISK_CACHE_MAX_BYTES = 32 * 1024 * 1024;\n/** Default write-behind debounce. Collapses a burst of writes into a single flush. */\nexport const DEFAULT_DISK_CACHE_FLUSH_DEBOUNCE_MS = 750;\n/** Default cap on a single persisted representation's text length. */\nexport const DEFAULT_DISK_CACHE_MAX_ENTRY_TEXT_LENGTH = 512 * 1024;\n\nexport interface DiskContextBudgetCacheOptionsV2 {\n\t/** Directory holding the snapshot file. Created on first flush. */\n\treadonly dir: string;\n\t/** Cache layer reported on reads. Defaults to `workspace`. */\n\treadonly layer?: ContextBudgetCacheLayerV2;\n\t/** Max persisted representation entries (LRU). */\n\treadonly maxEntries?: number;\n\t/** Max accepted snapshot size in bytes. */\n\treadonly maxBytes?: number;\n\t/** Max persisted text length for a single entry. */\n\treadonly maxEntryTextLength?: number;\n\t/** Write-behind debounce in ms. `0` flushes synchronously on every write. */\n\treadonly flushDebounceMs?: number;\n\t/** Injectable clock, for tests. */\n\treadonly now?: () => number;\n}\n\ninterface DiskSnapshotFileV2 {\n\treadonly schemaVersion: typeof CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION;\n\treadonly representations: readonly (readonly [string, ContextBudgetRepresentationCacheEntryV2])[];\n\treadonly negatives: readonly (readonly [string, ContextBudgetNegativeCacheEntryV2])[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction containsCredentialShapeInValue(value: unknown): boolean {\n\tconst pending: unknown[] = [value];\n\tconst seen = new Set<object>();\n\twhile (pending.length > 0) {\n\t\tconst current = pending.pop();\n\t\tif (typeof current === \"string\") {\n\t\t\tif (containsCredentialShape(current)) return true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof current !== \"object\" || current === null || seen.has(current)) continue;\n\t\tseen.add(current);\n\t\tif (Array.isArray(current)) {\n\t\t\tpending.push(...current);\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const [key, nested] of Object.entries(current)) {\n\t\t\tif (containsCredentialShape(key)) return true;\n\t\t\tpending.push(nested);\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction isPersistableRepresentation(\n\tvalue: unknown,\n\tmaxEntryTextLength: number,\n): value is ContextBudgetRepresentationCacheEntryV2 {\n\tif (!isRecord(value)) return false;\n\tif (typeof value.kind !== \"string\") return false;\n\tif (typeof value.text !== \"string\" || value.text.length > maxEntryTextLength) return false;\n\tif (containsCredentialShapeInValue(value)) return false;\n\tconst estimated = value.estimatedTokens;\n\tif (typeof estimated !== \"number\" || !Number.isFinite(estimated) || estimated < 0) return false;\n\tif (typeof value.fidelity !== \"string\") return false;\n\tif (typeof value.sourceHash !== \"string\" || value.sourceHash.length === 0) return false;\n\tif (typeof value.representationFingerprint !== \"string\") return false;\n\tif (typeof value.modelId !== \"string\") return false;\n\tif (typeof value.tokenizerId !== \"string\") return false;\n\tif (typeof value.policyVersion !== \"string\") return false;\n\treturn true;\n}\n\nfunction isPersistableNegative(value: unknown): value is ContextBudgetNegativeCacheEntryV2 {\n\treturn isRecord(value) && typeof value.reason === \"string\" && !containsCredentialShapeInValue(value);\n}\n\nfunction readLru<T>(store: Map<string, T>, key: string): T | undefined {\n\tconst entry = store.get(key);\n\tif (entry === undefined) return undefined;\n\tstore.delete(key);\n\tstore.set(key, entry);\n\treturn entry;\n}\n\nfunction writeLru<T>(store: Map<string, T>, key: string, entry: T, maxEntries: number): void {\n\tstore.delete(key);\n\tstore.set(key, entry);\n\twhile (store.size > maxEntries) {\n\t\tconst oldestKey = store.keys().next().value;\n\t\tif (oldestKey === undefined) return;\n\t\tstore.delete(oldestKey);\n\t}\n}\n\n/**\n * Create a disk backed workspace cache provider. Loading is synchronous and\n * best-effort: any failure yields an empty in-memory cache rather than throwing,\n * because a cache miss is always a legal outcome.\n */\nexport function createDiskContextBudgetCacheProviderV2(\n\toptions: DiskContextBudgetCacheOptionsV2,\n): DiskContextBudgetCacheProviderV2 {\n\treturn new DiskContextBudgetCacheProviderV2(options);\n}\n\nexport class DiskContextBudgetCacheProviderV2 implements ContextBudgetCacheProviderV2 {\n\tprivate readonly representations = new Map<string, ContextBudgetRepresentationCacheEntryV2>();\n\tprivate readonly negatives = new Map<string, ContextBudgetNegativeCacheEntryV2>();\n\t/** Plans stay in memory: their keys are session-scoped, so persisting them stores only misses. */\n\tprivate readonly plans = new Map<string, ContextBudgetPlanCacheEntryV2>();\n\tprivate readonly layer: ContextBudgetCacheLayerV2;\n\tprivate readonly maxEntries: number;\n\tprivate readonly maxBytes: number;\n\tprivate readonly maxEntryTextLength: number;\n\tprivate readonly flushDebounceMs: number;\n\tprivate readonly now: () => number;\n\tprivate readonly snapshotPath: string;\n\tprivate invalidationSnapshot: ContextCacheInvalidationSnapshot | undefined;\n\tprivate dirty = false;\n\tprivate flushTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate loadError: string | undefined;\n\n\tconstructor(options: DiskContextBudgetCacheOptionsV2) {\n\t\tthis.layer = options.layer ?? \"workspace\";\n\t\tthis.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_DISK_CACHE_MAX_ENTRIES);\n\t\tthis.maxBytes = Math.max(1024, options.maxBytes ?? DEFAULT_DISK_CACHE_MAX_BYTES);\n\t\tthis.maxEntryTextLength = Math.max(1, options.maxEntryTextLength ?? DEFAULT_DISK_CACHE_MAX_ENTRY_TEXT_LENGTH);\n\t\tthis.flushDebounceMs = Math.max(0, options.flushDebounceMs ?? DEFAULT_DISK_CACHE_FLUSH_DEBOUNCE_MS);\n\t\tthis.now = options.now ?? Date.now;\n\t\tthis.snapshotPath = path.join(options.dir, \"representations.json\");\n\t\tthis.load();\n\t}\n\n\t/** Absolute path of the persisted snapshot. */\n\tget snapshotFilePath(): string {\n\t\treturn this.snapshotPath;\n\t}\n\n\t/** Reason the on-disk snapshot was rejected, if it was. `undefined` on a clean load or a cold start. */\n\tget lastLoadError(): string | undefined {\n\t\treturn this.loadError;\n\t}\n\n\t/** Number of representation entries currently held. */\n\tget size(): number {\n\t\treturn this.representations.size;\n\t}\n\n\tprivate load(): void {\n\t\tlet raw: string;\n\t\ttry {\n\t\t\tconst stat = fs.statSync(this.snapshotPath);\n\t\t\tif (stat.size > this.maxBytes) {\n\t\t\t\tthis.loadError = `snapshot exceeds maxBytes (${stat.size} > ${this.maxBytes})`;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\traw = fs.readFileSync(this.snapshotPath, \"utf8\");\n\t\t} catch {\n\t\t\t// Cold start: no snapshot yet, or it is unreadable. Both are normal.\n\t\t\treturn;\n\t\t}\n\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(raw);\n\t\t} catch {\n\t\t\tthis.loadError = \"snapshot is not valid JSON\";\n\t\t\treturn;\n\t\t}\n\t\tif (!isRecord(parsed) || parsed.schemaVersion !== CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION) {\n\t\t\tthis.loadError = \"snapshot schema mismatch\";\n\t\t\treturn;\n\t\t}\n\n\t\tif (containsCredentialShapeInValue(parsed)) {\n\t\t\tthis.loadError = \"snapshot contains credential-shaped text\";\n\t\t\ttry {\n\t\t\t\tfs.rmSync(this.snapshotPath, { force: true });\n\t\t\t} catch {\n\t\t\t\t// The unsafe snapshot remains unreadable to this provider.\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst representations = Array.isArray(parsed.representations) ? parsed.representations : [];\n\t\tfor (const pair of representations) {\n\t\t\tif (!Array.isArray(pair) || pair.length !== 2) continue;\n\t\t\tconst [key, entry] = pair as [unknown, unknown];\n\t\t\tif (typeof key !== \"string\" || key.length === 0) continue;\n\t\t\tif (!isPersistableRepresentation(entry, this.maxEntryTextLength)) continue;\n\t\t\twriteLru(this.representations, key, entry, this.maxEntries);\n\t\t}\n\n\t\tconst negatives = Array.isArray(parsed.negatives) ? parsed.negatives : [];\n\t\tfor (const pair of negatives) {\n\t\t\tif (!Array.isArray(pair) || pair.length !== 2) continue;\n\t\t\tconst [key, entry] = pair as [unknown, unknown];\n\t\t\tif (typeof key !== \"string\" || key.length === 0) continue;\n\t\t\tif (!isPersistableNegative(entry)) continue;\n\t\t\twriteLru(this.negatives, key, entry, this.maxEntries);\n\t\t}\n\t}\n\n\tprivate markDirty(): void {\n\t\tthis.dirty = true;\n\t\tif (this.flushDebounceMs === 0) {\n\t\t\tthis.flush();\n\t\t\treturn;\n\t\t}\n\t\tif (this.flushTimer !== undefined) return;\n\t\tthis.flushTimer = setTimeout(() => {\n\t\t\tthis.flushTimer = undefined;\n\t\t\tthis.flush();\n\t\t}, this.flushDebounceMs);\n\t\tthis.flushTimer.unref?.();\n\t}\n\n\t/**\n\t * Write the snapshot to disk atomically (temp file + rename). Best-effort:\n\t * a failed flush leaves the previous snapshot intact and keeps the entries\n\t * in memory. Returns `true` when a snapshot was written.\n\t */\n\tflush(): boolean {\n\t\tif (this.flushTimer !== undefined) {\n\t\t\tclearTimeout(this.flushTimer);\n\t\t\tthis.flushTimer = undefined;\n\t\t}\n\t\tif (!this.dirty) return false;\n\t\tconst snapshot: DiskSnapshotFileV2 = {\n\t\t\tschemaVersion: CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION,\n\t\t\trepresentations: [...this.representations.entries()]\n\t\t\t\t.filter(\n\t\t\t\t\t([key, entry]) =>\n\t\t\t\t\t\t!containsCredentialShape(key) && isPersistableRepresentation(entry, this.maxEntryTextLength),\n\t\t\t\t)\n\t\t\t\t.map(([key, entry]) => [key, entry] as const),\n\t\t\tnegatives: [...this.negatives.entries()]\n\t\t\t\t.filter(([key, entry]) => !containsCredentialShape(key) && isPersistableNegative(entry))\n\t\t\t\t.map(([key, entry]) => [key, entry] as const),\n\t\t};\n\t\tlet serialized: string;\n\t\ttry {\n\t\t\tserialized = JSON.stringify(snapshot);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t\tif (Buffer.byteLength(serialized, \"utf8\") > this.maxBytes) {\n\t\t\t// Drop the oldest half and retry once rather than growing forever.\n\t\t\tconst keys = [...this.representations.keys()];\n\t\t\tif (keys.length === 0) return false;\n\t\t\tconst dropCount = Math.max(1, Math.ceil(keys.length / 2));\n\t\t\tfor (const key of keys.slice(0, dropCount)) this.representations.delete(key);\n\t\t\treturn this.flush();\n\t\t}\n\t\tconst tempPath = `${this.snapshotPath}.${process.pid}.tmp`;\n\t\ttry {\n\t\t\tfs.mkdirSync(path.dirname(this.snapshotPath), { recursive: true });\n\t\t\tfs.writeFileSync(tempPath, serialized, { encoding: \"utf8\", mode: 0o600 });\n\t\t\tfs.renameSync(tempPath, this.snapshotPath);\n\t\t\tthis.dirty = false;\n\t\t\treturn true;\n\t\t} catch {\n\t\t\ttry {\n\t\t\t\tfs.rmSync(tempPath, { force: true });\n\t\t\t} catch {\n\t\t\t\t// The temp file is unlinkable; nothing further to do.\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/** Cancel any pending flush and persist immediately. Safe to call more than once. */\n\tclose(): void {\n\t\tthis.flush();\n\t}\n\n\tgetInvalidationSnapshot(): ContextCacheInvalidationSnapshot | undefined {\n\t\treturn this.invalidationSnapshot;\n\t}\n\n\tsetInvalidationSnapshot(snapshot: ContextCacheInvalidationSnapshot): void {\n\t\tthis.invalidationSnapshot = createContextCacheInvalidationSnapshot({\n\t\t\tforkId: snapshot.forkId,\n\t\t\tworktreeFingerprint: snapshot.worktreeFingerprint,\n\t\t\tactiveModelId: snapshot.activeModelId,\n\t\t\tcompactionModelId: snapshot.compactionModelId,\n\t\t\tglobalEpoch: snapshot.globalEpoch,\n\t\t\ttranscriptRepair: snapshot.counters.transcriptRepair,\n\t\t\ttoolResultDisposition: snapshot.counters.toolResultDisposition,\n\t\t\tevidenceReceipt: snapshot.counters.evidenceReceipt,\n\t\t\tuserSteering: snapshot.counters.userSteering,\n\t\t\tsettings: snapshot.counters.settings,\n\t\t});\n\t}\n\n\treadRepresentation(key: string): ContextBudgetRepresentationCacheReadV2 | undefined {\n\t\tconst entry = readLru(this.representations, key);\n\t\treturn entry ? { entry, layer: this.layer } : undefined;\n\t}\n\n\twriteRepresentation(input: { readonly key: string; readonly entry: ContextBudgetRepresentationCacheEntryV2 }): void {\n\t\twriteLru(this.representations, input.key, input.entry, this.maxEntries);\n\t\tif (\n\t\t\t(!containsCredentialShape(input.key) && isPersistableRepresentation(input.entry, this.maxEntryTextLength)) ||\n\t\t\tfs.existsSync(this.snapshotPath)\n\t\t) {\n\t\t\tthis.markDirty();\n\t\t}\n\t}\n\n\tdeleteRepresentation(key: string): void {\n\t\tif (this.representations.delete(key)) this.markDirty();\n\t}\n\n\treadNegativeRepresentation(key: string): ContextBudgetNegativeCacheEntryV2 | undefined {\n\t\treturn readLru(this.negatives, key);\n\t}\n\n\twriteNegativeRepresentation(input: { readonly key: string; readonly reason: string }): void {\n\t\twriteLru(\n\t\t\tthis.negatives,\n\t\t\tinput.key,\n\t\t\t{ reason: input.reason, createdAtEpochMs: this.now(), layer: this.layer },\n\t\t\tthis.maxEntries,\n\t\t);\n\t\tthis.markDirty();\n\t}\n\n\tdeleteNegativeRepresentation(key: string): void {\n\t\tif (this.negatives.delete(key)) this.markDirty();\n\t}\n\n\treadPlan(key: string): ContextBudgetPlanCacheReadV2 | undefined {\n\t\tconst entry = readLru(this.plans, key);\n\t\treturn entry ? { entry, layer: \"session\" } : undefined;\n\t}\n\n\twritePlan(input: { readonly key: string; readonly entry: ContextBudgetPlanCacheEntryV2 }): void {\n\t\twriteLru(this.plans, input.key, input.entry, this.maxEntries);\n\t}\n\n\tdeletePlan(key: string): void {\n\t\tthis.plans.delete(key);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-layer disk persistence for the context-budget-v2 representation
|
|
3
|
+
* cache.
|
|
4
|
+
*
|
|
5
|
+
* The in-memory provider (`context-budget-v2-cache-provider.ts`) dies with the
|
|
6
|
+
* process, so every new session re-renders and re-compresses context it already
|
|
7
|
+
* paid for. This provider keeps the same synchronous
|
|
8
|
+
* `ContextBudgetCacheProviderV2` contract and adds a bounded, atomically
|
|
9
|
+
* written JSON snapshot on disk.
|
|
10
|
+
*
|
|
11
|
+
* Scope: representation entries and negative entries only. Plan entries stay in
|
|
12
|
+
* memory on purpose — a plan is session-scoped (its key folds in the
|
|
13
|
+
* invalidation snapshot), so persisting it would only ever store misses.
|
|
14
|
+
*
|
|
15
|
+
* Safety: reads go through the same validator as the memory provider
|
|
16
|
+
* (`validateContextBudgetRepresentationCacheEntryV2`), which re-checks source
|
|
17
|
+
* hash, representation fingerprint, model, tokenizer, policy version and TTL.
|
|
18
|
+
* A corrupt, truncated, oversized, or schema-mismatched snapshot is discarded
|
|
19
|
+
* and the cache simply starts empty; a cache is never load-bearing for
|
|
20
|
+
* correctness.
|
|
21
|
+
*/
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { containsCredentialShape } from "./compaction/transaction.js";
|
|
25
|
+
import { createContextCacheInvalidationSnapshot, } from "./context-budget-v2-cache-invalidation.js";
|
|
26
|
+
export const CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION = "context-budget-v2-diskcache-1";
|
|
27
|
+
/** Default cap on persisted representation entries. */
|
|
28
|
+
export const DEFAULT_DISK_CACHE_MAX_ENTRIES = 2048;
|
|
29
|
+
/** Default cap on the persisted snapshot size. Oversized snapshots are dropped on load. */
|
|
30
|
+
export const DEFAULT_DISK_CACHE_MAX_BYTES = 32 * 1024 * 1024;
|
|
31
|
+
/** Default write-behind debounce. Collapses a burst of writes into a single flush. */
|
|
32
|
+
export const DEFAULT_DISK_CACHE_FLUSH_DEBOUNCE_MS = 750;
|
|
33
|
+
/** Default cap on a single persisted representation's text length. */
|
|
34
|
+
export const DEFAULT_DISK_CACHE_MAX_ENTRY_TEXT_LENGTH = 512 * 1024;
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
function containsCredentialShapeInValue(value) {
|
|
39
|
+
const pending = [value];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
while (pending.length > 0) {
|
|
42
|
+
const current = pending.pop();
|
|
43
|
+
if (typeof current === "string") {
|
|
44
|
+
if (containsCredentialShape(current))
|
|
45
|
+
return true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (typeof current !== "object" || current === null || seen.has(current))
|
|
49
|
+
continue;
|
|
50
|
+
seen.add(current);
|
|
51
|
+
if (Array.isArray(current)) {
|
|
52
|
+
pending.push(...current);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
for (const [key, nested] of Object.entries(current)) {
|
|
56
|
+
if (containsCredentialShape(key))
|
|
57
|
+
return true;
|
|
58
|
+
pending.push(nested);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
function isPersistableRepresentation(value, maxEntryTextLength) {
|
|
64
|
+
if (!isRecord(value))
|
|
65
|
+
return false;
|
|
66
|
+
if (typeof value.kind !== "string")
|
|
67
|
+
return false;
|
|
68
|
+
if (typeof value.text !== "string" || value.text.length > maxEntryTextLength)
|
|
69
|
+
return false;
|
|
70
|
+
if (containsCredentialShapeInValue(value))
|
|
71
|
+
return false;
|
|
72
|
+
const estimated = value.estimatedTokens;
|
|
73
|
+
if (typeof estimated !== "number" || !Number.isFinite(estimated) || estimated < 0)
|
|
74
|
+
return false;
|
|
75
|
+
if (typeof value.fidelity !== "string")
|
|
76
|
+
return false;
|
|
77
|
+
if (typeof value.sourceHash !== "string" || value.sourceHash.length === 0)
|
|
78
|
+
return false;
|
|
79
|
+
if (typeof value.representationFingerprint !== "string")
|
|
80
|
+
return false;
|
|
81
|
+
if (typeof value.modelId !== "string")
|
|
82
|
+
return false;
|
|
83
|
+
if (typeof value.tokenizerId !== "string")
|
|
84
|
+
return false;
|
|
85
|
+
if (typeof value.policyVersion !== "string")
|
|
86
|
+
return false;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
function isPersistableNegative(value) {
|
|
90
|
+
return isRecord(value) && typeof value.reason === "string" && !containsCredentialShapeInValue(value);
|
|
91
|
+
}
|
|
92
|
+
function readLru(store, key) {
|
|
93
|
+
const entry = store.get(key);
|
|
94
|
+
if (entry === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
store.delete(key);
|
|
97
|
+
store.set(key, entry);
|
|
98
|
+
return entry;
|
|
99
|
+
}
|
|
100
|
+
function writeLru(store, key, entry, maxEntries) {
|
|
101
|
+
store.delete(key);
|
|
102
|
+
store.set(key, entry);
|
|
103
|
+
while (store.size > maxEntries) {
|
|
104
|
+
const oldestKey = store.keys().next().value;
|
|
105
|
+
if (oldestKey === undefined)
|
|
106
|
+
return;
|
|
107
|
+
store.delete(oldestKey);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create a disk backed workspace cache provider. Loading is synchronous and
|
|
112
|
+
* best-effort: any failure yields an empty in-memory cache rather than throwing,
|
|
113
|
+
* because a cache miss is always a legal outcome.
|
|
114
|
+
*/
|
|
115
|
+
export function createDiskContextBudgetCacheProviderV2(options) {
|
|
116
|
+
return new DiskContextBudgetCacheProviderV2(options);
|
|
117
|
+
}
|
|
118
|
+
export class DiskContextBudgetCacheProviderV2 {
|
|
119
|
+
representations = new Map();
|
|
120
|
+
negatives = new Map();
|
|
121
|
+
/** Plans stay in memory: their keys are session-scoped, so persisting them stores only misses. */
|
|
122
|
+
plans = new Map();
|
|
123
|
+
layer;
|
|
124
|
+
maxEntries;
|
|
125
|
+
maxBytes;
|
|
126
|
+
maxEntryTextLength;
|
|
127
|
+
flushDebounceMs;
|
|
128
|
+
now;
|
|
129
|
+
snapshotPath;
|
|
130
|
+
invalidationSnapshot;
|
|
131
|
+
dirty = false;
|
|
132
|
+
flushTimer;
|
|
133
|
+
loadError;
|
|
134
|
+
constructor(options) {
|
|
135
|
+
this.layer = options.layer ?? "workspace";
|
|
136
|
+
this.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_DISK_CACHE_MAX_ENTRIES);
|
|
137
|
+
this.maxBytes = Math.max(1024, options.maxBytes ?? DEFAULT_DISK_CACHE_MAX_BYTES);
|
|
138
|
+
this.maxEntryTextLength = Math.max(1, options.maxEntryTextLength ?? DEFAULT_DISK_CACHE_MAX_ENTRY_TEXT_LENGTH);
|
|
139
|
+
this.flushDebounceMs = Math.max(0, options.flushDebounceMs ?? DEFAULT_DISK_CACHE_FLUSH_DEBOUNCE_MS);
|
|
140
|
+
this.now = options.now ?? Date.now;
|
|
141
|
+
this.snapshotPath = path.join(options.dir, "representations.json");
|
|
142
|
+
this.load();
|
|
143
|
+
}
|
|
144
|
+
/** Absolute path of the persisted snapshot. */
|
|
145
|
+
get snapshotFilePath() {
|
|
146
|
+
return this.snapshotPath;
|
|
147
|
+
}
|
|
148
|
+
/** Reason the on-disk snapshot was rejected, if it was. `undefined` on a clean load or a cold start. */
|
|
149
|
+
get lastLoadError() {
|
|
150
|
+
return this.loadError;
|
|
151
|
+
}
|
|
152
|
+
/** Number of representation entries currently held. */
|
|
153
|
+
get size() {
|
|
154
|
+
return this.representations.size;
|
|
155
|
+
}
|
|
156
|
+
load() {
|
|
157
|
+
let raw;
|
|
158
|
+
try {
|
|
159
|
+
const stat = fs.statSync(this.snapshotPath);
|
|
160
|
+
if (stat.size > this.maxBytes) {
|
|
161
|
+
this.loadError = `snapshot exceeds maxBytes (${stat.size} > ${this.maxBytes})`;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
raw = fs.readFileSync(this.snapshotPath, "utf8");
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Cold start: no snapshot yet, or it is unreadable. Both are normal.
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
let parsed;
|
|
171
|
+
try {
|
|
172
|
+
parsed = JSON.parse(raw);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
this.loadError = "snapshot is not valid JSON";
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!isRecord(parsed) || parsed.schemaVersion !== CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION) {
|
|
179
|
+
this.loadError = "snapshot schema mismatch";
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (containsCredentialShapeInValue(parsed)) {
|
|
183
|
+
this.loadError = "snapshot contains credential-shaped text";
|
|
184
|
+
try {
|
|
185
|
+
fs.rmSync(this.snapshotPath, { force: true });
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// The unsafe snapshot remains unreadable to this provider.
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const representations = Array.isArray(parsed.representations) ? parsed.representations : [];
|
|
193
|
+
for (const pair of representations) {
|
|
194
|
+
if (!Array.isArray(pair) || pair.length !== 2)
|
|
195
|
+
continue;
|
|
196
|
+
const [key, entry] = pair;
|
|
197
|
+
if (typeof key !== "string" || key.length === 0)
|
|
198
|
+
continue;
|
|
199
|
+
if (!isPersistableRepresentation(entry, this.maxEntryTextLength))
|
|
200
|
+
continue;
|
|
201
|
+
writeLru(this.representations, key, entry, this.maxEntries);
|
|
202
|
+
}
|
|
203
|
+
const negatives = Array.isArray(parsed.negatives) ? parsed.negatives : [];
|
|
204
|
+
for (const pair of negatives) {
|
|
205
|
+
if (!Array.isArray(pair) || pair.length !== 2)
|
|
206
|
+
continue;
|
|
207
|
+
const [key, entry] = pair;
|
|
208
|
+
if (typeof key !== "string" || key.length === 0)
|
|
209
|
+
continue;
|
|
210
|
+
if (!isPersistableNegative(entry))
|
|
211
|
+
continue;
|
|
212
|
+
writeLru(this.negatives, key, entry, this.maxEntries);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
markDirty() {
|
|
216
|
+
this.dirty = true;
|
|
217
|
+
if (this.flushDebounceMs === 0) {
|
|
218
|
+
this.flush();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (this.flushTimer !== undefined)
|
|
222
|
+
return;
|
|
223
|
+
this.flushTimer = setTimeout(() => {
|
|
224
|
+
this.flushTimer = undefined;
|
|
225
|
+
this.flush();
|
|
226
|
+
}, this.flushDebounceMs);
|
|
227
|
+
this.flushTimer.unref?.();
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Write the snapshot to disk atomically (temp file + rename). Best-effort:
|
|
231
|
+
* a failed flush leaves the previous snapshot intact and keeps the entries
|
|
232
|
+
* in memory. Returns `true` when a snapshot was written.
|
|
233
|
+
*/
|
|
234
|
+
flush() {
|
|
235
|
+
if (this.flushTimer !== undefined) {
|
|
236
|
+
clearTimeout(this.flushTimer);
|
|
237
|
+
this.flushTimer = undefined;
|
|
238
|
+
}
|
|
239
|
+
if (!this.dirty)
|
|
240
|
+
return false;
|
|
241
|
+
const snapshot = {
|
|
242
|
+
schemaVersion: CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION,
|
|
243
|
+
representations: [...this.representations.entries()]
|
|
244
|
+
.filter(([key, entry]) => !containsCredentialShape(key) && isPersistableRepresentation(entry, this.maxEntryTextLength))
|
|
245
|
+
.map(([key, entry]) => [key, entry]),
|
|
246
|
+
negatives: [...this.negatives.entries()]
|
|
247
|
+
.filter(([key, entry]) => !containsCredentialShape(key) && isPersistableNegative(entry))
|
|
248
|
+
.map(([key, entry]) => [key, entry]),
|
|
249
|
+
};
|
|
250
|
+
let serialized;
|
|
251
|
+
try {
|
|
252
|
+
serialized = JSON.stringify(snapshot);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (Buffer.byteLength(serialized, "utf8") > this.maxBytes) {
|
|
258
|
+
// Drop the oldest half and retry once rather than growing forever.
|
|
259
|
+
const keys = [...this.representations.keys()];
|
|
260
|
+
if (keys.length === 0)
|
|
261
|
+
return false;
|
|
262
|
+
const dropCount = Math.max(1, Math.ceil(keys.length / 2));
|
|
263
|
+
for (const key of keys.slice(0, dropCount))
|
|
264
|
+
this.representations.delete(key);
|
|
265
|
+
return this.flush();
|
|
266
|
+
}
|
|
267
|
+
const tempPath = `${this.snapshotPath}.${process.pid}.tmp`;
|
|
268
|
+
try {
|
|
269
|
+
fs.mkdirSync(path.dirname(this.snapshotPath), { recursive: true });
|
|
270
|
+
fs.writeFileSync(tempPath, serialized, { encoding: "utf8", mode: 0o600 });
|
|
271
|
+
fs.renameSync(tempPath, this.snapshotPath);
|
|
272
|
+
this.dirty = false;
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
try {
|
|
277
|
+
fs.rmSync(tempPath, { force: true });
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// The temp file is unlinkable; nothing further to do.
|
|
281
|
+
}
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/** Cancel any pending flush and persist immediately. Safe to call more than once. */
|
|
286
|
+
close() {
|
|
287
|
+
this.flush();
|
|
288
|
+
}
|
|
289
|
+
getInvalidationSnapshot() {
|
|
290
|
+
return this.invalidationSnapshot;
|
|
291
|
+
}
|
|
292
|
+
setInvalidationSnapshot(snapshot) {
|
|
293
|
+
this.invalidationSnapshot = createContextCacheInvalidationSnapshot({
|
|
294
|
+
forkId: snapshot.forkId,
|
|
295
|
+
worktreeFingerprint: snapshot.worktreeFingerprint,
|
|
296
|
+
activeModelId: snapshot.activeModelId,
|
|
297
|
+
compactionModelId: snapshot.compactionModelId,
|
|
298
|
+
globalEpoch: snapshot.globalEpoch,
|
|
299
|
+
transcriptRepair: snapshot.counters.transcriptRepair,
|
|
300
|
+
toolResultDisposition: snapshot.counters.toolResultDisposition,
|
|
301
|
+
evidenceReceipt: snapshot.counters.evidenceReceipt,
|
|
302
|
+
userSteering: snapshot.counters.userSteering,
|
|
303
|
+
settings: snapshot.counters.settings,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
readRepresentation(key) {
|
|
307
|
+
const entry = readLru(this.representations, key);
|
|
308
|
+
return entry ? { entry, layer: this.layer } : undefined;
|
|
309
|
+
}
|
|
310
|
+
writeRepresentation(input) {
|
|
311
|
+
writeLru(this.representations, input.key, input.entry, this.maxEntries);
|
|
312
|
+
if ((!containsCredentialShape(input.key) && isPersistableRepresentation(input.entry, this.maxEntryTextLength)) ||
|
|
313
|
+
fs.existsSync(this.snapshotPath)) {
|
|
314
|
+
this.markDirty();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
deleteRepresentation(key) {
|
|
318
|
+
if (this.representations.delete(key))
|
|
319
|
+
this.markDirty();
|
|
320
|
+
}
|
|
321
|
+
readNegativeRepresentation(key) {
|
|
322
|
+
return readLru(this.negatives, key);
|
|
323
|
+
}
|
|
324
|
+
writeNegativeRepresentation(input) {
|
|
325
|
+
writeLru(this.negatives, input.key, { reason: input.reason, createdAtEpochMs: this.now(), layer: this.layer }, this.maxEntries);
|
|
326
|
+
this.markDirty();
|
|
327
|
+
}
|
|
328
|
+
deleteNegativeRepresentation(key) {
|
|
329
|
+
if (this.negatives.delete(key))
|
|
330
|
+
this.markDirty();
|
|
331
|
+
}
|
|
332
|
+
readPlan(key) {
|
|
333
|
+
const entry = readLru(this.plans, key);
|
|
334
|
+
return entry ? { entry, layer: "session" } : undefined;
|
|
335
|
+
}
|
|
336
|
+
writePlan(input) {
|
|
337
|
+
writeLru(this.plans, input.key, input.entry, this.maxEntries);
|
|
338
|
+
}
|
|
339
|
+
deletePlan(key) {
|
|
340
|
+
this.plans.delete(key);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=context-budget-v2-cache-disk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-budget-v2-cache-disk.js","sourceRoot":"","sources":["../../src/core/context-budget-v2-cache-disk.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Workspace-layer disk persistence for the context-budget-v2 representation\n * cache.\n *\n * The in-memory provider (`context-budget-v2-cache-provider.ts`) dies with the\n * process, so every new session re-renders and re-compresses context it already\n * paid for. This provider keeps the same synchronous\n * `ContextBudgetCacheProviderV2` contract and adds a bounded, atomically\n * written JSON snapshot on disk.\n *\n * Scope: representation entries and negative entries only. Plan entries stay in\n * memory on purpose — a plan is session-scoped (its key folds in the\n * invalidation snapshot), so persisting it would only ever store misses.\n *\n * Safety: reads go through the same validator as the memory provider\n * (`validateContextBudgetRepresentationCacheEntryV2`), which re-checks source\n * hash, representation fingerprint, model, tokenizer, policy version and TTL.\n * A corrupt, truncated, oversized, or schema-mismatched snapshot is discarded\n * and the cache simply starts empty; a cache is never load-bearing for\n * correctness.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { containsCredentialShape } from \"./compaction/transaction.ts\";\nimport {\n\ttype ContextCacheInvalidationSnapshot,\n\tcreateContextCacheInvalidationSnapshot,\n} from \"./context-budget-v2-cache-invalidation.ts\";\nimport type {\n\tContextBudgetCacheLayerV2,\n\tContextBudgetCacheProviderV2,\n\tContextBudgetNegativeCacheEntryV2,\n\tContextBudgetPlanCacheEntryV2,\n\tContextBudgetPlanCacheReadV2,\n\tContextBudgetRepresentationCacheEntryV2,\n\tContextBudgetRepresentationCacheReadV2,\n} from \"./context-budget-v2-types.ts\";\n\nexport const CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION = \"context-budget-v2-diskcache-1\" as const;\n\n/** Default cap on persisted representation entries. */\nexport const DEFAULT_DISK_CACHE_MAX_ENTRIES = 2048;\n/** Default cap on the persisted snapshot size. Oversized snapshots are dropped on load. */\nexport const DEFAULT_DISK_CACHE_MAX_BYTES = 32 * 1024 * 1024;\n/** Default write-behind debounce. Collapses a burst of writes into a single flush. */\nexport const DEFAULT_DISK_CACHE_FLUSH_DEBOUNCE_MS = 750;\n/** Default cap on a single persisted representation's text length. */\nexport const DEFAULT_DISK_CACHE_MAX_ENTRY_TEXT_LENGTH = 512 * 1024;\n\nexport interface DiskContextBudgetCacheOptionsV2 {\n\t/** Directory holding the snapshot file. Created on first flush. */\n\treadonly dir: string;\n\t/** Cache layer reported on reads. Defaults to `workspace`. */\n\treadonly layer?: ContextBudgetCacheLayerV2;\n\t/** Max persisted representation entries (LRU). */\n\treadonly maxEntries?: number;\n\t/** Max accepted snapshot size in bytes. */\n\treadonly maxBytes?: number;\n\t/** Max persisted text length for a single entry. */\n\treadonly maxEntryTextLength?: number;\n\t/** Write-behind debounce in ms. `0` flushes synchronously on every write. */\n\treadonly flushDebounceMs?: number;\n\t/** Injectable clock, for tests. */\n\treadonly now?: () => number;\n}\n\ninterface DiskSnapshotFileV2 {\n\treadonly schemaVersion: typeof CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION;\n\treadonly representations: readonly (readonly [string, ContextBudgetRepresentationCacheEntryV2])[];\n\treadonly negatives: readonly (readonly [string, ContextBudgetNegativeCacheEntryV2])[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction containsCredentialShapeInValue(value: unknown): boolean {\n\tconst pending: unknown[] = [value];\n\tconst seen = new Set<object>();\n\twhile (pending.length > 0) {\n\t\tconst current = pending.pop();\n\t\tif (typeof current === \"string\") {\n\t\t\tif (containsCredentialShape(current)) return true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof current !== \"object\" || current === null || seen.has(current)) continue;\n\t\tseen.add(current);\n\t\tif (Array.isArray(current)) {\n\t\t\tpending.push(...current);\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const [key, nested] of Object.entries(current)) {\n\t\t\tif (containsCredentialShape(key)) return true;\n\t\t\tpending.push(nested);\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction isPersistableRepresentation(\n\tvalue: unknown,\n\tmaxEntryTextLength: number,\n): value is ContextBudgetRepresentationCacheEntryV2 {\n\tif (!isRecord(value)) return false;\n\tif (typeof value.kind !== \"string\") return false;\n\tif (typeof value.text !== \"string\" || value.text.length > maxEntryTextLength) return false;\n\tif (containsCredentialShapeInValue(value)) return false;\n\tconst estimated = value.estimatedTokens;\n\tif (typeof estimated !== \"number\" || !Number.isFinite(estimated) || estimated < 0) return false;\n\tif (typeof value.fidelity !== \"string\") return false;\n\tif (typeof value.sourceHash !== \"string\" || value.sourceHash.length === 0) return false;\n\tif (typeof value.representationFingerprint !== \"string\") return false;\n\tif (typeof value.modelId !== \"string\") return false;\n\tif (typeof value.tokenizerId !== \"string\") return false;\n\tif (typeof value.policyVersion !== \"string\") return false;\n\treturn true;\n}\n\nfunction isPersistableNegative(value: unknown): value is ContextBudgetNegativeCacheEntryV2 {\n\treturn isRecord(value) && typeof value.reason === \"string\" && !containsCredentialShapeInValue(value);\n}\n\nfunction readLru<T>(store: Map<string, T>, key: string): T | undefined {\n\tconst entry = store.get(key);\n\tif (entry === undefined) return undefined;\n\tstore.delete(key);\n\tstore.set(key, entry);\n\treturn entry;\n}\n\nfunction writeLru<T>(store: Map<string, T>, key: string, entry: T, maxEntries: number): void {\n\tstore.delete(key);\n\tstore.set(key, entry);\n\twhile (store.size > maxEntries) {\n\t\tconst oldestKey = store.keys().next().value;\n\t\tif (oldestKey === undefined) return;\n\t\tstore.delete(oldestKey);\n\t}\n}\n\n/**\n * Create a disk backed workspace cache provider. Loading is synchronous and\n * best-effort: any failure yields an empty in-memory cache rather than throwing,\n * because a cache miss is always a legal outcome.\n */\nexport function createDiskContextBudgetCacheProviderV2(\n\toptions: DiskContextBudgetCacheOptionsV2,\n): DiskContextBudgetCacheProviderV2 {\n\treturn new DiskContextBudgetCacheProviderV2(options);\n}\n\nexport class DiskContextBudgetCacheProviderV2 implements ContextBudgetCacheProviderV2 {\n\tprivate readonly representations = new Map<string, ContextBudgetRepresentationCacheEntryV2>();\n\tprivate readonly negatives = new Map<string, ContextBudgetNegativeCacheEntryV2>();\n\t/** Plans stay in memory: their keys are session-scoped, so persisting them stores only misses. */\n\tprivate readonly plans = new Map<string, ContextBudgetPlanCacheEntryV2>();\n\tprivate readonly layer: ContextBudgetCacheLayerV2;\n\tprivate readonly maxEntries: number;\n\tprivate readonly maxBytes: number;\n\tprivate readonly maxEntryTextLength: number;\n\tprivate readonly flushDebounceMs: number;\n\tprivate readonly now: () => number;\n\tprivate readonly snapshotPath: string;\n\tprivate invalidationSnapshot: ContextCacheInvalidationSnapshot | undefined;\n\tprivate dirty = false;\n\tprivate flushTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate loadError: string | undefined;\n\n\tconstructor(options: DiskContextBudgetCacheOptionsV2) {\n\t\tthis.layer = options.layer ?? \"workspace\";\n\t\tthis.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_DISK_CACHE_MAX_ENTRIES);\n\t\tthis.maxBytes = Math.max(1024, options.maxBytes ?? DEFAULT_DISK_CACHE_MAX_BYTES);\n\t\tthis.maxEntryTextLength = Math.max(1, options.maxEntryTextLength ?? DEFAULT_DISK_CACHE_MAX_ENTRY_TEXT_LENGTH);\n\t\tthis.flushDebounceMs = Math.max(0, options.flushDebounceMs ?? DEFAULT_DISK_CACHE_FLUSH_DEBOUNCE_MS);\n\t\tthis.now = options.now ?? Date.now;\n\t\tthis.snapshotPath = path.join(options.dir, \"representations.json\");\n\t\tthis.load();\n\t}\n\n\t/** Absolute path of the persisted snapshot. */\n\tget snapshotFilePath(): string {\n\t\treturn this.snapshotPath;\n\t}\n\n\t/** Reason the on-disk snapshot was rejected, if it was. `undefined` on a clean load or a cold start. */\n\tget lastLoadError(): string | undefined {\n\t\treturn this.loadError;\n\t}\n\n\t/** Number of representation entries currently held. */\n\tget size(): number {\n\t\treturn this.representations.size;\n\t}\n\n\tprivate load(): void {\n\t\tlet raw: string;\n\t\ttry {\n\t\t\tconst stat = fs.statSync(this.snapshotPath);\n\t\t\tif (stat.size > this.maxBytes) {\n\t\t\t\tthis.loadError = `snapshot exceeds maxBytes (${stat.size} > ${this.maxBytes})`;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\traw = fs.readFileSync(this.snapshotPath, \"utf8\");\n\t\t} catch {\n\t\t\t// Cold start: no snapshot yet, or it is unreadable. Both are normal.\n\t\t\treturn;\n\t\t}\n\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(raw);\n\t\t} catch {\n\t\t\tthis.loadError = \"snapshot is not valid JSON\";\n\t\t\treturn;\n\t\t}\n\t\tif (!isRecord(parsed) || parsed.schemaVersion !== CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION) {\n\t\t\tthis.loadError = \"snapshot schema mismatch\";\n\t\t\treturn;\n\t\t}\n\n\t\tif (containsCredentialShapeInValue(parsed)) {\n\t\t\tthis.loadError = \"snapshot contains credential-shaped text\";\n\t\t\ttry {\n\t\t\t\tfs.rmSync(this.snapshotPath, { force: true });\n\t\t\t} catch {\n\t\t\t\t// The unsafe snapshot remains unreadable to this provider.\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst representations = Array.isArray(parsed.representations) ? parsed.representations : [];\n\t\tfor (const pair of representations) {\n\t\t\tif (!Array.isArray(pair) || pair.length !== 2) continue;\n\t\t\tconst [key, entry] = pair as [unknown, unknown];\n\t\t\tif (typeof key !== \"string\" || key.length === 0) continue;\n\t\t\tif (!isPersistableRepresentation(entry, this.maxEntryTextLength)) continue;\n\t\t\twriteLru(this.representations, key, entry, this.maxEntries);\n\t\t}\n\n\t\tconst negatives = Array.isArray(parsed.negatives) ? parsed.negatives : [];\n\t\tfor (const pair of negatives) {\n\t\t\tif (!Array.isArray(pair) || pair.length !== 2) continue;\n\t\t\tconst [key, entry] = pair as [unknown, unknown];\n\t\t\tif (typeof key !== \"string\" || key.length === 0) continue;\n\t\t\tif (!isPersistableNegative(entry)) continue;\n\t\t\twriteLru(this.negatives, key, entry, this.maxEntries);\n\t\t}\n\t}\n\n\tprivate markDirty(): void {\n\t\tthis.dirty = true;\n\t\tif (this.flushDebounceMs === 0) {\n\t\t\tthis.flush();\n\t\t\treturn;\n\t\t}\n\t\tif (this.flushTimer !== undefined) return;\n\t\tthis.flushTimer = setTimeout(() => {\n\t\t\tthis.flushTimer = undefined;\n\t\t\tthis.flush();\n\t\t}, this.flushDebounceMs);\n\t\tthis.flushTimer.unref?.();\n\t}\n\n\t/**\n\t * Write the snapshot to disk atomically (temp file + rename). Best-effort:\n\t * a failed flush leaves the previous snapshot intact and keeps the entries\n\t * in memory. Returns `true` when a snapshot was written.\n\t */\n\tflush(): boolean {\n\t\tif (this.flushTimer !== undefined) {\n\t\t\tclearTimeout(this.flushTimer);\n\t\t\tthis.flushTimer = undefined;\n\t\t}\n\t\tif (!this.dirty) return false;\n\t\tconst snapshot: DiskSnapshotFileV2 = {\n\t\t\tschemaVersion: CONTEXT_BUDGET_DISK_CACHE_SCHEMA_VERSION,\n\t\t\trepresentations: [...this.representations.entries()]\n\t\t\t\t.filter(\n\t\t\t\t\t([key, entry]) =>\n\t\t\t\t\t\t!containsCredentialShape(key) && isPersistableRepresentation(entry, this.maxEntryTextLength),\n\t\t\t\t)\n\t\t\t\t.map(([key, entry]) => [key, entry] as const),\n\t\t\tnegatives: [...this.negatives.entries()]\n\t\t\t\t.filter(([key, entry]) => !containsCredentialShape(key) && isPersistableNegative(entry))\n\t\t\t\t.map(([key, entry]) => [key, entry] as const),\n\t\t};\n\t\tlet serialized: string;\n\t\ttry {\n\t\t\tserialized = JSON.stringify(snapshot);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t\tif (Buffer.byteLength(serialized, \"utf8\") > this.maxBytes) {\n\t\t\t// Drop the oldest half and retry once rather than growing forever.\n\t\t\tconst keys = [...this.representations.keys()];\n\t\t\tif (keys.length === 0) return false;\n\t\t\tconst dropCount = Math.max(1, Math.ceil(keys.length / 2));\n\t\t\tfor (const key of keys.slice(0, dropCount)) this.representations.delete(key);\n\t\t\treturn this.flush();\n\t\t}\n\t\tconst tempPath = `${this.snapshotPath}.${process.pid}.tmp`;\n\t\ttry {\n\t\t\tfs.mkdirSync(path.dirname(this.snapshotPath), { recursive: true });\n\t\t\tfs.writeFileSync(tempPath, serialized, { encoding: \"utf8\", mode: 0o600 });\n\t\t\tfs.renameSync(tempPath, this.snapshotPath);\n\t\t\tthis.dirty = false;\n\t\t\treturn true;\n\t\t} catch {\n\t\t\ttry {\n\t\t\t\tfs.rmSync(tempPath, { force: true });\n\t\t\t} catch {\n\t\t\t\t// The temp file is unlinkable; nothing further to do.\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/** Cancel any pending flush and persist immediately. Safe to call more than once. */\n\tclose(): void {\n\t\tthis.flush();\n\t}\n\n\tgetInvalidationSnapshot(): ContextCacheInvalidationSnapshot | undefined {\n\t\treturn this.invalidationSnapshot;\n\t}\n\n\tsetInvalidationSnapshot(snapshot: ContextCacheInvalidationSnapshot): void {\n\t\tthis.invalidationSnapshot = createContextCacheInvalidationSnapshot({\n\t\t\tforkId: snapshot.forkId,\n\t\t\tworktreeFingerprint: snapshot.worktreeFingerprint,\n\t\t\tactiveModelId: snapshot.activeModelId,\n\t\t\tcompactionModelId: snapshot.compactionModelId,\n\t\t\tglobalEpoch: snapshot.globalEpoch,\n\t\t\ttranscriptRepair: snapshot.counters.transcriptRepair,\n\t\t\ttoolResultDisposition: snapshot.counters.toolResultDisposition,\n\t\t\tevidenceReceipt: snapshot.counters.evidenceReceipt,\n\t\t\tuserSteering: snapshot.counters.userSteering,\n\t\t\tsettings: snapshot.counters.settings,\n\t\t});\n\t}\n\n\treadRepresentation(key: string): ContextBudgetRepresentationCacheReadV2 | undefined {\n\t\tconst entry = readLru(this.representations, key);\n\t\treturn entry ? { entry, layer: this.layer } : undefined;\n\t}\n\n\twriteRepresentation(input: { readonly key: string; readonly entry: ContextBudgetRepresentationCacheEntryV2 }): void {\n\t\twriteLru(this.representations, input.key, input.entry, this.maxEntries);\n\t\tif (\n\t\t\t(!containsCredentialShape(input.key) && isPersistableRepresentation(input.entry, this.maxEntryTextLength)) ||\n\t\t\tfs.existsSync(this.snapshotPath)\n\t\t) {\n\t\t\tthis.markDirty();\n\t\t}\n\t}\n\n\tdeleteRepresentation(key: string): void {\n\t\tif (this.representations.delete(key)) this.markDirty();\n\t}\n\n\treadNegativeRepresentation(key: string): ContextBudgetNegativeCacheEntryV2 | undefined {\n\t\treturn readLru(this.negatives, key);\n\t}\n\n\twriteNegativeRepresentation(input: { readonly key: string; readonly reason: string }): void {\n\t\twriteLru(\n\t\t\tthis.negatives,\n\t\t\tinput.key,\n\t\t\t{ reason: input.reason, createdAtEpochMs: this.now(), layer: this.layer },\n\t\t\tthis.maxEntries,\n\t\t);\n\t\tthis.markDirty();\n\t}\n\n\tdeleteNegativeRepresentation(key: string): void {\n\t\tif (this.negatives.delete(key)) this.markDirty();\n\t}\n\n\treadPlan(key: string): ContextBudgetPlanCacheReadV2 | undefined {\n\t\tconst entry = readLru(this.plans, key);\n\t\treturn entry ? { entry, layer: \"session\" } : undefined;\n\t}\n\n\twritePlan(input: { readonly key: string; readonly entry: ContextBudgetPlanCacheEntryV2 }): void {\n\t\twriteLru(this.plans, input.key, input.entry, this.maxEntries);\n\t}\n\n\tdeletePlan(key: string): void {\n\t\tthis.plans.delete(key);\n\t}\n}\n"]}
|