@graphorin/memory 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -1
- package/README.md +31 -31
- package/dist/conflict/index.d.ts +3 -1
- package/dist/conflict/index.js +1 -1
- package/dist/conflict/locale-packs/en.d.ts +13 -0
- package/dist/conflict/locale-packs/en.d.ts.map +1 -0
- package/dist/conflict/locale-packs/en.js +1 -1
- package/dist/conflict/locale-packs/en.js.map +1 -1
- package/dist/conflict/pipeline.d.ts +2 -2
- package/dist/conflict/pipeline.js +3 -3
- package/dist/conflict/pipeline.js.map +1 -1
- package/dist/conflict/stages/helpers.js +1 -1
- package/dist/conflict/stages/helpers.js.map +1 -1
- package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -1
- package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
- package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -1
- package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -1
- package/dist/conflict/types.d.ts +7 -7
- package/dist/conflict/types.js +1 -1
- package/dist/conflict/types.js.map +1 -1
- package/dist/consolidator/budget.js +16 -3
- package/dist/consolidator/budget.js.map +1 -1
- package/dist/consolidator/decay.d.ts +13 -2
- package/dist/consolidator/decay.js +17 -5
- package/dist/consolidator/decay.js.map +1 -1
- package/dist/consolidator/dlq.js +1 -1
- package/dist/consolidator/dlq.js.map +1 -1
- package/dist/consolidator/errors.js +2 -2
- package/dist/consolidator/errors.js.map +1 -1
- package/dist/consolidator/index.js +8 -6
- package/dist/consolidator/index.js.map +1 -1
- package/dist/consolidator/lock.js +1 -1
- package/dist/consolidator/lock.js.map +1 -1
- package/dist/consolidator/noise-filter.js.map +1 -1
- package/dist/consolidator/phases/deep.js +6 -2
- package/dist/consolidator/phases/deep.js.map +1 -1
- package/dist/consolidator/phases/induce.d.ts +10 -10
- package/dist/consolidator/phases/induce.js +6 -6
- package/dist/consolidator/phases/induce.js.map +1 -1
- package/dist/consolidator/phases/learned-context.js +129 -0
- package/dist/consolidator/phases/learned-context.js.map +1 -0
- package/dist/consolidator/phases/light.js +2 -1
- package/dist/consolidator/phases/light.js.map +1 -1
- package/dist/consolidator/phases/reflect.js +5 -4
- package/dist/consolidator/phases/reflect.js.map +1 -1
- package/dist/consolidator/phases/standard.js +37 -8
- package/dist/consolidator/phases/standard.js.map +1 -1
- package/dist/consolidator/reconcile.js +1 -1
- package/dist/consolidator/reconcile.js.map +1 -1
- package/dist/consolidator/runtime.d.ts +4 -4
- package/dist/consolidator/runtime.d.ts.map +1 -1
- package/dist/consolidator/runtime.js +73 -16
- package/dist/consolidator/runtime.js.map +1 -1
- package/dist/consolidator/scheduler.d.ts +5 -5
- package/dist/consolidator/scheduler.js +1 -1
- package/dist/consolidator/scheduler.js.map +1 -1
- package/dist/consolidator/triggers.js +7 -7
- package/dist/consolidator/triggers.js.map +1 -1
- package/dist/consolidator/types.d.ts +68 -20
- package/dist/consolidator/types.d.ts.map +1 -1
- package/dist/consolidator/types.js +16 -6
- package/dist/consolidator/types.js.map +1 -1
- package/dist/context-engine/annotations.d.ts +9 -9
- package/dist/context-engine/annotations.js +3 -3
- package/dist/context-engine/annotations.js.map +1 -1
- package/dist/context-engine/auto-recall.js.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.d.ts +18 -3
- package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js +26 -5
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
- package/dist/context-engine/compaction/compactor.js +19 -6
- package/dist/context-engine/compaction/compactor.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +4 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +2 -1
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.d.ts +28 -0
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.js +71 -0
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.js.map +1 -0
- package/dist/context-engine/compaction/hooks/types.d.ts +15 -3
- package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -1
- package/dist/context-engine/compaction/index.d.ts +1 -0
- package/dist/context-engine/compaction/index.js +2 -1
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +10 -3
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.js +42 -5
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
- package/dist/context-engine/compaction/thresholds.js.map +1 -1
- package/dist/context-engine/compaction/types.d.ts +47 -7
- package/dist/context-engine/compaction/types.d.ts.map +1 -1
- package/dist/context-engine/engine.d.ts +23 -4
- package/dist/context-engine/engine.d.ts.map +1 -1
- package/dist/context-engine/engine.js +69 -15
- package/dist/context-engine/engine.js.map +1 -1
- package/dist/context-engine/index.d.ts +1 -0
- package/dist/context-engine/index.d.ts.map +1 -1
- package/dist/context-engine/index.js +2 -1
- package/dist/context-engine/locale-packs/en.js +14 -13
- package/dist/context-engine/locale-packs/en.js.map +1 -1
- package/dist/context-engine/locale-packs/types.d.ts +10 -10
- package/dist/context-engine/locale-packs/types.d.ts.map +1 -1
- package/dist/context-engine/locale-packs/types.js.map +1 -1
- package/dist/context-engine/metadata.d.ts +1 -1
- package/dist/context-engine/metadata.js +2 -2
- package/dist/context-engine/metadata.js.map +1 -1
- package/dist/context-engine/preambles/inbound-en.d.ts +1 -1
- package/dist/context-engine/preambles/inbound-en.js +1 -1
- package/dist/context-engine/preambles/inbound-en.js.map +1 -1
- package/dist/context-engine/privacy-filter.d.ts +2 -2
- package/dist/context-engine/privacy-filter.js +1 -1
- package/dist/context-engine/privacy-filter.js.map +1 -1
- package/dist/context-engine/templates/base-en.d.ts +1 -1
- package/dist/context-engine/templates/base-en.js +1 -1
- package/dist/context-engine/templates/base-en.js.map +1 -1
- package/dist/context-engine/templates/composer.d.ts +1 -1
- package/dist/context-engine/templates/composer.js.map +1 -1
- package/dist/context-engine/token-budget.d.ts +1 -1
- package/dist/context-engine/token-budget.js +1 -1
- package/dist/context-engine/token-budget.js.map +1 -1
- package/dist/context-engine/token-counter.d.ts +16 -7
- package/dist/context-engine/token-counter.d.ts.map +1 -1
- package/dist/context-engine/token-counter.js +31 -9
- package/dist/context-engine/token-counter.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +4 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/facade.d.ts +44 -15
- package/dist/facade.d.ts.map +1 -1
- package/dist/facade.js +16 -8
- package/dist/facade.js.map +1 -1
- package/dist/graph/entity-resolver.d.ts +2 -2
- package/dist/graph/entity-resolver.js +2 -2
- package/dist/graph/entity-resolver.js.map +1 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/contextualize.js +2 -2
- package/dist/internal/contextualize.js.map +1 -1
- package/dist/internal/embedder-binding.js +1 -1
- package/dist/internal/embedder-binding.js.map +1 -1
- package/dist/internal/id.js +1 -1
- package/dist/internal/id.js.map +1 -1
- package/dist/internal/injection-heuristics.js +25 -2
- package/dist/internal/injection-heuristics.js.map +1 -1
- package/dist/internal/spans.js +1 -1
- package/dist/internal/spans.js.map +1 -1
- package/dist/internal/storage-adapter.d.ts +71 -22
- package/dist/internal/storage-adapter.d.ts.map +1 -1
- package/dist/migration/embedder-migration.d.ts +7 -7
- package/dist/migration/embedder-migration.js.map +1 -1
- package/dist/search/explain.d.ts +7 -4
- package/dist/search/explain.d.ts.map +1 -1
- package/dist/search/explain.js +1 -1
- package/dist/search/explain.js.map +1 -1
- package/dist/search/fit-weights.d.ts +48 -0
- package/dist/search/fit-weights.d.ts.map +1 -0
- package/dist/search/fit-weights.js +70 -0
- package/dist/search/fit-weights.js.map +1 -0
- package/dist/search/graph-ppr.d.ts +36 -0
- package/dist/search/graph-ppr.d.ts.map +1 -0
- package/dist/search/graph-ppr.js +38 -0
- package/dist/search/graph-ppr.js.map +1 -0
- package/dist/search/index.d.ts +4 -1
- package/dist/search/index.js +4 -1
- package/dist/search/iterative.d.ts +15 -8
- package/dist/search/iterative.d.ts.map +1 -1
- package/dist/search/iterative.js +10 -9
- package/dist/search/iterative.js.map +1 -1
- package/dist/search/query-transform.d.ts +6 -6
- package/dist/search/query-transform.js +6 -6
- package/dist/search/query-transform.js.map +1 -1
- package/dist/search/rrf.d.ts +4 -4
- package/dist/search/rrf.js +4 -4
- package/dist/search/rrf.js.map +1 -1
- package/dist/search/trust.d.ts +30 -0
- package/dist/search/trust.d.ts.map +1 -0
- package/dist/search/trust.js +50 -0
- package/dist/search/trust.js.map +1 -0
- package/dist/search/types.d.ts +1 -1
- package/dist/tiers/episodic-memory.d.ts +6 -4
- package/dist/tiers/episodic-memory.d.ts.map +1 -1
- package/dist/tiers/episodic-memory.js +3 -2
- package/dist/tiers/episodic-memory.js.map +1 -1
- package/dist/tiers/index.d.ts +1 -1
- package/dist/tiers/index.js +1 -1
- package/dist/tiers/insight-memory.d.ts +4 -4
- package/dist/tiers/insight-memory.js +3 -3
- package/dist/tiers/insight-memory.js.map +1 -1
- package/dist/tiers/procedural-memory.d.ts +29 -7
- package/dist/tiers/procedural-memory.d.ts.map +1 -1
- package/dist/tiers/procedural-memory.js +66 -6
- package/dist/tiers/procedural-memory.js.map +1 -1
- package/dist/tiers/semantic-memory.d.ts +111 -30
- package/dist/tiers/semantic-memory.d.ts.map +1 -1
- package/dist/tiers/semantic-memory.js +120 -27
- package/dist/tiers/semantic-memory.js.map +1 -1
- package/dist/tiers/session-memory.d.ts +7 -7
- package/dist/tiers/session-memory.js +7 -7
- package/dist/tiers/session-memory.js.map +1 -1
- package/dist/tiers/shared-memory.d.ts +1 -1
- package/dist/tiers/shared-memory.js +1 -1
- package/dist/tiers/shared-memory.js.map +1 -1
- package/dist/tiers/working-memory.d.ts +3 -3
- package/dist/tiers/working-memory.js +2 -2
- package/dist/tiers/working-memory.js.map +1 -1
- package/dist/tools/block-tools.d.ts +9 -9
- package/dist/tools/block-tools.d.ts.map +1 -1
- package/dist/tools/block-tools.js +3 -3
- package/dist/tools/block-tools.js.map +1 -1
- package/dist/tools/fact-tools.d.ts +48 -31
- package/dist/tools/fact-tools.d.ts.map +1 -1
- package/dist/tools/fact-tools.js +30 -18
- package/dist/tools/fact-tools.js.map +1 -1
- package/dist/tools/index.d.ts +9 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/recall-tools.d.ts +40 -20
- package/dist/tools/recall-tools.d.ts.map +1 -1
- package/dist/tools/recall-tools.js +29 -10
- package/dist/tools/recall-tools.js.map +1 -1
- package/dist/tools/runbook-tools.d.ts +76 -0
- package/dist/tools/runbook-tools.d.ts.map +1 -0
- package/dist/tools/runbook-tools.js +53 -0
- package/dist/tools/runbook-tools.js.map +1 -0
- package/dist/tools/taint.js +33 -0
- package/dist/tools/taint.js.map +1 -0
- package/dist/tools/types.d.ts +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"annotations.js","names":["NON_INBOUND_ORIGINS: ReadonlySet<ContentOrigin>"],"sources":["../../src/context-engine/annotations.ts"],"sourcesContent":["/**\n * Cooperation contract between the {@link ContextEngine} and the\n * outbound prompt-redaction middleware (D3, see ADR-045 / DEC-157)\n * + the inbound tool-result sanitization middleware (D4, see\n * RB-43 / DEC-159).\n *\n * Every {@link MessageContent} part assembled by the ContextEngine\n * carries two **independent** annotations:\n *\n * - `graphorin.content.origin`
|
|
1
|
+
{"version":3,"file":"annotations.js","names":["NON_INBOUND_ORIGINS: ReadonlySet<ContentOrigin>"],"sources":["../../src/context-engine/annotations.ts"],"sourcesContent":["/**\n * Cooperation contract between the {@link ContextEngine} and the\n * outbound prompt-redaction middleware (D3, see ADR-045 / DEC-157)\n * + the inbound tool-result sanitization middleware (D4, see\n * RB-43 / DEC-159).\n *\n * Every {@link MessageContent} part assembled by the ContextEngine\n * carries two **independent** annotations:\n *\n * - `graphorin.content.origin` - where the content came from. Used\n * by D3 (`withRedaction`) under `scanScope: 'untrusted'` to\n * decide whether to re-scan a part that already passed the D2\n * sensitivity-tier filter.\n * - `graphorin.content.inbound.trust` - what trust class the source\n * carries. Used by D4 (`withInboundSanitization`) and by Phase 12\n * (agent runtime) to gate the per-step preamble injection.\n *\n * Both annotations live as **span attributes only** (observability)\n * - they are never serialized to the wire payload. The wire-stable\n * `ProviderRequest` shape is unchanged by Phase 10d.\n *\n * @packageDocumentation\n */\n\n/**\n * Span-attribute key for the origin axis.\n *\n * @stable\n */\nexport const CONTENT_ORIGIN_ATTR = 'graphorin.content.origin' as const;\n\n/**\n * Span-attribute key for the inbound-trust axis.\n *\n * @stable\n */\nexport const INBOUND_TRUST_ATTR = 'graphorin.content.inbound.trust' as const;\n\n/**\n * Origin discriminator for an assembled message-content part. The\n * non-ContextEngine origins (`'user:input'`, `'tool:result'`,\n * `'mcp:response'`, `'tool-call:args'`) are tagged by Phase 12\n * (agent runtime) when the corresponding payload enters\n * `session_messages`; the ContextEngine then propagates the tag\n * through the assembled message list.\n *\n * @stable\n */\nexport type ContentOrigin =\n | 'memory:tier-filtered'\n | 'system:framework'\n | 'agent:instructions'\n | 'skill:content'\n | 'user:input'\n | 'tool:result'\n | 'mcp:response'\n | 'tool-call:args';\n\n/**\n * Trust-class discriminator for an assembled message-content part.\n * Sibling axis to {@link ContentOrigin}; the two are independent.\n *\n * - `'trusted'` - built-in framework tools + trusted-skill-bundled\n * tools; D4 preamble does NOT fire on steps containing only\n * these parts.\n * - `'user-defined'` - tools registered via `tool({...})` from user\n * application code; D4 preamble fires.\n * - `'untrusted-skill'` - tools bundled by an untrusted skill; D4\n * preamble fires; default policy is strip-and-wrap.\n * - `'mcp'` - every `Tool` produced by `MCPClient.toTools(...)`; D4\n * preamble fires; default policy is strip-and-wrap.\n * - `'web-search'` - built-in `web_search` adapter; D4 preamble\n * fires; default policy is strip-and-wrap.\n * - `'n/a'` - non-tool-result parts for which the inbound-trust\n * axis is meaningless (`'user:input'`, `'memory:tier-filtered'`,\n * `'system:framework'`, `'agent:instructions'`,\n * `'tool-call:args'`).\n *\n * @stable\n */\nexport type InboundTrust =\n | 'trusted'\n | 'user-defined'\n | 'untrusted-skill'\n | 'mcp'\n | 'web-search'\n | 'n/a';\n\n/**\n * Origins for which the inbound-trust axis is meaningless and is\n * always set to `'n/a'`. Surfaced as a frozen constant so consumers\n * can introspect the contract without re-implementing the rule.\n *\n * @stable\n */\nexport const NON_INBOUND_ORIGINS: ReadonlySet<ContentOrigin> = Object.freeze(\n new Set<ContentOrigin>([\n 'memory:tier-filtered',\n 'system:framework',\n 'agent:instructions',\n 'user:input',\n 'tool-call:args',\n ]),\n);\n\n/**\n * Single typed annotation for an assembled message-content part.\n *\n * @stable\n */\nexport interface ContentAnnotation {\n readonly origin: ContentOrigin;\n readonly inboundTrust: InboundTrust;\n}\n\n/**\n * Build an annotation, enforcing the rule that\n * non-tool-result origins always carry `inboundTrust: 'n/a'`. A\n * caller that requests a non-`'n/a'` value for a non-inbound origin\n * is silently corrected (defense-in-depth: the rule is enforced\n * here so callers cannot accidentally violate it).\n *\n * @stable\n */\nexport function annotate(origin: ContentOrigin, inboundTrust: InboundTrust): ContentAnnotation {\n if (NON_INBOUND_ORIGINS.has(origin)) {\n return Object.freeze({ origin, inboundTrust: 'n/a' });\n }\n return Object.freeze({ origin, inboundTrust });\n}\n\n/**\n * Convert a {@link ContentAnnotation} to a span-attributes record\n * suitable for `AISpan.setAttributes(...)`.\n *\n * @stable\n */\nexport function toSpanAttributes(annotation: ContentAnnotation): Readonly<Record<string, string>> {\n return Object.freeze({\n [CONTENT_ORIGIN_ATTR]: annotation.origin,\n [INBOUND_TRUST_ATTR]: annotation.inboundTrust,\n });\n}\n\n/**\n * Decide whether the per-step inbound-sanitization preamble (D4)\n * should fire for an assembled message list. The preamble fires\n * iff at least one part carries an inbound-trust value other than\n * `'trusted'` and `'n/a'`. Trusted-only steps skip the preamble for\n * cache-friendliness; preamble is emitted exactly once per step\n * regardless of how many untrusted parts the step carries.\n *\n * @stable\n */\nexport function shouldFireInboundPreamble(annotations: ReadonlyArray<ContentAnnotation>): boolean {\n for (const annotation of annotations) {\n const trust = annotation.inboundTrust;\n if (trust !== 'trusted' && trust !== 'n/a') return true;\n }\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,sBAAsB;;;;;;AAOnC,MAAa,qBAAqB;;;;;;;;AA2DlC,MAAaA,sBAAkD,OAAO,OACpE,IAAI,IAAmB;CACrB;CACA;CACA;CACA;CACA;CACD,CAAC,CACH;;;;;;;;;;AAqBD,SAAgB,SAAS,QAAuB,cAA+C;AAC7F,KAAI,oBAAoB,IAAI,OAAO,CACjC,QAAO,OAAO,OAAO;EAAE;EAAQ,cAAc;EAAO,CAAC;AAEvD,QAAO,OAAO,OAAO;EAAE;EAAQ;EAAc,CAAC;;;;;;;;AAShD,SAAgB,iBAAiB,YAAiE;AAChG,QAAO,OAAO,OAAO;GAClB,sBAAsB,WAAW;GACjC,qBAAqB,WAAW;EAClC,CAAC;;;;;;;;;;;;AAaJ,SAAgB,0BAA0B,aAAwD;AAChG,MAAK,MAAM,cAAc,aAAa;EACpC,MAAM,QAAQ,WAAW;AACzB,MAAI,UAAU,aAAa,UAAU,MAAO,QAAO;;AAErD,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-recall.js","names":[],"sources":["../../src/context-engine/auto-recall.ts"],"sourcesContent":["/**\n * Auto-recall heuristic
|
|
1
|
+
{"version":3,"file":"auto-recall.js","names":[],"sources":["../../src/context-engine/auto-recall.ts"],"sourcesContent":["/**\n * Auto-recall heuristic - opt-in regex-based pre-call that injects\n * the top-K facts into Layer 6 when the last user message matches one\n * of the configured trigger phrases. (Episode auto-recall is not\n * implemented; episodes are reachable via the `recall_episodes` tool.)\n *\n * The framework ships English defaults; per-locale trigger\n * phrases extend the default via {@link defineContextLocalePack}.\n * Application code can also register a fully-custom strategy via\n * {@link defineAutoRecallStrategy}.\n *\n * @packageDocumentation\n */\n\nimport type { ContextLocalePack } from './locale-packs/index.js';\n\n/**\n * Per-call scope passed to a custom strategy.\n *\n * @stable\n */\nexport interface AutoRecallStrategyContext {\n readonly locale: string;\n readonly lastUserMessage: string;\n}\n\n/**\n * Outcome of a strategy invocation.\n *\n * @stable\n */\nexport interface AutoRecallTriggerResult {\n readonly factsTriggered: boolean;\n /** Optional reason surfaced in spans / metadata. */\n readonly reason?: string;\n}\n\n/**\n * Pluggable strategy signature.\n *\n * @stable\n */\nexport type AutoRecallStrategy = (ctx: AutoRecallStrategyContext) => AutoRecallTriggerResult;\n\n/**\n * Build a locale-driven heuristic strategy. The default for every\n * configured locale.\n *\n * @stable\n */\nexport function defaultLocaleHeuristicStrategy(pack: ContextLocalePack): AutoRecallStrategy {\n const factTriggers = pack.autoRecallTriggers.factTriggers;\n return (ctx) => {\n const text = ctx.lastUserMessage ?? '';\n if (text.length === 0) {\n return { factsTriggered: false };\n }\n const factsTriggered = factTriggers.some((re) => re.test(text));\n if (factsTriggered) {\n return { factsTriggered, reason: 'locale-heuristic-trigger-matched' };\n }\n return { factsTriggered: false };\n };\n}\n\n/**\n * Builder for application-supplied custom strategies. Accepts a\n * raw function and returns a tagged version so the engine can\n * surface the strategy name on spans.\n *\n * @stable\n */\nexport function defineAutoRecallStrategy(opts: {\n readonly id: string;\n readonly evaluate: AutoRecallStrategy;\n}): AutoRecallStrategy & { readonly id: string } {\n if (typeof opts.id !== 'string' || opts.id.length === 0) {\n throw new TypeError(\n '[graphorin/memory] defineAutoRecallStrategy: `id` must be a non-empty string.',\n );\n }\n if (typeof opts.evaluate !== 'function') {\n throw new TypeError(\n '[graphorin/memory] defineAutoRecallStrategy: `evaluate` must be a function.',\n );\n }\n const wrapped = ((ctx: AutoRecallStrategyContext) => opts.evaluate(ctx)) as AutoRecallStrategy & {\n readonly id: string;\n };\n Object.defineProperty(wrapped, 'id', { value: opts.id, enumerable: true });\n return wrapped;\n}\n"],"mappings":";;;;;;;AAkDA,SAAgB,+BAA+B,MAA6C;CAC1F,MAAM,eAAe,KAAK,mBAAmB;AAC7C,SAAQ,QAAQ;EACd,MAAM,OAAO,IAAI,mBAAmB;AACpC,MAAI,KAAK,WAAW,EAClB,QAAO,EAAE,gBAAgB,OAAO;EAElC,MAAM,iBAAiB,aAAa,MAAM,OAAO,GAAG,KAAK,KAAK,CAAC;AAC/D,MAAI,eACF,QAAO;GAAE;GAAgB,QAAQ;GAAoC;AAEvE,SAAO,EAAE,gBAAgB,OAAO;;;;;;;;;;AAWpC,SAAgB,yBAAyB,MAGQ;AAC/C,KAAI,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,WAAW,EACpD,OAAM,IAAI,UACR,gFACD;AAEH,KAAI,OAAO,KAAK,aAAa,WAC3B,OAAM,IAAI,UACR,8EACD;CAEH,MAAM,YAAY,QAAmC,KAAK,SAAS,IAAI;AAGvE,QAAO,eAAe,SAAS,MAAM;EAAE,OAAO,KAAK;EAAI,YAAY;EAAM,CAAC;AAC1E,QAAO"}
|
|
@@ -3,7 +3,7 @@ import { Message } from "@graphorin/core";
|
|
|
3
3
|
|
|
4
4
|
//#region src/context-engine/compaction/clear-tool-results.d.ts
|
|
5
5
|
|
|
6
|
-
/** Placeholder prefix on a cleared tool result
|
|
6
|
+
/** Placeholder prefix on a cleared tool result - makes clearing idempotent. */
|
|
7
7
|
declare const CLEARED_TOOL_RESULT_MARKER = "[cleared tool result";
|
|
8
8
|
/** Knobs for {@link clearOldToolResults} (mirrors the strategy variant). */
|
|
9
9
|
interface ClearToolResultsOptions {
|
|
@@ -20,7 +20,7 @@ interface ClearToolResultsOptions {
|
|
|
20
20
|
readonly clearedTokens: number;
|
|
21
21
|
}) => string;
|
|
22
22
|
/**
|
|
23
|
-
* A6 / SOTA-2
|
|
23
|
+
* A6 / SOTA-2 - recoverable clearing. When provided, the original tool-result
|
|
24
24
|
* text of each cleared message is handed to this callback (wire it to a spill
|
|
25
25
|
* store / the `read_result` handle registry) and the placeholder references the
|
|
26
26
|
* returned handle id + preview, so the model can re-fetch the full result via
|
|
@@ -36,6 +36,21 @@ interface ClearToolResultsOptions {
|
|
|
36
36
|
readonly handleId: string;
|
|
37
37
|
readonly preview?: string;
|
|
38
38
|
}>;
|
|
39
|
+
/**
|
|
40
|
+
* C4 (context-engine-11): the tool the externalized-handle placeholder
|
|
41
|
+
* advertises. The memory package cannot know whether the agent
|
|
42
|
+
* registered `read_result` (that depends on spill wiring), so callers
|
|
43
|
+
* whose runtime does NOT expose it pass `null` and the placeholder
|
|
44
|
+
* degrades to a tool-neutral phrasing instead of promising a tool the
|
|
45
|
+
* model cannot call. Default `'read_result'` (the agent built-in).
|
|
46
|
+
*/
|
|
47
|
+
readonly readResultToolName?: string | null;
|
|
48
|
+
/**
|
|
49
|
+
* C4 (clear_tool_uses_20250919 parity): additionally blank the PAIRED
|
|
50
|
+
* assistant message's tool-call arguments for every cleared result,
|
|
51
|
+
* reclaiming the input side of verbose calls too. Default `false`.
|
|
52
|
+
*/
|
|
53
|
+
readonly clearToolInputs?: boolean;
|
|
39
54
|
}
|
|
40
55
|
/** Result of a clearing pass. `clearedIndices` empty ⇒ nothing changed. */
|
|
41
56
|
interface ClearToolResultsOutcome {
|
|
@@ -45,7 +60,7 @@ interface ClearToolResultsOutcome {
|
|
|
45
60
|
}
|
|
46
61
|
/**
|
|
47
62
|
* Replace the oldest clearable tool results with placeholders. Returns the new
|
|
48
|
-
* buffer (same length
|
|
63
|
+
* buffer (same length - content is replaced in place, never removed) plus the
|
|
49
64
|
* cleared indices and reclaimed token count. Idempotent: already-cleared
|
|
50
65
|
* placeholders are skipped on a second pass.
|
|
51
66
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clear-tool-results.d.ts","names":[],"sources":["../../../src/context-engine/compaction/clear-tool-results.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"clear-tool-results.d.ts","names":[],"sources":["../../../src/context-engine/compaction/clear-tool-results.ts"],"sourcesContent":[],"mappings":";;;;;;AAoHW,cA/FE,0BAAA,GA+FF,sBAAA;;UAzFM,uBAAA;;;;;;0BAMS;;;;;;;;;;;;;;;;;;;;QAuBnB;;;;;;;;;;;;;;;;;;;;;UAmBU,uBAAA;qBACI,cAAc;2BACR;;;;;;;;;iBAoCL,mBAAA,WACV,cAAc,mBACf,kCACA,sBACR,QAAQ"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { countMessageTokens, renderMessageText } from "../token-counter.js";
|
|
2
2
|
|
|
3
3
|
//#region src/context-engine/compaction/clear-tool-results.ts
|
|
4
|
-
/** Placeholder prefix on a cleared tool result
|
|
4
|
+
/** Placeholder prefix on a cleared tool result - makes clearing idempotent. */
|
|
5
5
|
const CLEARED_TOOL_RESULT_MARKER = "[cleared tool result";
|
|
6
6
|
/** Default count of most-recent tool results kept verbatim. */
|
|
7
7
|
const DEFAULT_KEEP_TOOL_USES = 3;
|
|
@@ -11,14 +11,15 @@ function defaultPlaceholder(info) {
|
|
|
11
11
|
/** A6: a placeholder that points at the externalized handle (recoverable via read_result). */
|
|
12
12
|
function handlePlaceholder(info) {
|
|
13
13
|
const preview = info.preview !== void 0 && info.preview.length > 0 ? ` · ${info.preview}` : "";
|
|
14
|
-
|
|
14
|
+
const retrieval = info.readToolName === null ? `full result externalized to handle: ${info.handleId}` : `full result via ${info.readToolName ?? "read_result"} handle: ${info.handleId}`;
|
|
15
|
+
return `${CLEARED_TOOL_RESULT_MARKER} · ${info.toolName ?? "tool"} · ${info.clearedTokens} tokens · ${retrieval}${preview}]`;
|
|
15
16
|
}
|
|
16
17
|
function isAlreadyCleared(content) {
|
|
17
18
|
return typeof content === "string" && content.startsWith(CLEARED_TOOL_RESULT_MARKER);
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
21
|
* Replace the oldest clearable tool results with placeholders. Returns the new
|
|
21
|
-
* buffer (same length
|
|
22
|
+
* buffer (same length - content is replaced in place, never removed) plus the
|
|
22
23
|
* cleared indices and reclaimed token count. Idempotent: already-cleared
|
|
23
24
|
* placeholders are skipped on a second pass.
|
|
24
25
|
*/
|
|
@@ -81,12 +82,32 @@ async function clearOldToolResults(messages, options, counter) {
|
|
|
81
82
|
...name !== void 0 ? { toolName: name } : {},
|
|
82
83
|
clearedTokens,
|
|
83
84
|
handleId: handle.handleId,
|
|
84
|
-
...handle.preview !== void 0 ? { preview: handle.preview } : {}
|
|
85
|
+
...handle.preview !== void 0 ? { preview: handle.preview } : {},
|
|
86
|
+
...options.readResultToolName !== void 0 ? { readToolName: options.readResultToolName } : {}
|
|
85
87
|
})
|
|
86
88
|
});
|
|
87
89
|
}
|
|
90
|
+
let out = messages.map((m, i) => replacements.get(i) ?? m);
|
|
91
|
+
if (options.clearToolInputs === true && replacements.size > 0) {
|
|
92
|
+
const clearedCallIds = /* @__PURE__ */ new Set();
|
|
93
|
+
for (const idx of replacements.keys()) {
|
|
94
|
+
const tm = messages[idx];
|
|
95
|
+
clearedCallIds.add(tm.toolCallId);
|
|
96
|
+
}
|
|
97
|
+
out = out.map((m) => {
|
|
98
|
+
if (m.role !== "assistant" || m.toolCalls === void 0) return m;
|
|
99
|
+
if (!m.toolCalls.some((tc) => clearedCallIds.has(tc.toolCallId))) return m;
|
|
100
|
+
return {
|
|
101
|
+
...m,
|
|
102
|
+
toolCalls: m.toolCalls.map((tc) => clearedCallIds.has(tc.toolCallId) ? {
|
|
103
|
+
...tc,
|
|
104
|
+
args: { cleared: "[tool input elided by context clearing]" }
|
|
105
|
+
} : tc)
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
}
|
|
88
109
|
return {
|
|
89
|
-
messages:
|
|
110
|
+
messages: out,
|
|
90
111
|
clearedIndices: [...replacements.keys()],
|
|
91
112
|
reclaimedTokens: reclaimable
|
|
92
113
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clear-tool-results.js","names":["toolIdx: number[]","replaced: ToolMessage"],"sources":["../../../src/context-engine/compaction/clear-tool-results.ts"],"sourcesContent":["/**\n * SOTA-1: zero-LLM tool-result clearing — the cheapest pre-compaction tier.\n *\n * Mirrors Anthropic context editing (`clear_tool_uses_20250919`): when the\n * buffer crosses the compaction threshold, replace the oldest tool results with\n * a compact placeholder BEFORE paying for a summarizer LLM call. The most recent\n * `keepToolUses` results stay verbatim, `excludeTools` are never touched, and\n * clearing is skipped entirely when it would reclaim fewer than `clearAtLeast`\n * tokens (not worth the context churn). Pure + deterministic — no provider call.\n *\n * @packageDocumentation\n */\n\nimport type { Message, ToolMessage } from '@graphorin/core';\nimport {\n type ContextTokenCounter,\n countMessageTokens,\n renderMessageText,\n} from '../token-counter.js';\n\n/** Placeholder prefix on a cleared tool result — makes clearing idempotent. */\nexport const CLEARED_TOOL_RESULT_MARKER = '[cleared tool result';\n\n/** Default count of most-recent tool results kept verbatim. */\nexport const DEFAULT_KEEP_TOOL_USES = 3;\n\n/** Knobs for {@link clearOldToolResults} (mirrors the strategy variant). */\nexport interface ClearToolResultsOptions {\n /** Most-recent tool results kept verbatim (default {@link DEFAULT_KEEP_TOOL_USES}). */\n readonly keepToolUses?: number;\n /** Only clear if at least this many tokens are reclaimable; else leave untouched (default 0). */\n readonly clearAtLeast?: number;\n /** Tool names whose results are never cleared. */\n readonly excludeTools?: ReadonlyArray<string>;\n /** Placeholder builder; defaults to a one-line `[cleared tool result …]` marker. */\n readonly placeholder?: (info: {\n readonly toolCallId: string;\n readonly toolName?: string;\n readonly clearedTokens: number;\n }) => string;\n /**\n * A6 / SOTA-2 — recoverable clearing. When provided, the original tool-result\n * text of each cleared message is handed to this callback (wire it to a spill\n * store / the `read_result` handle registry) and the placeholder references the\n * returned handle id + preview, so the model can re-fetch the full result via\n * `read_result` rather than losing it. Invoked only for clears that actually\n * commit (after the `clearAtLeast` floor), so a rejected clearing never spills.\n * Omitted ⇒ the bare `placeholder` (irrecoverable, byte-identical default).\n */\n readonly externalize?: (\n content: string,\n info: {\n readonly toolCallId: string;\n readonly toolName?: string;\n readonly clearedTokens: number;\n },\n ) => Promise<{ readonly handleId: string; readonly preview?: string }>;\n}\n\n/** Result of a clearing pass. `clearedIndices` empty ⇒ nothing changed. */\nexport interface ClearToolResultsOutcome {\n readonly messages: ReadonlyArray<Message>;\n readonly clearedIndices: ReadonlyArray<number>;\n readonly reclaimedTokens: number;\n}\n\nfunction defaultPlaceholder(info: { toolName?: string; clearedTokens: number }): string {\n return `${CLEARED_TOOL_RESULT_MARKER} · ${info.toolName ?? 'tool'} · ${info.clearedTokens} tokens elided · re-run the tool if needed]`;\n}\n\n/** A6: a placeholder that points at the externalized handle (recoverable via read_result). */\nfunction handlePlaceholder(info: {\n toolName?: string;\n clearedTokens: number;\n handleId: string;\n preview?: string;\n}): string {\n const preview = info.preview !== undefined && info.preview.length > 0 ? ` · ${info.preview}` : '';\n return `${CLEARED_TOOL_RESULT_MARKER} · ${info.toolName ?? 'tool'} · ${info.clearedTokens} tokens · full result via read_result handle: ${info.handleId}${preview}]`;\n}\n\nfunction isAlreadyCleared(content: Message['content']): boolean {\n return typeof content === 'string' && content.startsWith(CLEARED_TOOL_RESULT_MARKER);\n}\n\n/**\n * Replace the oldest clearable tool results with placeholders. Returns the new\n * buffer (same length — content is replaced in place, never removed) plus the\n * cleared indices and reclaimed token count. Idempotent: already-cleared\n * placeholders are skipped on a second pass.\n */\nexport async function clearOldToolResults(\n messages: ReadonlyArray<Message>,\n options: ClearToolResultsOptions,\n counter: ContextTokenCounter,\n): Promise<ClearToolResultsOutcome> {\n const keep = options.keepToolUses ?? DEFAULT_KEEP_TOOL_USES;\n const exclude = new Set(options.excludeTools ?? []);\n const placeholder = options.placeholder ?? defaultPlaceholder;\n\n // toolCallId → toolName from every assistant tool-call in the buffer.\n const toolNameById = new Map<string, string>();\n for (const m of messages) {\n if (m.role === 'assistant' && m.toolCalls) {\n for (const tc of m.toolCalls) toolNameById.set(tc.toolCallId, tc.toolName);\n }\n }\n\n // Indices of all not-yet-cleared tool-result messages, oldest first.\n const toolIdx: number[] = [];\n for (let i = 0; i < messages.length; i++) {\n const m = messages[i];\n if (m?.role === 'tool' && !isAlreadyCleared(m.content)) toolIdx.push(i);\n }\n\n // Keep the most-recent `keep`; older ones are candidates unless excluded.\n const keptCutoff = toolIdx.length - Math.max(0, keep);\n const replacements = new Map<number, ToolMessage>();\n const clearedTokensByIdx = new Map<number, number>();\n let reclaimable = 0;\n for (let j = 0; j < toolIdx.length && j < keptCutoff; j++) {\n const idx = toolIdx[j];\n if (idx === undefined) continue;\n const tm = messages[idx] as ToolMessage;\n const name = toolNameById.get(tm.toolCallId);\n if (name !== undefined && exclude.has(name)) continue;\n const originalTokens = await countMessageTokens([tm], counter);\n const replaced: ToolMessage = {\n role: 'tool',\n toolCallId: tm.toolCallId,\n content: placeholder({\n toolCallId: tm.toolCallId,\n ...(name !== undefined ? { toolName: name } : {}),\n clearedTokens: originalTokens,\n }),\n };\n const placeholderTokens = await countMessageTokens([replaced], counter);\n reclaimable += Math.max(0, originalTokens - placeholderTokens);\n replacements.set(idx, replaced);\n clearedTokensByIdx.set(idx, originalTokens);\n }\n\n const clearAtLeast = options.clearAtLeast ?? 0;\n if (replacements.size === 0 || reclaimable < clearAtLeast) {\n return { messages, clearedIndices: [], reclaimedTokens: 0 };\n }\n\n // A6: only now that the clearing commits, externalize each cleared result to a\n // recoverable handle and rewrite its placeholder to reference it. Skipped\n // clears (above) never reach here, so the spill never fires for nothing.\n const externalize = options.externalize;\n if (externalize !== undefined) {\n for (const idx of replacements.keys()) {\n const tm = messages[idx] as ToolMessage;\n const name = toolNameById.get(tm.toolCallId);\n const clearedTokens = clearedTokensByIdx.get(idx) ?? 0;\n const content = typeof tm.content === 'string' ? tm.content : renderMessageText(tm);\n const handle = await externalize(content, {\n toolCallId: tm.toolCallId,\n ...(name !== undefined ? { toolName: name } : {}),\n clearedTokens,\n });\n replacements.set(idx, {\n role: 'tool',\n toolCallId: tm.toolCallId,\n content: handlePlaceholder({\n ...(name !== undefined ? { toolName: name } : {}),\n clearedTokens,\n handleId: handle.handleId,\n ...(handle.preview !== undefined ? { preview: handle.preview } : {}),\n }),\n });\n }\n }\n\n const out = messages.map((m, i) => replacements.get(i) ?? m);\n return {\n messages: out,\n clearedIndices: [...replacements.keys()],\n reclaimedTokens: reclaimable,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"clear-tool-results.js","names":["toolIdx: number[]","replaced: ToolMessage","out: Message[]"],"sources":["../../../src/context-engine/compaction/clear-tool-results.ts"],"sourcesContent":["/**\n * SOTA-1: zero-LLM tool-result clearing - the cheapest pre-compaction tier.\n *\n * Mirrors Anthropic context editing (`clear_tool_uses_20250919`): when the\n * buffer crosses the compaction threshold, replace the oldest tool results with\n * a compact placeholder BEFORE paying for a summarizer LLM call. The most recent\n * `keepToolUses` results stay verbatim, `excludeTools` are never touched, and\n * clearing is skipped entirely when it would reclaim fewer than `clearAtLeast`\n * tokens (not worth the context churn). Pure + deterministic - no provider call.\n *\n * @packageDocumentation\n */\n\nimport type { Message, ToolMessage } from '@graphorin/core';\nimport {\n type ContextTokenCounter,\n countMessageTokens,\n renderMessageText,\n} from '../token-counter.js';\n\n/** Placeholder prefix on a cleared tool result - makes clearing idempotent. */\nexport const CLEARED_TOOL_RESULT_MARKER = '[cleared tool result';\n\n/** Default count of most-recent tool results kept verbatim. */\nexport const DEFAULT_KEEP_TOOL_USES = 3;\n\n/** Knobs for {@link clearOldToolResults} (mirrors the strategy variant). */\nexport interface ClearToolResultsOptions {\n /** Most-recent tool results kept verbatim (default {@link DEFAULT_KEEP_TOOL_USES}). */\n readonly keepToolUses?: number;\n /** Only clear if at least this many tokens are reclaimable; else leave untouched (default 0). */\n readonly clearAtLeast?: number;\n /** Tool names whose results are never cleared. */\n readonly excludeTools?: ReadonlyArray<string>;\n /** Placeholder builder; defaults to a one-line `[cleared tool result …]` marker. */\n readonly placeholder?: (info: {\n readonly toolCallId: string;\n readonly toolName?: string;\n readonly clearedTokens: number;\n }) => string;\n /**\n * A6 / SOTA-2 - recoverable clearing. When provided, the original tool-result\n * text of each cleared message is handed to this callback (wire it to a spill\n * store / the `read_result` handle registry) and the placeholder references the\n * returned handle id + preview, so the model can re-fetch the full result via\n * `read_result` rather than losing it. Invoked only for clears that actually\n * commit (after the `clearAtLeast` floor), so a rejected clearing never spills.\n * Omitted ⇒ the bare `placeholder` (irrecoverable, byte-identical default).\n */\n readonly externalize?: (\n content: string,\n info: {\n readonly toolCallId: string;\n readonly toolName?: string;\n readonly clearedTokens: number;\n },\n ) => Promise<{ readonly handleId: string; readonly preview?: string }>;\n /**\n * C4 (context-engine-11): the tool the externalized-handle placeholder\n * advertises. The memory package cannot know whether the agent\n * registered `read_result` (that depends on spill wiring), so callers\n * whose runtime does NOT expose it pass `null` and the placeholder\n * degrades to a tool-neutral phrasing instead of promising a tool the\n * model cannot call. Default `'read_result'` (the agent built-in).\n */\n readonly readResultToolName?: string | null;\n /**\n * C4 (clear_tool_uses_20250919 parity): additionally blank the PAIRED\n * assistant message's tool-call arguments for every cleared result,\n * reclaiming the input side of verbose calls too. Default `false`.\n */\n readonly clearToolInputs?: boolean;\n}\n\n/** Result of a clearing pass. `clearedIndices` empty ⇒ nothing changed. */\nexport interface ClearToolResultsOutcome {\n readonly messages: ReadonlyArray<Message>;\n readonly clearedIndices: ReadonlyArray<number>;\n readonly reclaimedTokens: number;\n}\n\nfunction defaultPlaceholder(info: { toolName?: string; clearedTokens: number }): string {\n return `${CLEARED_TOOL_RESULT_MARKER} · ${info.toolName ?? 'tool'} · ${info.clearedTokens} tokens elided · re-run the tool if needed]`;\n}\n\n/** A6: a placeholder that points at the externalized handle (recoverable via read_result). */\nfunction handlePlaceholder(info: {\n toolName?: string;\n clearedTokens: number;\n handleId: string;\n preview?: string;\n readToolName?: string | null;\n}): string {\n const preview = info.preview !== undefined && info.preview.length > 0 ? ` · ${info.preview}` : '';\n // C4 (context-engine-11): only advertise a retrieval tool the caller\n // vouches exists; `null` degrades to a tool-neutral phrasing.\n const retrieval =\n info.readToolName === null\n ? `full result externalized to handle: ${info.handleId}`\n : `full result via ${info.readToolName ?? 'read_result'} handle: ${info.handleId}`;\n return `${CLEARED_TOOL_RESULT_MARKER} · ${info.toolName ?? 'tool'} · ${info.clearedTokens} tokens · ${retrieval}${preview}]`;\n}\n\nfunction isAlreadyCleared(content: Message['content']): boolean {\n return typeof content === 'string' && content.startsWith(CLEARED_TOOL_RESULT_MARKER);\n}\n\n/**\n * Replace the oldest clearable tool results with placeholders. Returns the new\n * buffer (same length - content is replaced in place, never removed) plus the\n * cleared indices and reclaimed token count. Idempotent: already-cleared\n * placeholders are skipped on a second pass.\n */\nexport async function clearOldToolResults(\n messages: ReadonlyArray<Message>,\n options: ClearToolResultsOptions,\n counter: ContextTokenCounter,\n): Promise<ClearToolResultsOutcome> {\n const keep = options.keepToolUses ?? DEFAULT_KEEP_TOOL_USES;\n const exclude = new Set(options.excludeTools ?? []);\n const placeholder = options.placeholder ?? defaultPlaceholder;\n\n // toolCallId → toolName from every assistant tool-call in the buffer.\n const toolNameById = new Map<string, string>();\n for (const m of messages) {\n if (m.role === 'assistant' && m.toolCalls) {\n for (const tc of m.toolCalls) toolNameById.set(tc.toolCallId, tc.toolName);\n }\n }\n\n // Indices of all not-yet-cleared tool-result messages, oldest first.\n const toolIdx: number[] = [];\n for (let i = 0; i < messages.length; i++) {\n const m = messages[i];\n if (m?.role === 'tool' && !isAlreadyCleared(m.content)) toolIdx.push(i);\n }\n\n // Keep the most-recent `keep`; older ones are candidates unless excluded.\n const keptCutoff = toolIdx.length - Math.max(0, keep);\n const replacements = new Map<number, ToolMessage>();\n const clearedTokensByIdx = new Map<number, number>();\n let reclaimable = 0;\n for (let j = 0; j < toolIdx.length && j < keptCutoff; j++) {\n const idx = toolIdx[j];\n if (idx === undefined) continue;\n const tm = messages[idx] as ToolMessage;\n const name = toolNameById.get(tm.toolCallId);\n if (name !== undefined && exclude.has(name)) continue;\n const originalTokens = await countMessageTokens([tm], counter);\n const replaced: ToolMessage = {\n role: 'tool',\n toolCallId: tm.toolCallId,\n content: placeholder({\n toolCallId: tm.toolCallId,\n ...(name !== undefined ? { toolName: name } : {}),\n clearedTokens: originalTokens,\n }),\n };\n const placeholderTokens = await countMessageTokens([replaced], counter);\n reclaimable += Math.max(0, originalTokens - placeholderTokens);\n replacements.set(idx, replaced);\n clearedTokensByIdx.set(idx, originalTokens);\n }\n\n const clearAtLeast = options.clearAtLeast ?? 0;\n if (replacements.size === 0 || reclaimable < clearAtLeast) {\n return { messages, clearedIndices: [], reclaimedTokens: 0 };\n }\n\n // A6: only now that the clearing commits, externalize each cleared result to a\n // recoverable handle and rewrite its placeholder to reference it. Skipped\n // clears (above) never reach here, so the spill never fires for nothing.\n const externalize = options.externalize;\n if (externalize !== undefined) {\n for (const idx of replacements.keys()) {\n const tm = messages[idx] as ToolMessage;\n const name = toolNameById.get(tm.toolCallId);\n const clearedTokens = clearedTokensByIdx.get(idx) ?? 0;\n const content = typeof tm.content === 'string' ? tm.content : renderMessageText(tm);\n const handle = await externalize(content, {\n toolCallId: tm.toolCallId,\n ...(name !== undefined ? { toolName: name } : {}),\n clearedTokens,\n });\n replacements.set(idx, {\n role: 'tool',\n toolCallId: tm.toolCallId,\n content: handlePlaceholder({\n ...(name !== undefined ? { toolName: name } : {}),\n clearedTokens,\n handleId: handle.handleId,\n ...(handle.preview !== undefined ? { preview: handle.preview } : {}),\n ...(options.readResultToolName !== undefined\n ? { readToolName: options.readResultToolName }\n : {}),\n }),\n });\n }\n }\n\n let out: Message[] = messages.map((m, i) => replacements.get(i) ?? m);\n\n // C4 (clear_tool_uses parity): blank the paired assistant tool-call\n // ARGUMENTS for every cleared result. The call's name and id survive\n // (the transcript stays well-formed); only the argument payload is\n // replaced with a marker object.\n if (options.clearToolInputs === true && replacements.size > 0) {\n const clearedCallIds = new Set<string>();\n for (const idx of replacements.keys()) {\n const tm = messages[idx] as ToolMessage;\n clearedCallIds.add(tm.toolCallId);\n }\n out = out.map((m) => {\n if (m.role !== 'assistant' || m.toolCalls === undefined) return m;\n if (!m.toolCalls.some((tc) => clearedCallIds.has(tc.toolCallId))) return m;\n return {\n ...m,\n toolCalls: m.toolCalls.map((tc) =>\n clearedCallIds.has(tc.toolCallId)\n ? { ...tc, args: { cleared: '[tool input elided by context clearing]' } }\n : tc,\n ),\n };\n });\n }\n\n return {\n messages: out,\n clearedIndices: [...replacements.keys()],\n reclaimedTokens: reclaimable,\n };\n}\n"],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ const UNTRUSTED_MARKER = "<<<untrusted_content";
|
|
|
18
18
|
* output. The scanner's 5ms default exists for the per-tool-result
|
|
19
19
|
* hot path; here a compaction already paid for an LLM call, and on a
|
|
20
20
|
* contended host (slow CI runners included) scheduler noise can cross
|
|
21
|
-
* 5ms between pattern iterations
|
|
21
|
+
* 5ms between pattern iterations - making the scanner return `null`
|
|
22
22
|
* (verdict unknown) and the security check silently fail open.
|
|
23
23
|
*/
|
|
24
24
|
const COMPACTION_SCAN_BUDGET_MS = 50;
|
|
@@ -67,9 +67,19 @@ async function executeCompaction(input) {
|
|
|
67
67
|
const preserveRecentTurns = input.strategy.preserveRecentTurns ?? DEFAULT_PRESERVE_RECENT_TURNS;
|
|
68
68
|
const summarizerModelInput = input.strategy.summarizerModel;
|
|
69
69
|
const summarizerTimeoutMs = input.strategy.summarizerTimeoutMs;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
70
|
+
let olderCount = Math.max(0, input.messages.length - preserveRecentTurns);
|
|
71
|
+
while (olderCount > 0 && input.messages[olderCount]?.role === "tool") olderCount -= 1;
|
|
72
|
+
const olderRaw = input.messages.slice(0, olderCount);
|
|
73
|
+
const preservedTail = input.messages.slice(olderCount);
|
|
74
|
+
const preserveUserMessages = Math.max(0, input.strategy.preserveUserMessages ?? 2);
|
|
75
|
+
const carriedUserIndices = /* @__PURE__ */ new Set();
|
|
76
|
+
if (preserveUserMessages > 0) for (let i = olderRaw.length - 1; i >= 0 && carriedUserIndices.size < preserveUserMessages;) {
|
|
77
|
+
if (olderRaw[i]?.role === "user") carriedUserIndices.add(i);
|
|
78
|
+
i -= 1;
|
|
79
|
+
}
|
|
80
|
+
if (carriedUserIndices.size > 0 && carriedUserIndices.size === olderRaw.length) carriedUserIndices.clear();
|
|
81
|
+
const olderMessages = olderRaw.filter((_, idx) => !carriedUserIndices.has(idx));
|
|
82
|
+
const preservedMessages = [...olderRaw.filter((_, idx) => carriedUserIndices.has(idx)), ...preservedTail];
|
|
73
83
|
if (olderMessages.length === 0) return Object.freeze({
|
|
74
84
|
summary: "",
|
|
75
85
|
summaryTokens: 0,
|
|
@@ -90,7 +100,8 @@ async function executeCompaction(input) {
|
|
|
90
100
|
};
|
|
91
101
|
const summarizerInput = { prompt: buildSummarizerPrompt({
|
|
92
102
|
template,
|
|
93
|
-
olderMessages
|
|
103
|
+
olderMessages,
|
|
104
|
+
...input.strategy.summarizerInputCharBudget !== void 0 ? { maxDumpChars: input.strategy.summarizerInputCharBudget } : {}
|
|
94
105
|
}) };
|
|
95
106
|
if (summarizerModelInput !== void 0) summarizerInput.model = summarizerModelInput;
|
|
96
107
|
if (input.signal !== void 0) summarizerInput.signal = input.signal;
|
|
@@ -148,7 +159,9 @@ async function executeClearStrategy(input, strategy, counter, now, startTs, befo
|
|
|
148
159
|
...strategy.keepToolUses !== void 0 ? { keepToolUses: strategy.keepToolUses } : {},
|
|
149
160
|
...strategy.clearAtLeast !== void 0 ? { clearAtLeast: strategy.clearAtLeast } : {},
|
|
150
161
|
...strategy.excludeTools !== void 0 ? { excludeTools: strategy.excludeTools } : {},
|
|
151
|
-
...strategy.externalize !== void 0 ? { externalize: strategy.externalize } : {}
|
|
162
|
+
...strategy.externalize !== void 0 ? { externalize: strategy.externalize } : {},
|
|
163
|
+
...strategy.clearToolInputs !== void 0 ? { clearToolInputs: strategy.clearToolInputs } : {},
|
|
164
|
+
...strategy.readResultToolName !== void 0 ? { readResultToolName: strategy.readResultToolName } : {}
|
|
152
165
|
}, counter);
|
|
153
166
|
const afterClearTokens = await countMessageTokens(outcome.messages, counter);
|
|
154
167
|
const fallback = strategy.summarizeFallback;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compactor.js","names":["ctx: CompactionContext","summarizerInput: {\n prompt: string;\n model?: ModelSpec | string;\n signal?: AbortSignal;\n timeoutMs?: number;\n }","metadata: CompactionMetadataPayload","summaryTrust: 'trusted' | 'untrusted-derived'","summaryMessage: SystemMessage","trimmedMessages: ReadonlyArray<Message>"],"sources":["../../../src/context-engine/compaction/compactor.ts"],"sourcesContent":["/**\n * Auto-compaction subsystem core (RB-46 / suggested DEC-162 /\n * ADR-050). Trims the in-flight ContextEngine message buffer when\n * the assembled token count crosses a per-provider threshold; the\n * trim is summarized via the configured 9-section template; the\n * trim is followed by synchronous-await firing of registered\n * post-compaction hooks (lifecycle owned by Phase 12; this module\n * exposes the trim primitive).\n *\n * Disjoint from the Phase 10c consolidator: never writes to\n * `semantic_facts` / `episodic_episodes` / `procedural_rules`;\n * reads only `memory.metadata(...)` / `memory.semantic.search(...)`\n * / `memory.procedural.activate(...)` / `memory.working.read(...)`\n * (non-mutating).\n *\n * @packageDocumentation\n */\n\nimport type { Message, ModelSpec, SystemMessage } from '@graphorin/core';\nimport { scanImperativePatterns } from '@graphorin/observability/redaction';\nimport type { ContextLocalePack } from '../locale-packs/index.js';\nimport {\n type ContextTokenCounter,\n countMessageTokens,\n HEURISTIC_TOKEN_COUNTER,\n renderMessageText,\n} from '../token-counter.js';\nimport { clearOldToolResults } from './clear-tool-results.js';\nimport {\n buildSummarizerPrompt,\n type CompactionMetadataPayload,\n renderFinalSummary,\n SUMMARY_TEMPLATE_NAME,\n SUMMARY_TEMPLATE_VERSION,\n} from './templates/summary-9-section.js';\nimport type {\n CompactionContext,\n CompactionResult,\n CompactionSource,\n CompactionStrategy,\n CompactionSummarizer,\n} from './types.js';\n\n/**\n * Default count of recent turns preserved verbatim by the\n * `summarize-old-preserve-recent` strategy.\n *\n * @stable\n */\nexport const DEFAULT_PRESERVE_RECENT_TURNS = 6;\n\n/** Opening marker of the inbound-untrusted envelope (tools sanitize layer). */\nconst UNTRUSTED_MARKER = '<<<untrusted_content';\n\n/**\n * Wall-clock budget for the CE-15 injection scan of the summarizer\n * output. The scanner's 5ms default exists for the per-tool-result\n * hot path; here a compaction already paid for an LLM call, and on a\n * contended host (slow CI runners included) scheduler noise can cross\n * 5ms between pattern iterations — making the scanner return `null`\n * (verdict unknown) and the security check silently fail open.\n */\nconst COMPACTION_SCAN_BUDGET_MS = 50;\n\n/** CE-15: does the compacted window carry inbound-untrusted envelopes? */\nfunction windowContainsUntrusted(messages: ReadonlyArray<Message>): boolean {\n return messages.some((message) => renderMessageText(message).includes(UNTRUSTED_MARKER));\n}\n\n/**\n * CE-15: wrap the LLM-authored summary body in a derived-trust\n * envelope. Envelope marker sequences inside the body are neutralized\n * first so summarizer output influenced by injected text cannot break\n * out of the envelope and masquerade as authoritative system text.\n */\nfunction wrapSummaryAsDerived(body: string): string {\n const neutralized = body\n .replaceAll('<<</untrusted_content>>>', '[[/untrusted_content]]')\n .replaceAll(UNTRUSTED_MARKER, '[[untrusted_content');\n return `<<<untrusted_content trust=\"derived\" tool=\"compaction-summarizer\">>>\\n${neutralized}\\n<<</untrusted_content>>>`;\n}\n\n/**\n * Trim the in-flight buffer using the\n * `summarize-old-preserve-recent` strategy. Returns the trimmed\n * messages + summary metadata; the caller is responsible for\n * appending the summary to the message buffer (Phase 12 owns the\n * lifecycle; this module exposes the primitive).\n *\n * @stable\n */\nexport interface ExecuteCompactionInput {\n readonly messages: ReadonlyArray<Message>;\n readonly source: CompactionSource;\n readonly strategy: CompactionStrategy;\n readonly localePack: ContextLocalePack;\n readonly summarizer: CompactionSummarizer;\n readonly tokenCounter?: ContextTokenCounter;\n readonly thresholdTokens: number;\n readonly runId: string;\n readonly sessionId: string;\n readonly agentId: string;\n readonly scope: import('@graphorin/core').SessionScope;\n readonly providerTrust?: import('@graphorin/core').LocalProviderTrust;\n readonly now?: () => number;\n readonly signal?: AbortSignal;\n}\n\n/**\n * Perform a compaction call. Returns the result envelope containing\n * the produced summary, the dropped/preserved message slices, and\n * the per-event metadata. Phase 12 / `agent.compact()` is the\n * lifecycle owner; this function is the trim primitive.\n *\n * @stable\n */\nexport async function executeCompaction(input: ExecuteCompactionInput): Promise<CompactionResult> {\n const counter = input.tokenCounter ?? HEURISTIC_TOKEN_COUNTER;\n const now = input.now ?? Date.now;\n const startTs = now();\n const beforeTokens = await countMessageTokens(input.messages, counter);\n\n if (input.strategy.kind === 'custom') {\n const ctx: CompactionContext = {\n runId: input.runId,\n sessionId: input.sessionId,\n agentId: input.agentId,\n scope: input.scope,\n source: input.source,\n messages: input.messages,\n beforeTokens,\n thresholdTokens: input.thresholdTokens,\n preserveRecentTurns: DEFAULT_PRESERVE_RECENT_TURNS,\n providerTrust: input.providerTrust ?? 'public-tls',\n };\n const result = await input.strategy.compact(ctx);\n return result;\n }\n\n if (input.strategy.kind === 'clear-old-tool-results') {\n return executeClearStrategy(input, input.strategy, counter, now, startTs, beforeTokens);\n }\n\n const preserveRecentTurns = input.strategy.preserveRecentTurns ?? DEFAULT_PRESERVE_RECENT_TURNS;\n const summarizerModelInput = input.strategy.summarizerModel;\n const summarizerTimeoutMs = input.strategy.summarizerTimeoutMs;\n\n // Slice the older portion + preserve the recent N turns. We treat\n // a \"turn\" as a single message for the v0.1 surface; the spec\n // notes more nuanced segmentation is post-MVP per Q-121.\n const olderCount = Math.max(0, input.messages.length - preserveRecentTurns);\n const olderMessages = input.messages.slice(0, olderCount);\n const preservedMessages = input.messages.slice(olderCount);\n\n if (olderMessages.length === 0) {\n return Object.freeze({\n summary: '',\n summaryTokens: 0,\n beforeTokens,\n afterTokens: beforeTokens,\n droppedMessageIds: Object.freeze([]),\n droppedMessageIndices: Object.freeze([]),\n preservedMessages: Object.freeze([...preservedMessages]),\n trimmedMessages: Object.freeze([...preservedMessages]),\n source: input.source,\n durationMs: now() - startTs,\n hooksFiredCount: 0,\n summaryTrust: 'trusted',\n });\n }\n\n const template = {\n preamble: input.localePack.compactionSummaryTemplate.preamble,\n sections: input.localePack.compactionSummaryTemplate.sections,\n };\n const prompt = buildSummarizerPrompt({ template, olderMessages });\n\n const summarizerInput: {\n prompt: string;\n model?: ModelSpec | string;\n signal?: AbortSignal;\n timeoutMs?: number;\n } = { prompt };\n if (summarizerModelInput !== undefined) summarizerInput.model = summarizerModelInput;\n if (input.signal !== undefined) summarizerInput.signal = input.signal;\n if (summarizerTimeoutMs !== undefined) summarizerInput.timeoutMs = summarizerTimeoutMs;\n const summarized = await input.summarizer.summarize(summarizerInput);\n\n // Section 9 metadata payload — stable shape so consumers can\n // deserialize and reason about a compaction event.\n const metadata: CompactionMetadataPayload = {\n compactedAtIso: new Date(startTs).toISOString(),\n // CE-14: positional labels, prefixed with the compaction instant so\n // they no longer collide across compaction events (core Message has\n // no id — these are indices into THIS compaction's dropped slice).\n compactedFromMessageIds: Object.freeze(olderMessages.map((_, idx) => `c${startTs}_msg_${idx}`)),\n compactedFromMessageIndices: Object.freeze(olderMessages.map((_, idx) => idx)),\n compactedFromTokens: await countMessageTokens(olderMessages, counter),\n summaryTokens: summarized.usageTokens ?? (await counter.countText(summarized.text)),\n summarizerModel: resolveSummarizerModelLabel(summarizerModelInput, input.summarizer.id),\n templateName: SUMMARY_TEMPLATE_NAME,\n templateVersion: SUMMARY_TEMPLATE_VERSION,\n preserveRecentTurns,\n };\n\n // CE-15: the summary must not launder untrusted content into a\n // trusted system message. When the compacted window carried\n // untrusted envelopes — or the injection heuristics flag the\n // summarizer output itself — the LLM-authored body is committed\n // inside a derived-trust envelope (sticky across re-compactions:\n // the envelope re-triggers this detection next time around).\n const summaryScan = scanImperativePatterns(summarized.text, undefined, COMPACTION_SCAN_BUDGET_MS);\n const summaryTrust: 'trusted' | 'untrusted-derived' =\n windowContainsUntrusted(olderMessages) || (summaryScan !== null && summaryScan.hits.length > 0)\n ? 'untrusted-derived'\n : 'trusted';\n const finalSummary = renderFinalSummary({\n template,\n summaryFromLlm:\n summaryTrust === 'untrusted-derived'\n ? wrapSummaryAsDerived(summarized.text)\n : summarized.text,\n preservedMessages,\n metadata,\n });\n const summaryTokens = await counter.countText(finalSummary);\n\n const summaryMessage: SystemMessage = {\n role: 'system',\n content: finalSummary,\n };\n const trimmedMessages: ReadonlyArray<Message> = Object.freeze([\n summaryMessage,\n ...preservedMessages,\n ]);\n const afterTokens = await countMessageTokens(trimmedMessages, counter);\n const durationMs = now() - startTs;\n\n return Object.freeze({\n summary: finalSummary,\n summaryTokens,\n beforeTokens,\n afterTokens,\n droppedMessageIds: metadata.compactedFromMessageIds,\n droppedMessageIndices: metadata.compactedFromMessageIndices,\n preservedMessages: Object.freeze([...preservedMessages]),\n trimmedMessages,\n source: input.source,\n durationMs,\n hooksFiredCount: 0,\n summaryTrust,\n });\n}\n\n/**\n * SOTA-1: clear the oldest tool results (zero-LLM), then summarize only if the\n * cleared buffer is still over the threshold. The summarizer runs on the\n * already-reduced window, so a few-tool-result buffer compacts for free.\n */\nasync function executeClearStrategy(\n input: ExecuteCompactionInput,\n strategy: Extract<CompactionStrategy, { kind: 'clear-old-tool-results' }>,\n counter: ContextTokenCounter,\n now: () => number,\n startTs: number,\n beforeTokens: number,\n): Promise<CompactionResult> {\n const outcome = await clearOldToolResults(\n input.messages,\n {\n ...(strategy.keepToolUses !== undefined ? { keepToolUses: strategy.keepToolUses } : {}),\n ...(strategy.clearAtLeast !== undefined ? { clearAtLeast: strategy.clearAtLeast } : {}),\n ...(strategy.excludeTools !== undefined ? { excludeTools: strategy.excludeTools } : {}),\n ...(strategy.externalize !== undefined ? { externalize: strategy.externalize } : {}),\n },\n counter,\n );\n const afterClearTokens = await countMessageTokens(outcome.messages, counter);\n\n const fallback = strategy.summarizeFallback;\n if (fallback !== false && afterClearTokens > input.thresholdTokens) {\n // Clearing did not reclaim enough — summarize the ALREADY-cleared buffer.\n const summarized = await executeCompaction({\n ...input,\n messages: outcome.messages,\n strategy: { kind: 'summarize-old-preserve-recent', ...(fallback ?? {}) },\n });\n return Object.freeze({\n ...summarized,\n // Report against the original buffer + carry the cleared indices.\n beforeTokens,\n droppedMessageIndices: Object.freeze([\n ...outcome.clearedIndices,\n ...summarized.droppedMessageIndices.filter((i) => !outcome.clearedIndices.includes(i)),\n ]),\n durationMs: now() - startTs,\n });\n }\n\n // Zero-LLM clear-only outcome: content replaced in place, nothing summarized.\n const clearedSet = new Set(outcome.clearedIndices);\n const preservedMessages = outcome.messages.filter((_, i) => !clearedSet.has(i));\n return Object.freeze({\n summary: '',\n summaryTokens: 0,\n beforeTokens,\n afterTokens: afterClearTokens,\n droppedMessageIds: Object.freeze(outcome.clearedIndices.map((i) => `c${startTs}_cleared_${i}`)),\n droppedMessageIndices: Object.freeze([...outcome.clearedIndices]),\n preservedMessages: Object.freeze(preservedMessages),\n trimmedMessages: Object.freeze([...outcome.messages]),\n source: input.source,\n durationMs: now() - startTs,\n hooksFiredCount: 0,\n summaryTrust: 'trusted',\n });\n}\n\nfunction resolveSummarizerModelLabel(\n modelInput: ModelSpec | string | undefined,\n summarizerId: string | undefined,\n): string | null {\n if (typeof modelInput === 'string') return modelInput;\n if (modelInput !== undefined) {\n const provider =\n 'provider' in modelInput\n ? `${modelInput.provider.name}:${modelInput.model}`\n : `${modelInput.name}:${modelInput.modelId}`;\n return provider;\n }\n return summarizerId ?? null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compactor.js","names":["ctx: CompactionContext","summarizerInput: {\n prompt: string;\n model?: ModelSpec | string;\n signal?: AbortSignal;\n timeoutMs?: number;\n }","metadata: CompactionMetadataPayload","summaryTrust: 'trusted' | 'untrusted-derived'","summaryMessage: SystemMessage","trimmedMessages: ReadonlyArray<Message>"],"sources":["../../../src/context-engine/compaction/compactor.ts"],"sourcesContent":["/**\n * Auto-compaction subsystem core (RB-46 / suggested DEC-162 /\n * ADR-050). Trims the in-flight ContextEngine message buffer when\n * the assembled token count crosses a per-provider threshold; the\n * trim is summarized via the configured section template; the\n * trim is followed by synchronous-await firing of registered\n * post-compaction hooks (lifecycle owned by Phase 12; this module\n * exposes the trim primitive).\n *\n * Disjoint from the Phase 10c consolidator: never writes to\n * `semantic_facts` / `episodic_episodes` / `procedural_rules`;\n * reads only `memory.metadata(...)` / `memory.semantic.search(...)`\n * / `memory.procedural.activate(...)` / `memory.working.read(...)`\n * (non-mutating).\n *\n * @packageDocumentation\n */\n\nimport type { Message, ModelSpec, SystemMessage } from '@graphorin/core';\nimport { scanImperativePatterns } from '@graphorin/observability/redaction';\nimport type { ContextLocalePack } from '../locale-packs/index.js';\nimport {\n type ContextTokenCounter,\n countMessageTokens,\n HEURISTIC_TOKEN_COUNTER,\n renderMessageText,\n} from '../token-counter.js';\nimport { clearOldToolResults } from './clear-tool-results.js';\nimport {\n buildSummarizerPrompt,\n type CompactionMetadataPayload,\n renderFinalSummary,\n SUMMARY_TEMPLATE_NAME,\n SUMMARY_TEMPLATE_VERSION,\n} from './templates/summary-9-section.js';\nimport type {\n CompactionContext,\n CompactionResult,\n CompactionSource,\n CompactionStrategy,\n CompactionSummarizer,\n} from './types.js';\n\n/**\n * Default count of recent turns preserved verbatim by the\n * `summarize-old-preserve-recent` strategy.\n *\n * @stable\n */\nexport const DEFAULT_PRESERVE_RECENT_TURNS = 6;\n\n/** Opening marker of the inbound-untrusted envelope (tools sanitize layer). */\nconst UNTRUSTED_MARKER = '<<<untrusted_content';\n\n/**\n * Wall-clock budget for the CE-15 injection scan of the summarizer\n * output. The scanner's 5ms default exists for the per-tool-result\n * hot path; here a compaction already paid for an LLM call, and on a\n * contended host (slow CI runners included) scheduler noise can cross\n * 5ms between pattern iterations - making the scanner return `null`\n * (verdict unknown) and the security check silently fail open.\n */\nconst COMPACTION_SCAN_BUDGET_MS = 50;\n\n/** CE-15: does the compacted window carry inbound-untrusted envelopes? */\nfunction windowContainsUntrusted(messages: ReadonlyArray<Message>): boolean {\n return messages.some((message) => renderMessageText(message).includes(UNTRUSTED_MARKER));\n}\n\n/**\n * CE-15: wrap the LLM-authored summary body in a derived-trust\n * envelope. Envelope marker sequences inside the body are neutralized\n * first so summarizer output influenced by injected text cannot break\n * out of the envelope and masquerade as authoritative system text.\n */\nfunction wrapSummaryAsDerived(body: string): string {\n const neutralized = body\n .replaceAll('<<</untrusted_content>>>', '[[/untrusted_content]]')\n .replaceAll(UNTRUSTED_MARKER, '[[untrusted_content');\n return `<<<untrusted_content trust=\"derived\" tool=\"compaction-summarizer\">>>\\n${neutralized}\\n<<</untrusted_content>>>`;\n}\n\n/**\n * Trim the in-flight buffer using the\n * `summarize-old-preserve-recent` strategy. Returns the trimmed\n * messages + summary metadata; the caller is responsible for\n * appending the summary to the message buffer (Phase 12 owns the\n * lifecycle; this module exposes the primitive).\n *\n * @stable\n */\nexport interface ExecuteCompactionInput {\n readonly messages: ReadonlyArray<Message>;\n readonly source: CompactionSource;\n readonly strategy: CompactionStrategy;\n readonly localePack: ContextLocalePack;\n readonly summarizer: CompactionSummarizer;\n readonly tokenCounter?: ContextTokenCounter;\n readonly thresholdTokens: number;\n readonly runId: string;\n readonly sessionId: string;\n readonly agentId: string;\n readonly scope: import('@graphorin/core').SessionScope;\n readonly providerTrust?: import('@graphorin/core').LocalProviderTrust;\n readonly now?: () => number;\n readonly signal?: AbortSignal;\n}\n\n/**\n * Perform a compaction call. Returns the result envelope containing\n * the produced summary, the dropped/preserved message slices, and\n * the per-event metadata. Phase 12 / `agent.compact()` is the\n * lifecycle owner; this function is the trim primitive.\n *\n * @stable\n */\nexport async function executeCompaction(input: ExecuteCompactionInput): Promise<CompactionResult> {\n const counter = input.tokenCounter ?? HEURISTIC_TOKEN_COUNTER;\n const now = input.now ?? Date.now;\n const startTs = now();\n const beforeTokens = await countMessageTokens(input.messages, counter);\n\n if (input.strategy.kind === 'custom') {\n const ctx: CompactionContext = {\n runId: input.runId,\n sessionId: input.sessionId,\n agentId: input.agentId,\n scope: input.scope,\n source: input.source,\n messages: input.messages,\n beforeTokens,\n thresholdTokens: input.thresholdTokens,\n preserveRecentTurns: DEFAULT_PRESERVE_RECENT_TURNS,\n providerTrust: input.providerTrust ?? 'public-tls',\n };\n const result = await input.strategy.compact(ctx);\n return result;\n }\n\n if (input.strategy.kind === 'clear-old-tool-results') {\n return executeClearStrategy(input, input.strategy, counter, now, startTs, beforeTokens);\n }\n\n const preserveRecentTurns = input.strategy.preserveRecentTurns ?? DEFAULT_PRESERVE_RECENT_TURNS;\n const summarizerModelInput = input.strategy.summarizerModel;\n const summarizerTimeoutMs = input.strategy.summarizerTimeoutMs;\n\n // Slice the older portion + preserve the recent N turns. We treat\n // a \"turn\" as a single message for the v0.1 surface; the spec\n // notes more nuanced segmentation is post-MVP per Q-121.\n let olderCount = Math.max(0, input.messages.length - preserveRecentTurns);\n // context-engine-01: never split an assistant/tool pair. A purely\n // positional slice can strand a `role:'tool'` message at the head of\n // the preserved window while its `tool_calls` assistant partner is\n // summarized away - OpenAI-compatible servers 400 on the orphan and\n // `invalid-request` is fallback-ineligible, so the run would die right\n // after an otherwise successful compaction. Snap the boundary backward\n // until the preserved slice starts on a non-`tool` message: preserving\n // more than requested is always transcript-safe. (The\n // clear-tool-results strategy is immune by construction - it replaces\n // content in place and never removes messages.)\n while (olderCount > 0 && input.messages[olderCount]?.role === 'tool') {\n olderCount -= 1;\n }\n const olderRaw = input.messages.slice(0, olderCount);\n const preservedTail = input.messages.slice(olderCount);\n\n // C4: keep the most recent USER messages verbatim across compaction -\n // only assistant/tool content gets summarized away. User words are the\n // task statement; a paraphrase in the summary routinely drops the\n // constraint that mattered. The carried messages are removed from the\n // summarizer's input (no double-counting) and re-inserted between the\n // summary and the preserved tail, keeping their relative order.\n const preserveUserMessages = Math.max(0, input.strategy.preserveUserMessages ?? 2);\n const carriedUserIndices = new Set<number>();\n if (preserveUserMessages > 0) {\n for (let i = olderRaw.length - 1; i >= 0 && carriedUserIndices.size < preserveUserMessages; ) {\n if (olderRaw[i]?.role === 'user') carriedUserIndices.add(i);\n i -= 1;\n }\n }\n // Degenerate guard: on a tiny (user-dominated) window, carrying users\n // could empty the summarizable slice and turn the whole compaction into\n // a no-op - there, positional summarization wins.\n if (carriedUserIndices.size > 0 && carriedUserIndices.size === olderRaw.length) {\n carriedUserIndices.clear();\n }\n const olderMessages = olderRaw.filter((_, idx) => !carriedUserIndices.has(idx));\n const carriedUserMessages = olderRaw.filter((_, idx) => carriedUserIndices.has(idx));\n const preservedMessages = [...carriedUserMessages, ...preservedTail];\n\n if (olderMessages.length === 0) {\n return Object.freeze({\n summary: '',\n summaryTokens: 0,\n beforeTokens,\n afterTokens: beforeTokens,\n droppedMessageIds: Object.freeze([]),\n droppedMessageIndices: Object.freeze([]),\n preservedMessages: Object.freeze([...preservedMessages]),\n trimmedMessages: Object.freeze([...preservedMessages]),\n source: input.source,\n durationMs: now() - startTs,\n hooksFiredCount: 0,\n summaryTrust: 'trusted',\n });\n }\n\n const template = {\n preamble: input.localePack.compactionSummaryTemplate.preamble,\n sections: input.localePack.compactionSummaryTemplate.sections,\n };\n const prompt = buildSummarizerPrompt({\n template,\n olderMessages,\n ...(input.strategy.summarizerInputCharBudget !== undefined\n ? { maxDumpChars: input.strategy.summarizerInputCharBudget }\n : {}),\n });\n\n const summarizerInput: {\n prompt: string;\n model?: ModelSpec | string;\n signal?: AbortSignal;\n timeoutMs?: number;\n } = { prompt };\n if (summarizerModelInput !== undefined) summarizerInput.model = summarizerModelInput;\n if (input.signal !== undefined) summarizerInput.signal = input.signal;\n if (summarizerTimeoutMs !== undefined) summarizerInput.timeoutMs = summarizerTimeoutMs;\n const summarized = await input.summarizer.summarize(summarizerInput);\n\n // Section 9 metadata payload - stable shape so consumers can\n // deserialize and reason about a compaction event.\n const metadata: CompactionMetadataPayload = {\n compactedAtIso: new Date(startTs).toISOString(),\n // CE-14: positional labels, prefixed with the compaction instant so\n // they no longer collide across compaction events (core Message has\n // no id - these are indices into THIS compaction's dropped slice).\n compactedFromMessageIds: Object.freeze(olderMessages.map((_, idx) => `c${startTs}_msg_${idx}`)),\n compactedFromMessageIndices: Object.freeze(olderMessages.map((_, idx) => idx)),\n compactedFromTokens: await countMessageTokens(olderMessages, counter),\n summaryTokens: summarized.usageTokens ?? (await counter.countText(summarized.text)),\n summarizerModel: resolveSummarizerModelLabel(summarizerModelInput, input.summarizer.id),\n templateName: SUMMARY_TEMPLATE_NAME,\n templateVersion: SUMMARY_TEMPLATE_VERSION,\n preserveRecentTurns,\n };\n\n // CE-15: the summary must not launder untrusted content into a\n // trusted system message. When the compacted window carried\n // untrusted envelopes - or the injection heuristics flag the\n // summarizer output itself - the LLM-authored body is committed\n // inside a derived-trust envelope (sticky across re-compactions:\n // the envelope re-triggers this detection next time around).\n const summaryScan = scanImperativePatterns(summarized.text, undefined, COMPACTION_SCAN_BUDGET_MS);\n const summaryTrust: 'trusted' | 'untrusted-derived' =\n windowContainsUntrusted(olderMessages) || (summaryScan !== null && summaryScan.hits.length > 0)\n ? 'untrusted-derived'\n : 'trusted';\n const finalSummary = renderFinalSummary({\n template,\n summaryFromLlm:\n summaryTrust === 'untrusted-derived'\n ? wrapSummaryAsDerived(summarized.text)\n : summarized.text,\n preservedMessages,\n metadata,\n });\n const summaryTokens = await counter.countText(finalSummary);\n\n const summaryMessage: SystemMessage = {\n role: 'system',\n content: finalSummary,\n };\n const trimmedMessages: ReadonlyArray<Message> = Object.freeze([\n summaryMessage,\n ...preservedMessages,\n ]);\n const afterTokens = await countMessageTokens(trimmedMessages, counter);\n const durationMs = now() - startTs;\n\n return Object.freeze({\n summary: finalSummary,\n summaryTokens,\n beforeTokens,\n afterTokens,\n droppedMessageIds: metadata.compactedFromMessageIds,\n droppedMessageIndices: metadata.compactedFromMessageIndices,\n preservedMessages: Object.freeze([...preservedMessages]),\n trimmedMessages,\n source: input.source,\n durationMs,\n hooksFiredCount: 0,\n summaryTrust,\n });\n}\n\n/**\n * SOTA-1: clear the oldest tool results (zero-LLM), then summarize only if the\n * cleared buffer is still over the threshold. The summarizer runs on the\n * already-reduced window, so a few-tool-result buffer compacts for free.\n */\nasync function executeClearStrategy(\n input: ExecuteCompactionInput,\n strategy: Extract<CompactionStrategy, { kind: 'clear-old-tool-results' }>,\n counter: ContextTokenCounter,\n now: () => number,\n startTs: number,\n beforeTokens: number,\n): Promise<CompactionResult> {\n const outcome = await clearOldToolResults(\n input.messages,\n {\n ...(strategy.keepToolUses !== undefined ? { keepToolUses: strategy.keepToolUses } : {}),\n ...(strategy.clearAtLeast !== undefined ? { clearAtLeast: strategy.clearAtLeast } : {}),\n ...(strategy.excludeTools !== undefined ? { excludeTools: strategy.excludeTools } : {}),\n ...(strategy.externalize !== undefined ? { externalize: strategy.externalize } : {}),\n ...(strategy.clearToolInputs !== undefined\n ? { clearToolInputs: strategy.clearToolInputs }\n : {}),\n ...(strategy.readResultToolName !== undefined\n ? { readResultToolName: strategy.readResultToolName }\n : {}),\n },\n counter,\n );\n const afterClearTokens = await countMessageTokens(outcome.messages, counter);\n\n const fallback = strategy.summarizeFallback;\n if (fallback !== false && afterClearTokens > input.thresholdTokens) {\n // Clearing did not reclaim enough - summarize the ALREADY-cleared buffer.\n const summarized = await executeCompaction({\n ...input,\n messages: outcome.messages,\n strategy: { kind: 'summarize-old-preserve-recent', ...(fallback ?? {}) },\n });\n return Object.freeze({\n ...summarized,\n // Report against the original buffer + carry the cleared indices.\n beforeTokens,\n droppedMessageIndices: Object.freeze([\n ...outcome.clearedIndices,\n ...summarized.droppedMessageIndices.filter((i) => !outcome.clearedIndices.includes(i)),\n ]),\n durationMs: now() - startTs,\n });\n }\n\n // Zero-LLM clear-only outcome: content replaced in place, nothing summarized.\n const clearedSet = new Set(outcome.clearedIndices);\n const preservedMessages = outcome.messages.filter((_, i) => !clearedSet.has(i));\n return Object.freeze({\n summary: '',\n summaryTokens: 0,\n beforeTokens,\n afterTokens: afterClearTokens,\n droppedMessageIds: Object.freeze(outcome.clearedIndices.map((i) => `c${startTs}_cleared_${i}`)),\n droppedMessageIndices: Object.freeze([...outcome.clearedIndices]),\n preservedMessages: Object.freeze(preservedMessages),\n trimmedMessages: Object.freeze([...outcome.messages]),\n source: input.source,\n durationMs: now() - startTs,\n hooksFiredCount: 0,\n summaryTrust: 'trusted',\n });\n}\n\nfunction resolveSummarizerModelLabel(\n modelInput: ModelSpec | string | undefined,\n summarizerId: string | undefined,\n): string | null {\n if (typeof modelInput === 'string') return modelInput;\n if (modelInput !== undefined) {\n const provider =\n 'provider' in modelInput\n ? `${modelInput.provider.name}:${modelInput.model}`\n : `${modelInput.name}:${modelInput.modelId}`;\n return provider;\n }\n return summarizerId ?? null;\n}\n"],"mappings":"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"}
|
|
@@ -11,6 +11,10 @@ function reanchorPersonaBlock(options = {}) {
|
|
|
11
11
|
async resolveContent(deps) {
|
|
12
12
|
const value = await deps.memory.working.read(deps.scope, blockLabel);
|
|
13
13
|
if (value === null || value.length === 0) return [];
|
|
14
|
+
if (deps.allowSensitivity !== void 0) {
|
|
15
|
+
const block = (await deps.memory.working.list(deps.scope)).find((b) => b.label === blockLabel);
|
|
16
|
+
if (!deps.allowSensitivity(block?.sensitivity)) return [];
|
|
17
|
+
}
|
|
14
18
|
return [{
|
|
15
19
|
type: "text",
|
|
16
20
|
text: `<memory_blocks anchor="post-compaction">\n <block label="${escapeXml(blockLabel)}">\n ${escapeXml(value)}\n </block>\n</memory_blocks>`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reanchor-persona-block.js","names":[],"sources":["../../../../src/context-engine/compaction/hooks/reanchor-persona-block.ts"],"sourcesContent":["/**\n * `reanchorPersonaBlock({ blockLabel = 'persona' })`
|
|
1
|
+
{"version":3,"file":"reanchor-persona-block.js","names":[],"sources":["../../../../src/context-engine/compaction/hooks/reanchor-persona-block.ts"],"sourcesContent":["/**\n * `reanchorPersonaBlock({ blockLabel = 'persona' })` - built-in\n * post-compaction hook. Queries `memory.working.read(...)` for the\n * persona block and returns it as a system-content fragment for\n * re-injection.\n *\n * @packageDocumentation\n */\n\nimport type { MessageContent } from '@graphorin/core';\nimport type { HookDeps, NamedPostCompactionHook } from './types.js';\n\n/**\n * Build a `reanchorPersonaBlock` hook.\n *\n * @stable\n */\nexport function reanchorPersonaBlock(\n options: { readonly blockLabel?: string } = {},\n): NamedPostCompactionHook {\n const blockLabel = options.blockLabel ?? 'persona';\n return {\n id: 'reanchorPersonaBlock',\n async resolveContent(deps: HookDeps): Promise<ReadonlyArray<MessageContent>> {\n const value = await deps.memory.working.read(deps.scope, blockLabel);\n if (value === null || value.length === 0) return [];\n // context-engine-02: the D2 privacy filter gates re-anchored content\n // exactly as it gates the assembled prompt - a secret-tier persona\n // block the assembly withheld must not ship to the provider via the\n // post-compaction splice. `read()` returns only the value, so the\n // sensitivity comes from the block listing; a block absent from the\n // listing (unwritten, default-value fallback) is evaluated at the\n // engine's default sensitivity.\n if (deps.allowSensitivity !== undefined) {\n const blocks = await deps.memory.working.list(deps.scope);\n const block = blocks.find((b) => b.label === blockLabel);\n if (!deps.allowSensitivity(block?.sensitivity)) return [];\n }\n return [\n {\n type: 'text',\n text: `<memory_blocks anchor=\"post-compaction\">\\n <block label=\"${escapeXml(blockLabel)}\">\\n ${escapeXml(value)}\\n </block>\\n</memory_blocks>`,\n },\n ];\n },\n };\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n"],"mappings":";;;;;;AAiBA,SAAgB,qBACd,UAA4C,EAAE,EACrB;CACzB,MAAM,aAAa,QAAQ,cAAc;AACzC,QAAO;EACL,IAAI;EACJ,MAAM,eAAe,MAAwD;GAC3E,MAAM,QAAQ,MAAM,KAAK,OAAO,QAAQ,KAAK,KAAK,OAAO,WAAW;AACpE,OAAI,UAAU,QAAQ,MAAM,WAAW,EAAG,QAAO,EAAE;AAQnD,OAAI,KAAK,qBAAqB,QAAW;IAEvC,MAAM,SADS,MAAM,KAAK,OAAO,QAAQ,KAAK,KAAK,MAAM,EACpC,MAAM,MAAM,EAAE,UAAU,WAAW;AACxD,QAAI,CAAC,KAAK,iBAAiB,OAAO,YAAY,CAAE,QAAO,EAAE;;AAE3D,UAAO,CACL;IACE,MAAM;IACN,MAAM,6DAA6D,UAAU,WAAW,CAAC,UAAU,UAAU,MAAM,CAAC;IACrH,CACF;;EAEJ;;AAGH,SAAS,UAAU,OAAuB;AACxC,QAAO,MACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS"}
|
|
@@ -19,6 +19,7 @@ function reanchorPinnedFacts(options) {
|
|
|
19
19
|
for (const id of ids) {
|
|
20
20
|
const fact = await deps.memory.semantic.get(deps.scope, id).catch(() => null);
|
|
21
21
|
if (fact === null || fact.deletedAt !== void 0) continue;
|
|
22
|
+
if (deps.allowSensitivity !== void 0 && !deps.allowSensitivity(fact.sensitivity)) continue;
|
|
22
23
|
const tagBlob = fact.tags !== void 0 ? ` tags="${escapeXml((fact.tags ?? []).join(","))}"` : "";
|
|
23
24
|
const fragment = ` <fact id="${escapeXml(fact.id)}"${tagBlob}>${escapeXml(fact.text)}</fact>`;
|
|
24
25
|
const fragmentTokens = await counter.countText(fragment);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reanchor-pinned-facts.js","names":[],"sources":["../../../../src/context-engine/compaction/hooks/reanchor-pinned-facts.ts"],"sourcesContent":["/**\n * `reanchorPinnedFacts({ pinnedFactIds, maxTokens })`
|
|
1
|
+
{"version":3,"file":"reanchor-pinned-facts.js","names":[],"sources":["../../../../src/context-engine/compaction/hooks/reanchor-pinned-facts.ts"],"sourcesContent":["/**\n * `reanchorPinnedFacts({ pinnedFactIds, maxTokens })` - built-in\n * post-compaction hook. Looks up the explicitly-pinned fact IDs\n * via `memory.semantic.get(...)` and returns them as a\n * system-content fragment with their tags.\n *\n * @packageDocumentation\n */\n\nimport type { MessageContent } from '@graphorin/core';\nimport { type ContextTokenCounter, HEURISTIC_TOKEN_COUNTER } from '../../token-counter.js';\nimport type { HookDeps, NamedPostCompactionHook } from './types.js';\n\n/**\n * Build a `reanchorPinnedFacts` hook.\n *\n * @stable\n */\nexport function reanchorPinnedFacts(options: {\n readonly pinnedFactIds: ReadonlyArray<string>;\n readonly maxTokens?: number;\n readonly tokenCounter?: ContextTokenCounter;\n}): NamedPostCompactionHook {\n const ids = options.pinnedFactIds;\n const maxTokens = Math.max(0, options.maxTokens ?? 2000);\n const counter = options.tokenCounter ?? HEURISTIC_TOKEN_COUNTER;\n return {\n id: 'reanchorPinnedFacts',\n async resolveContent(deps: HookDeps): Promise<ReadonlyArray<MessageContent>> {\n if (ids.length === 0) return [];\n const lines = ['<memory_pinned_facts anchor=\"post-compaction\">'];\n let runningTokens = 0;\n for (const id of ids) {\n const fact = await deps.memory.semantic.get(deps.scope, id).catch(() => null);\n if (fact === null || fact.deletedAt !== undefined) continue;\n // context-engine-02: pinning is not a privacy override - a\n // secret-tier fact the D2 filter withholds from the assembled\n // prompt must not re-enter via the post-compaction splice.\n if (deps.allowSensitivity !== undefined && !deps.allowSensitivity(fact.sensitivity)) {\n continue;\n }\n const tagBlob =\n fact.tags !== undefined ? ` tags=\"${escapeXml((fact.tags ?? []).join(','))}\"` : '';\n const fragment = ` <fact id=\"${escapeXml(fact.id)}\"${tagBlob}>${escapeXml(fact.text)}</fact>`;\n const fragmentTokens = await counter.countText(fragment);\n if (maxTokens > 0 && runningTokens + fragmentTokens > maxTokens) {\n lines.push(' <!-- additional pinned facts truncated to fit budget -->');\n break;\n }\n runningTokens += fragmentTokens;\n lines.push(fragment);\n }\n if (lines.length === 1) return [];\n lines.push('</memory_pinned_facts>');\n return [{ type: 'text', text: lines.join('\\n') }];\n },\n };\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n"],"mappings":";;;;;;;;AAkBA,SAAgB,oBAAoB,SAIR;CAC1B,MAAM,MAAM,QAAQ;CACpB,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,IAAK;CACxD,MAAM,UAAU,QAAQ,gBAAgB;AACxC,QAAO;EACL,IAAI;EACJ,MAAM,eAAe,MAAwD;AAC3E,OAAI,IAAI,WAAW,EAAG,QAAO,EAAE;GAC/B,MAAM,QAAQ,CAAC,mDAAiD;GAChE,IAAI,gBAAgB;AACpB,QAAK,MAAM,MAAM,KAAK;IACpB,MAAM,OAAO,MAAM,KAAK,OAAO,SAAS,IAAI,KAAK,OAAO,GAAG,CAAC,YAAY,KAAK;AAC7E,QAAI,SAAS,QAAQ,KAAK,cAAc,OAAW;AAInD,QAAI,KAAK,qBAAqB,UAAa,CAAC,KAAK,iBAAiB,KAAK,YAAY,CACjF;IAEF,MAAM,UACJ,KAAK,SAAS,SAAY,UAAU,WAAW,KAAK,QAAQ,EAAE,EAAE,KAAK,IAAI,CAAC,CAAC,KAAK;IAClF,MAAM,WAAW,eAAe,UAAU,KAAK,GAAG,CAAC,GAAG,QAAQ,GAAG,UAAU,KAAK,KAAK,CAAC;IACtF,MAAM,iBAAiB,MAAM,QAAQ,UAAU,SAAS;AACxD,QAAI,YAAY,KAAK,gBAAgB,iBAAiB,WAAW;AAC/D,WAAM,KAAK,6DAA6D;AACxE;;AAEF,qBAAiB;AACjB,UAAM,KAAK,SAAS;;AAEtB,OAAI,MAAM,WAAW,EAAG,QAAO,EAAE;AACjC,SAAM,KAAK,yBAAyB;AACpC,UAAO,CAAC;IAAE,MAAM;IAAQ,MAAM,MAAM,KAAK,KAAK;IAAE,CAAC;;EAEpD;;AAGH,SAAS,UAAU,OAAuB;AACxC,QAAO,MACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS"}
|
|
@@ -11,7 +11,8 @@ function reanchorProjectRules(options = {}) {
|
|
|
11
11
|
async resolveContent(deps) {
|
|
12
12
|
const procedural = deps.procedural ?? {};
|
|
13
13
|
const rules = await deps.memory.procedural.activate(deps.scope, procedural);
|
|
14
|
-
const
|
|
14
|
+
const visible = deps.allowSensitivity === void 0 ? rules : rules.filter((rule) => deps.allowSensitivity?.(rule.sensitivity) === true);
|
|
15
|
+
const filtered = allowlist === null ? visible : visible.filter((rule) => (rule.tags ?? []).some((tag) => allowlist.has(tag)));
|
|
15
16
|
if (filtered.length === 0) return [];
|
|
16
17
|
const lines = ["<memory_rules anchor=\"post-compaction\">"];
|
|
17
18
|
for (const rule of filtered) lines.push(` <rule priority="${rule.priority}" tags="${escapeXml((rule.tags ?? []).join(","))}">${escapeXml(rule.text)}</rule>`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reanchor-project-rules.js","names":[],"sources":["../../../../src/context-engine/compaction/hooks/reanchor-project-rules.ts"],"sourcesContent":["/**\n * `reanchorProjectRules({ ruleTagsAllowlist? })`
|
|
1
|
+
{"version":3,"file":"reanchor-project-rules.js","names":[],"sources":["../../../../src/context-engine/compaction/hooks/reanchor-project-rules.ts"],"sourcesContent":["/**\n * `reanchorProjectRules({ ruleTagsAllowlist? })` - built-in\n * post-compaction hook. Queries `memory.procedural.activate(...)`\n * and returns the active rule set as a system-content fragment for\n * re-injection into the trimmed buffer.\n *\n * @packageDocumentation\n */\n\nimport type { MessageContent } from '@graphorin/core';\nimport type { HookDeps, NamedPostCompactionHook } from './types.js';\n\n/**\n * Build a `reanchorProjectRules` hook.\n *\n * @stable\n */\nexport function reanchorProjectRules(\n options: { readonly ruleTagsAllowlist?: ReadonlyArray<string> } = {},\n): NamedPostCompactionHook {\n const allowlist =\n options.ruleTagsAllowlist !== undefined ? new Set(options.ruleTagsAllowlist) : null;\n return {\n id: 'reanchorProjectRules',\n async resolveContent(deps: HookDeps): Promise<ReadonlyArray<MessageContent>> {\n const procedural = deps.procedural ?? {};\n const rules = await deps.memory.procedural.activate(deps.scope, procedural);\n // context-engine-02: apply the same D2 privacy decision `assemble()`\n // applies to active rules - the hook's output ships to the provider.\n const visible =\n deps.allowSensitivity === undefined\n ? rules\n : rules.filter((rule) => deps.allowSensitivity?.(rule.sensitivity) === true);\n const filtered =\n allowlist === null\n ? visible\n : visible.filter((rule) => (rule.tags ?? []).some((tag) => allowlist.has(tag)));\n if (filtered.length === 0) return [];\n const lines = ['<memory_rules anchor=\"post-compaction\">'];\n for (const rule of filtered) {\n lines.push(\n ` <rule priority=\"${rule.priority}\" tags=\"${escapeXml((rule.tags ?? []).join(','))}\">${escapeXml(rule.text)}</rule>`,\n );\n }\n lines.push('</memory_rules>');\n return [{ type: 'text', text: lines.join('\\n') }];\n },\n };\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n"],"mappings":";;;;;;AAiBA,SAAgB,qBACd,UAAkE,EAAE,EAC3C;CACzB,MAAM,YACJ,QAAQ,sBAAsB,SAAY,IAAI,IAAI,QAAQ,kBAAkB,GAAG;AACjF,QAAO;EACL,IAAI;EACJ,MAAM,eAAe,MAAwD;GAC3E,MAAM,aAAa,KAAK,cAAc,EAAE;GACxC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,SAAS,KAAK,OAAO,WAAW;GAG3E,MAAM,UACJ,KAAK,qBAAqB,SACtB,QACA,MAAM,QAAQ,SAAS,KAAK,mBAAmB,KAAK,YAAY,KAAK,KAAK;GAChF,MAAM,WACJ,cAAc,OACV,UACA,QAAQ,QAAQ,UAAU,KAAK,QAAQ,EAAE,EAAE,MAAM,QAAQ,UAAU,IAAI,IAAI,CAAC,CAAC;AACnF,OAAI,SAAS,WAAW,EAAG,QAAO,EAAE;GACpC,MAAM,QAAQ,CAAC,4CAA0C;AACzD,QAAK,MAAM,QAAQ,SACjB,OAAM,KACJ,qBAAqB,KAAK,SAAS,UAAU,WAAW,KAAK,QAAQ,EAAE,EAAE,KAAK,IAAI,CAAC,CAAC,IAAI,UAAU,KAAK,KAAK,CAAC,SAC9G;AAEH,SAAM,KAAK,kBAAkB;AAC7B,UAAO,CAAC;IAAE,MAAM;IAAQ,MAAM,MAAM,KAAK,KAAK;IAAE,CAAC;;EAEpD;;AAGH,SAAS,UAAU,OAAuB;AACxC,QAAO,MACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NamedPostCompactionHook } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/context-engine/compaction/hooks/reanchor-recent-results.d.ts
|
|
4
|
+
|
|
5
|
+
/** Options for {@link reanchorRecentResults}. */
|
|
6
|
+
interface ReanchorRecentResultsOptions {
|
|
7
|
+
/** Most-recent distinct handles re-anchored. Default `3`. */
|
|
8
|
+
readonly maxResults?: number;
|
|
9
|
+
/** Character budget for the whole injected block. Default `4000`. */
|
|
10
|
+
readonly maxChars?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Optional preview resolver - wire it to the runtime's result reader
|
|
13
|
+
* (e.g. an adapter over the agent's spill reader). Returns the preview
|
|
14
|
+
* text or `null` when the handle cannot be read; failures are treated
|
|
15
|
+
* as `null`. Without it the hook lists the handles alone, which is
|
|
16
|
+
* still enough for the model to `read_result` them on demand.
|
|
17
|
+
*/
|
|
18
|
+
readonly readPreview?: (uri: string) => Promise<string | null>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build a `reanchorRecentResults` hook (C4).
|
|
22
|
+
*
|
|
23
|
+
* @stable
|
|
24
|
+
*/
|
|
25
|
+
declare function reanchorRecentResults(options?: ReanchorRecentResultsOptions): NamedPostCompactionHook;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { ReanchorRecentResultsOptions, reanchorRecentResults };
|
|
28
|
+
//# sourceMappingURL=reanchor-recent-results.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reanchor-recent-results.d.ts","names":[],"sources":["../../../../src/context-engine/compaction/hooks/reanchor-recent-results.ts"],"sourcesContent":[],"mappings":";;;;;UA0BiB,4BAAA;;;;;;;;;;;;0CAYyB;;;;;;;iBAQ1B,qBAAA,WACL,+BACR"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region src/context-engine/compaction/hooks/reanchor-recent-results.ts
|
|
2
|
+
/** The agent's inline spill hint: `Call read_result with handle "<uri>"`. */
|
|
3
|
+
const SPILL_HINT_RE = /read_result with handle "([^"]+)"/g;
|
|
4
|
+
/** The clearing tier's placeholder: `via read_result handle: <id>`. */
|
|
5
|
+
const CLEARED_HANDLE_RE = /via read_result handle: ([^\s\]·]+)/g;
|
|
6
|
+
/**
|
|
7
|
+
* Build a `reanchorRecentResults` hook (C4).
|
|
8
|
+
*
|
|
9
|
+
* @stable
|
|
10
|
+
*/
|
|
11
|
+
function reanchorRecentResults(options = {}) {
|
|
12
|
+
const maxResults = Math.max(1, options.maxResults ?? 3);
|
|
13
|
+
const maxChars = Math.max(0, options.maxChars ?? 4e3);
|
|
14
|
+
return {
|
|
15
|
+
id: "reanchorRecentResults",
|
|
16
|
+
async resolveContent(_deps, ctx) {
|
|
17
|
+
const dropped = ctx?.droppedMessages;
|
|
18
|
+
if (dropped === void 0 || dropped.length === 0) return [];
|
|
19
|
+
const handles = [];
|
|
20
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21
|
+
for (let i = dropped.length - 1; i >= 0 && handles.length < maxResults; i -= 1) {
|
|
22
|
+
const msg = dropped[i];
|
|
23
|
+
if (msg?.role !== "tool" || typeof msg.content !== "string") continue;
|
|
24
|
+
for (const re of [SPILL_HINT_RE, CLEARED_HANDLE_RE]) {
|
|
25
|
+
re.lastIndex = 0;
|
|
26
|
+
let match = re.exec(msg.content);
|
|
27
|
+
while (match !== null && handles.length < maxResults) {
|
|
28
|
+
const uri = match[1];
|
|
29
|
+
if (uri !== void 0 && !seen.has(uri)) {
|
|
30
|
+
seen.add(uri);
|
|
31
|
+
handles.push(uri);
|
|
32
|
+
}
|
|
33
|
+
match = re.exec(msg.content);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (handles.length === 0) return [];
|
|
38
|
+
const lines = ["<recent_results anchor=\"post-compaction\">"];
|
|
39
|
+
let used = lines[0]?.length ?? 0;
|
|
40
|
+
for (const uri of handles) {
|
|
41
|
+
let entry = ` <result handle="${escapeXml(uri)}" note="re-read via read_result" />`;
|
|
42
|
+
if (options.readPreview !== void 0) {
|
|
43
|
+
const preview = await options.readPreview(uri).catch(() => null);
|
|
44
|
+
if (preview !== null && preview.length > 0) {
|
|
45
|
+
const remaining = maxChars - used - entry.length;
|
|
46
|
+
const bounded = preview.slice(0, Math.max(0, Math.min(preview.length, remaining)));
|
|
47
|
+
if (bounded.length > 0) entry = ` <result handle="${escapeXml(uri)}">${escapeXml(bounded)}</result>`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (maxChars > 0 && used + entry.length > maxChars) {
|
|
51
|
+
lines.push(" <!-- additional recent results truncated to fit budget -->");
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
used += entry.length;
|
|
55
|
+
lines.push(entry);
|
|
56
|
+
}
|
|
57
|
+
lines.push("</recent_results>");
|
|
58
|
+
return [{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: lines.join("\n")
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function escapeXml(value) {
|
|
66
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { reanchorRecentResults };
|
|
71
|
+
//# sourceMappingURL=reanchor-recent-results.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reanchor-recent-results.js","names":["handles: string[]"],"sources":["../../../../src/context-engine/compaction/hooks/reanchor-recent-results.ts"],"sourcesContent":["/**\n * `reanchorRecentResults({ ... })` - C4 post-compaction hook.\n *\n * A compaction routinely summarizes away tool results the model was\n * actively working from; the summary paraphrases them and the handles\n * vanish. This hook scans the DROPPED messages for result handles (the\n * agent's spill hint and the clearing tier's placeholder both embed\n * them), keeps the most recent `maxResults`, optionally re-reads a\n * bounded preview for each via the caller-supplied `readPreview`, and\n * re-injects a `<recent_results>` block under a character budget so the\n * model can pick its working set back up via `read_result` instead of\n * re-running tools. (Claude Code re-reads recently-accessed files after\n * compaction for the same reason.)\n *\n * @packageDocumentation\n */\n\nimport type { MessageContent } from '@graphorin/core';\nimport type { HookDeps, NamedPostCompactionHook } from './types.js';\n\n/** The agent's inline spill hint: `Call read_result with handle \"<uri>\"`. */\nconst SPILL_HINT_RE = /read_result with handle \"([^\"]+)\"/g;\n/** The clearing tier's placeholder: `via read_result handle: <id>`. */\nconst CLEARED_HANDLE_RE = /via read_result handle: ([^\\s\\]·]+)/g;\n\n/** Options for {@link reanchorRecentResults}. */\nexport interface ReanchorRecentResultsOptions {\n /** Most-recent distinct handles re-anchored. Default `3`. */\n readonly maxResults?: number;\n /** Character budget for the whole injected block. Default `4000`. */\n readonly maxChars?: number;\n /**\n * Optional preview resolver - wire it to the runtime's result reader\n * (e.g. an adapter over the agent's spill reader). Returns the preview\n * text or `null` when the handle cannot be read; failures are treated\n * as `null`. Without it the hook lists the handles alone, which is\n * still enough for the model to `read_result` them on demand.\n */\n readonly readPreview?: (uri: string) => Promise<string | null>;\n}\n\n/**\n * Build a `reanchorRecentResults` hook (C4).\n *\n * @stable\n */\nexport function reanchorRecentResults(\n options: ReanchorRecentResultsOptions = {},\n): NamedPostCompactionHook {\n const maxResults = Math.max(1, options.maxResults ?? 3);\n const maxChars = Math.max(0, options.maxChars ?? 4000);\n return {\n id: 'reanchorRecentResults',\n async resolveContent(\n _deps: HookDeps,\n ctx?: import('../types.js').PostCompactionHookContext,\n ): Promise<ReadonlyArray<MessageContent>> {\n const dropped = ctx?.droppedMessages;\n if (dropped === undefined || dropped.length === 0) return [];\n\n // Collect handles newest-first (scan the dropped slice backwards);\n // dedupe on the handle string.\n const handles: string[] = [];\n const seen = new Set<string>();\n for (let i = dropped.length - 1; i >= 0 && handles.length < maxResults; i -= 1) {\n const msg = dropped[i];\n if (msg?.role !== 'tool' || typeof msg.content !== 'string') continue;\n for (const re of [SPILL_HINT_RE, CLEARED_HANDLE_RE]) {\n re.lastIndex = 0;\n let match = re.exec(msg.content);\n while (match !== null && handles.length < maxResults) {\n const uri = match[1];\n if (uri !== undefined && !seen.has(uri)) {\n seen.add(uri);\n handles.push(uri);\n }\n match = re.exec(msg.content);\n }\n }\n }\n if (handles.length === 0) return [];\n\n const lines = ['<recent_results anchor=\"post-compaction\">'];\n let used = lines[0]?.length ?? 0;\n for (const uri of handles) {\n let entry = ` <result handle=\"${escapeXml(uri)}\" note=\"re-read via read_result\" />`;\n if (options.readPreview !== undefined) {\n const preview = await options.readPreview(uri).catch(() => null);\n if (preview !== null && preview.length > 0) {\n const remaining = maxChars - used - entry.length;\n const bounded = preview.slice(0, Math.max(0, Math.min(preview.length, remaining)));\n if (bounded.length > 0) {\n entry = ` <result handle=\"${escapeXml(uri)}\">${escapeXml(bounded)}</result>`;\n }\n }\n }\n if (maxChars > 0 && used + entry.length > maxChars) {\n lines.push(' <!-- additional recent results truncated to fit budget -->');\n break;\n }\n used += entry.length;\n lines.push(entry);\n }\n lines.push('</recent_results>');\n return [{ type: 'text', text: lines.join('\\n') }];\n },\n };\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n"],"mappings":";;AAqBA,MAAM,gBAAgB;;AAEtB,MAAM,oBAAoB;;;;;;AAuB1B,SAAgB,sBACd,UAAwC,EAAE,EACjB;CACzB,MAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,cAAc,EAAE;CACvD,MAAM,WAAW,KAAK,IAAI,GAAG,QAAQ,YAAY,IAAK;AACtD,QAAO;EACL,IAAI;EACJ,MAAM,eACJ,OACA,KACwC;GACxC,MAAM,UAAU,KAAK;AACrB,OAAI,YAAY,UAAa,QAAQ,WAAW,EAAG,QAAO,EAAE;GAI5D,MAAMA,UAAoB,EAAE;GAC5B,MAAM,uBAAO,IAAI,KAAa;AAC9B,QAAK,IAAI,IAAI,QAAQ,SAAS,GAAG,KAAK,KAAK,QAAQ,SAAS,YAAY,KAAK,GAAG;IAC9E,MAAM,MAAM,QAAQ;AACpB,QAAI,KAAK,SAAS,UAAU,OAAO,IAAI,YAAY,SAAU;AAC7D,SAAK,MAAM,MAAM,CAAC,eAAe,kBAAkB,EAAE;AACnD,QAAG,YAAY;KACf,IAAI,QAAQ,GAAG,KAAK,IAAI,QAAQ;AAChC,YAAO,UAAU,QAAQ,QAAQ,SAAS,YAAY;MACpD,MAAM,MAAM,MAAM;AAClB,UAAI,QAAQ,UAAa,CAAC,KAAK,IAAI,IAAI,EAAE;AACvC,YAAK,IAAI,IAAI;AACb,eAAQ,KAAK,IAAI;;AAEnB,cAAQ,GAAG,KAAK,IAAI,QAAQ;;;;AAIlC,OAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;GAEnC,MAAM,QAAQ,CAAC,8CAA4C;GAC3D,IAAI,OAAO,MAAM,IAAI,UAAU;AAC/B,QAAK,MAAM,OAAO,SAAS;IACzB,IAAI,QAAQ,qBAAqB,UAAU,IAAI,CAAC;AAChD,QAAI,QAAQ,gBAAgB,QAAW;KACrC,MAAM,UAAU,MAAM,QAAQ,YAAY,IAAI,CAAC,YAAY,KAAK;AAChE,SAAI,YAAY,QAAQ,QAAQ,SAAS,GAAG;MAC1C,MAAM,YAAY,WAAW,OAAO,MAAM;MAC1C,MAAM,UAAU,QAAQ,MAAM,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,QAAQ,UAAU,CAAC,CAAC;AAClF,UAAI,QAAQ,SAAS,EACnB,SAAQ,qBAAqB,UAAU,IAAI,CAAC,IAAI,UAAU,QAAQ,CAAC;;;AAIzE,QAAI,WAAW,KAAK,OAAO,MAAM,SAAS,UAAU;AAClD,WAAM,KAAK,+DAA+D;AAC1E;;AAEF,YAAQ,MAAM;AACd,UAAM,KAAK,MAAM;;AAEnB,SAAM,KAAK,oBAAoB;AAC/B,UAAO,CAAC;IAAE,MAAM;IAAQ,MAAM,MAAM,KAAK,KAAK;IAAE,CAAC;;EAEpD;;AAGH,SAAS,UAAU,OAAuB;AACxC,QAAO,MACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS"}
|