@remnic/core 9.3.760 → 9.3.762
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/dist/access-admin-ops-surface.d.ts +3 -3
- package/dist/access-admin-ops-surface.js +11 -10
- package/dist/access-boundary.d.ts +3 -3
- package/dist/access-boundary.js +12 -11
- package/dist/access-cli.js +46 -45
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +3 -3
- package/dist/access-http.js +18 -17
- package/dist/access-identity-continuity-surface.d.ts +2 -2
- package/dist/access-identity-continuity-surface.js +11 -10
- package/dist/access-lcm-surface.d.ts +3 -3
- package/dist/access-lcm-surface.js +11 -10
- package/dist/access-mcp.d.ts +3 -3
- package/dist/access-mcp.js +15 -14
- package/dist/access-observe-write-surface.d.ts +3 -3
- package/dist/access-observe-write-surface.js +11 -10
- package/dist/access-operations-batch.js +13 -12
- package/dist/access-operations.d.ts +7 -7
- package/dist/access-operations.js +14 -13
- package/dist/access-recall-surface.d.ts +3 -3
- package/dist/access-recall-surface.js +11 -10
- package/dist/access-schema.d.ts +68 -68
- package/dist/{access-service-BW-gb3DL.d.ts → access-service-CaWJP_f6.d.ts} +2 -2
- package/dist/access-service.d.ts +3 -3
- package/dist/access-service.js +11 -10
- package/dist/access-surface-catalog.d.ts +3 -3
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/bootstrap.d.ts +2 -2
- package/dist/briefing.js +4 -3
- package/dist/{catalog-DaKAFbSJ.d.ts → catalog-bflYNWsM.d.ts} +8 -4
- package/dist/causal-consolidation.js +5 -4
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-OLEBPUZ7.js → chunk-2KYXXMTE.js} +6 -6
- package/dist/{chunk-6CUUFFU7.js → chunk-2PGB63VU.js} +131 -39
- package/dist/chunk-2PGB63VU.js.map +1 -0
- package/dist/{chunk-FYJWEGYP.js → chunk-4HYJETVO.js} +2 -2
- package/dist/{chunk-APJQ6UEA.js → chunk-AGNBY3VG.js} +4 -4
- package/dist/{chunk-W3M34XT4.js → chunk-BAVVI5GP.js} +2 -2
- package/dist/{chunk-XQT5GSWU.js → chunk-BRYPUVJU.js} +3 -3
- package/dist/{chunk-2XE4F3VZ.js → chunk-DB647OZ4.js} +24 -24
- package/dist/{chunk-3JBW3GHN.js → chunk-DPZ5GJMD.js} +2 -2
- package/dist/chunk-DQC3ZZYM.js +35 -0
- package/dist/chunk-DQC3ZZYM.js.map +1 -0
- package/dist/{chunk-SMLMH7S5.js → chunk-EDC7RZCW.js} +24 -24
- package/dist/{chunk-ZFXCQPNO.js → chunk-EGGE52UU.js} +2 -2
- package/dist/chunk-EGGE52UU.js.map +1 -0
- package/dist/{chunk-IOZZG75J.js → chunk-ES3GAMSQ.js} +2 -2
- package/dist/{chunk-QUGPXPFJ.js → chunk-ESDBHQY6.js} +5 -5
- package/dist/{chunk-ILVF6TIY.js → chunk-HDFS6RWQ.js} +2 -2
- package/dist/{chunk-SN2EWIAI.js → chunk-HDYFNXVO.js} +2 -2
- package/dist/{chunk-ZEQVS6AZ.js → chunk-HMUEAKSD.js} +2 -2
- package/dist/{chunk-P26O6R3Q.js → chunk-IIURIWJG.js} +102 -12
- package/dist/chunk-IIURIWJG.js.map +1 -0
- package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
- package/dist/{chunk-QQJMVF6P.js → chunk-JU6BKZHX.js} +3 -3
- package/dist/{chunk-SLAZZXBU.js → chunk-KZWUJC7Y.js} +2 -2
- package/dist/{chunk-IZLGHKWA.js → chunk-LAJIXABF.js} +10 -10
- package/dist/{chunk-3QLPBCFG.js → chunk-NKP6II6G.js} +4 -4
- package/dist/{chunk-WIXQ3F5P.js → chunk-OQ6F3WBX.js} +2 -2
- package/dist/{chunk-YP23FQPT.js → chunk-P7W4CDX6.js} +92 -41
- package/dist/chunk-P7W4CDX6.js.map +1 -0
- package/dist/{chunk-EGQ3AVZV.js → chunk-PNFN7SIN.js} +3 -3
- package/dist/{chunk-M2AAYJVI.js → chunk-QPFO54VC.js} +4 -4
- package/dist/{chunk-7HWOEYHW.js → chunk-QWIE43BS.js} +2 -2
- package/dist/chunk-TEKTCF2V.js +118 -0
- package/dist/chunk-TEKTCF2V.js.map +1 -0
- package/dist/{chunk-QOM3F6YW.js → chunk-TIWRP5NW.js} +5 -5
- package/dist/{chunk-CXW6Y7SI.js → chunk-V5RVMULT.js} +4 -4
- package/dist/{chunk-DTSOB2YF.js → chunk-VHYFY3JI.js} +2 -2
- package/dist/{chunk-AZSSKOG6.js → chunk-WAIMHMO3.js} +3 -3
- package/dist/{chunk-PKRRBRY2.js → chunk-ZTBH2OHD.js} +2 -2
- package/dist/{cli-D551dnva.d.ts → cli-CvWpIbMD.d.ts} +2 -2
- package/dist/cli.d.ts +4 -4
- package/dist/cli.js +35 -34
- package/dist/compounding/engine.js +4 -3
- package/dist/connectors/codex-materialize-runner.js +4 -3
- package/dist/connectors/index.js +4 -3
- package/dist/entity-retrieval.js +4 -3
- package/dist/explicit-capture.d.ts +2 -2
- package/dist/{forget-OEE5OELK.js → forget-WYLR4ZFV.js} +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +48 -47
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +4 -4
- package/dist/maintenance/memory-governance.js +4 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +11 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -3
- package/dist/maintenance/rebuild-memory-projection.d.ts +22 -1
- package/dist/maintenance/rebuild-memory-projection.js +5 -4
- package/dist/mcp-memory-inspector-app.d.ts +3 -3
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +9 -8
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +5 -4
- package/dist/operator-toolkit.js +13 -12
- package/dist/orchestration/maintenance.d.ts +1 -1
- package/dist/orchestration/maintenance.js +8 -7
- package/dist/{orchestrator-BnM187cb.d.ts → orchestrator-C04Q4Zao.d.ts} +1 -1
- package/dist/orchestrator.d.ts +2 -2
- package/dist/orchestrator.js +46 -45
- package/dist/schemas.d.ts +84 -84
- package/dist/search/factory.js +2 -2
- package/dist/search/index.js +4 -4
- package/dist/semantic-consolidation.js +5 -4
- package/dist/semantic-rule-promotion.js +4 -3
- package/dist/semantic-rule-verifier.js +4 -3
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/storage-guards.d.ts +9 -0
- package/dist/storage-guards.js +11 -0
- package/dist/storage-guards.js.map +1 -0
- package/dist/storage.js +3 -2
- package/dist/transfer/import-sqlite.js +2 -2
- package/dist/transfer/types.d.ts +66 -66
- package/dist/verified-recall.js +4 -3
- package/package.json +2 -2
- package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +59 -8
- package/src/maintenance/rebuild-memory-projection.ts +149 -9
- package/src/namespaces/catalog.test.ts +133 -1
- package/src/namespaces/catalog.ts +163 -48
- package/src/namespaces/identity.ts +2 -2
- package/src/storage/secure-line-reader.ts +79 -0
- package/src/storage-guards.ts +30 -0
- package/src/storage-memory-lifecycle-ledger.test.ts +130 -0
- package/src/storage.ts +24 -41
- package/dist/chunk-6CUUFFU7.js.map +0 -1
- package/dist/chunk-LLVKDFNU.js +0 -77
- package/dist/chunk-LLVKDFNU.js.map +0 -1
- package/dist/chunk-P26O6R3Q.js.map +0 -1
- package/dist/chunk-YP23FQPT.js.map +0 -1
- package/dist/chunk-ZFXCQPNO.js.map +0 -1
- /package/dist/{chunk-OLEBPUZ7.js.map → chunk-2KYXXMTE.js.map} +0 -0
- /package/dist/{chunk-FYJWEGYP.js.map → chunk-4HYJETVO.js.map} +0 -0
- /package/dist/{chunk-APJQ6UEA.js.map → chunk-AGNBY3VG.js.map} +0 -0
- /package/dist/{chunk-W3M34XT4.js.map → chunk-BAVVI5GP.js.map} +0 -0
- /package/dist/{chunk-XQT5GSWU.js.map → chunk-BRYPUVJU.js.map} +0 -0
- /package/dist/{chunk-2XE4F3VZ.js.map → chunk-DB647OZ4.js.map} +0 -0
- /package/dist/{chunk-3JBW3GHN.js.map → chunk-DPZ5GJMD.js.map} +0 -0
- /package/dist/{chunk-SMLMH7S5.js.map → chunk-EDC7RZCW.js.map} +0 -0
- /package/dist/{chunk-IOZZG75J.js.map → chunk-ES3GAMSQ.js.map} +0 -0
- /package/dist/{chunk-QUGPXPFJ.js.map → chunk-ESDBHQY6.js.map} +0 -0
- /package/dist/{chunk-ILVF6TIY.js.map → chunk-HDFS6RWQ.js.map} +0 -0
- /package/dist/{chunk-SN2EWIAI.js.map → chunk-HDYFNXVO.js.map} +0 -0
- /package/dist/{chunk-ZEQVS6AZ.js.map → chunk-HMUEAKSD.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-QQJMVF6P.js.map → chunk-JU6BKZHX.js.map} +0 -0
- /package/dist/{chunk-SLAZZXBU.js.map → chunk-KZWUJC7Y.js.map} +0 -0
- /package/dist/{chunk-IZLGHKWA.js.map → chunk-LAJIXABF.js.map} +0 -0
- /package/dist/{chunk-3QLPBCFG.js.map → chunk-NKP6II6G.js.map} +0 -0
- /package/dist/{chunk-WIXQ3F5P.js.map → chunk-OQ6F3WBX.js.map} +0 -0
- /package/dist/{chunk-EGQ3AVZV.js.map → chunk-PNFN7SIN.js.map} +0 -0
- /package/dist/{chunk-M2AAYJVI.js.map → chunk-QPFO54VC.js.map} +0 -0
- /package/dist/{chunk-7HWOEYHW.js.map → chunk-QWIE43BS.js.map} +0 -0
- /package/dist/{chunk-QOM3F6YW.js.map → chunk-TIWRP5NW.js.map} +0 -0
- /package/dist/{chunk-CXW6Y7SI.js.map → chunk-V5RVMULT.js.map} +0 -0
- /package/dist/{chunk-DTSOB2YF.js.map → chunk-VHYFY3JI.js.map} +0 -0
- /package/dist/{chunk-AZSSKOG6.js.map → chunk-WAIMHMO3.js.map} +0 -0
- /package/dist/{chunk-PKRRBRY2.js.map → chunk-ZTBH2OHD.js.map} +0 -0
- /package/dist/{forget-OEE5OELK.js.map → forget-WYLR4ZFV.js.map} +0 -0
|
@@ -95,7 +95,10 @@ declare class NamespaceCatalog {
|
|
|
95
95
|
private readonly catalogPath;
|
|
96
96
|
private readonly rebuildLockPath;
|
|
97
97
|
private readonly criticalSection;
|
|
98
|
+
private compactedCache;
|
|
98
99
|
protected onTouchCriticalSectionForTest?: () => Promise<void>;
|
|
100
|
+
protected onCatalogReadForTest?: () => void;
|
|
101
|
+
protected onAfterCatalogAppendForTest?: () => Promise<void>;
|
|
99
102
|
protected onRebuildBeforeRenameForTest?: () => Promise<void>;
|
|
100
103
|
protected onRebuildAfterScanForTest?: () => Promise<void>;
|
|
101
104
|
protected onBeforeBreakStaleUnlinkForTest?: () => Promise<void>;
|
|
@@ -394,12 +397,11 @@ declare class NamespaceCatalog {
|
|
|
394
397
|
private mergeForRebuild;
|
|
395
398
|
/** Load the JSONL log and fold it into current state (last-record-wins). */
|
|
396
399
|
private loadCompacted;
|
|
400
|
+
private cloneCompactedRecords;
|
|
397
401
|
/**
|
|
398
402
|
* Serialize an arbitrary read-modify-write critical section through the single
|
|
399
|
-
* per-instance chain. Every catalog mutation
|
|
400
|
-
*
|
|
401
|
-
* reads the latest persisted state before appending, and a rebuild rewrite
|
|
402
|
-
* cannot interleave with a touch's append.
|
|
403
|
+
* per-instance chain. Every catalog mutation and cached catalog read runs
|
|
404
|
+
* through this queue, so cache validation and updates cannot race in-process.
|
|
403
405
|
*
|
|
404
406
|
* Issue #1524 adoption: delegates to the shared `MutationSerializer` (stored
|
|
405
407
|
* as `criticalSection`). The util owns the rejection-recovery invariant
|
|
@@ -425,6 +427,8 @@ declare class NamespaceCatalog {
|
|
|
425
427
|
* chain from within a held turn would deadlock.
|
|
426
428
|
*/
|
|
427
429
|
private rewriteUnchained;
|
|
430
|
+
private refreshCacheAfterAppend;
|
|
431
|
+
private refreshCacheIdentity;
|
|
428
432
|
}
|
|
429
433
|
|
|
430
434
|
export { NamespaceCatalog as N, type NamespaceRecord as a, type NamespaceKind as b, type NamespaceCatalogFilter as c, type NamespaceCatalogRebuildResult as d, type NamespaceCatalogSkippedRoot as e, type NamespaceDiscoverySource as f, type NamespaceTouchMetadata as g };
|
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
} from "./chunk-ORAK3LNZ.js";
|
|
5
5
|
import {
|
|
6
6
|
buildExtensionsBlockForConsolidation
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HDFS6RWQ.js";
|
|
8
8
|
import {
|
|
9
9
|
runPostConsolidationMaterialize
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-OQ6F3WBX.js";
|
|
11
11
|
import "./chunk-UUH4YQOF.js";
|
|
12
12
|
import "./chunk-JLNBQWZ2.js";
|
|
13
13
|
import "./chunk-3UXOZBHV.js";
|
|
@@ -15,16 +15,17 @@ import "./chunk-7FL4CNPV.js";
|
|
|
15
15
|
import {
|
|
16
16
|
resolveCausalTrajectoryStoreDir
|
|
17
17
|
} from "./chunk-SQNED75R.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-P7W4CDX6.js";
|
|
19
19
|
import "./chunk-M7XQSUBB.js";
|
|
20
20
|
import "./chunk-L2B3EE7B.js";
|
|
21
21
|
import "./chunk-5UZXUTVO.js";
|
|
22
22
|
import "./chunk-5GPPACXK.js";
|
|
23
|
+
import "./chunk-LTJAMRGI.js";
|
|
23
24
|
import "./chunk-TKZ3AL5V.js";
|
|
24
25
|
import "./chunk-6CIAGZ6X.js";
|
|
25
26
|
import "./chunk-J6A3CX5N.js";
|
|
27
|
+
import "./chunk-DQC3ZZYM.js";
|
|
26
28
|
import "./chunk-SVOZFLIQ.js";
|
|
27
|
-
import "./chunk-LTJAMRGI.js";
|
|
28
29
|
import "./chunk-JS2HKKOK.js";
|
|
29
30
|
import "./chunk-AWJ2FHCF.js";
|
|
30
31
|
import "./chunk-42JKGUFJ.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/causal-consolidation.ts"],"sourcesContent":["/**\n * causal-consolidation.ts — CMC Phase 2: LLM-Assisted Causal Consolidation\n *\n * Uses an LLM to analyze causal trajectory patterns across sessions.\n * The LLM receives the causal chain graph as context — connected trajectories\n * from different sessions — and identifies recurring behavioral patterns,\n * preference signals, and actionable rules.\n *\n * This is the core CMC innovation: the LLM gets cross-session causal context\n * that no other memory system provides. It can see that a user investigated\n * a bug in session 1, attempted a fix in session 2, and succeeded in session 3 —\n * and synthesize a rule or preference from that chain.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { resolveCausalTrajectoryStoreDir, type CausalTrajectoryRecord } from \"./causal-trajectory.js\";\nimport { readChainIndex, resolveChainsDir, type CausalChainIndex, type CausalEdge } from \"./causal-chain.js\";\nimport { listJsonFiles, readJsonFile } from \"./json-store.js\";\nimport { isRecord } from \"./store-contract.js\";\nimport { FallbackLlmClient, fallbackLlmRuntimeContextFromConfig } from \"./fallback-llm.js\";\nimport type { AgentPersonaModelConfig, GatewayConfig, MemoryFile, PluginConfig } from \"./types.js\";\nimport path from \"node:path\";\nimport { log } from \"./logger.js\";\nimport { runPostConsolidationMaterialize } from \"./connectors/codex-materialize-runner.js\";\nimport type { MaterializeResult, RolloutSummaryInput } from \"./connectors/codex-materialize.js\";\nimport { buildExtensionsBlockForConsolidation } from \"./semantic-consolidation.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CausalPatternCandidate {\n id: string;\n sourceType: \"causal-pattern\";\n subject: string;\n category: \"principle\" | \"rule\" | \"preference\";\n content: string;\n score: number;\n rationale: string;\n outcome: null;\n provenance: string[];\n agent: string | null;\n workflow: string | null;\n}\n\nexport interface ConsolidationConfig {\n minRecurrence: number;\n minSessions: number;\n successThreshold: number;\n}\n\nexport interface LlmConsolidationResult {\n rules: Array<{\n content: string;\n category: \"rule\" | \"principle\" | \"preference\";\n confidence: number;\n evidence: string[];\n }>;\n preferences: Array<{\n statement: string;\n confidence: number;\n evidence: string[];\n }>;\n}\n\nconst CAUSAL_RULE_CATEGORIES = new Set([\"rule\", \"principle\", \"preference\"]);\n\ninterface ConsolidationLlmClient {\n isAvailable(options?: { agentId?: string; modelChain?: AgentPersonaModelConfig }): boolean;\n chatCompletion(\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options?: { temperature?: number; maxTokens?: number; agentId?: string; modelChain?: AgentPersonaModelConfig },\n ): Promise<{ content: string } | null>;\n}\n\n// ─── Trajectory Reading ──────────────────────────────────────────────────────\n\nasync function readAllTrajectories(\n memoryDir: string,\n causalTrajectoryStoreDir?: string,\n): Promise<CausalTrajectoryRecord[]> {\n const root = resolveCausalTrajectoryStoreDir(memoryDir, causalTrajectoryStoreDir);\n const trajectoriesDir = path.join(root, \"trajectories\");\n\n const files = await listJsonFiles(trajectoriesDir).catch(() => [] as string[]);\n const results: CausalTrajectoryRecord[] = [];\n\n for (const filePath of files) {\n try {\n const raw = await readJsonFile(filePath);\n if (isRecord(raw) && typeof raw.trajectoryId === \"string\") {\n results.push(raw as unknown as CausalTrajectoryRecord);\n }\n } catch {\n // skip invalid\n }\n }\n\n return results;\n}\n\n// ─── Context Formatting ──────────────────────────────────────────────────────\n\n/**\n * Format trajectories and their causal connections as a readable context\n * for the LLM. Groups by session and shows chain connections.\n */\nfunction formatCausalContext(\n trajectories: CausalTrajectoryRecord[],\n chainIndex: CausalChainIndex,\n maxChars: number = 8000,\n): string {\n // Group trajectories by session\n const bySession = new Map<string, CausalTrajectoryRecord[]>();\n for (const t of trajectories) {\n const list = bySession.get(t.sessionKey) ?? [];\n list.push(t);\n bySession.set(t.sessionKey, list);\n }\n\n const lines: string[] = [];\n lines.push(`## Causal Trajectories (${trajectories.length} across ${bySession.size} sessions)`);\n lines.push(\"\");\n\n // Format each session's trajectories\n for (const [sessionKey, sessionTrajs] of bySession) {\n lines.push(`### Session: ${sessionKey}`);\n for (const t of sessionTrajs.slice(0, 5)) {\n const outcome = t.outcomeKind === \"success\" ? \"+\" : t.outcomeKind === \"failure\" ? \"-\" : \"~\";\n lines.push(`[${outcome}] Goal: ${t.goal}`);\n lines.push(` Action: ${t.actionSummary}`);\n lines.push(` Outcome: ${t.outcomeSummary}`);\n if (t.followUpSummary) lines.push(` Follow-up: ${t.followUpSummary}`);\n if (t.entityRefs?.length) lines.push(` Entities: ${t.entityRefs.join(\", \")}`);\n }\n lines.push(\"\");\n }\n\n // Format causal chain connections\n const edgeCount = Object.keys(chainIndex.edges).length;\n if (edgeCount > 0) {\n lines.push(`## Cross-Session Causal Chains (${edgeCount} connections)`);\n lines.push(\"\");\n\n const trajectoryMap = new Map(trajectories.map((t) => [t.trajectoryId, t]));\n const shown = new Set<string>();\n\n for (const [edgeId, edge] of Object.entries(chainIndex.edges)) {\n if (shown.size >= 10) break; // limit output size\n const from = trajectoryMap.get(edge.fromTrajectoryId);\n const to = trajectoryMap.get(edge.toTrajectoryId);\n if (!from || !to) continue;\n\n lines.push(`${edge.edgeType}: \"${from.goal}\" (${from.sessionKey}) → \"${to.goal}\" (${to.sessionKey})`);\n shown.add(edgeId);\n }\n lines.push(\"\");\n }\n\n const result = lines.join(\"\\n\");\n return result.length > maxChars ? result.slice(0, maxChars) + \"\\n[truncated]\" : result;\n}\n\n// ─── LLM Consolidation ──────────────────────────────────────────────────────\n\nconst CONSOLIDATION_PROMPT = `You are analyzing a user's causal trajectory history across multiple sessions. Trajectories record what the user tried to do (goal), what they did (action), and what happened (outcome).\n\nYour job is to identify:\n1. BEHAVIORAL RULES: Recurring patterns where the same approach consistently succeeds or fails. These should be actionable guidance for future sessions.\n2. PREFERENCES: What the user cares about, prefers, or consistently chooses — even if never explicitly stated. Infer preferences from what they repeatedly do, retry until successful, or always include in their workflow.\n\nIMPORTANT:\n- Look for CROSS-SESSION patterns — things that repeat across different sessions are more significant than within-session patterns.\n- A user who retries the same goal across sessions has a strong implicit preference for that outcome.\n- Consistent action choices reveal preferences even when the user never says \"I prefer X.\"\n- Frame preferences as \"The user would prefer responses that...\" when applicable.\n\nOutput valid JSON only:\n{\n \"rules\": [\n {\"content\": \"actionable rule text\", \"category\": \"rule|principle\", \"confidence\": 0.0-1.0, \"evidence\": [\"trajectory IDs\"]}\n ],\n \"preferences\": [\n {\"statement\": \"The user would prefer...\", \"confidence\": 0.0-1.0, \"evidence\": [\"trajectory IDs\"]}\n ]\n}\n\nIf no clear patterns exist, return {\"rules\": [], \"preferences\": []}.`;\n\nasync function consolidateWithLlm(\n context: string,\n llm: ConsolidationLlmClient,\n options: { agentId?: string; modelChain?: AgentPersonaModelConfig } = {},\n): Promise<LlmConsolidationResult> {\n const response = await llm.chatCompletion(\n [\n { role: \"system\", content: CONSOLIDATION_PROMPT },\n { role: \"user\", content: context },\n ],\n { temperature: 0.2, maxTokens: 2000, agentId: options.agentId, modelChain: options.modelChain },\n );\n\n if (!response?.content) {\n return { rules: [], preferences: [] };\n }\n\n try {\n // Extract JSON from response (may have markdown code fences)\n let jsonStr = response.content.trim();\n const fenceMatch = jsonStr.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n if (fenceMatch) jsonStr = fenceMatch[1];\n\n const parsed = JSON.parse(jsonStr);\n const rawRules: unknown[] = Array.isArray(parsed.rules) ? parsed.rules : [];\n const rawPreferences: unknown[] = Array.isArray(parsed.preferences) ? parsed.preferences : [];\n return {\n rules: rawRules\n .map(parseLlmRule)\n .filter((rule): rule is LlmConsolidationResult[\"rules\"][number] => !!rule),\n preferences: rawPreferences\n .map(parseLlmPreference)\n .filter((pref): pref is LlmConsolidationResult[\"preferences\"][number] => !!pref),\n };\n } catch {\n log.warn(\"[cmc] failed to parse LLM consolidation response\");\n return { rules: [], preferences: [] };\n }\n}\n\n// ─── Candidate Generation ────────────────────────────────────────────────────\n\nfunction stablePatternId(content: string): string {\n const digest = createHash(\"sha256\")\n .update(`causal-pattern\\0${content}`)\n .digest(\"hex\")\n .slice(0, 16);\n return `causal-pattern:${digest}`;\n}\n\nfunction parseConfidence(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return undefined;\n return Math.max(0, Math.min(1, value));\n}\n\nfunction parseEvidence(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\" && item.trim().length > 0);\n}\n\nfunction parseLlmRule(raw: unknown): LlmConsolidationResult[\"rules\"][number] | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const r = raw as Record<string, unknown>;\n const content = typeof r.content === \"string\" ? r.content.trim() : \"\";\n const category = typeof r.category === \"string\" ? r.category : \"\";\n const confidence = parseConfidence(r.confidence);\n if (content.length <= 5 || !CAUSAL_RULE_CATEGORIES.has(category) || confidence === undefined) {\n return undefined;\n }\n return {\n content,\n category: category as \"rule\" | \"principle\" | \"preference\",\n confidence,\n evidence: parseEvidence(r.evidence),\n };\n}\n\nfunction parseLlmPreference(raw: unknown): LlmConsolidationResult[\"preferences\"][number] | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const p = raw as Record<string, unknown>;\n const statement = typeof p.statement === \"string\" ? p.statement.trim() : \"\";\n const confidence = parseConfidence(p.confidence);\n if (statement.length <= 5 || confidence === undefined) return undefined;\n return {\n statement,\n confidence,\n evidence: parseEvidence(p.evidence),\n };\n}\n\nfunction llmResultToCandidates(result: LlmConsolidationResult): CausalPatternCandidate[] {\n const candidates: CausalPatternCandidate[] = [];\n\n for (const rule of result.rules) {\n const category =\n rule.category === \"principle\" || rule.category === \"preference\"\n ? rule.category\n : \"rule\";\n candidates.push({\n id: stablePatternId(rule.content),\n sourceType: \"causal-pattern\",\n subject: rule.content.slice(0, 80),\n category,\n content: rule.content,\n score: Math.min(1, rule.confidence ?? 0.7),\n rationale: \"LLM-identified causal pattern from cross-session trajectory analysis\",\n outcome: null,\n provenance: (rule.evidence ?? []).slice(0, 5),\n agent: null,\n workflow: null,\n });\n }\n\n for (const preference of result.preferences) {\n candidates.push({\n id: stablePatternId(preference.statement),\n sourceType: \"causal-pattern\",\n subject: preference.statement.slice(0, 80),\n category: \"preference\",\n content: preference.statement,\n score: Math.min(1, preference.confidence ?? 0.7),\n rationale: \"LLM-identified preference from cross-session trajectory analysis\",\n outcome: null,\n provenance: (preference.evidence ?? []).slice(0, 5),\n agent: null,\n workflow: null,\n });\n }\n\n return candidates;\n}\n\nfunction normalizePositiveInteger(value: number, fallback: number): number {\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;\n}\n\nfunction normalizeSuccessThreshold(value: number): number {\n if (!Number.isFinite(value) || value <= 0) return 0;\n return Math.min(1, value);\n}\n\nfunction trajectorySuccessScore(trajectory: CausalTrajectoryRecord): number {\n if (trajectory.outcomeKind === \"success\") return 1;\n if (trajectory.outcomeKind === \"partial\") return 0.5;\n return 0;\n}\n\nfunction averageSuccessScore(trajectories: CausalTrajectoryRecord[]): number {\n if (trajectories.length === 0) return 0;\n const total = trajectories.reduce((sum, trajectory) => sum + trajectorySuccessScore(trajectory), 0);\n return total / trajectories.length;\n}\n\nfunction passesConsolidationGates(\n trajectories: CausalTrajectoryRecord[],\n config: ConsolidationConfig,\n): boolean {\n const minRecurrence = normalizePositiveInteger(config.minRecurrence, 1);\n if (trajectories.length < minRecurrence) return false;\n\n const minSessions = normalizePositiveInteger(config.minSessions, 1);\n const sessionCount = new Set(trajectories.map((trajectory) => trajectory.sessionKey)).size;\n if (sessionCount < minSessions) return false;\n\n const successThreshold = normalizeSuccessThreshold(config.successThreshold);\n if (successThreshold > 0 && averageSuccessScore(trajectories) < successThreshold) {\n return false;\n }\n\n return true;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Run LLM-assisted consolidation: read trajectories, format causal context,\n * ask LLM to identify patterns and preferences.\n */\nexport async function deriveCausalPromotionCandidates(options: {\n memoryDir: string;\n causalTrajectoryStoreDir?: string;\n config: ConsolidationConfig;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n pluginConfig?: PluginConfig;\n llmClient?: ConsolidationLlmClient;\n}): Promise<CausalPatternCandidate[]> {\n try {\n const trajectories = await readAllTrajectories(options.memoryDir, options.causalTrajectoryStoreDir);\n if (!passesConsolidationGates(trajectories, options.config)) return [];\n\n const chainsDir = resolveChainsDir(options.memoryDir, options.causalTrajectoryStoreDir);\n const chainIndex = await readChainIndex(chainsDir);\n\n // Format the causal context for the LLM\n let context = formatCausalContext(trajectories, chainIndex);\n\n // Append memory extensions block if available (#382)\n if (options.pluginConfig) {\n const extBlock = await buildExtensionsBlockForConsolidation(options.pluginConfig);\n if (extBlock.length > 0) {\n context += \"\\n\\n\" + extBlock;\n }\n }\n\n // If no LLM available, fall back to empty (no deterministic fallback)\n const llm = options.llmClient ?? new FallbackLlmClient(\n options.gatewayConfig,\n options.pluginConfig\n ? fallbackLlmRuntimeContextFromConfig(options.pluginConfig, {\n workspaceDir: options.workspaceDir ?? options.pluginConfig.workspaceDir,\n })\n : { workspaceDir: options.workspaceDir },\n );\n const llmOptions = { agentId: options.gatewayAgentId, modelChain: options.modelChain };\n if (!llm.isAvailable(llmOptions)) {\n log.debug(\"[cmc] no LLM available for consolidation — skipping\");\n return [];\n }\n\n // Call LLM for pattern analysis\n const result = await consolidateWithLlm(context, llm, llmOptions);\n const candidates = llmResultToCandidates(result);\n\n log.debug(`[cmc] LLM consolidation produced ${candidates.length} rule(s) and ${result.preferences.length} preference(s)`);\n return candidates;\n } catch (error) {\n log.warn(`[cmc] consolidation failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);\n return [];\n }\n}\n\n/**\n * Get LLM-synthesized preferences from causal trajectory analysis.\n * Returns formatted preference statements for recall injection.\n */\nexport async function synthesizeCausalPreferencesViaLlm(options: {\n memoryDir: string;\n causalTrajectoryStoreDir?: string;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n minTrajectories?: number;\n}): Promise<string | null> {\n try {\n const trajectories = await readAllTrajectories(options.memoryDir, options.causalTrajectoryStoreDir);\n if (trajectories.length < (options.minTrajectories ?? 2)) return null;\n\n const chainsDir = resolveChainsDir(options.memoryDir, options.causalTrajectoryStoreDir);\n const chainIndex = await readChainIndex(chainsDir);\n const context = formatCausalContext(trajectories, chainIndex);\n\n const llm = new FallbackLlmClient(options.gatewayConfig, {\n workspaceDir: options.workspaceDir,\n });\n const llmOptions = { agentId: options.gatewayAgentId, modelChain: options.modelChain };\n if (!llm.isAvailable(llmOptions)) return null;\n\n const result = await consolidateWithLlm(context, llm, llmOptions);\n if (result.preferences.length === 0 && result.rules.length === 0) return null;\n\n const lines: string[] = [\"## Behavioral Insights (from Causal Chain Analysis)\", \"\"];\n\n for (const pref of result.preferences) {\n lines.push(`- ${pref.statement}`);\n }\n\n for (const rule of result.rules) {\n lines.push(`- ${rule.content}`);\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n } catch (error) {\n log.warn(`[cmc] preference synthesis failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);\n return null;\n }\n}\n\n/**\n * Optional post-consolidation hook — materializes Codex-native memory artifacts\n * after a causal consolidation run. Guarded by `codexMaterializeMemories` and\n * `codexMaterializeOnConsolidation`. Returns `null` when disabled or when the\n * sentinel is missing (honors user hand-edits).\n *\n * Split from the orchestrator-owned flow so #378 avoids touching\n * orchestrator.ts while Wave 1 edits are in flight.\n */\nexport async function materializeAfterCausalConsolidation(options: {\n config: PluginConfig;\n namespace?: string;\n memories?: MemoryFile[];\n memoryDir?: string;\n codexHome?: string;\n rolloutSummaries?: RolloutSummaryInput[];\n now?: Date;\n}): Promise<MaterializeResult | null> {\n // Delegates to the shared post-consolidation helper — see\n // runPostConsolidationMaterialize in codex-materialize-runner.ts.\n return runPostConsolidationMaterialize(\"[cmc]\", options);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/causal-consolidation.ts"],"sourcesContent":["/**\n * causal-consolidation.ts — CMC Phase 2: LLM-Assisted Causal Consolidation\n *\n * Uses an LLM to analyze causal trajectory patterns across sessions.\n * The LLM receives the causal chain graph as context — connected trajectories\n * from different sessions — and identifies recurring behavioral patterns,\n * preference signals, and actionable rules.\n *\n * This is the core CMC innovation: the LLM gets cross-session causal context\n * that no other memory system provides. It can see that a user investigated\n * a bug in session 1, attempted a fix in session 2, and succeeded in session 3 —\n * and synthesize a rule or preference from that chain.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { resolveCausalTrajectoryStoreDir, type CausalTrajectoryRecord } from \"./causal-trajectory.js\";\nimport { readChainIndex, resolveChainsDir, type CausalChainIndex, type CausalEdge } from \"./causal-chain.js\";\nimport { listJsonFiles, readJsonFile } from \"./json-store.js\";\nimport { isRecord } from \"./store-contract.js\";\nimport { FallbackLlmClient, fallbackLlmRuntimeContextFromConfig } from \"./fallback-llm.js\";\nimport type { AgentPersonaModelConfig, GatewayConfig, MemoryFile, PluginConfig } from \"./types.js\";\nimport path from \"node:path\";\nimport { log } from \"./logger.js\";\nimport { runPostConsolidationMaterialize } from \"./connectors/codex-materialize-runner.js\";\nimport type { MaterializeResult, RolloutSummaryInput } from \"./connectors/codex-materialize.js\";\nimport { buildExtensionsBlockForConsolidation } from \"./semantic-consolidation.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CausalPatternCandidate {\n id: string;\n sourceType: \"causal-pattern\";\n subject: string;\n category: \"principle\" | \"rule\" | \"preference\";\n content: string;\n score: number;\n rationale: string;\n outcome: null;\n provenance: string[];\n agent: string | null;\n workflow: string | null;\n}\n\nexport interface ConsolidationConfig {\n minRecurrence: number;\n minSessions: number;\n successThreshold: number;\n}\n\nexport interface LlmConsolidationResult {\n rules: Array<{\n content: string;\n category: \"rule\" | \"principle\" | \"preference\";\n confidence: number;\n evidence: string[];\n }>;\n preferences: Array<{\n statement: string;\n confidence: number;\n evidence: string[];\n }>;\n}\n\nconst CAUSAL_RULE_CATEGORIES = new Set([\"rule\", \"principle\", \"preference\"]);\n\ninterface ConsolidationLlmClient {\n isAvailable(options?: { agentId?: string; modelChain?: AgentPersonaModelConfig }): boolean;\n chatCompletion(\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options?: { temperature?: number; maxTokens?: number; agentId?: string; modelChain?: AgentPersonaModelConfig },\n ): Promise<{ content: string } | null>;\n}\n\n// ─── Trajectory Reading ──────────────────────────────────────────────────────\n\nasync function readAllTrajectories(\n memoryDir: string,\n causalTrajectoryStoreDir?: string,\n): Promise<CausalTrajectoryRecord[]> {\n const root = resolveCausalTrajectoryStoreDir(memoryDir, causalTrajectoryStoreDir);\n const trajectoriesDir = path.join(root, \"trajectories\");\n\n const files = await listJsonFiles(trajectoriesDir).catch(() => [] as string[]);\n const results: CausalTrajectoryRecord[] = [];\n\n for (const filePath of files) {\n try {\n const raw = await readJsonFile(filePath);\n if (isRecord(raw) && typeof raw.trajectoryId === \"string\") {\n results.push(raw as unknown as CausalTrajectoryRecord);\n }\n } catch {\n // skip invalid\n }\n }\n\n return results;\n}\n\n// ─── Context Formatting ──────────────────────────────────────────────────────\n\n/**\n * Format trajectories and their causal connections as a readable context\n * for the LLM. Groups by session and shows chain connections.\n */\nfunction formatCausalContext(\n trajectories: CausalTrajectoryRecord[],\n chainIndex: CausalChainIndex,\n maxChars: number = 8000,\n): string {\n // Group trajectories by session\n const bySession = new Map<string, CausalTrajectoryRecord[]>();\n for (const t of trajectories) {\n const list = bySession.get(t.sessionKey) ?? [];\n list.push(t);\n bySession.set(t.sessionKey, list);\n }\n\n const lines: string[] = [];\n lines.push(`## Causal Trajectories (${trajectories.length} across ${bySession.size} sessions)`);\n lines.push(\"\");\n\n // Format each session's trajectories\n for (const [sessionKey, sessionTrajs] of bySession) {\n lines.push(`### Session: ${sessionKey}`);\n for (const t of sessionTrajs.slice(0, 5)) {\n const outcome = t.outcomeKind === \"success\" ? \"+\" : t.outcomeKind === \"failure\" ? \"-\" : \"~\";\n lines.push(`[${outcome}] Goal: ${t.goal}`);\n lines.push(` Action: ${t.actionSummary}`);\n lines.push(` Outcome: ${t.outcomeSummary}`);\n if (t.followUpSummary) lines.push(` Follow-up: ${t.followUpSummary}`);\n if (t.entityRefs?.length) lines.push(` Entities: ${t.entityRefs.join(\", \")}`);\n }\n lines.push(\"\");\n }\n\n // Format causal chain connections\n const edgeCount = Object.keys(chainIndex.edges).length;\n if (edgeCount > 0) {\n lines.push(`## Cross-Session Causal Chains (${edgeCount} connections)`);\n lines.push(\"\");\n\n const trajectoryMap = new Map(trajectories.map((t) => [t.trajectoryId, t]));\n const shown = new Set<string>();\n\n for (const [edgeId, edge] of Object.entries(chainIndex.edges)) {\n if (shown.size >= 10) break; // limit output size\n const from = trajectoryMap.get(edge.fromTrajectoryId);\n const to = trajectoryMap.get(edge.toTrajectoryId);\n if (!from || !to) continue;\n\n lines.push(`${edge.edgeType}: \"${from.goal}\" (${from.sessionKey}) → \"${to.goal}\" (${to.sessionKey})`);\n shown.add(edgeId);\n }\n lines.push(\"\");\n }\n\n const result = lines.join(\"\\n\");\n return result.length > maxChars ? result.slice(0, maxChars) + \"\\n[truncated]\" : result;\n}\n\n// ─── LLM Consolidation ──────────────────────────────────────────────────────\n\nconst CONSOLIDATION_PROMPT = `You are analyzing a user's causal trajectory history across multiple sessions. Trajectories record what the user tried to do (goal), what they did (action), and what happened (outcome).\n\nYour job is to identify:\n1. BEHAVIORAL RULES: Recurring patterns where the same approach consistently succeeds or fails. These should be actionable guidance for future sessions.\n2. PREFERENCES: What the user cares about, prefers, or consistently chooses — even if never explicitly stated. Infer preferences from what they repeatedly do, retry until successful, or always include in their workflow.\n\nIMPORTANT:\n- Look for CROSS-SESSION patterns — things that repeat across different sessions are more significant than within-session patterns.\n- A user who retries the same goal across sessions has a strong implicit preference for that outcome.\n- Consistent action choices reveal preferences even when the user never says \"I prefer X.\"\n- Frame preferences as \"The user would prefer responses that...\" when applicable.\n\nOutput valid JSON only:\n{\n \"rules\": [\n {\"content\": \"actionable rule text\", \"category\": \"rule|principle\", \"confidence\": 0.0-1.0, \"evidence\": [\"trajectory IDs\"]}\n ],\n \"preferences\": [\n {\"statement\": \"The user would prefer...\", \"confidence\": 0.0-1.0, \"evidence\": [\"trajectory IDs\"]}\n ]\n}\n\nIf no clear patterns exist, return {\"rules\": [], \"preferences\": []}.`;\n\nasync function consolidateWithLlm(\n context: string,\n llm: ConsolidationLlmClient,\n options: { agentId?: string; modelChain?: AgentPersonaModelConfig } = {},\n): Promise<LlmConsolidationResult> {\n const response = await llm.chatCompletion(\n [\n { role: \"system\", content: CONSOLIDATION_PROMPT },\n { role: \"user\", content: context },\n ],\n { temperature: 0.2, maxTokens: 2000, agentId: options.agentId, modelChain: options.modelChain },\n );\n\n if (!response?.content) {\n return { rules: [], preferences: [] };\n }\n\n try {\n // Extract JSON from response (may have markdown code fences)\n let jsonStr = response.content.trim();\n const fenceMatch = jsonStr.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n if (fenceMatch) jsonStr = fenceMatch[1];\n\n const parsed = JSON.parse(jsonStr);\n const rawRules: unknown[] = Array.isArray(parsed.rules) ? parsed.rules : [];\n const rawPreferences: unknown[] = Array.isArray(parsed.preferences) ? parsed.preferences : [];\n return {\n rules: rawRules\n .map(parseLlmRule)\n .filter((rule): rule is LlmConsolidationResult[\"rules\"][number] => !!rule),\n preferences: rawPreferences\n .map(parseLlmPreference)\n .filter((pref): pref is LlmConsolidationResult[\"preferences\"][number] => !!pref),\n };\n } catch {\n log.warn(\"[cmc] failed to parse LLM consolidation response\");\n return { rules: [], preferences: [] };\n }\n}\n\n// ─── Candidate Generation ────────────────────────────────────────────────────\n\nfunction stablePatternId(content: string): string {\n const digest = createHash(\"sha256\")\n .update(`causal-pattern\\0${content}`)\n .digest(\"hex\")\n .slice(0, 16);\n return `causal-pattern:${digest}`;\n}\n\nfunction parseConfidence(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return undefined;\n return Math.max(0, Math.min(1, value));\n}\n\nfunction parseEvidence(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\" && item.trim().length > 0);\n}\n\nfunction parseLlmRule(raw: unknown): LlmConsolidationResult[\"rules\"][number] | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const r = raw as Record<string, unknown>;\n const content = typeof r.content === \"string\" ? r.content.trim() : \"\";\n const category = typeof r.category === \"string\" ? r.category : \"\";\n const confidence = parseConfidence(r.confidence);\n if (content.length <= 5 || !CAUSAL_RULE_CATEGORIES.has(category) || confidence === undefined) {\n return undefined;\n }\n return {\n content,\n category: category as \"rule\" | \"principle\" | \"preference\",\n confidence,\n evidence: parseEvidence(r.evidence),\n };\n}\n\nfunction parseLlmPreference(raw: unknown): LlmConsolidationResult[\"preferences\"][number] | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const p = raw as Record<string, unknown>;\n const statement = typeof p.statement === \"string\" ? p.statement.trim() : \"\";\n const confidence = parseConfidence(p.confidence);\n if (statement.length <= 5 || confidence === undefined) return undefined;\n return {\n statement,\n confidence,\n evidence: parseEvidence(p.evidence),\n };\n}\n\nfunction llmResultToCandidates(result: LlmConsolidationResult): CausalPatternCandidate[] {\n const candidates: CausalPatternCandidate[] = [];\n\n for (const rule of result.rules) {\n const category =\n rule.category === \"principle\" || rule.category === \"preference\"\n ? rule.category\n : \"rule\";\n candidates.push({\n id: stablePatternId(rule.content),\n sourceType: \"causal-pattern\",\n subject: rule.content.slice(0, 80),\n category,\n content: rule.content,\n score: Math.min(1, rule.confidence ?? 0.7),\n rationale: \"LLM-identified causal pattern from cross-session trajectory analysis\",\n outcome: null,\n provenance: (rule.evidence ?? []).slice(0, 5),\n agent: null,\n workflow: null,\n });\n }\n\n for (const preference of result.preferences) {\n candidates.push({\n id: stablePatternId(preference.statement),\n sourceType: \"causal-pattern\",\n subject: preference.statement.slice(0, 80),\n category: \"preference\",\n content: preference.statement,\n score: Math.min(1, preference.confidence ?? 0.7),\n rationale: \"LLM-identified preference from cross-session trajectory analysis\",\n outcome: null,\n provenance: (preference.evidence ?? []).slice(0, 5),\n agent: null,\n workflow: null,\n });\n }\n\n return candidates;\n}\n\nfunction normalizePositiveInteger(value: number, fallback: number): number {\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;\n}\n\nfunction normalizeSuccessThreshold(value: number): number {\n if (!Number.isFinite(value) || value <= 0) return 0;\n return Math.min(1, value);\n}\n\nfunction trajectorySuccessScore(trajectory: CausalTrajectoryRecord): number {\n if (trajectory.outcomeKind === \"success\") return 1;\n if (trajectory.outcomeKind === \"partial\") return 0.5;\n return 0;\n}\n\nfunction averageSuccessScore(trajectories: CausalTrajectoryRecord[]): number {\n if (trajectories.length === 0) return 0;\n const total = trajectories.reduce((sum, trajectory) => sum + trajectorySuccessScore(trajectory), 0);\n return total / trajectories.length;\n}\n\nfunction passesConsolidationGates(\n trajectories: CausalTrajectoryRecord[],\n config: ConsolidationConfig,\n): boolean {\n const minRecurrence = normalizePositiveInteger(config.minRecurrence, 1);\n if (trajectories.length < minRecurrence) return false;\n\n const minSessions = normalizePositiveInteger(config.minSessions, 1);\n const sessionCount = new Set(trajectories.map((trajectory) => trajectory.sessionKey)).size;\n if (sessionCount < minSessions) return false;\n\n const successThreshold = normalizeSuccessThreshold(config.successThreshold);\n if (successThreshold > 0 && averageSuccessScore(trajectories) < successThreshold) {\n return false;\n }\n\n return true;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Run LLM-assisted consolidation: read trajectories, format causal context,\n * ask LLM to identify patterns and preferences.\n */\nexport async function deriveCausalPromotionCandidates(options: {\n memoryDir: string;\n causalTrajectoryStoreDir?: string;\n config: ConsolidationConfig;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n pluginConfig?: PluginConfig;\n llmClient?: ConsolidationLlmClient;\n}): Promise<CausalPatternCandidate[]> {\n try {\n const trajectories = await readAllTrajectories(options.memoryDir, options.causalTrajectoryStoreDir);\n if (!passesConsolidationGates(trajectories, options.config)) return [];\n\n const chainsDir = resolveChainsDir(options.memoryDir, options.causalTrajectoryStoreDir);\n const chainIndex = await readChainIndex(chainsDir);\n\n // Format the causal context for the LLM\n let context = formatCausalContext(trajectories, chainIndex);\n\n // Append memory extensions block if available (#382)\n if (options.pluginConfig) {\n const extBlock = await buildExtensionsBlockForConsolidation(options.pluginConfig);\n if (extBlock.length > 0) {\n context += \"\\n\\n\" + extBlock;\n }\n }\n\n // If no LLM available, fall back to empty (no deterministic fallback)\n const llm = options.llmClient ?? new FallbackLlmClient(\n options.gatewayConfig,\n options.pluginConfig\n ? fallbackLlmRuntimeContextFromConfig(options.pluginConfig, {\n workspaceDir: options.workspaceDir ?? options.pluginConfig.workspaceDir,\n })\n : { workspaceDir: options.workspaceDir },\n );\n const llmOptions = { agentId: options.gatewayAgentId, modelChain: options.modelChain };\n if (!llm.isAvailable(llmOptions)) {\n log.debug(\"[cmc] no LLM available for consolidation — skipping\");\n return [];\n }\n\n // Call LLM for pattern analysis\n const result = await consolidateWithLlm(context, llm, llmOptions);\n const candidates = llmResultToCandidates(result);\n\n log.debug(`[cmc] LLM consolidation produced ${candidates.length} rule(s) and ${result.preferences.length} preference(s)`);\n return candidates;\n } catch (error) {\n log.warn(`[cmc] consolidation failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);\n return [];\n }\n}\n\n/**\n * Get LLM-synthesized preferences from causal trajectory analysis.\n * Returns formatted preference statements for recall injection.\n */\nexport async function synthesizeCausalPreferencesViaLlm(options: {\n memoryDir: string;\n causalTrajectoryStoreDir?: string;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n minTrajectories?: number;\n}): Promise<string | null> {\n try {\n const trajectories = await readAllTrajectories(options.memoryDir, options.causalTrajectoryStoreDir);\n if (trajectories.length < (options.minTrajectories ?? 2)) return null;\n\n const chainsDir = resolveChainsDir(options.memoryDir, options.causalTrajectoryStoreDir);\n const chainIndex = await readChainIndex(chainsDir);\n const context = formatCausalContext(trajectories, chainIndex);\n\n const llm = new FallbackLlmClient(options.gatewayConfig, {\n workspaceDir: options.workspaceDir,\n });\n const llmOptions = { agentId: options.gatewayAgentId, modelChain: options.modelChain };\n if (!llm.isAvailable(llmOptions)) return null;\n\n const result = await consolidateWithLlm(context, llm, llmOptions);\n if (result.preferences.length === 0 && result.rules.length === 0) return null;\n\n const lines: string[] = [\"## Behavioral Insights (from Causal Chain Analysis)\", \"\"];\n\n for (const pref of result.preferences) {\n lines.push(`- ${pref.statement}`);\n }\n\n for (const rule of result.rules) {\n lines.push(`- ${rule.content}`);\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n } catch (error) {\n log.warn(`[cmc] preference synthesis failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);\n return null;\n }\n}\n\n/**\n * Optional post-consolidation hook — materializes Codex-native memory artifacts\n * after a causal consolidation run. Guarded by `codexMaterializeMemories` and\n * `codexMaterializeOnConsolidation`. Returns `null` when disabled or when the\n * sentinel is missing (honors user hand-edits).\n *\n * Split from the orchestrator-owned flow so #378 avoids touching\n * orchestrator.ts while Wave 1 edits are in flight.\n */\nexport async function materializeAfterCausalConsolidation(options: {\n config: PluginConfig;\n namespace?: string;\n memories?: MemoryFile[];\n memoryDir?: string;\n codexHome?: string;\n rolloutSummaries?: RolloutSummaryInput[];\n now?: Date;\n}): Promise<MaterializeResult | null> {\n // Delegates to the shared post-consolidation helper — see\n // runPostConsolidationMaterialize in codex-materialize-runner.ts.\n return runPostConsolidationMaterialize(\"[cmc]\", options);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-7WV3F5DQ.js";
|
|
4
4
|
import {
|
|
5
5
|
AdapterRegistry
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JBPKEARU.js";
|
|
7
7
|
import {
|
|
8
8
|
EngramMcpServer,
|
|
9
9
|
cleanupExpiredChatSessions,
|
|
@@ -11,23 +11,23 @@ import {
|
|
|
11
11
|
processChatMessage,
|
|
12
12
|
sessionBelongsToPrincipal,
|
|
13
13
|
subscribeChatTranscript
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WAIMHMO3.js";
|
|
15
15
|
import {
|
|
16
16
|
validateRequest
|
|
17
17
|
} from "./chunk-2MR3MFQB.js";
|
|
18
18
|
import {
|
|
19
19
|
getOperation
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-DPZ5GJMD.js";
|
|
21
21
|
import {
|
|
22
22
|
CorrectionContractError,
|
|
23
23
|
EngramAccessInputError
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-LAJIXABF.js";
|
|
25
25
|
import {
|
|
26
26
|
isTrustZoneName
|
|
27
27
|
} from "./chunk-JGSKJHF7.js";
|
|
28
28
|
import {
|
|
29
29
|
projectTagProjectId
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-BAVVI5GP.js";
|
|
31
31
|
import {
|
|
32
32
|
subscribeGraphEvents
|
|
33
33
|
} from "./chunk-DEUNUKTD.js";
|
|
@@ -2627,4 +2627,4 @@ function positiveIntQueryParam(value, label) {
|
|
|
2627
2627
|
export {
|
|
2628
2628
|
EngramAccessHttpServer
|
|
2629
2629
|
};
|
|
2630
|
-
//# sourceMappingURL=chunk-
|
|
2630
|
+
//# sourceMappingURL=chunk-2KYXXMTE.js.map
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveDefaultNamespaceRoot,
|
|
3
3
|
resolveNamespaceStorageRoot
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PNFN7SIN.js";
|
|
5
5
|
import {
|
|
6
6
|
getConfiguredNamespaces
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BAVVI5GP.js";
|
|
8
8
|
import {
|
|
9
9
|
namespaceIdentityFromToken,
|
|
10
10
|
namespaceIdentityToken,
|
|
11
11
|
normalizeNamespaceIdentity
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-EGGE52UU.js";
|
|
13
13
|
import {
|
|
14
14
|
MutationSerializer,
|
|
15
15
|
withHeldFileLock
|
|
@@ -34,10 +34,11 @@ import {
|
|
|
34
34
|
appendFile,
|
|
35
35
|
lstat,
|
|
36
36
|
mkdir,
|
|
37
|
+
open,
|
|
37
38
|
readdir,
|
|
38
|
-
readFile,
|
|
39
39
|
realpath,
|
|
40
40
|
rename,
|
|
41
|
+
stat,
|
|
41
42
|
writeFile
|
|
42
43
|
} from "fs/promises";
|
|
43
44
|
var NAMESPACE_KINDS = [
|
|
@@ -256,12 +257,19 @@ var NamespaceCatalog = class {
|
|
|
256
257
|
// neither mistakes the other's lock for self-held (round 6, codex P2 — NBsGP
|
|
257
258
|
// invariant preserved and tightened).
|
|
258
259
|
criticalSection = new MutationSerializer();
|
|
260
|
+
compactedCache;
|
|
259
261
|
// Test-only seam (round 7 — NEZkA): fires inside a touch's HELD-lock critical
|
|
260
262
|
// section, after the lock is acquired but BEFORE the read→merge→append. A
|
|
261
263
|
// deterministic concurrency test installs a hook here to widen the (otherwise
|
|
262
264
|
// microscopic) window and prove that a cross-process rebuild CANNOT run its
|
|
263
265
|
// load→rename while a touch holds the lock. Never set in production code.
|
|
264
266
|
onTouchCriticalSectionForTest;
|
|
267
|
+
// Test-only observation seam: fires only when the JSONL file is fully read.
|
|
268
|
+
// Cached loads do not invoke it.
|
|
269
|
+
onCatalogReadForTest;
|
|
270
|
+
// Test-only seam: fires after this process appends but before it stats the
|
|
271
|
+
// file to refresh the cache identity.
|
|
272
|
+
onAfterCatalogAppendForTest;
|
|
265
273
|
// Test-only seam (round 7 — NEZkA): fires inside a mutating rebuild's HELD-lock
|
|
266
274
|
// critical section, after the final cross-process re-merge `loadCompacted()` and
|
|
267
275
|
// BEFORE the atomic `rename()`. This is the EXACT window in which a check-then-
|
|
@@ -376,14 +384,16 @@ var NamespaceCatalog = class {
|
|
|
376
384
|
}
|
|
377
385
|
return [...byStorageDir.values()];
|
|
378
386
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
+
loadSanitizedRecords() {
|
|
388
|
+
return this.queueCritical(async () => {
|
|
389
|
+
const records = await this.loadCompacted();
|
|
390
|
+
const sanitized = await Promise.all(
|
|
391
|
+
[...records.values()].map((r) => this.sanitizeRecordForRead(r))
|
|
392
|
+
);
|
|
393
|
+
return this.dropDuplicateStorageRootAliases(
|
|
394
|
+
sanitized.filter((r) => r !== null)
|
|
395
|
+
);
|
|
396
|
+
});
|
|
387
397
|
}
|
|
388
398
|
async listNamespaces(filter) {
|
|
389
399
|
if (!this.enabled) return [];
|
|
@@ -1040,7 +1050,7 @@ var NamespaceCatalog = class {
|
|
|
1040
1050
|
*/
|
|
1041
1051
|
async finishRebuild(rebuilt, skipped, dryRun, canMutate, memoryReal, nowIso) {
|
|
1042
1052
|
if (canMutate) {
|
|
1043
|
-
const latest = await this.loadCompacted();
|
|
1053
|
+
const latest = await this.loadCompacted(true);
|
|
1044
1054
|
for (const [ns, fresh] of latest) {
|
|
1045
1055
|
const current = rebuilt.get(ns);
|
|
1046
1056
|
if (!current) {
|
|
@@ -1168,36 +1178,71 @@ var NamespaceCatalog = class {
|
|
|
1168
1178
|
}
|
|
1169
1179
|
// ── Persistence ──────────────────────────────────────────────────────────
|
|
1170
1180
|
/** Load the JSONL log and fold it into current state (last-record-wins). */
|
|
1171
|
-
async loadCompacted() {
|
|
1181
|
+
async loadCompacted(forceFresh = false) {
|
|
1172
1182
|
const records = /* @__PURE__ */ new Map();
|
|
1173
|
-
let
|
|
1183
|
+
let handle;
|
|
1174
1184
|
try {
|
|
1175
|
-
|
|
1185
|
+
handle = await open(this.catalogPath, "r");
|
|
1176
1186
|
} catch {
|
|
1187
|
+
this.compactedCache = void 0;
|
|
1177
1188
|
return records;
|
|
1178
1189
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1190
|
+
let identity;
|
|
1191
|
+
try {
|
|
1192
|
+
const fileStat = await handle.stat();
|
|
1193
|
+
identity = {
|
|
1194
|
+
size: fileStat.size,
|
|
1195
|
+
mtimeMs: fileStat.mtimeMs,
|
|
1196
|
+
ctimeMs: fileStat.ctimeMs,
|
|
1197
|
+
ino: fileStat.ino
|
|
1198
|
+
};
|
|
1199
|
+
if (!forceFresh && this.compactedCache && this.compactedCache.identity.size === identity.size && this.compactedCache.identity.mtimeMs === identity.mtimeMs && this.compactedCache.identity.ctimeMs === identity.ctimeMs && this.compactedCache.identity.ino === identity.ino) {
|
|
1200
|
+
await handle.close().catch(() => void 0);
|
|
1201
|
+
return this.cloneCompactedRecords(this.compactedCache.records);
|
|
1187
1202
|
}
|
|
1188
|
-
|
|
1189
|
-
if (!record) continue;
|
|
1190
|
-
const prior = records.get(record.namespace);
|
|
1191
|
-
records.set(record.namespace, prior ? mergeNewerTouchFields(record, prior) : record);
|
|
1203
|
+
} catch {
|
|
1192
1204
|
}
|
|
1193
|
-
|
|
1205
|
+
try {
|
|
1206
|
+
this.onCatalogReadForTest?.();
|
|
1207
|
+
const raw = await handle.readFile("utf8");
|
|
1208
|
+
for (const line of raw.split("\n")) {
|
|
1209
|
+
const trimmed = line.trim();
|
|
1210
|
+
if (trimmed.length === 0) continue;
|
|
1211
|
+
let parsed;
|
|
1212
|
+
try {
|
|
1213
|
+
parsed = JSON.parse(trimmed);
|
|
1214
|
+
} catch {
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
const record = coerceRecord(parsed);
|
|
1218
|
+
if (!record) continue;
|
|
1219
|
+
const prior = records.get(record.namespace);
|
|
1220
|
+
records.set(record.namespace, prior ? mergeNewerTouchFields(record, prior) : record);
|
|
1221
|
+
}
|
|
1222
|
+
} catch {
|
|
1223
|
+
this.compactedCache = void 0;
|
|
1224
|
+
return /* @__PURE__ */ new Map();
|
|
1225
|
+
} finally {
|
|
1226
|
+
await handle.close().catch(() => void 0);
|
|
1227
|
+
}
|
|
1228
|
+
this.compactedCache = identity ? { identity, records } : void 0;
|
|
1229
|
+
return this.cloneCompactedRecords(records);
|
|
1230
|
+
}
|
|
1231
|
+
cloneCompactedRecords(records) {
|
|
1232
|
+
return new Map(
|
|
1233
|
+
[...records].map(([namespace, record]) => [
|
|
1234
|
+
namespace,
|
|
1235
|
+
{
|
|
1236
|
+
...record,
|
|
1237
|
+
lastMaintenanceAt: record.lastMaintenanceAt ? { ...record.lastMaintenanceAt } : void 0
|
|
1238
|
+
}
|
|
1239
|
+
])
|
|
1240
|
+
);
|
|
1194
1241
|
}
|
|
1195
1242
|
/**
|
|
1196
1243
|
* Serialize an arbitrary read-modify-write critical section through the single
|
|
1197
|
-
* per-instance chain. Every catalog mutation
|
|
1198
|
-
*
|
|
1199
|
-
* reads the latest persisted state before appending, and a rebuild rewrite
|
|
1200
|
-
* cannot interleave with a touch's append.
|
|
1244
|
+
* per-instance chain. Every catalog mutation and cached catalog read runs
|
|
1245
|
+
* through this queue, so cache validation and updates cannot race in-process.
|
|
1201
1246
|
*
|
|
1202
1247
|
* Issue #1524 adoption: delegates to the shared `MutationSerializer` (stored
|
|
1203
1248
|
* as `criticalSection`). The util owns the rejection-recovery invariant
|
|
@@ -1217,8 +1262,13 @@ var NamespaceCatalog = class {
|
|
|
1217
1262
|
*/
|
|
1218
1263
|
async appendUnchained(record) {
|
|
1219
1264
|
const line = serializeRecord(record) + "\n";
|
|
1265
|
+
const lineByteLength = Buffer.byteLength(line, "utf8");
|
|
1220
1266
|
await mkdir(this.stateDir, { recursive: true });
|
|
1221
1267
|
await appendFile(this.catalogPath, line, "utf8");
|
|
1268
|
+
if (this.onAfterCatalogAppendForTest) {
|
|
1269
|
+
await this.onAfterCatalogAppendForTest();
|
|
1270
|
+
}
|
|
1271
|
+
await this.refreshCacheAfterAppend(record, lineByteLength);
|
|
1222
1272
|
}
|
|
1223
1273
|
/**
|
|
1224
1274
|
* Atomic temp-file + rename rewrite (CLAUDE.md rule #54: write temp, then
|
|
@@ -1234,6 +1284,48 @@ var NamespaceCatalog = class {
|
|
|
1234
1284
|
const tmp = `${this.catalogPath}.${process.pid}.${Date.now()}.tmp`;
|
|
1235
1285
|
await writeFile(tmp, body, "utf8");
|
|
1236
1286
|
await rename(tmp, this.catalogPath);
|
|
1287
|
+
await this.refreshCacheIdentity(new Map(records.map((record) => [record.namespace, record])));
|
|
1288
|
+
}
|
|
1289
|
+
async refreshCacheAfterAppend(record, lineByteLength) {
|
|
1290
|
+
const cached = this.compactedCache;
|
|
1291
|
+
if (!cached) return;
|
|
1292
|
+
const expectedSize = cached.identity.size + lineByteLength;
|
|
1293
|
+
try {
|
|
1294
|
+
const fileStat = await stat(this.catalogPath);
|
|
1295
|
+
if (fileStat.size !== expectedSize || fileStat.ino !== cached.identity.ino) {
|
|
1296
|
+
this.compactedCache = void 0;
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const prior = cached.records.get(record.namespace);
|
|
1300
|
+
cached.records.set(record.namespace, prior ? mergeNewerTouchFields(record, prior) : record);
|
|
1301
|
+
this.compactedCache = {
|
|
1302
|
+
identity: {
|
|
1303
|
+
size: fileStat.size,
|
|
1304
|
+
mtimeMs: fileStat.mtimeMs,
|
|
1305
|
+
ctimeMs: fileStat.ctimeMs,
|
|
1306
|
+
ino: fileStat.ino
|
|
1307
|
+
},
|
|
1308
|
+
records: cached.records
|
|
1309
|
+
};
|
|
1310
|
+
} catch {
|
|
1311
|
+
this.compactedCache = void 0;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
async refreshCacheIdentity(records) {
|
|
1315
|
+
try {
|
|
1316
|
+
const fileStat = await stat(this.catalogPath);
|
|
1317
|
+
this.compactedCache = {
|
|
1318
|
+
identity: {
|
|
1319
|
+
size: fileStat.size,
|
|
1320
|
+
mtimeMs: fileStat.mtimeMs,
|
|
1321
|
+
ctimeMs: fileStat.ctimeMs,
|
|
1322
|
+
ino: fileStat.ino
|
|
1323
|
+
},
|
|
1324
|
+
records
|
|
1325
|
+
};
|
|
1326
|
+
} catch {
|
|
1327
|
+
this.compactedCache = void 0;
|
|
1328
|
+
}
|
|
1237
1329
|
}
|
|
1238
1330
|
};
|
|
1239
1331
|
function isPathInside(root, child) {
|
|
@@ -1243,13 +1335,13 @@ function isPathInside(root, child) {
|
|
|
1243
1335
|
|
|
1244
1336
|
// src/maintenance/namespace-planner.ts
|
|
1245
1337
|
import { createHash, randomUUID } from "crypto";
|
|
1246
|
-
import { lstat as lstat2, mkdir as mkdir2, open
|
|
1338
|
+
import { lstat as lstat2, mkdir as mkdir2, open as open2, readFile, readdir as readdir2, rename as rename2, rm, rmdir, utimes, writeFile as writeFile2 } from "fs/promises";
|
|
1247
1339
|
import path2 from "path";
|
|
1248
1340
|
var DEFAULT_MAX_NAMESPACES_PER_CYCLE = 20;
|
|
1249
1341
|
var DEFAULT_LOCK_STALE_MS = 10 * 6e4;
|
|
1250
1342
|
var LOCK_BASE = "maintenance-locks";
|
|
1251
1343
|
var STATUS_BASE = "namespace-maintenance-status";
|
|
1252
|
-
var namespaceMaintenanceFs = { open, rm };
|
|
1344
|
+
var namespaceMaintenanceFs = { open: open2, rm };
|
|
1253
1345
|
function inferConfiguredKind(config, namespace) {
|
|
1254
1346
|
if (namespace === config.defaultNamespace.trim()) return "default";
|
|
1255
1347
|
if (namespace === config.sharedNamespace.trim()) return "shared";
|
|
@@ -1516,7 +1608,7 @@ async function tryAcquireNamespaceMaintenanceLock(config, jobName, namespace) {
|
|
|
1516
1608
|
path: filePath,
|
|
1517
1609
|
async touch() {
|
|
1518
1610
|
try {
|
|
1519
|
-
const parsed = JSON.parse(await
|
|
1611
|
+
const parsed = JSON.parse(await readFile(ownerPath, "utf8"));
|
|
1520
1612
|
if (parsed.lockId === lockId) {
|
|
1521
1613
|
const now = /* @__PURE__ */ new Date();
|
|
1522
1614
|
await utimes(ownerPath, now, now);
|
|
@@ -1527,7 +1619,7 @@ async function tryAcquireNamespaceMaintenanceLock(config, jobName, namespace) {
|
|
|
1527
1619
|
},
|
|
1528
1620
|
async release() {
|
|
1529
1621
|
try {
|
|
1530
|
-
const parsed = JSON.parse(await
|
|
1622
|
+
const parsed = JSON.parse(await readFile(ownerPath, "utf8"));
|
|
1531
1623
|
if (parsed.lockId === lockId) {
|
|
1532
1624
|
await namespaceMaintenanceFs.rm(ownerPath, { force: true });
|
|
1533
1625
|
await rmdir(filePath).catch(() => void 0);
|
|
@@ -1607,7 +1699,7 @@ async function readLatestStatusAtByNamespace(config, jobName) {
|
|
|
1607
1699
|
}
|
|
1608
1700
|
async function readStatusFile(filePath) {
|
|
1609
1701
|
try {
|
|
1610
|
-
const parsed = JSON.parse(await
|
|
1702
|
+
const parsed = JSON.parse(await readFile(filePath, "utf8"));
|
|
1611
1703
|
return parseStatus(parsed);
|
|
1612
1704
|
} catch {
|
|
1613
1705
|
return null;
|
|
@@ -1950,4 +2042,4 @@ export {
|
|
|
1950
2042
|
runNamespaceMaintenancePlan,
|
|
1951
2043
|
runNamespaceMaintenanceBatchPlan
|
|
1952
2044
|
};
|
|
1953
|
-
//# sourceMappingURL=chunk-
|
|
2045
|
+
//# sourceMappingURL=chunk-2PGB63VU.js.map
|