@theokit/sdk 5.14.0 → 5.15.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 +23 -0
- package/dist/{agent-Dfw3VsIX.d.ts → agent-B6C9i_qu.d.ts} +23 -213
- package/dist/{agent-DExqjqYy.d.cts → agent-B6FQad6P.d.cts} +23 -213
- package/dist/{agent-T34LCGQM.js → agent-DRDGUU6T.js} +18 -17
- package/dist/{agent-T34LCGQM.js.map → agent-DRDGUU6T.js.map} +1 -1
- package/dist/{agent-TL4K7BTG.cjs → agent-VI2HITPT.cjs} +19 -18
- package/dist/{agent-TL4K7BTG.cjs.map → agent-VI2HITPT.cjs.map} +1 -1
- package/dist/agent.d.ts +2 -3
- package/dist/{batch-EP3FCL6D.js → batch-N2XDYZED.js} +4 -4
- package/dist/{batch-EP3FCL6D.js.map → batch-N2XDYZED.js.map} +1 -1
- package/dist/{batch-IZ5EDKJ5.cjs → batch-VFR2MAUE.cjs} +7 -7
- package/dist/{batch-IZ5EDKJ5.cjs.map → batch-VFR2MAUE.cjs.map} +1 -1
- package/dist/{chunk-3ONIVO2I.js → chunk-23SS4ID4.js} +3 -3
- package/dist/{chunk-3ONIVO2I.js.map → chunk-23SS4ID4.js.map} +1 -1
- package/dist/{chunk-2C24NSNV.js → chunk-3BBTHFGJ.js} +3 -3
- package/dist/{chunk-2C24NSNV.js.map → chunk-3BBTHFGJ.js.map} +1 -1
- package/dist/{chunk-NP362THY.cjs → chunk-3F53FLQ7.cjs} +5 -7
- package/dist/chunk-3F53FLQ7.cjs.map +1 -0
- package/dist/{chunk-SXH37C7X.cjs → chunk-55LLQFHW.cjs} +6 -6
- package/dist/{chunk-SXH37C7X.cjs.map → chunk-55LLQFHW.cjs.map} +1 -1
- package/dist/{chunk-GARVAUCU.js → chunk-5M5GQYBM.js} +534 -236
- package/dist/chunk-5M5GQYBM.js.map +1 -0
- package/dist/{chunk-2WY2OFSC.cjs → chunk-64ECM3XM.cjs} +616 -318
- package/dist/chunk-64ECM3XM.cjs.map +1 -0
- package/dist/{chunk-ZJAYFSVI.js → chunk-7ACS6P5Z.js} +5 -7
- package/dist/chunk-7ACS6P5Z.js.map +1 -0
- package/dist/{chunk-NAVUTK6X.js → chunk-ARC2V7NY.js} +3 -3
- package/dist/{chunk-NAVUTK6X.js.map → chunk-ARC2V7NY.js.map} +1 -1
- package/dist/{chunk-WDVG5R2Q.cjs → chunk-AUH2EVWY.cjs} +4 -3
- package/dist/chunk-AUH2EVWY.cjs.map +1 -0
- package/dist/chunk-BNEUB52I.cjs +55 -0
- package/dist/chunk-BNEUB52I.cjs.map +1 -0
- package/dist/chunk-BPL5CVZ5.js +53 -0
- package/dist/chunk-BPL5CVZ5.js.map +1 -0
- package/dist/{chunk-NVQSBRG6.js → chunk-CBBT42VX.js} +3 -3
- package/dist/{chunk-NVQSBRG6.js.map → chunk-CBBT42VX.js.map} +1 -1
- package/dist/chunk-CHKV2L2Y.cjs +416 -0
- package/dist/chunk-CHKV2L2Y.cjs.map +1 -0
- package/dist/{chunk-SNVYOX46.cjs → chunk-DJKGDLYZ.cjs} +91 -61
- package/dist/chunk-DJKGDLYZ.cjs.map +1 -0
- package/dist/{chunk-354F3OF5.js → chunk-FGGVSHDW.js} +3 -3
- package/dist/{chunk-354F3OF5.js.map → chunk-FGGVSHDW.js.map} +1 -1
- package/dist/{chunk-6OCJWGVD.cjs → chunk-FTXY4D44.cjs} +4 -4
- package/dist/{chunk-6OCJWGVD.cjs.map → chunk-FTXY4D44.cjs.map} +1 -1
- package/dist/chunk-INVEGVGO.js +405 -0
- package/dist/chunk-INVEGVGO.js.map +1 -0
- package/dist/{chunk-ZOL3L4EH.js → chunk-IOBHFP2E.js} +49 -19
- package/dist/chunk-IOBHFP2E.js.map +1 -0
- package/dist/{chunk-BZK5QL3N.cjs → chunk-KMFBNKCT.cjs} +4 -4
- package/dist/{chunk-BZK5QL3N.cjs.map → chunk-KMFBNKCT.cjs.map} +1 -1
- package/dist/{chunk-DF77RBF4.js → chunk-LCCT5ZXJ.js} +6 -6
- package/dist/{chunk-DF77RBF4.js.map → chunk-LCCT5ZXJ.js.map} +1 -1
- package/dist/{chunk-G7XBVM3T.cjs → chunk-MHUOVS5I.cjs} +5 -5
- package/dist/{chunk-G7XBVM3T.cjs.map → chunk-MHUOVS5I.cjs.map} +1 -1
- package/dist/{chunk-CWQPQTUZ.cjs → chunk-NEH3BPFD.cjs} +15 -15
- package/dist/{chunk-CWQPQTUZ.cjs.map → chunk-NEH3BPFD.cjs.map} +1 -1
- package/dist/{chunk-5AR6O7GW.js → chunk-NK5EOTDY.js} +3 -3
- package/dist/{chunk-5AR6O7GW.js.map → chunk-NK5EOTDY.js.map} +1 -1
- package/dist/{chunk-4TEGFMSP.cjs → chunk-P7QYG4RZ.cjs} +23 -15
- package/dist/chunk-P7QYG4RZ.cjs.map +1 -0
- package/dist/chunk-PPU2D4FZ.js +19 -0
- package/dist/chunk-PPU2D4FZ.js.map +1 -0
- package/dist/{chunk-AB4OPJLD.js → chunk-RGVMJU57.js} +3 -3
- package/dist/{chunk-AB4OPJLD.js.map → chunk-RGVMJU57.js.map} +1 -1
- package/dist/{chunk-4KBI37F3.cjs → chunk-RTDVSHJR.cjs} +5 -5
- package/dist/{chunk-4KBI37F3.cjs.map → chunk-RTDVSHJR.cjs.map} +1 -1
- package/dist/{chunk-CJN5CEI3.js → chunk-RZL2W4C3.js} +23 -15
- package/dist/chunk-RZL2W4C3.js.map +1 -0
- package/dist/chunk-SPEN7RJU.cjs +18 -0
- package/dist/{chunk-R5UQL2R5.cjs.map → chunk-SPEN7RJU.cjs.map} +1 -1
- package/dist/{chunk-ZF2LDKQQ.cjs → chunk-U3DW3WDE.cjs} +9 -8
- package/dist/chunk-U3DW3WDE.cjs.map +1 -0
- package/dist/{chunk-LXG6VL6R.cjs → chunk-U7PRI3XM.cjs} +3 -3
- package/dist/{chunk-LXG6VL6R.cjs.map → chunk-U7PRI3XM.cjs.map} +1 -1
- package/dist/{chunk-H7G65PNI.js → chunk-VCQ37TC5.js} +3 -3
- package/dist/{chunk-H7G65PNI.js.map → chunk-VCQ37TC5.js.map} +1 -1
- package/dist/{chunk-SDZ4XDWS.cjs → chunk-VWYFGE27.cjs} +9 -9
- package/dist/{chunk-SDZ4XDWS.cjs.map → chunk-VWYFGE27.cjs.map} +1 -1
- package/dist/{chunk-RN3XDZGS.js → chunk-WM646HEN.js} +4 -3
- package/dist/chunk-WM646HEN.js.map +1 -0
- package/dist/chunk-X7CE7Y5W.js +305 -0
- package/dist/chunk-X7CE7Y5W.js.map +1 -0
- package/dist/{chunk-CGH4BCIY.cjs → chunk-XE3CAY6Z.cjs} +5 -5
- package/dist/{chunk-CGH4BCIY.cjs.map → chunk-XE3CAY6Z.cjs.map} +1 -1
- package/dist/chunk-XOU3CL7S.cjs +312 -0
- package/dist/chunk-XOU3CL7S.cjs.map +1 -0
- package/dist/{chunk-ULEK4OM4.js → chunk-YKNBTHMD.js} +3 -3
- package/dist/{chunk-ULEK4OM4.js.map → chunk-YKNBTHMD.js.map} +1 -1
- package/dist/{compact-session-D2MQDB6G.cjs → compact-session-NX7HUNTB.cjs} +16 -16
- package/dist/{compact-session-D2MQDB6G.cjs.map → compact-session-NX7HUNTB.cjs.map} +1 -1
- package/dist/{compact-session-N2PPHQO6.js → compact-session-Q5IGNFPW.js} +7 -7
- package/dist/{compact-session-N2PPHQO6.js.map → compact-session-Q5IGNFPW.js.map} +1 -1
- package/dist/{cron-CK2v0VWA.d.cts → cron-Bh3aWgQ2.d.cts} +2 -2
- package/dist/{cron-BubDpFJG.d.ts → cron-CCDxeeFZ.d.ts} +2 -2
- package/dist/cron.cjs +18 -17
- package/dist/cron.d.cts +4 -3
- package/dist/cron.d.ts +4 -3
- package/dist/cron.js +17 -16
- package/dist/eval.cjs +17 -16
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +16 -15
- package/dist/eval.js.map +1 -1
- package/dist/fs-session-store-CYNTEPJZ.cjs +20 -0
- package/dist/{fs-session-store-S2AXVJIX.cjs.map → fs-session-store-CYNTEPJZ.cjs.map} +1 -1
- package/dist/fs-session-store-MYQEVUFL.js +11 -0
- package/dist/{fs-session-store-6ZCCJX3K.js.map → fs-session-store-MYQEVUFL.js.map} +1 -1
- package/dist/{index-manager-HO456AVW.cjs → index-manager-2KH75BRW.cjs} +7 -7
- package/dist/{index-manager-HO456AVW.cjs.map → index-manager-2KH75BRW.cjs.map} +1 -1
- package/dist/{index-manager-QDH56FER.js → index-manager-NF2WRLOU.js} +6 -6
- package/dist/{index-manager-QDH56FER.js.map → index-manager-NF2WRLOU.js.map} +1 -1
- package/dist/index.cjs +66 -262
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -46
- package/dist/index.d.ts +10 -46
- package/dist/index.js +27 -227
- package/dist/index.js.map +1 -1
- package/dist/{inject-session-J26SHHIO.js → inject-session-4ROYLZHY.js} +3 -3
- package/dist/{inject-session-J26SHHIO.js.map → inject-session-4ROYLZHY.js.map} +1 -1
- package/dist/{inject-session-3ICZIJ2N.cjs → inject-session-AWKDCGHV.cjs} +4 -4
- package/dist/{inject-session-3ICZIJ2N.cjs.map → inject-session-AWKDCGHV.cjs.map} +1 -1
- package/dist/internal/agent-loop/doom-loop-tracker.d.ts +3 -0
- package/dist/internal/agent-loop/types.d.ts +6 -0
- package/dist/internal/error-mappers/anthropic.d.ts +3 -0
- package/dist/internal/error-mappers/openai-compatible.d.ts +2 -3
- package/dist/internal/local-agent/local-agent-dispatch.d.ts +3 -1
- package/dist/internal/local-agent/send-queue.d.ts +31 -0
- package/dist/internal/mcp/client.d.ts +2 -0
- package/dist/internal/mcp/oauth-bearer.d.ts +2 -0
- package/dist/internal/memory/adapters/index.cjs +3 -3
- package/dist/internal/memory/adapters/index.js +2 -2
- package/dist/internal/memory/dreaming/phases.d.ts +15 -0
- package/dist/internal/memory/dreaming/run.d.ts +29 -0
- package/dist/internal/memory/sdk-memory-peer-loader.d.ts +2 -0
- package/dist/internal/memory/storage/index.cjs +51 -35
- package/dist/internal/memory/storage/index.d.cts +2 -0
- package/dist/internal/memory/storage/index.d.ts +2 -0
- package/dist/internal/memory/storage/index.js +4 -4
- package/dist/internal/persistence/cwd-mutex.d.cts +4 -2
- package/dist/internal/persistence/cwd-mutex.d.ts +4 -2
- package/dist/internal/persistence/index.cjs +4 -4
- package/dist/internal/persistence/index.js +2 -2
- package/dist/internal/persistence/sqlite-session-store.d.cts +25 -0
- package/dist/internal/persistence/sqlite-session-store.d.ts +25 -0
- package/dist/internal/providers/registry.d.ts +5 -1
- package/dist/mcp-auth.cjs +25 -307
- package/dist/mcp-auth.cjs.map +1 -1
- package/dist/mcp-auth.js +5 -305
- package/dist/mcp-auth.js.map +1 -1
- package/dist/models.cjs +9 -9
- package/dist/models.js +2 -2
- package/dist/persistence.cjs +9 -4
- package/dist/persistence.cjs.map +1 -1
- package/dist/persistence.d.cts +744 -40
- package/dist/persistence.d.ts +744 -40
- package/dist/persistence.js +3 -2
- package/dist/persistence.js.map +1 -1
- package/dist/providers.cjs +4 -4
- package/dist/providers.js +2 -2
- package/dist/sandbox/docker-sandbox.d.cts +58 -0
- package/dist/sandbox/docker-sandbox.d.ts +58 -0
- package/dist/sandbox/index.cjs +129 -1
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.d.cts +1 -0
- package/dist/sandbox/index.d.ts +1 -0
- package/dist/sandbox/index.js +132 -5
- package/dist/sandbox/index.js.map +1 -1
- package/dist/{sdk-agent-DwzHcuxy.d.cts → sdk-agent-BA_6_wtc.d.cts} +30 -2
- package/dist/{sdk-agent-a3fW1xUK.d.ts → sdk-agent-CnwPODYH.d.ts} +30 -2
- package/dist/session-store-Dh07jr9N.d.cts +209 -0
- package/dist/session-store-Dh07jr9N.d.ts +209 -0
- package/dist/sqlite-session-store-BbcpKY1L.d.ts +64 -0
- package/dist/sqlite-session-store-TKXtT4ON.d.cts +64 -0
- package/dist/subagents-loader-KLWF4QPD.cjs +16 -0
- package/dist/{subagents-loader-TXOA5EJJ.cjs.map → subagents-loader-KLWF4QPD.cjs.map} +1 -1
- package/dist/subagents-loader-SXETS2F2.js +7 -0
- package/dist/{subagents-loader-Z744TAKD.js.map → subagents-loader-SXETS2F2.js.map} +1 -1
- package/dist/subagents-loader.cjs +2 -2
- package/dist/subagents-loader.d.cts +3 -2
- package/dist/subagents-loader.d.ts +3 -2
- package/dist/subagents-loader.js +1 -1
- package/dist/types/agent.d.ts +14 -1
- package/dist/types/budget-tracker.d.ts +6 -2
- package/dist/types/mcp.d.ts +14 -0
- package/dist/types/run.d.ts +15 -1
- package/dist/workflow.d.cts +1 -1
- package/dist/workflow.d.ts +1 -1
- package/docs/adr/mutation-testing-scope.md +12 -2
- package/docs/error-codes.md +41 -40
- package/docs/harness-capability-map.md +32 -6
- package/package.json +1 -1
- package/dist/chunk-2WY2OFSC.cjs.map +0 -1
- package/dist/chunk-4TEGFMSP.cjs.map +0 -1
- package/dist/chunk-6CXRYPG7.cjs +0 -211
- package/dist/chunk-6CXRYPG7.cjs.map +0 -1
- package/dist/chunk-CJN5CEI3.js.map +0 -1
- package/dist/chunk-GARVAUCU.js.map +0 -1
- package/dist/chunk-MSEL5E5P.js +0 -204
- package/dist/chunk-MSEL5E5P.js.map +0 -1
- package/dist/chunk-NP362THY.cjs.map +0 -1
- package/dist/chunk-Q5EWJPRY.js +0 -18
- package/dist/chunk-Q5EWJPRY.js.map +0 -1
- package/dist/chunk-R5UQL2R5.cjs +0 -18
- package/dist/chunk-RN3XDZGS.js.map +0 -1
- package/dist/chunk-SNVYOX46.cjs.map +0 -1
- package/dist/chunk-WDVG5R2Q.cjs.map +0 -1
- package/dist/chunk-ZF2LDKQQ.cjs.map +0 -1
- package/dist/chunk-ZJAYFSVI.js.map +0 -1
- package/dist/chunk-ZOL3L4EH.js.map +0 -1
- package/dist/fs-session-store-6ZCCJX3K.js +0 -11
- package/dist/fs-session-store-S2AXVJIX.cjs +0 -20
- package/dist/subagents-loader-TXOA5EJJ.cjs +0 -16
- package/dist/subagents-loader-Z744TAKD.js +0 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { withCwdMutex } from './chunk-
|
|
1
|
+
import { withCwdMutex } from './chunk-PPU2D4FZ.js';
|
|
2
2
|
import { diag } from './chunk-3OY6H3I7.js';
|
|
3
3
|
|
|
4
4
|
// src/internal/persistence/file-lock.ts
|
|
@@ -78,5 +78,5 @@ async function withFileLock(path, fn, options) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export { withFileLock };
|
|
81
|
-
//# sourceMappingURL=chunk-
|
|
82
|
-
//# sourceMappingURL=chunk-
|
|
81
|
+
//# sourceMappingURL=chunk-FGGVSHDW.js.map
|
|
82
|
+
//# sourceMappingURL=chunk-FGGVSHDW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/persistence/file-lock.ts"],"names":[],"mappings":"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","file":"chunk-354F3OF5.js","sourcesContent":["/**\n * Cross-process file lock helper (ADR D61).\n *\n * Uses `proper-lockfile` (optional peer dep) for cross-process locks. When\n * the peer dep is absent, falls back to `withCwdMutex` (in-process only)\n * with a one-shot stderr warning.\n *\n * EC-1 fix: uses a companion `<path>.lock` file with `realpath: false` so\n * `withFileLock` works even when the target `path` does not exist yet.\n * Without this, fresh installs that lock-then-create would crash with ENOENT.\n *\n * @internal\n */\n\nimport { diag } from \"../diagnostics.js\";\nimport { withCwdMutex } from \"./cwd-mutex.js\";\n\ninterface ProperLockfileModule {\n lock: (file: string, options: ProperLockfileOptions) => Promise<() => Promise<void>>;\n}\n\ninterface ProperLockfileOptions {\n lockfilePath?: string;\n realpath?: boolean;\n stale?: number;\n retries?: {\n retries: number;\n factor?: number;\n minTimeout?: number;\n maxTimeout?: number;\n };\n}\n\nlet cached: ProperLockfileModule | null | undefined;\nlet warnedMissing = false;\nlet warnedStructural = false;\n/** Why the import failed, kept so the warning can report the observation instead of a guess (#174). */\nlet loadFailure: unknown;\n\n/** Node reports a missing module as `ERR_MODULE_NOT_FOUND` (ESM loader) or `MODULE_NOT_FOUND` (CJS). */\nfunction isModuleAbsent(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\n/**\n * #174 — build the fallback warning from what actually happened.\n *\n * This used to always read \"proper-lockfile not installed\", because the import was wrapped in a\n * bare `catch` that discarded the error (Unbreakable Rule 8: a swallowed error is the dangerous\n * kind). A consumer whose package WAS installed, declared and resolvable spent a debugging session\n * re-verifying the install, because the message named a cause the code had never checked.\n *\n * Absence is now the only case that claims absence. Every other failure — a broken install, an\n * interop problem, a bundler that rewrote the specifier — reports its own code and message, which\n * is what points at the real cause.\n *\n * @internal\n */\nfunction describeLockLoadFailure(err: unknown): string {\n const consequence =\n \"cross-process file lock unavailable — concurrent processes over the same file are NOT serialized.\";\n if (isModuleAbsent(err)) {\n return `[theokit-sdk] proper-lockfile not installed; ${consequence} Install with: pnpm add proper-lockfile\\n`;\n }\n const code = (err as { code?: unknown } | null | undefined)?.code;\n const detail = err instanceof Error ? err.message : String(err);\n return (\n `[theokit-sdk] proper-lockfile could not be loaded${code !== undefined ? ` (${String(code)})` : \"\"}: ` +\n `${detail}. ${consequence} The package resolves for the SDK but failed to load — check bundling ` +\n \"and module-format interop before re-checking the install.\\n\"\n );\n}\n\n/** Test seam for the message builder — NOT in the public barrel. @internal */\nexport function __TESTING__describeLockLoadFailure(err: unknown): string {\n return describeLockLoadFailure(err);\n}\n\nasync function getProperLockfile(): Promise<ProperLockfileModule | null> {\n if (cached !== undefined) return cached;\n try {\n const mod = await import(\"proper-lockfile\");\n // T5.9 — supply-chain hardening: validate the imported module\n // exposes the API surface we depend on BEFORE caching it. A\n // tampered or incompatible version that lacks `lock`/`unlock`\n // functions gets treated as \"not installed\" with an advisory\n // warning — never silently used.\n if (!validateLockModule(mod)) {\n if (!warnedStructural) {\n warnedStructural = true;\n diag(\n \"[theokit-sdk] proper-lockfile: imported module does NOT expose \" +\n \"the expected `lock`/`unlock` API surface. This may indicate a \" +\n \"supply-chain compromise or an incompatible major version. \" +\n \"Falling back to in-process mutex (no cross-process safety). \" +\n \"Reinstall with: pnpm add proper-lockfile@^11\\n\",\n );\n }\n cached = null;\n return cached;\n }\n cached = mod as ProperLockfileModule;\n } catch (err) {\n // #174 — keep WHY. Discarding it here is what made the fallback warning assert a cause it had\n // never observed, and sent a consumer to re-check an install that was already correct.\n loadFailure = err;\n cached = null;\n }\n return cached;\n}\n\n/**\n * T5.9 — Structural validation of the dynamically-imported\n * `proper-lockfile` module. Verifies the API surface we depend on\n * (`lock` and `unlock` as functions) is present. Pure function —\n * never throws, never mutates, never performs I/O.\n *\n * Exported via `__TESTING__validateLockModule` seam so unit tests\n * can drive the check without spinning up the dynamic import.\n *\n * @internal\n */\nfunction validateLockModule(mod: unknown): boolean {\n if (mod === null || mod === undefined || typeof mod !== \"object\") return false;\n const m = mod as Record<string, unknown>;\n return typeof m.lock === \"function\" && typeof m.unlock === \"function\";\n}\n\n/**\n * T5.9 — Test seam: expose the structural validator for unit tests.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__validateLockModule(mod: unknown): boolean {\n return validateLockModule(mod);\n}\n\n/**\n * T5.9 — Test seam: reset the module cache + warning flags between\n * tests so each test starts fresh. NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__resetFileLockCache(): void {\n cached = undefined;\n warnedMissing = false;\n warnedStructural = false;\n loadFailure = undefined;\n}\n\n/**\n * Retry and staleness tuning for `withFileLock`.\n *\n * Every field is optional and the defaults are applied per call, so `undefined` means the default\n * rather than \"no limit\". They are handed to `proper-lockfile` and have NO effect on the fallback\n * path: when that package is absent, `withFileLock` degrades to the in-process mutex, which\n * queues indefinitely and never goes stale.\n *\n * `stale` (default 30000 ms) is how old a lock file must be before it is treated as abandoned by a\n * dead process and broken. Setting it below the longest a legitimate holder can run is what turns\n * a slow operation into two concurrent ones.\n *\n * `retries` (default 5) is the number of acquisition attempts AFTER the first, and `retryFactor`\n * (default 1.5) the backoff multiplier between them. The per-attempt wait is clamped to\n * 100–5000 ms, which the caller cannot change. Exhausting the retries rejects — it does not fall\n * through to running `fn` unlocked.\n *\n * @public — re-exported from the semver-protected `@theokit/sdk/persistence` barrel, and (for\n * back-compat) from the semver-exempt `@theokit/sdk/internal/persistence` alias.\n */\nexport interface FileLockOptions {\n /** Stale lock timeout in ms. Default 30_000 (30s). */\n stale?: number;\n /** Max retries on busy lock. Default 5. */\n retries?: number;\n /** Backoff factor between retries. Default 1.5. */\n retryFactor?: number;\n}\n\n/**\n * Run `fn` while holding an OS-level cross-process lock on `path`.\n *\n * If `proper-lockfile` is installed, uses it with a companion `<path>.lock`\n * file (`realpath: false`, so target file does NOT need to exist yet).\n * Otherwise falls back to in-process `withCwdMutex` and prints a one-shot\n * stderr warning telling the user to install `proper-lockfile` for\n * cross-process safety.\n *\n * The lock is released even when `fn` throws.\n *\n */\nexport async function withFileLock<T>(\n path: string,\n fn: () => Promise<T>,\n options?: FileLockOptions,\n): Promise<T> {\n const lib = await getProperLockfile();\n\n if (lib === null) {\n // #174 — stay silent when the structural check already explained itself. Emitting \"not\n // installed\" on top of \"does not expose lock/unlock\" would be two contradictory diagnoses of\n // one failure, and the reader has no way to tell which is true.\n if (!warnedMissing && !warnedStructural) {\n warnedMissing = true;\n diag(describeLockLoadFailure(loadFailure));\n }\n return withCwdMutex(`file-lock:${path}`, fn);\n }\n\n // proper-lockfile errors immediately on same-process concurrent acquire\n // (\"Lock file is already being held\"). Wrap with cwd-mutex first so\n // in-process callers queue and only ONE thread at a time enters the\n // cross-process acquire path. Combined: full in-process + cross-process\n // serialization.\n return withCwdMutex(`file-lock:${path}`, async () => {\n const release = await lib.lock(path, {\n // EC-1: companion lockfile, target path may not exist yet.\n lockfilePath: `${path}.lock`,\n realpath: false,\n stale: options?.stale ?? 30_000,\n retries: {\n retries: options?.retries ?? 5,\n factor: options?.retryFactor ?? 1.5,\n minTimeout: 100,\n maxTimeout: 5_000,\n },\n });\n\n try {\n return await fn();\n } finally {\n await release();\n }\n });\n}\n\n/**\n * Test helper — resets the cached proper-lockfile module + warning flag.\n * Allows tests to simulate \"module absent\" by clearing cache then\n * monkey-patching the dynamic import resolution.\n *\n * @internal\n */\nexport function _resetFileLockCacheForTesting(): void {\n cached = undefined;\n warnedMissing = false;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/persistence/file-lock.ts"],"names":[],"mappings":"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","file":"chunk-FGGVSHDW.js","sourcesContent":["/**\n * Cross-process file lock helper (ADR D61).\n *\n * Uses `proper-lockfile` (optional peer dep) for cross-process locks. When\n * the peer dep is absent, falls back to `withCwdMutex` (in-process only)\n * with a one-shot stderr warning.\n *\n * EC-1 fix: uses a companion `<path>.lock` file with `realpath: false` so\n * `withFileLock` works even when the target `path` does not exist yet.\n * Without this, fresh installs that lock-then-create would crash with ENOENT.\n *\n * @internal\n */\n\nimport { diag } from \"../diagnostics.js\";\nimport { withCwdMutex } from \"./cwd-mutex.js\";\n\ninterface ProperLockfileModule {\n lock: (file: string, options: ProperLockfileOptions) => Promise<() => Promise<void>>;\n}\n\ninterface ProperLockfileOptions {\n lockfilePath?: string;\n realpath?: boolean;\n stale?: number;\n retries?: {\n retries: number;\n factor?: number;\n minTimeout?: number;\n maxTimeout?: number;\n };\n}\n\nlet cached: ProperLockfileModule | null | undefined;\nlet warnedMissing = false;\nlet warnedStructural = false;\n/** Why the import failed, kept so the warning can report the observation instead of a guess (#174). */\nlet loadFailure: unknown;\n\n/** Node reports a missing module as `ERR_MODULE_NOT_FOUND` (ESM loader) or `MODULE_NOT_FOUND` (CJS). */\nfunction isModuleAbsent(err: unknown): boolean {\n const code = (err as { code?: unknown } | null | undefined)?.code;\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n\n/**\n * #174 — build the fallback warning from what actually happened.\n *\n * This used to always read \"proper-lockfile not installed\", because the import was wrapped in a\n * bare `catch` that discarded the error (Unbreakable Rule 8: a swallowed error is the dangerous\n * kind). A consumer whose package WAS installed, declared and resolvable spent a debugging session\n * re-verifying the install, because the message named a cause the code had never checked.\n *\n * Absence is now the only case that claims absence. Every other failure — a broken install, an\n * interop problem, a bundler that rewrote the specifier — reports its own code and message, which\n * is what points at the real cause.\n *\n * @internal\n */\nfunction describeLockLoadFailure(err: unknown): string {\n const consequence =\n \"cross-process file lock unavailable — concurrent processes over the same file are NOT serialized.\";\n if (isModuleAbsent(err)) {\n return `[theokit-sdk] proper-lockfile not installed; ${consequence} Install with: pnpm add proper-lockfile\\n`;\n }\n const code = (err as { code?: unknown } | null | undefined)?.code;\n const detail = err instanceof Error ? err.message : String(err);\n return (\n `[theokit-sdk] proper-lockfile could not be loaded${code !== undefined ? ` (${String(code)})` : \"\"}: ` +\n `${detail}. ${consequence} The package resolves for the SDK but failed to load — check bundling ` +\n \"and module-format interop before re-checking the install.\\n\"\n );\n}\n\n/** Test seam for the message builder — NOT in the public barrel. @internal */\nexport function __TESTING__describeLockLoadFailure(err: unknown): string {\n return describeLockLoadFailure(err);\n}\n\nasync function getProperLockfile(): Promise<ProperLockfileModule | null> {\n if (cached !== undefined) return cached;\n try {\n const mod = await import(\"proper-lockfile\");\n // T5.9 — supply-chain hardening: validate the imported module\n // exposes the API surface we depend on BEFORE caching it. A\n // tampered or incompatible version that lacks `lock`/`unlock`\n // functions gets treated as \"not installed\" with an advisory\n // warning — never silently used.\n if (!validateLockModule(mod)) {\n if (!warnedStructural) {\n warnedStructural = true;\n diag(\n \"[theokit-sdk] proper-lockfile: imported module does NOT expose \" +\n \"the expected `lock`/`unlock` API surface. This may indicate a \" +\n \"supply-chain compromise or an incompatible major version. \" +\n \"Falling back to in-process mutex (no cross-process safety). \" +\n \"Reinstall with: pnpm add proper-lockfile@^11\\n\",\n );\n }\n cached = null;\n return cached;\n }\n cached = mod as ProperLockfileModule;\n } catch (err) {\n // #174 — keep WHY. Discarding it here is what made the fallback warning assert a cause it had\n // never observed, and sent a consumer to re-check an install that was already correct.\n loadFailure = err;\n cached = null;\n }\n return cached;\n}\n\n/**\n * T5.9 — Structural validation of the dynamically-imported\n * `proper-lockfile` module. Verifies the API surface we depend on\n * (`lock` and `unlock` as functions) is present. Pure function —\n * never throws, never mutates, never performs I/O.\n *\n * Exported via `__TESTING__validateLockModule` seam so unit tests\n * can drive the check without spinning up the dynamic import.\n *\n * @internal\n */\nfunction validateLockModule(mod: unknown): boolean {\n if (mod === null || mod === undefined || typeof mod !== \"object\") return false;\n const m = mod as Record<string, unknown>;\n return typeof m.lock === \"function\" && typeof m.unlock === \"function\";\n}\n\n/**\n * T5.9 — Test seam: expose the structural validator for unit tests.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__validateLockModule(mod: unknown): boolean {\n return validateLockModule(mod);\n}\n\n/**\n * T5.9 — Test seam: reset the module cache + warning flags between\n * tests so each test starts fresh. NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__resetFileLockCache(): void {\n cached = undefined;\n warnedMissing = false;\n warnedStructural = false;\n loadFailure = undefined;\n}\n\n/**\n * Retry and staleness tuning for `withFileLock`.\n *\n * Every field is optional and the defaults are applied per call, so `undefined` means the default\n * rather than \"no limit\". They are handed to `proper-lockfile` and have NO effect on the fallback\n * path: when that package is absent, `withFileLock` degrades to the in-process mutex, which\n * queues indefinitely and never goes stale.\n *\n * `stale` (default 30000 ms) is how old a lock file must be before it is treated as abandoned by a\n * dead process and broken. Setting it below the longest a legitimate holder can run is what turns\n * a slow operation into two concurrent ones.\n *\n * `retries` (default 5) is the number of acquisition attempts AFTER the first, and `retryFactor`\n * (default 1.5) the backoff multiplier between them. The per-attempt wait is clamped to\n * 100–5000 ms, which the caller cannot change. Exhausting the retries rejects — it does not fall\n * through to running `fn` unlocked.\n *\n * @public — re-exported from the semver-protected `@theokit/sdk/persistence` barrel, and (for\n * back-compat) from the semver-exempt `@theokit/sdk/internal/persistence` alias.\n */\nexport interface FileLockOptions {\n /** Stale lock timeout in ms. Default 30_000 (30s). */\n stale?: number;\n /** Max retries on busy lock. Default 5. */\n retries?: number;\n /** Backoff factor between retries. Default 1.5. */\n retryFactor?: number;\n}\n\n/**\n * Run `fn` while holding an OS-level cross-process lock on `path`.\n *\n * If `proper-lockfile` is installed, uses it with a companion `<path>.lock`\n * file (`realpath: false`, so target file does NOT need to exist yet).\n * Otherwise falls back to in-process `withCwdMutex` and prints a one-shot\n * stderr warning telling the user to install `proper-lockfile` for\n * cross-process safety.\n *\n * The lock is released even when `fn` throws.\n *\n */\nexport async function withFileLock<T>(\n path: string,\n fn: () => Promise<T>,\n options?: FileLockOptions,\n): Promise<T> {\n const lib = await getProperLockfile();\n\n if (lib === null) {\n // #174 — stay silent when the structural check already explained itself. Emitting \"not\n // installed\" on top of \"does not expose lock/unlock\" would be two contradictory diagnoses of\n // one failure, and the reader has no way to tell which is true.\n if (!warnedMissing && !warnedStructural) {\n warnedMissing = true;\n diag(describeLockLoadFailure(loadFailure));\n }\n return withCwdMutex(`file-lock:${path}`, fn);\n }\n\n // proper-lockfile errors immediately on same-process concurrent acquire\n // (\"Lock file is already being held\"). Wrap with cwd-mutex first so\n // in-process callers queue and only ONE thread at a time enters the\n // cross-process acquire path. Combined: full in-process + cross-process\n // serialization.\n return withCwdMutex(`file-lock:${path}`, async () => {\n const release = await lib.lock(path, {\n // EC-1: companion lockfile, target path may not exist yet.\n lockfilePath: `${path}.lock`,\n realpath: false,\n stale: options?.stale ?? 30_000,\n retries: {\n retries: options?.retries ?? 5,\n factor: options?.retryFactor ?? 1.5,\n minTimeout: 100,\n maxTimeout: 5_000,\n },\n });\n\n try {\n return await fn();\n } finally {\n await release();\n }\n });\n}\n\n/**\n * Test helper — resets the cached proper-lockfile module + warning flag.\n * Allows tests to simulate \"module absent\" by clearing cache then\n * monkey-patching the dynamic import resolution.\n *\n * @internal\n */\nexport function _resetFileLockCacheForTesting(): void {\n cached = undefined;\n warnedMissing = false;\n}\n"]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var chunkEQ5IDL6W_cjs = require('./chunk-EQ5IDL6W.cjs');
|
|
4
4
|
var chunk3WKWBYPO_cjs = require('./chunk-3WKWBYPO.cjs');
|
|
5
5
|
var chunkPOBZXULR_cjs = require('./chunk-POBZXULR.cjs');
|
|
6
|
-
var
|
|
6
|
+
var chunkMHUOVS5I_cjs = require('./chunk-MHUOVS5I.cjs');
|
|
7
7
|
var fs = require('fs');
|
|
8
8
|
var promises = require('fs/promises');
|
|
9
9
|
var path = require('path');
|
|
@@ -80,7 +80,7 @@ var FsSessionStore = class {
|
|
|
80
80
|
if (records.length === 0) return;
|
|
81
81
|
const path$1 = this.#pathFor(agentId);
|
|
82
82
|
await promises.mkdir(path.dirname(path$1), { recursive: true });
|
|
83
|
-
await
|
|
83
|
+
await chunkMHUOVS5I_cjs.withFileLock(path$1, async () => {
|
|
84
84
|
for (const record of records) chunkPOBZXULR_cjs.appendJsonl(path$1, record);
|
|
85
85
|
});
|
|
86
86
|
}
|
|
@@ -132,5 +132,5 @@ var FsSessionStore = class {
|
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
exports.FsSessionStore = FsSessionStore;
|
|
135
|
-
//# sourceMappingURL=chunk-
|
|
136
|
-
//# sourceMappingURL=chunk-
|
|
135
|
+
//# sourceMappingURL=chunk-FTXY4D44.cjs.map
|
|
136
|
+
//# sourceMappingURL=chunk-FTXY4D44.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/persistence/fs-session-store.ts"],"names":["acquireSessionWriter","transcriptPath","legacyTranscriptPath","existsSync","readTranscript","path","mkdir","dirname","withFileLock","appendJsonl"],"mappings":"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","file":"chunk-6OCJWGVD.cjs","sourcesContent":["/**\n * SE41 — `FsSessionStore`, the DEFAULT reference implementation of the pluggable\n * {@link SessionStore} seam. It reads and append-writes the native Claude-shaped\n * `.jsonl` transcript at `<baseDir>/projects/<encoded-cwd>/<agentId>.jsonl` — the\n * exact on-disk format SE40 introduced (the file the Claude Code CLI can\n * `--continue`). Omitting `local.sessionStore` resolves to this store, so the\n * default persistence path is byte-identical to SE40 behavior.\n *\n * `readRecords` is `readTranscript(transcriptPath(...))` (a missing session →\n * `[]`, not an error — a fresh agent has no history). `appendRecords` is a TRUE\n * append: it reads the prior records, concatenates the new-turn delta, and\n * rewrites the whole line set atomically under the SE40 cross-process file lock\n * (`writeTranscript` never shrinks — the native format is an append-only\n * `parentUuid` DAG). The parent dir is created BEFORE acquiring the lock because\n * the lock's companion `<path>.lock` file needs an existing parent dir (the SE40\n * `mkdir(dirname)`-before-lock fix).\n *\n * @internal\n */\n\nimport { existsSync } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport { withFileLock } from \"./file-lock.js\";\nimport { appendJsonl } from \"./jsonl.js\";\nimport {\n legacyTranscriptPath,\n readTranscript,\n type SessionRecord,\n transcriptPath,\n} from \"./session-transcript.js\";\nimport { acquireSessionWriter, type SessionWriterLease } from \"./session-writer.js\";\n\n/** Options identifying the on-disk transcript location for the FS default store. */\nexport interface FsSessionStoreOptions {\n /** Transcript base dir (`~/.theokit` default, `~/.claude` for CLI interop) — already `~`-expanded. */\n baseDir: string;\n /** The workspace cwd whose encoded form is the transcript project dir. */\n cwd: string;\n}\n\n/**\n * Reference-counted leases, shared per path.\n *\n * `acquireSessionWriter` is STRICT on purpose: two concurrent acquisitions of the same path, and\n * exactly one wins — \"a lease that let both through would be decorative\" (the M81 test says exactly\n * that, and it is right). That is the primitive's contract, and it does not change.\n *\n * But within ONE process it is normal to have more than one store over the same session: the golden\n * compaction tests and the concurrent-send tests do exactly that, and they record real runtime\n * behavior. Applying the raw primitive there would turn a legitimate pattern into a\n * `SessionBusyError`.\n *\n * The reconciliation belongs here, in the primitive's consumer: the process takes ONE lease per path\n * and counts how many stores use it. The last to release actually frees it. Cross-process stays\n * strict — which is the problem M81 states (\"`exec resume --last` can write into the TUI's live\n * session\").\n */\nconst sharedLeases = new Map<string, { lease: SessionWriterLease; refs: number }>();\n\nasync function acquireShared(path: string): Promise<SessionWriterLease> {\n const existing = sharedLeases.get(path);\n if (existing !== undefined) {\n existing.refs++;\n return createProxy(path);\n }\n const lease = await acquireSessionWriter(path);\n sharedLeases.set(path, { lease, refs: 1 });\n return createProxy(path);\n}\n\n/** A handle that decrements the count; the last to release frees the real lease. */\nfunction createProxy(path: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath: path,\n // The proxy forwards the renewal to the ONE real lease this path shares. Renewing through any\n // holder is correct and is the point: the record must say \"someone in this process is still\n // writing\", and the refcount already guarantees they are all the same owner.\n renew: (): void => {\n if (released) return;\n sharedLeases.get(path)?.lease.renew();\n },\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n const entry = sharedLeases.get(path);\n if (entry === undefined) return;\n entry.refs--;\n if (entry.refs > 0) return;\n sharedLeases.delete(path);\n await entry.lease.release();\n },\n };\n}\n\n/**\n * The default `SessionStore` — reads/append-writes the native `.jsonl` transcript.\n */\nexport class FsSessionStore implements SessionStore {\n readonly #baseDir: string;\n readonly #cwd: string;\n /** One lease per `agentId` — a store serves more than one session over the process lifetime. */\n readonly #leases = new Map<string, SessionWriterLease>();\n /** Memoized per agent so read, append and lease can never disagree about which file is the session. */\n readonly #paths = new Map<string, string>();\n\n constructor(options: FsSessionStoreOptions) {\n this.#baseDir = options.baseDir;\n this.#cwd = options.cwd;\n }\n\n /**\n * The file that IS this agent's session.\n *\n * #400 made transcript filenames UUIDs so `claude --continue` can find them. A transcript written\n * before that lives under the old name, and switching to the new one would not lose the file but\n * would abandon it: the agent would start a second, empty history beside its real one. So an\n * existing legacy file wins, and only a session with no file yet gets the new name.\n *\n * Resolved ONCE per agent. Re-deciding per call would let `acquire` lock one path while\n * `appendRecords` wrote another — the lease would be guarding a file nobody was writing.\n */\n #pathFor(agentId: string): string {\n const memoized = this.#paths.get(agentId);\n if (memoized !== undefined) return memoized;\n const canonical = transcriptPath(this.#baseDir, this.#cwd, agentId);\n const legacy = legacyTranscriptPath(this.#baseDir, this.#cwd, agentId);\n const resolved = legacy !== canonical && existsSync(legacy) ? legacy : canonical;\n this.#paths.set(agentId, resolved);\n return resolved;\n }\n\n async readRecords(agentId: string): Promise<SessionRecord[]> {\n return readTranscript(this.#pathFor(agentId));\n }\n\n async appendRecords(agentId: string, records: readonly SessionRecord[]): Promise<void> {\n // Empty delta → nothing to persist (avoids an unnecessary lock + rewrite).\n if (records.length === 0) return;\n const path = this.#pathFor(agentId);\n // mkdir BEFORE the lock: withFileLock's companion `<path>.lock` needs the parent dir.\n await mkdir(dirname(path), { recursive: true });\n\n await withFileLock(path, async () => {\n // M93 — appends the DELTA instead of rewriting the whole file.\n //\n // Before: `readTranscript` + `writeTranscript` of everything, per turn. O(n) of I/O **and** of\n // parsing on every turn, O(n^2) per session — the consumer note in\n // `agents/lib/session/backtrack.ts` records 1.4 MB / 3000 lines over 200 turns.\n //\n // Correct because the format **is already append-only**: the `parentUuid` DAG does not depend\n // on line order, and every record carries its own parent. `appendJsonl` **already existed in\n // the package** and had a single caller (`eval/runner.ts`) — the primitive was there, it was\n // the store that ignored it (rung 4).\n //\n // `withFileLock` stays — but the earlier claim that \"it is what serializes two concurrent\n // `appendRecords`\" was too strong, and M93's adversarial review measured it: removing it fails\n // no test. The reason is the paragraph above — the `parentUuid` DAG does not depend on line\n // order, so two interleaved batches reconstruct identically. The work the lock was doing\n // (protecting a read-modify-write) disappeared along with the rewrite.\n //\n // What it still covers is the TOCTOU window in `needsLineBreakBefore` (read the last byte,\n // then write): without it, two processes can both conclude \"a \\n is missing\" and produce a\n // blank line — which the reader discards, i.e. benign. It stays as a **declared, not\n // mechanized** defense (the discipline in `error-handling.md` § 4: enumerate the residue\n // rather than let a missing test pass for coverage).\n //\n // `writeTranscript` still exists for **compaction**, the one operation that legitimately\n // rewrites the file.\n for (const record of records) appendJsonl(path, record);\n });\n }\n\n /**\n * Takes the session's writer lease. Throws `SessionBusyError` when another process holds it.\n *\n * **Explicit, and NOT inside `appendRecords`** — M95's adversarial review measured why that\n * matters: the `SessionStore` contract states that \"an `appendRecords` rejection is logged to\n * stderr, NOT thrown to the caller (best-effort write)\". Acquiring there made the\n * `SessionBusyError` get **swallowed**, and the result was worse than the original problem:\n * instead of two writers interleaving lines, the loser **lost the turn silently** — nothing on\n * disk, a stderr warning invisible under the TUI, and no way for the caller to react.\n *\n * At init the error reaches someone who can act: `exec` forks to a new id, which is exactly what\n * the error message itself prescribes. It is the difference between failing where a decision is\n * possible and failing where only loss is.\n */\n async acquire(agentId: string): Promise<void> {\n if (this.#leases.has(agentId)) return;\n const path = this.#pathFor(agentId);\n await mkdir(dirname(path), { recursive: true });\n this.#leases.set(agentId, await acquireShared(path));\n }\n\n /**\n * Releases ONE agent's lease.\n *\n * It exists because `dispose()` releases **every** lease the store holds, and a store injected by\n * the consumer may serve several agents: an init that fails for agent B must not free agent A's\n * lease, which is still live and writing.\n */\n async release(agentId: string): Promise<void> {\n const lease = this.#leases.get(agentId);\n if (lease === undefined) return;\n this.#leases.delete(agentId);\n await lease.release();\n }\n\n /**\n * Releases every lease this store holds.\n *\n * Without it the `.writer.lock` outlives the process and the next open would have to wait out the\n * heartbeat window — recoverable, but 30 s of waiting after a CLEAN shutdown would be an avoidable\n * defect. Idempotent: calling it twice is not an error.\n */\n async dispose(): Promise<void> {\n const leases = [...this.#leases.values()];\n this.#leases.clear();\n for (const lease of leases) await lease.release();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/persistence/fs-session-store.ts"],"names":["acquireSessionWriter","transcriptPath","legacyTranscriptPath","existsSync","readTranscript","path","mkdir","dirname","withFileLock","appendJsonl"],"mappings":"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","file":"chunk-FTXY4D44.cjs","sourcesContent":["/**\n * SE41 — `FsSessionStore`, the DEFAULT reference implementation of the pluggable\n * {@link SessionStore} seam. It reads and append-writes the native Claude-shaped\n * `.jsonl` transcript at `<baseDir>/projects/<encoded-cwd>/<agentId>.jsonl` — the\n * exact on-disk format SE40 introduced (the file the Claude Code CLI can\n * `--continue`). Omitting `local.sessionStore` resolves to this store, so the\n * default persistence path is byte-identical to SE40 behavior.\n *\n * `readRecords` is `readTranscript(transcriptPath(...))` (a missing session →\n * `[]`, not an error — a fresh agent has no history). `appendRecords` is a TRUE\n * append: it reads the prior records, concatenates the new-turn delta, and\n * rewrites the whole line set atomically under the SE40 cross-process file lock\n * (`writeTranscript` never shrinks — the native format is an append-only\n * `parentUuid` DAG). The parent dir is created BEFORE acquiring the lock because\n * the lock's companion `<path>.lock` file needs an existing parent dir (the SE40\n * `mkdir(dirname)`-before-lock fix).\n *\n * @internal\n */\n\nimport { existsSync } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport { withFileLock } from \"./file-lock.js\";\nimport { appendJsonl } from \"./jsonl.js\";\nimport {\n legacyTranscriptPath,\n readTranscript,\n type SessionRecord,\n transcriptPath,\n} from \"./session-transcript.js\";\nimport { acquireSessionWriter, type SessionWriterLease } from \"./session-writer.js\";\n\n/** Options identifying the on-disk transcript location for the FS default store. */\nexport interface FsSessionStoreOptions {\n /** Transcript base dir (`~/.theokit` default, `~/.claude` for CLI interop) — already `~`-expanded. */\n baseDir: string;\n /** The workspace cwd whose encoded form is the transcript project dir. */\n cwd: string;\n}\n\n/**\n * Reference-counted leases, shared per path.\n *\n * `acquireSessionWriter` is STRICT on purpose: two concurrent acquisitions of the same path, and\n * exactly one wins — \"a lease that let both through would be decorative\" (the M81 test says exactly\n * that, and it is right). That is the primitive's contract, and it does not change.\n *\n * But within ONE process it is normal to have more than one store over the same session: the golden\n * compaction tests and the concurrent-send tests do exactly that, and they record real runtime\n * behavior. Applying the raw primitive there would turn a legitimate pattern into a\n * `SessionBusyError`.\n *\n * The reconciliation belongs here, in the primitive's consumer: the process takes ONE lease per path\n * and counts how many stores use it. The last to release actually frees it. Cross-process stays\n * strict — which is the problem M81 states (\"`exec resume --last` can write into the TUI's live\n * session\").\n */\nconst sharedLeases = new Map<string, { lease: SessionWriterLease; refs: number }>();\n\nasync function acquireShared(path: string): Promise<SessionWriterLease> {\n const existing = sharedLeases.get(path);\n if (existing !== undefined) {\n existing.refs++;\n return createProxy(path);\n }\n const lease = await acquireSessionWriter(path);\n sharedLeases.set(path, { lease, refs: 1 });\n return createProxy(path);\n}\n\n/** A handle that decrements the count; the last to release frees the real lease. */\nfunction createProxy(path: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath: path,\n // The proxy forwards the renewal to the ONE real lease this path shares. Renewing through any\n // holder is correct and is the point: the record must say \"someone in this process is still\n // writing\", and the refcount already guarantees they are all the same owner.\n renew: (): void => {\n if (released) return;\n sharedLeases.get(path)?.lease.renew();\n },\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n const entry = sharedLeases.get(path);\n if (entry === undefined) return;\n entry.refs--;\n if (entry.refs > 0) return;\n sharedLeases.delete(path);\n await entry.lease.release();\n },\n };\n}\n\n/**\n * The default `SessionStore` — reads/append-writes the native `.jsonl` transcript.\n */\nexport class FsSessionStore implements SessionStore {\n readonly #baseDir: string;\n readonly #cwd: string;\n /** One lease per `agentId` — a store serves more than one session over the process lifetime. */\n readonly #leases = new Map<string, SessionWriterLease>();\n /** Memoized per agent so read, append and lease can never disagree about which file is the session. */\n readonly #paths = new Map<string, string>();\n\n constructor(options: FsSessionStoreOptions) {\n this.#baseDir = options.baseDir;\n this.#cwd = options.cwd;\n }\n\n /**\n * The file that IS this agent's session.\n *\n * #400 made transcript filenames UUIDs so `claude --continue` can find them. A transcript written\n * before that lives under the old name, and switching to the new one would not lose the file but\n * would abandon it: the agent would start a second, empty history beside its real one. So an\n * existing legacy file wins, and only a session with no file yet gets the new name.\n *\n * Resolved ONCE per agent. Re-deciding per call would let `acquire` lock one path while\n * `appendRecords` wrote another — the lease would be guarding a file nobody was writing.\n */\n #pathFor(agentId: string): string {\n const memoized = this.#paths.get(agentId);\n if (memoized !== undefined) return memoized;\n const canonical = transcriptPath(this.#baseDir, this.#cwd, agentId);\n const legacy = legacyTranscriptPath(this.#baseDir, this.#cwd, agentId);\n const resolved = legacy !== canonical && existsSync(legacy) ? legacy : canonical;\n this.#paths.set(agentId, resolved);\n return resolved;\n }\n\n async readRecords(agentId: string): Promise<SessionRecord[]> {\n return readTranscript(this.#pathFor(agentId));\n }\n\n async appendRecords(agentId: string, records: readonly SessionRecord[]): Promise<void> {\n // Empty delta → nothing to persist (avoids an unnecessary lock + rewrite).\n if (records.length === 0) return;\n const path = this.#pathFor(agentId);\n // mkdir BEFORE the lock: withFileLock's companion `<path>.lock` needs the parent dir.\n await mkdir(dirname(path), { recursive: true });\n\n await withFileLock(path, async () => {\n // M93 — appends the DELTA instead of rewriting the whole file.\n //\n // Before: `readTranscript` + `writeTranscript` of everything, per turn. O(n) of I/O **and** of\n // parsing on every turn, O(n^2) per session — the consumer note in\n // `agents/lib/session/backtrack.ts` records 1.4 MB / 3000 lines over 200 turns.\n //\n // Correct because the format **is already append-only**: the `parentUuid` DAG does not depend\n // on line order, and every record carries its own parent. `appendJsonl` **already existed in\n // the package** and had a single caller (`eval/runner.ts`) — the primitive was there, it was\n // the store that ignored it (rung 4).\n //\n // `withFileLock` stays — but the earlier claim that \"it is what serializes two concurrent\n // `appendRecords`\" was too strong, and M93's adversarial review measured it: removing it fails\n // no test. The reason is the paragraph above — the `parentUuid` DAG does not depend on line\n // order, so two interleaved batches reconstruct identically. The work the lock was doing\n // (protecting a read-modify-write) disappeared along with the rewrite.\n //\n // What it still covers is the TOCTOU window in `needsLineBreakBefore` (read the last byte,\n // then write): without it, two processes can both conclude \"a \\n is missing\" and produce a\n // blank line — which the reader discards, i.e. benign. It stays as a **declared, not\n // mechanized** defense (the discipline in `error-handling.md` § 4: enumerate the residue\n // rather than let a missing test pass for coverage).\n //\n // `writeTranscript` still exists for **compaction**, the one operation that legitimately\n // rewrites the file.\n for (const record of records) appendJsonl(path, record);\n });\n }\n\n /**\n * Takes the session's writer lease. Throws `SessionBusyError` when another process holds it.\n *\n * **Explicit, and NOT inside `appendRecords`** — M95's adversarial review measured why that\n * matters: the `SessionStore` contract states that \"an `appendRecords` rejection is logged to\n * stderr, NOT thrown to the caller (best-effort write)\". Acquiring there made the\n * `SessionBusyError` get **swallowed**, and the result was worse than the original problem:\n * instead of two writers interleaving lines, the loser **lost the turn silently** — nothing on\n * disk, a stderr warning invisible under the TUI, and no way for the caller to react.\n *\n * At init the error reaches someone who can act: `exec` forks to a new id, which is exactly what\n * the error message itself prescribes. It is the difference between failing where a decision is\n * possible and failing where only loss is.\n */\n async acquire(agentId: string): Promise<void> {\n if (this.#leases.has(agentId)) return;\n const path = this.#pathFor(agentId);\n await mkdir(dirname(path), { recursive: true });\n this.#leases.set(agentId, await acquireShared(path));\n }\n\n /**\n * Releases ONE agent's lease.\n *\n * It exists because `dispose()` releases **every** lease the store holds, and a store injected by\n * the consumer may serve several agents: an init that fails for agent B must not free agent A's\n * lease, which is still live and writing.\n */\n async release(agentId: string): Promise<void> {\n const lease = this.#leases.get(agentId);\n if (lease === undefined) return;\n this.#leases.delete(agentId);\n await lease.release();\n }\n\n /**\n * Releases every lease this store holds.\n *\n * Without it the `.writer.lock` outlives the process and the next open would have to wait out the\n * heartbeat window — recoverable, but 30 s of waiting after a CLEAN shutdown would be an avoidable\n * defect. Idempotent: calling it twice is not an error.\n */\n async dispose(): Promise<void> {\n const leases = [...this.#leases.values()];\n this.#leases.clear();\n for (const lease of leases) await lease.release();\n }\n}\n"]}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { resolveMemoryRoot, readFactsFromMarkdown, defaultIndexPath, openMemoryDb, lanceStoragePath, LanceIndex } from './chunk-LCCT5ZXJ.js';
|
|
2
|
+
import { withCwdMutex } from './chunk-PPU2D4FZ.js';
|
|
3
|
+
import { replaceFileAtomic } from './chunk-M4U24U6G.js';
|
|
4
|
+
import { ConfigurationError } from './chunk-TNFOU6SB.js';
|
|
5
|
+
import { diag, redactSecrets } from './chunk-3OY6H3I7.js';
|
|
6
|
+
import { createHash } from 'crypto';
|
|
7
|
+
import { readFile, mkdir } from 'fs/promises';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { existsSync, rmSync, mkdirSync, renameSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
function diaryPath(root) {
|
|
12
|
+
return join(root, "dream-diary.md");
|
|
13
|
+
}
|
|
14
|
+
function renderDiaryEntry(entry) {
|
|
15
|
+
const stamp = new Date(entry.timestampMs).toISOString();
|
|
16
|
+
const hash = entryHash(entry).slice(0, 8);
|
|
17
|
+
return [
|
|
18
|
+
`## ${stamp}`,
|
|
19
|
+
"",
|
|
20
|
+
`- entry-hash: ${hash}`,
|
|
21
|
+
`- facts before: ${entry.factsBefore}`,
|
|
22
|
+
`- facts after: ${entry.factsAfter}`,
|
|
23
|
+
`- duplicates removed: ${entry.duplicatesRemoved}`,
|
|
24
|
+
`- clusters created: ${entry.clustersCreated}`,
|
|
25
|
+
`- notes written: ${entry.notesWritten}`,
|
|
26
|
+
""
|
|
27
|
+
].join("\n");
|
|
28
|
+
}
|
|
29
|
+
async function appendDiaryEntry(root, entry) {
|
|
30
|
+
const path = diaryPath(root);
|
|
31
|
+
let raw = "";
|
|
32
|
+
try {
|
|
33
|
+
raw = await readFile(path, "utf8");
|
|
34
|
+
} catch {
|
|
35
|
+
raw = "# Dream Diary\n\n";
|
|
36
|
+
}
|
|
37
|
+
const next = `${raw.endsWith("\n") ? raw : `${raw}
|
|
38
|
+
`}${renderDiaryEntry(entry)}`;
|
|
39
|
+
await replaceFileAtomic(path, next);
|
|
40
|
+
}
|
|
41
|
+
function entryHash(entry) {
|
|
42
|
+
return createHash("sha256").update(
|
|
43
|
+
[
|
|
44
|
+
entry.factsBefore,
|
|
45
|
+
entry.factsAfter,
|
|
46
|
+
entry.duplicatesRemoved,
|
|
47
|
+
entry.clustersCreated,
|
|
48
|
+
entry.notesWritten
|
|
49
|
+
].join("|")
|
|
50
|
+
).digest("hex");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/internal/memory/dreaming/phases.ts
|
|
54
|
+
var DEFAULT_DEDUP_THRESHOLD = 0.95;
|
|
55
|
+
var DEFAULT_CLUSTER_THRESHOLD = 0.75;
|
|
56
|
+
var CONSOLIDATABLE_KINDS = /* @__PURE__ */ new Set(["project"]);
|
|
57
|
+
var ATOMIC_KINDS = /* @__PURE__ */ new Set(["user", "feedback", "reference"]);
|
|
58
|
+
function dedupPolicy(fact) {
|
|
59
|
+
if (fact.kind === void 0) return "exact";
|
|
60
|
+
if (ATOMIC_KINDS.has(fact.kind)) return "never";
|
|
61
|
+
return CONSOLIDATABLE_KINDS.has(fact.kind) ? "similar" : "never";
|
|
62
|
+
}
|
|
63
|
+
function normalizeForExactMatch(text) {
|
|
64
|
+
return text.trim().toLowerCase().replace(/\s+/g, " ").replace(/[.!?]+$/, "");
|
|
65
|
+
}
|
|
66
|
+
async function lightPhase(facts, embedding, threshold = DEFAULT_DEDUP_THRESHOLD) {
|
|
67
|
+
if (facts.length <= 1) return { kept: [...facts], duplicatesRemoved: 0 };
|
|
68
|
+
const never = facts.filter((f) => dedupPolicy(f) === "never");
|
|
69
|
+
const exact = facts.filter((f) => dedupPolicy(f) === "exact");
|
|
70
|
+
const similar = facts.filter((f) => dedupPolicy(f) === "similar");
|
|
71
|
+
const seen = /* @__PURE__ */ new Set();
|
|
72
|
+
const exactKept = [];
|
|
73
|
+
let removed = 0;
|
|
74
|
+
for (const f of exact) {
|
|
75
|
+
const key = normalizeForExactMatch(f.text);
|
|
76
|
+
if (seen.has(key)) {
|
|
77
|
+
removed += 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
seen.add(key);
|
|
81
|
+
exactKept.push(f);
|
|
82
|
+
}
|
|
83
|
+
const sim = similar.length > 1 ? await dedupCandidates(similar, embedding, threshold) : { kept: [...similar], duplicatesRemoved: 0 };
|
|
84
|
+
return {
|
|
85
|
+
kept: [...never, ...exactKept, ...sim.kept],
|
|
86
|
+
duplicatesRemoved: removed + sim.duplicatesRemoved
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function dedupCandidates(facts, embedding, threshold) {
|
|
90
|
+
if (facts.length <= 1) return { kept: [...facts], duplicatesRemoved: 0 };
|
|
91
|
+
const vectors = await embedding.embed(facts.map((f) => f.text));
|
|
92
|
+
const keptIdx = [];
|
|
93
|
+
const keptVecs = [];
|
|
94
|
+
for (let i = 0; i < facts.length; i++) {
|
|
95
|
+
const vec = vectors[i] ?? [];
|
|
96
|
+
const isDup = keptVecs.some((kept2) => cosineSimilarity(vec, kept2) >= threshold);
|
|
97
|
+
if (isDup) continue;
|
|
98
|
+
keptIdx.push(i);
|
|
99
|
+
keptVecs.push(vec);
|
|
100
|
+
}
|
|
101
|
+
const kept = keptIdx.map((i) => facts[i]);
|
|
102
|
+
return { kept, duplicatesRemoved: facts.length - kept.length };
|
|
103
|
+
}
|
|
104
|
+
var DEFAULT_MAX_FACTS_PER_SWEEP = 500;
|
|
105
|
+
async function remPhase(facts, embedding, threshold = DEFAULT_CLUSTER_THRESHOLD, maxFactsPerSweep = DEFAULT_MAX_FACTS_PER_SWEEP) {
|
|
106
|
+
if (facts.length === 0) return { clusters: [], factsLeftOut: 0 };
|
|
107
|
+
const capped = facts.length > maxFactsPerSweep ? facts.slice(0, maxFactsPerSweep) : facts;
|
|
108
|
+
const vectors = await embedding.embed(capped.map((f) => f.text));
|
|
109
|
+
const clusterOfIdx = unionFindByPairs(vectors, threshold);
|
|
110
|
+
const groups = bucketFactsByClusterRoot(capped, clusterOfIdx);
|
|
111
|
+
return {
|
|
112
|
+
clusters: [...groups.values()].map(buildClusterFromMembers),
|
|
113
|
+
factsLeftOut: facts.length - capped.length
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function unionFindByPairs(vectors, threshold) {
|
|
117
|
+
const clusterOfIdx = vectors.map((_, i) => i);
|
|
118
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
119
|
+
for (let j = i + 1; j < vectors.length; j++) {
|
|
120
|
+
if (cosineSimilarity(vectors[i] ?? [], vectors[j] ?? []) >= threshold) {
|
|
121
|
+
unifyClusters(clusterOfIdx, i, j);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return clusterOfIdx;
|
|
126
|
+
}
|
|
127
|
+
function bucketFactsByClusterRoot(facts, clusterOfIdx) {
|
|
128
|
+
const groups = /* @__PURE__ */ new Map();
|
|
129
|
+
for (let i = 0; i < facts.length; i++) {
|
|
130
|
+
const root = findRoot(clusterOfIdx, i);
|
|
131
|
+
const list = groups.get(root) ?? [];
|
|
132
|
+
list.push(facts[i]);
|
|
133
|
+
groups.set(root, list);
|
|
134
|
+
}
|
|
135
|
+
return groups;
|
|
136
|
+
}
|
|
137
|
+
function buildClusterFromMembers(members) {
|
|
138
|
+
const sorted = [...members].sort((a, b) => b.text.length - a.text.length);
|
|
139
|
+
return { representativeText: sorted[0]?.text ?? "", members };
|
|
140
|
+
}
|
|
141
|
+
function deepPhase(clusters, timestampMs) {
|
|
142
|
+
if (clusters.length === 0) return "";
|
|
143
|
+
const isoStamp = new Date(timestampMs).toISOString();
|
|
144
|
+
const lines = [`# Dreamed ${isoStamp}`, ""];
|
|
145
|
+
for (let i = 0; i < clusters.length; i++) {
|
|
146
|
+
const c = clusters[i];
|
|
147
|
+
if (c === void 0) continue;
|
|
148
|
+
lines.push(`## Cluster ${i + 1}: ${c.representativeText}`);
|
|
149
|
+
lines.push("");
|
|
150
|
+
for (const member of c.members) lines.push(`- ${member.text}`);
|
|
151
|
+
lines.push("");
|
|
152
|
+
}
|
|
153
|
+
return `${lines.join("\n")}
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
function cosineSimilarity(a, b) {
|
|
157
|
+
if (a.length === 0 || b.length === 0 || a.length !== b.length) return 0;
|
|
158
|
+
let dot = 0;
|
|
159
|
+
let aNorm = 0;
|
|
160
|
+
let bNorm = 0;
|
|
161
|
+
for (let i = 0; i < a.length; i++) {
|
|
162
|
+
const ai = a[i] ?? 0;
|
|
163
|
+
const bi = b[i] ?? 0;
|
|
164
|
+
dot += ai * bi;
|
|
165
|
+
aNorm += ai * ai;
|
|
166
|
+
bNorm += bi * bi;
|
|
167
|
+
}
|
|
168
|
+
const denom = Math.sqrt(aNorm) * Math.sqrt(bNorm);
|
|
169
|
+
return denom === 0 ? 0 : dot / denom;
|
|
170
|
+
}
|
|
171
|
+
function findRoot(parents, i) {
|
|
172
|
+
let root = i;
|
|
173
|
+
while (parents[root] !== root) {
|
|
174
|
+
const next = parents[root] ?? root;
|
|
175
|
+
if (next === root) break;
|
|
176
|
+
root = next;
|
|
177
|
+
}
|
|
178
|
+
parents[i] = root;
|
|
179
|
+
return root;
|
|
180
|
+
}
|
|
181
|
+
function unifyClusters(parents, a, b) {
|
|
182
|
+
const rootA = findRoot(parents, a);
|
|
183
|
+
const rootB = findRoot(parents, b);
|
|
184
|
+
if (rootA !== rootB) parents[rootB] = rootA;
|
|
185
|
+
}
|
|
186
|
+
function runDreamingSweep(options) {
|
|
187
|
+
return withCwdMutex(`dream:${options.cwd}`, () => runInner(options));
|
|
188
|
+
}
|
|
189
|
+
async function runInner(options) {
|
|
190
|
+
const now = options.now ?? Date.now;
|
|
191
|
+
const timestampMs = now();
|
|
192
|
+
try {
|
|
193
|
+
const root = options.memoryRoot ?? resolveMemoryRoot(options.cwd);
|
|
194
|
+
const facts = await readFactsFromMarkdown(
|
|
195
|
+
options.cwd,
|
|
196
|
+
options.memoryRoot ? { directory: options.memoryRoot } : void 0
|
|
197
|
+
);
|
|
198
|
+
if (facts.length === 0) {
|
|
199
|
+
return emptyResult("skipped");
|
|
200
|
+
}
|
|
201
|
+
const dedup = await lightPhase(facts, options.embedding, options.dedupThreshold);
|
|
202
|
+
const rem = await remPhase(dedup.kept, options.embedding, options.clusterThreshold);
|
|
203
|
+
const notesWritten = await writeConsolidatedNotes(root, rem.clusters, timestampMs);
|
|
204
|
+
const result = {
|
|
205
|
+
status: "ok",
|
|
206
|
+
factsBefore: facts.length,
|
|
207
|
+
factsAfter: dedup.kept.length,
|
|
208
|
+
duplicatesRemoved: dedup.duplicatesRemoved,
|
|
209
|
+
clustersCreated: rem.clusters.length,
|
|
210
|
+
notesWritten,
|
|
211
|
+
diaryEntryHash: void 0,
|
|
212
|
+
factsLeftOut: rem.factsLeftOut
|
|
213
|
+
};
|
|
214
|
+
await appendDiaryEntry(root, {
|
|
215
|
+
timestampMs,
|
|
216
|
+
factsBefore: result.factsBefore,
|
|
217
|
+
factsAfter: result.factsAfter,
|
|
218
|
+
duplicatesRemoved: result.duplicatesRemoved,
|
|
219
|
+
clustersCreated: result.clustersCreated,
|
|
220
|
+
notesWritten: result.notesWritten
|
|
221
|
+
});
|
|
222
|
+
return result;
|
|
223
|
+
} catch (cause) {
|
|
224
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
225
|
+
diag(`[theokit-sdk] dreaming sweep failed: ${message}
|
|
226
|
+
`);
|
|
227
|
+
return emptyResult("error");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async function writeConsolidatedNotes(root, clusters, timestampMs) {
|
|
231
|
+
if (clusters.length === 0) return 0;
|
|
232
|
+
const notesDir = join(root, "notes");
|
|
233
|
+
await mkdir(notesDir, { recursive: true });
|
|
234
|
+
const isoSlug = new Date(timestampMs).toISOString().replace(/[^\dT]/g, "-");
|
|
235
|
+
const file = join(notesDir, `dreamed-${isoSlug}.md`);
|
|
236
|
+
const body = deepPhase(clusters, timestampMs);
|
|
237
|
+
await replaceFileAtomic(file, body);
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
function emptyResult(status) {
|
|
241
|
+
const result = {
|
|
242
|
+
status,
|
|
243
|
+
factsBefore: 0,
|
|
244
|
+
factsAfter: 0,
|
|
245
|
+
duplicatesRemoved: 0,
|
|
246
|
+
clustersCreated: 0,
|
|
247
|
+
notesWritten: 0,
|
|
248
|
+
diaryEntryHash: void 0
|
|
249
|
+
};
|
|
250
|
+
return status === "skipped" ? { ...result, factsLeftOut: 0 } : result;
|
|
251
|
+
}
|
|
252
|
+
async function readAllSqliteFacts(root) {
|
|
253
|
+
const dbPath = defaultIndexPath(root);
|
|
254
|
+
if (!existsSync(dbPath)) return [];
|
|
255
|
+
const db = await openMemoryDb({ filePath: dbPath });
|
|
256
|
+
try {
|
|
257
|
+
const stmt = db.prepare("SELECT id, path, source, start_line, end_line, text FROM chunks");
|
|
258
|
+
const rows = stmt.all();
|
|
259
|
+
return rows.map((r) => ({
|
|
260
|
+
...r,
|
|
261
|
+
namespace: "default",
|
|
262
|
+
scope: "agent",
|
|
263
|
+
user_id: "default"
|
|
264
|
+
}));
|
|
265
|
+
} finally {
|
|
266
|
+
db.close();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function nfcEqual(a, b) {
|
|
270
|
+
return a.normalize("NFC") === b.normalize("NFC");
|
|
271
|
+
}
|
|
272
|
+
function assertUsableBatchSize(batchSize) {
|
|
273
|
+
if (batchSize === void 0) return;
|
|
274
|
+
if (!Number.isInteger(batchSize) || batchSize < 1) {
|
|
275
|
+
throw new ConfigurationError(
|
|
276
|
+
`migrateSqliteToLance: batchSize must be a whole number of at least 1; received ${String(batchSize)}.`,
|
|
277
|
+
{ code: "invalid_batch_size" }
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function migrateSqliteToLance(opts) {
|
|
282
|
+
assertUsableBatchSize(opts.batchSize);
|
|
283
|
+
const cwd = opts.cwd;
|
|
284
|
+
const memoryRoot = resolveMemoryRoot(cwd, { directory: opts.directory });
|
|
285
|
+
const finalPath = lanceStoragePath(memoryRoot);
|
|
286
|
+
const newPath = join(memoryRoot, "lance-new");
|
|
287
|
+
const rawLog = opts.logger ?? ((m) => console.log(m));
|
|
288
|
+
const log = (m) => rawLog(redactSecrets(m));
|
|
289
|
+
if (existsSync(finalPath)) {
|
|
290
|
+
throw new ConfigurationError(
|
|
291
|
+
`Destination already exists: ${finalPath}. Remove it manually (\`rm -rf ${finalPath}\`) and re-run.`,
|
|
292
|
+
{ code: "migration_destination_exists" }
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
if (existsSync(newPath)) {
|
|
296
|
+
log(`Removing stale ${newPath} from a previous run...`);
|
|
297
|
+
rmSync(newPath, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
log(`Reading SQLite facts from ${cwd}/.theokit/memory/index.sqlite ...`);
|
|
300
|
+
const sqliteFacts = await readAllSqliteFacts(memoryRoot);
|
|
301
|
+
log(`SQLite has ${sqliteFacts.length} facts.`);
|
|
302
|
+
if (sqliteFacts.length === 0) {
|
|
303
|
+
return {
|
|
304
|
+
countSqlite: 0,
|
|
305
|
+
countLance: 0,
|
|
306
|
+
validated: true,
|
|
307
|
+
sampleComparisons: [],
|
|
308
|
+
lancePath: finalPath,
|
|
309
|
+
committed: false
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
const placeholderEmbedding = {
|
|
313
|
+
id: "migration-placeholder",
|
|
314
|
+
model: "deterministic-hash",
|
|
315
|
+
dimension: 8,
|
|
316
|
+
embed: async (texts) => {
|
|
317
|
+
return texts.map((t) => {
|
|
318
|
+
const arr = new Array(8).fill(0);
|
|
319
|
+
for (let i = 0; i < t.length; i += 1) {
|
|
320
|
+
arr[i % 8] += t.charCodeAt(i);
|
|
321
|
+
}
|
|
322
|
+
return arr.map((x) => x / 1e3);
|
|
323
|
+
});
|
|
324
|
+
},
|
|
325
|
+
stats: () => ({ cacheHits: 0, cacheMisses: 0, httpCalls: 0, retries: 0 })
|
|
326
|
+
};
|
|
327
|
+
mkdirSync(newPath, { recursive: true });
|
|
328
|
+
log(`Writing Lance index to ${newPath} ...`);
|
|
329
|
+
const lance = await LanceIndex.open({
|
|
330
|
+
cwd,
|
|
331
|
+
embedding: placeholderEmbedding,
|
|
332
|
+
storagePath: newPath
|
|
333
|
+
});
|
|
334
|
+
const batchSize = opts.batchSize ?? 100;
|
|
335
|
+
for (let i = 0; i < sqliteFacts.length; i += batchSize) {
|
|
336
|
+
const batch = sqliteFacts.slice(i, i + batchSize).map((f) => ({
|
|
337
|
+
id: f.id,
|
|
338
|
+
text: f.text,
|
|
339
|
+
source: f.source,
|
|
340
|
+
namespace: f.namespace ?? "default",
|
|
341
|
+
scope: f.scope ?? "agent",
|
|
342
|
+
user_id: f.user_id ?? "default",
|
|
343
|
+
timestamp: 0
|
|
344
|
+
}));
|
|
345
|
+
await lance.addFacts(batch);
|
|
346
|
+
log(` Migrated ${Math.min(i + batchSize, sqliteFacts.length)}/${sqliteFacts.length}`);
|
|
347
|
+
}
|
|
348
|
+
const lanceCount = await lance.countFacts("default");
|
|
349
|
+
log(`Lance has ${lanceCount} facts (expected ${sqliteFacts.length}).`);
|
|
350
|
+
const sampleSize = Math.min(10, sqliteFacts.length);
|
|
351
|
+
const sampleComparisons = [];
|
|
352
|
+
for (let i = 0; i < sampleSize; i += 1) {
|
|
353
|
+
const sqliteFact = sqliteFacts[Math.floor(i * sqliteFacts.length / sampleSize)];
|
|
354
|
+
if (sqliteFact === void 0) continue;
|
|
355
|
+
const hits = await lance.search(sqliteFact.text, {
|
|
356
|
+
namespace: sqliteFact.namespace ?? "default",
|
|
357
|
+
limit: 1
|
|
358
|
+
});
|
|
359
|
+
const hit = hits[0];
|
|
360
|
+
const match = hit !== void 0 && nfcEqual(hit.text, sqliteFact.text);
|
|
361
|
+
sampleComparisons.push({ id: sqliteFact.id, match });
|
|
362
|
+
}
|
|
363
|
+
const allMatch = sampleComparisons.every((c) => c.match);
|
|
364
|
+
const countMatch = lanceCount === sqliteFacts.length;
|
|
365
|
+
const validated = allMatch && countMatch;
|
|
366
|
+
await lance.close();
|
|
367
|
+
if (opts.dryRun === true) {
|
|
368
|
+
log("Dry-run mode \u2014 discarding Lance staging dir.");
|
|
369
|
+
rmSync(newPath, { recursive: true, force: true });
|
|
370
|
+
return {
|
|
371
|
+
countSqlite: sqliteFacts.length,
|
|
372
|
+
countLance: lanceCount,
|
|
373
|
+
validated,
|
|
374
|
+
sampleComparisons,
|
|
375
|
+
lancePath: finalPath,
|
|
376
|
+
committed: false
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
if (!validated) {
|
|
380
|
+
log("Validation FAILED \u2014 leaving SQLite intact, removing Lance staging dir.");
|
|
381
|
+
rmSync(newPath, { recursive: true, force: true });
|
|
382
|
+
return {
|
|
383
|
+
countSqlite: sqliteFacts.length,
|
|
384
|
+
countLance: lanceCount,
|
|
385
|
+
validated: false,
|
|
386
|
+
sampleComparisons,
|
|
387
|
+
lancePath: finalPath,
|
|
388
|
+
committed: false
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
renameSync(newPath, finalPath);
|
|
392
|
+
log(`Migration committed to ${finalPath}.`);
|
|
393
|
+
return {
|
|
394
|
+
countSqlite: sqliteFacts.length,
|
|
395
|
+
countLance: lanceCount,
|
|
396
|
+
validated: true,
|
|
397
|
+
sampleComparisons,
|
|
398
|
+
lancePath: finalPath,
|
|
399
|
+
committed: true
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export { appendDiaryEntry, deepPhase, diaryPath, entryHash, lightPhase, migrateSqliteToLance, readAllSqliteFacts, remPhase, renderDiaryEntry, runDreamingSweep };
|
|
404
|
+
//# sourceMappingURL=chunk-INVEGVGO.js.map
|
|
405
|
+
//# sourceMappingURL=chunk-INVEGVGO.js.map
|