@theokit/sdk 5.13.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 +57 -0
- package/dist/{agent-DSMuwADp.d.ts → agent-B6C9i_qu.d.ts} +31 -213
- package/dist/{agent-iW5kPpfT.d.cts → agent-B6FQad6P.d.cts} +31 -213
- package/dist/{agent-5DSDDH7K.js → agent-DRDGUU6T.js} +21 -20
- package/dist/{agent-5DSDDH7K.js.map → agent-DRDGUU6T.js.map} +1 -1
- package/dist/{agent-WOMRT3KX.cjs → agent-VI2HITPT.cjs} +22 -21
- package/dist/{agent-WOMRT3KX.cjs.map → agent-VI2HITPT.cjs.map} +1 -1
- package/dist/agent.d.ts +2 -3
- package/dist/auth/index.cjs +17 -16
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.js +5 -4
- package/dist/auth/index.js.map +1 -1
- 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-XOYNB3YN.js → chunk-23SS4ID4.js} +3 -3
- package/dist/{chunk-XOYNB3YN.js.map → chunk-23SS4ID4.js.map} +1 -1
- package/dist/{chunk-NHOEXFZT.js → chunk-2HXTNVLM.js} +5 -2
- package/dist/chunk-2HXTNVLM.js.map +1 -0
- 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-3FSBE4PS.js → chunk-3GTN4HOO.js} +10 -6
- package/dist/chunk-3GTN4HOO.js.map +1 -0
- package/dist/{chunk-7WARNHWW.cjs → chunk-3UMPP553.cjs} +5 -2
- package/dist/chunk-3UMPP553.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-QISSETTG.cjs → chunk-5645PMVU.cjs} +6 -4
- package/dist/chunk-5645PMVU.cjs.map +1 -0
- package/dist/{chunk-JX4LB6XO.js → chunk-5M5GQYBM.js} +617 -250
- package/dist/chunk-5M5GQYBM.js.map +1 -0
- package/dist/{chunk-KL7ZUMK3.cjs → chunk-64ECM3XM.cjs} +704 -337
- 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-5A4UFBHW.cjs → chunk-A77UMVMD.cjs} +15 -11
- package/dist/chunk-A77UMVMD.cjs.map +1 -0
- package/dist/{chunk-ZTP6ZACR.js → chunk-ARC2V7NY.js} +3 -3
- package/dist/{chunk-ZTP6ZACR.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-2SFLLOMN.js → chunk-CBBT42VX.js} +3 -3
- package/dist/{chunk-2SFLLOMN.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-4UYHP6NF.cjs → chunk-DJKGDLYZ.cjs} +93 -63
- 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-2PRGRYEQ.js → chunk-IOBHFP2E.js} +50 -20
- package/dist/chunk-IOBHFP2E.js.map +1 -0
- package/dist/{chunk-47EACDOC.cjs → chunk-KMFBNKCT.cjs} +4 -4
- package/dist/{chunk-47EACDOC.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-S46WJTDL.cjs → chunk-P7QYG4RZ.cjs} +27 -19
- 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-MS3TQSXU.js → chunk-QD6B6W6I.js} +6 -4
- package/dist/chunk-QD6B6W6I.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-NWWTYBD3.cjs → chunk-RTDVSHJR.cjs} +5 -5
- package/dist/{chunk-NWWTYBD3.cjs.map → chunk-RTDVSHJR.cjs.map} +1 -1
- package/dist/{chunk-5HSMH2ZZ.js → chunk-RZL2W4C3.js} +25 -17
- package/dist/chunk-RZL2W4C3.js.map +1 -0
- package/dist/chunk-SPEN7RJU.cjs +18 -0
- package/dist/{chunk-LO6LIUZ6.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-DVP3YMDA.cjs → chunk-U7PRI3XM.cjs} +3 -3
- package/dist/{chunk-DVP3YMDA.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-NKQVLWDF.js → chunk-YKNBTHMD.js} +3 -3
- package/dist/{chunk-NKQVLWDF.js.map → chunk-YKNBTHMD.js.map} +1 -1
- package/dist/{compact-session-B7P6EU3Z.cjs → compact-session-NX7HUNTB.cjs} +18 -18
- package/dist/{compact-session-B7P6EU3Z.cjs.map → compact-session-NX7HUNTB.cjs.map} +1 -1
- package/dist/{compact-session-5HMX6KYT.js → compact-session-Q5IGNFPW.js} +9 -9
- package/dist/{compact-session-5HMX6KYT.js.map → compact-session-Q5IGNFPW.js.map} +1 -1
- package/dist/{cron-BvxrLW5J.d.cts → cron-Bh3aWgQ2.d.cts} +2 -2
- package/dist/{cron-DdKzwJqK.d.ts → cron-CCDxeeFZ.d.ts} +2 -2
- package/dist/cron.cjs +21 -20
- package/dist/cron.d.cts +4 -3
- package/dist/cron.d.ts +4 -3
- package/dist/cron.js +20 -19
- package/dist/eval.cjs +20 -19
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +19 -18
- 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 +93 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -46
- package/dist/index.d.ts +13 -46
- package/dist/index.js +52 -236
- package/dist/index.js.map +1 -1
- package/dist/{inject-session-75L3NCNL.js → inject-session-4ROYLZHY.js} +3 -3
- package/dist/{inject-session-75L3NCNL.js.map → inject-session-4ROYLZHY.js.map} +1 -1
- package/dist/{inject-session-WOH7TPDX.cjs → inject-session-AWKDCGHV.cjs} +4 -4
- package/dist/{inject-session-WOH7TPDX.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/prune-tool-results.d.ts +37 -0
- package/dist/internal/agent-loop/types.d.ts +6 -0
- package/dist/internal/auth/credential-store.d.ts +1 -1
- package/dist/internal/auth/types.d.ts +4 -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/internal/runtime/skills/binary-on-path.d.ts +1 -0
- package/dist/internal/runtime/skills/discover-skills.d.ts +2 -0
- 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 +11 -11
- package/dist/models.js +4 -4
- 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 +6 -6
- package/dist/providers.js +4 -4
- 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-4-dXI2GS.d.cts → sdk-agent-BA_6_wtc.d.cts} +57 -4
- package/dist/{sdk-agent-CTOJlWA_.d.ts → sdk-agent-CnwPODYH.d.ts} +57 -4
- package/dist/session-store-Dh07jr9N.d.cts +209 -0
- package/dist/session-store-Dh07jr9N.d.ts +209 -0
- package/dist/skills.cjs +4 -4
- package/dist/skills.js +1 -1
- 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 +20 -1
- package/dist/types/budget-tracker.d.ts +6 -2
- package/dist/types/mcp.d.ts +14 -0
- package/dist/types/run.d.ts +42 -3
- package/dist/workflow.d.cts +1 -1
- package/dist/workflow.d.ts +1 -1
- package/docs/adr/mutation-testing-scope.md +66 -0
- package/docs/error-codes.md +48 -45
- package/docs/harness-capability-map.md +60 -6
- package/package.json +1 -1
- package/dist/chunk-2PRGRYEQ.js.map +0 -1
- package/dist/chunk-3FSBE4PS.js.map +0 -1
- package/dist/chunk-4UYHP6NF.cjs.map +0 -1
- package/dist/chunk-5A4UFBHW.cjs.map +0 -1
- package/dist/chunk-5HSMH2ZZ.js.map +0 -1
- package/dist/chunk-6CXRYPG7.cjs +0 -211
- package/dist/chunk-6CXRYPG7.cjs.map +0 -1
- package/dist/chunk-7WARNHWW.cjs.map +0 -1
- package/dist/chunk-JX4LB6XO.js.map +0 -1
- package/dist/chunk-KL7ZUMK3.cjs.map +0 -1
- package/dist/chunk-LO6LIUZ6.cjs +0 -18
- package/dist/chunk-MS3TQSXU.js.map +0 -1
- package/dist/chunk-MSEL5E5P.js +0 -204
- package/dist/chunk-MSEL5E5P.js.map +0 -1
- package/dist/chunk-NHOEXFZT.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-QISSETTG.cjs.map +0 -1
- package/dist/chunk-RN3XDZGS.js.map +0 -1
- package/dist/chunk-S46WJTDL.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/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
package/dist/persistence.d.cts
CHANGED
|
@@ -1,41 +1,745 @@
|
|
|
1
|
+
export { S as SqliteSessionStore, a as SqliteSessionStoreOptions, w as withCwdMutex } from './sqlite-session-store-TKXtT4ON.cjs';
|
|
2
|
+
import { T as TheokitAgentError } from './errors-base-B5g01neU.cjs';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export { T as TranscriptBlock, b as TranscriptMessage } from './session-store-Dh07jr9N.cjs';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
* M107 — temp-file creation control, shared by `replaceFileAtomic`,
|
|
8
|
+
* `atomicWriteJson` and `atomicWriteText`.
|
|
9
|
+
*
|
|
10
|
+
* Both fields are OPTIONAL and the default is byte-identical to the behavior
|
|
11
|
+
* before M107. See `replaceFileAtomic` for why the mode reassertion
|
|
12
|
+
* is conditional.
|
|
13
|
+
*
|
|
14
|
+
*/
|
|
15
|
+
interface AtomicWriteFileOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Permission bits of the created file. Default: `0o600`, the previous fixed
|
|
18
|
+
* literal — filtered by the `umask`, as it always was. When provided, the mode is
|
|
19
|
+
* reasserted on the descriptor, so the `umask` cannot silently
|
|
20
|
+
* clearing bits the caller asked for.
|
|
21
|
+
*/
|
|
22
|
+
mode?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Create the temp file with `wx` (exclusive creation) instead of `w`. Default:
|
|
25
|
+
* `false` — the previous flag. With `true`, a pre-existing temp file becomes
|
|
26
|
+
* `EEXIST` instead of being truncated.
|
|
27
|
+
*/
|
|
28
|
+
exclusive?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* U-9 — the file a leftover temp was replacing, or `undefined` if this is not one of ours.
|
|
32
|
+
*
|
|
33
|
+
* A crash between the open and the rename leaves `<file>.<pid>.<hex>.tmp` behind, and nothing here
|
|
34
|
+
* collects it: this module creates temps and has no opinion about sweeping them. A consumer that
|
|
35
|
+
* wants to has to know the format — which lived only in the implementation, so one copied it out of
|
|
36
|
+
* a compiled chunk as a regex. That copy would have gone on reporting "nothing to collect" the day
|
|
37
|
+
* the format changed, which is the quietest possible way for a cleanup to stop working.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately strict: the pid must be digits and the suffix exactly 16 hex characters. Matching any
|
|
40
|
+
* `.tmp` would claim editors' swap files and other tools' scratch, on a path whose purpose is
|
|
41
|
+
* deleting them.
|
|
42
|
+
*/
|
|
43
|
+
declare function atomicWriteTempTarget(name: string): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Atomic file replacement: write content to a per-call unique tmp path,
|
|
46
|
+
* fsync, then rename over the target. Crash mid-write leaves either the old
|
|
47
|
+
* file intact or the new file complete — never a half-written file.
|
|
48
|
+
*
|
|
49
|
+
* The tmp suffix is `<pid>.<rand>.tmp` so parallel processes (and concurrent
|
|
50
|
+
* burst writes within one process) never collide on the same tmp path — a
|
|
51
|
+
* race that would manifest as `ENOENT` on `rename` after the rival process
|
|
52
|
+
* already moved its tmp into place.
|
|
53
|
+
*
|
|
54
|
+
* Mirrors peer-project's `replaceFileAtomic` from
|
|
55
|
+
* `reference/peer-project/packages/memory-host-sdk/src/host/fs-utils.ts` with
|
|
56
|
+
* the multi-writer robustness fix.
|
|
57
|
+
*
|
|
58
|
+
* ## M107 — `options` is optional, and the default is byte-identical
|
|
59
|
+
*
|
|
60
|
+
* The third parameter is additive: every earlier caller still compiles and
|
|
61
|
+
* writing exactly the same bytes, with the same mode, to the same path.
|
|
62
|
+
*
|
|
63
|
+
* The mode reassertion (`handle.chmod`) is **conditional on `mode !== undefined`**,
|
|
64
|
+
* and that is not cosmetic. `open`'s mode argument is filtered by the `umask`,
|
|
65
|
+
* which only CLEARS bits — measured in this codebase before the change:
|
|
66
|
+
*
|
|
67
|
+
* ```
|
|
68
|
+
* umask 0o002 -> 0o600 umask 0o022 -> 0o600 umask 0o200 -> 0o400
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* An unconditional `chmod` would take the third case from `0o400` to `0o600` — a
|
|
72
|
+
* an on-disk change for every caller that asked for nothing, including external
|
|
73
|
+
* consumers. When the caller DOES ask for a mode, however, letting the `umask` decide
|
|
74
|
+
* silently is the defect this parameter exists to close; hence the reassertion.
|
|
75
|
+
*
|
|
76
|
+
* It goes on the DESCRIPTOR, before the `rename`, never after: chmod-ing the final
|
|
77
|
+
* final one would leave a window where it carries the `umask`'s mode — a known anti-pattern.
|
|
78
|
+
* The chosen shape passes the mode as an `open` argument.
|
|
79
|
+
*
|
|
80
|
+
*/
|
|
81
|
+
declare function replaceFileAtomic(filePath: string, content: string, options?: AtomicWriteFileOptions): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Options for `atomicWriteJson`.
|
|
84
|
+
*
|
|
85
|
+
*/
|
|
86
|
+
interface AtomicWriteJsonOptions extends AtomicWriteFileOptions {
|
|
87
|
+
/** Indent passed to `JSON.stringify`. Default: 2. */
|
|
88
|
+
indent?: number;
|
|
89
|
+
/** Whether to append a trailing newline (POSIX convention). Default: true. */
|
|
90
|
+
trailingNewline?: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Typed JSON atomic write helper.
|
|
94
|
+
*
|
|
95
|
+
* Serializes `data` to JSON, then delegates to `replaceFileAtomic`. The
|
|
96
|
+
* parent directory is auto-created (recursive `mkdir`) to make this helper
|
|
97
|
+
* safe for callers who haven't ensured the directory exists (EC-4 in the
|
|
98
|
+
* persistence-state-hardening plan).
|
|
99
|
+
*
|
|
100
|
+
* Throws `TypeError` on circular refs or `undefined` data (propagates from
|
|
101
|
+
* `JSON.stringify`).
|
|
102
|
+
*
|
|
103
|
+
*/
|
|
104
|
+
declare function atomicWriteJson<T>(filePath: string, data: T, options?: AtomicWriteJsonOptions): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Atomic text write. Same crash-safety guarantees as `replaceFileAtomic` +
|
|
107
|
+
* auto-mkdir of the parent directory. Written for `theokit-migrate-config` (T4.1, EC-2 MUST FIX) so
|
|
108
|
+
* a crash mid-migration left previous MD files intact rather than corrupting them; that CLI is no
|
|
109
|
+
* longer published, and the guarantee is the reason every other caller uses this too.
|
|
110
|
+
*
|
|
111
|
+
*/
|
|
112
|
+
declare function atomicWriteText(filePath: string, content: string): Promise<void>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Cross-process file lock helper (ADR D61).
|
|
116
|
+
*
|
|
117
|
+
* Uses `proper-lockfile` (optional peer dep) for cross-process locks. When
|
|
118
|
+
* the peer dep is absent, falls back to `withCwdMutex` (in-process only)
|
|
119
|
+
* with a one-shot stderr warning.
|
|
120
|
+
*
|
|
121
|
+
* EC-1 fix: uses a companion `<path>.lock` file with `realpath: false` so
|
|
122
|
+
* `withFileLock` works even when the target `path` does not exist yet.
|
|
123
|
+
* Without this, fresh installs that lock-then-create would crash with ENOENT.
|
|
124
|
+
*
|
|
125
|
+
* @internal
|
|
126
|
+
*/
|
|
127
|
+
/**
|
|
128
|
+
* Retry and staleness tuning for `withFileLock`.
|
|
129
|
+
*
|
|
130
|
+
* Every field is optional and the defaults are applied per call, so `undefined` means the default
|
|
131
|
+
* rather than "no limit". They are handed to `proper-lockfile` and have NO effect on the fallback
|
|
132
|
+
* path: when that package is absent, `withFileLock` degrades to the in-process mutex, which
|
|
133
|
+
* queues indefinitely and never goes stale.
|
|
134
|
+
*
|
|
135
|
+
* `stale` (default 30000 ms) is how old a lock file must be before it is treated as abandoned by a
|
|
136
|
+
* dead process and broken. Setting it below the longest a legitimate holder can run is what turns
|
|
137
|
+
* a slow operation into two concurrent ones.
|
|
138
|
+
*
|
|
139
|
+
* `retries` (default 5) is the number of acquisition attempts AFTER the first, and `retryFactor`
|
|
140
|
+
* (default 1.5) the backoff multiplier between them. The per-attempt wait is clamped to
|
|
141
|
+
* 100–5000 ms, which the caller cannot change. Exhausting the retries rejects — it does not fall
|
|
142
|
+
* through to running `fn` unlocked.
|
|
143
|
+
*
|
|
144
|
+
* @public — re-exported from the semver-protected `@theokit/sdk/persistence` barrel, and (for
|
|
145
|
+
* back-compat) from the semver-exempt `@theokit/sdk/internal/persistence` alias.
|
|
146
|
+
*/
|
|
147
|
+
interface FileLockOptions {
|
|
148
|
+
/** Stale lock timeout in ms. Default 30_000 (30s). */
|
|
149
|
+
stale?: number;
|
|
150
|
+
/** Max retries on busy lock. Default 5. */
|
|
151
|
+
retries?: number;
|
|
152
|
+
/** Backoff factor between retries. Default 1.5. */
|
|
153
|
+
retryFactor?: number;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Run `fn` while holding an OS-level cross-process lock on `path`.
|
|
157
|
+
*
|
|
158
|
+
* If `proper-lockfile` is installed, uses it with a companion `<path>.lock`
|
|
159
|
+
* file (`realpath: false`, so target file does NOT need to exist yet).
|
|
160
|
+
* Otherwise falls back to in-process `withCwdMutex` and prints a one-shot
|
|
161
|
+
* stderr warning telling the user to install `proper-lockfile` for
|
|
162
|
+
* cross-process safety.
|
|
163
|
+
*
|
|
164
|
+
* The lock is released even when `fn` throws.
|
|
165
|
+
*
|
|
166
|
+
*/
|
|
167
|
+
declare function withFileLock<T>(path: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* FTS5 query sanitization + CJK detection (ADR D64).
|
|
171
|
+
*
|
|
172
|
+
* Port of Hermes' 6-step sanitizer (`hermes_state.py:1797-1847`). Prevents
|
|
173
|
+
* crashes on user inputs with hyphens, dots, underscores, and unmatched
|
|
174
|
+
* specials. Auto-quotes identifier-shaped tokens so `error-code` finds
|
|
175
|
+
* literal `error-code` instead of `error AND code`.
|
|
176
|
+
*
|
|
177
|
+
* CJK detection returns true for inputs containing characters in the main
|
|
178
|
+
* CJK Unicode ranges (Chinese, Japanese, Korean). CJK trigram routing is
|
|
179
|
+
* deferred to v1.4 — callers receiving `containsCjk === true` should
|
|
180
|
+
* short-circuit to an empty result or LIKE fallback for v1.3.
|
|
181
|
+
*
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
/**
|
|
185
|
+
* Six-step FTS5 query sanitizer. Returns a query string safe for passing
|
|
186
|
+
* to `WHERE <fts5_table> MATCH ?` as a parameter.
|
|
187
|
+
*
|
|
188
|
+
* Returns empty string when the input contains only specials (caller MUST
|
|
189
|
+
* short-circuit to avoid runtime SQL error — EC-3).
|
|
190
|
+
*
|
|
191
|
+
*/
|
|
192
|
+
declare function sanitizeFts5Query(query: string): string;
|
|
193
|
+
|
|
194
|
+
/** Raised when a JSONL line is not valid JSON or is not a JSON object. Carries the 1-based line number. */
|
|
195
|
+
declare class JsonlParseError extends TheokitAgentError {
|
|
196
|
+
readonly line: number;
|
|
197
|
+
readonly name = "JsonlParseError";
|
|
198
|
+
constructor(message: string, line: number);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Parse a JSONL file into rows. Blank lines are skipped. A malformed or
|
|
202
|
+
* non-object line throws {@link JsonlParseError} naming the 1-based line. When
|
|
203
|
+
* `map` is provided, each raw object is mapped to the typed row (the SWE-bench
|
|
204
|
+
* schema lives in the caller's `map`, per M6 ADR D3).
|
|
205
|
+
*/
|
|
206
|
+
declare function loadJsonl<T = Record<string, unknown>>(path: string, opts?: {
|
|
207
|
+
map?: (raw: Record<string, unknown>, lineNumber: number) => T;
|
|
208
|
+
/**
|
|
209
|
+
* M81 — tolerate a truncated LAST line (a crash artifact: the process died mid-write).
|
|
210
|
+
*
|
|
211
|
+
* Opt-in on purpose. As a default it would also swallow corruption in the MIDDLE of the file,
|
|
212
|
+
* turning loud data loss into silent data loss — the wrong trade for a session store.
|
|
213
|
+
*/
|
|
214
|
+
tolerateTrailingPartialLine?: boolean;
|
|
215
|
+
}): T[];
|
|
216
|
+
/**
|
|
217
|
+
* Append one record as a whole `\n`-terminated JSON line. Creates the parent dir
|
|
218
|
+
* if missing. `appendFileSync` is synchronous, so within a single Node process
|
|
219
|
+
* the event loop serializes writes and each call writes its line atomically —
|
|
220
|
+
* interleave-safe for the bounded-concurrency batch runner.
|
|
221
|
+
*
|
|
222
|
+
* reference: swebench-batch.ts:192 (mkdir-before-append), :205 (per-line flush).
|
|
223
|
+
*/
|
|
224
|
+
declare function appendJsonl(path: string, record: unknown): void;
|
|
225
|
+
/**
|
|
226
|
+
* Read the set of keys from an existing JSONL file for which `keyFn(parsed)`
|
|
227
|
+
* returns a non-empty string. Used to resume a crashed batch by skipping rows
|
|
228
|
+
* already persisted with a successful result. A trailing partial line from an
|
|
229
|
+
* interrupted append is tolerated (skipped, not thrown), and a missing file
|
|
230
|
+
* yields an empty set.
|
|
231
|
+
*
|
|
232
|
+
* reference: swebench-batch.ts:113 (readDoneIds), :129 (success-only),
|
|
233
|
+
* :131 (tolerate partial line).
|
|
234
|
+
*/
|
|
235
|
+
declare function readJsonlIds(path: string, keyFn: (parsed: Record<string, unknown>) => string | undefined): Set<string>;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* #598 — enumerate the sessions on disk, saying where each id came from.
|
|
239
|
+
*
|
|
240
|
+
* ## Why this exists
|
|
241
|
+
*
|
|
242
|
+
* Every transcript helper this package published mapped FORWARD — `sessionUuidFor`,
|
|
243
|
+
* `transcriptPath`, `legacyTranscriptPath`, `encodeProjectDir`, `transcriptRoot`. **None enumerated.**
|
|
244
|
+
* So a consumer that needed the list rebuilt it, and two independent ones did, in opposite
|
|
245
|
+
* directions, and both got it wrong on the first attempt:
|
|
246
|
+
*
|
|
247
|
+
* | consumer | direction | what broke |
|
|
248
|
+
* |---|---|---|
|
|
249
|
+
* | `@theokit/agents` | file → id | derived the id from the file STEM |
|
|
250
|
+
* | a downstream agent runtime | id → file | compared session ids against filenames |
|
|
251
|
+
*
|
|
252
|
+
* The second measured, against 5.0.1: the protected set never matched, **so neither the registered
|
|
253
|
+
* sessions nor the live one were protected and everything classified as an orphan** — a garbage
|
|
254
|
+
* collector that would delete the session in use.
|
|
255
|
+
*
|
|
256
|
+
* ## Why documentation was not the fix
|
|
257
|
+
*
|
|
258
|
+
* `b85dab4` documented the rename thoroughly. One of those two consumers had READ it and broke
|
|
259
|
+
* anyway; the other had not and broke identically. Two samples, one informed and one not, same
|
|
260
|
+
* defect. If reading were sufficient the informed sample would have survived — so the cause is the
|
|
261
|
+
* shape of the surface, not the reader.
|
|
262
|
+
*
|
|
263
|
+
* And it cannot be closed by publishing an inverse: the filename is a UUIDv8 over SHA-256, which has
|
|
264
|
+
* none. Whoever holds the file must read the id from INSIDE it, and that the id lives in the first
|
|
265
|
+
* record is this package's knowledge. Both consumers had to discover it by reading bytes.
|
|
266
|
+
*
|
|
267
|
+
* ## Why the id is not just a string
|
|
268
|
+
*
|
|
269
|
+
* An `id: string` that is sometimes read from the transcript and sometimes inferred from the
|
|
270
|
+
* filename is the same defect one layer up: a value that reads as authoritative and occasionally is
|
|
271
|
+
* not. That is precisely what produced the garbage-collector failure above.
|
|
272
|
+
*
|
|
273
|
+
* So every entry carries {@link SessionListing.idSource}, and an entry whose id could not be
|
|
274
|
+
* determined is `undefined` rather than guessed. A caller deciding what to DELETE needs to tell
|
|
275
|
+
* "this session is not registered" from "I could not read this file" — those look identical in a
|
|
276
|
+
* plain list and mean opposite things.
|
|
277
|
+
*
|
|
278
|
+
* @public
|
|
279
|
+
*/
|
|
280
|
+
/** How the `id` on a {@link SessionListing} was obtained. */
|
|
281
|
+
type SessionIdSource =
|
|
282
|
+
/** Read from the `sessionId` of the transcript's first well-formed record. Authoritative. */
|
|
283
|
+
"transcript"
|
|
284
|
+
/**
|
|
285
|
+
* Not determined: the file was unreadable, empty, or its first records carried no `sessionId`.
|
|
286
|
+
* `id` is `undefined` — deliberately not the filename, which is a hash and not an id.
|
|
287
|
+
*/
|
|
288
|
+
| "unavailable";
|
|
289
|
+
/** One session found on disk. */
|
|
290
|
+
interface SessionListing {
|
|
291
|
+
/**
|
|
292
|
+
* The session id, or `undefined` when {@link idSource} is `"unavailable"`.
|
|
293
|
+
*
|
|
294
|
+
* Never derived from the filename. The filename is a UUIDv8 over SHA-256 of the id, so treating
|
|
295
|
+
* it as the id is the exact defect this function exists to prevent.
|
|
296
|
+
*/
|
|
297
|
+
readonly id: string | undefined;
|
|
298
|
+
/** Where {@link id} came from. Check this before acting on `id`. */
|
|
299
|
+
readonly idSource: SessionIdSource;
|
|
300
|
+
/** Absolute path to the `.jsonl` transcript. */
|
|
301
|
+
readonly transcript: string;
|
|
302
|
+
/** Last modification time of the transcript. */
|
|
303
|
+
readonly modifiedAt: Date;
|
|
304
|
+
}
|
|
305
|
+
/** Options for {@link listSessions}. */
|
|
306
|
+
interface ListSessionsOptions {
|
|
307
|
+
/**
|
|
308
|
+
* Root under which `projects/<encoded-cwd>/` lives. Defaults to {@link transcriptRoot}, which
|
|
309
|
+
* honours `THEOKIT_HOME`.
|
|
310
|
+
*/
|
|
311
|
+
readonly baseDir?: string;
|
|
312
|
+
/**
|
|
313
|
+
* How many bytes of each transcript to read looking for the id. Default 65536.
|
|
314
|
+
*
|
|
315
|
+
* A cap rather than a full read because a transcript grows without bound and the id is in the
|
|
316
|
+
* first record. A session whose id is not in the first 64KB reports `"unavailable"` rather than
|
|
317
|
+
* being read to the end — bounded work with a declared outcome beats an unbounded read.
|
|
318
|
+
*/
|
|
319
|
+
readonly idScanBytes?: number;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Every session transcript under `<baseDir>/projects/<encoded-cwd>/`, with the id read from inside
|
|
323
|
+
* each file.
|
|
324
|
+
*
|
|
325
|
+
* An absent directory yields `[]` — a cwd with no sessions is the common case, not an error.
|
|
326
|
+
*
|
|
327
|
+
* ```ts
|
|
328
|
+
* for (const s of await listSessions(process.cwd())) {
|
|
329
|
+
* if (s.idSource === "unavailable") continue; // do not guess, and do not delete
|
|
330
|
+
* …
|
|
331
|
+
* }
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
declare function listSessions(cwd: string, options?: ListSessionsOptions): Promise<readonly SessionListing[]>;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Shared Zod schema for the `{ backend: "memory" | "json", dir? }` options
|
|
338
|
+
* used by both `Workflow` snapshots and `Cache` semantic store persistence.
|
|
339
|
+
* Extracted to remove the cross-module clone flagged by jscpd.
|
|
340
|
+
*
|
|
341
|
+
* `PersistenceSchema` is re-exported from the semver-protected `@theokit/sdk/persistence` barrel
|
|
342
|
+
* and from the semver-exempt `@theokit/sdk/internal/persistence` alias.
|
|
343
|
+
*
|
|
344
|
+
* NOTE — no internal-visibility tag in this block. `tsconfig.base.json` sets `stripInternal: true`,
|
|
345
|
+
* and TypeScript scans EVERY leading comment range of the declaration that follows, including the
|
|
346
|
+
* import right below this one. The tag that used to sit here deleted that import from the emitted
|
|
347
|
+
* `.d.ts`, leaving the types it binds unresolvable for any consumer running type-aware lint
|
|
348
|
+
* (usetheodev/theokit-sdk#283 records the same trap on a declaration).
|
|
349
|
+
*/
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* `persistence?` opt-in JSON disk backend with `dir` required when chosen.
|
|
353
|
+
*
|
|
354
|
+
* `.refine()` restored after Zod v4-only migration (plan zod-v4-migration
|
|
355
|
+
* T1.1, ADR D4). With single v4 instance across workspace, ZodEffects
|
|
356
|
+
* inside `z.object()` works correctly.
|
|
357
|
+
*/
|
|
358
|
+
declare const PersistenceSchema: z.ZodOptional<z.ZodObject<{
|
|
359
|
+
backend: z.ZodEnum<{
|
|
360
|
+
memory: "memory";
|
|
361
|
+
json: "json";
|
|
362
|
+
}>;
|
|
363
|
+
dir: z.ZodOptional<z.ZodString>;
|
|
364
|
+
}, z.core.$strip>>;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The kinds of file this SDK leaves in a project's transcript directory.
|
|
368
|
+
*
|
|
369
|
+
* - `transcript` — the session itself (`transcriptPath`).
|
|
370
|
+
* - `writer-lock` — the cross-process writer lease (`session-writer.ts`, `<file>.writer.lock`).
|
|
371
|
+
* - `lock-directory` — `withFileLock`'s companion, taken by `mkdir`, so it is a DIRECTORY.
|
|
372
|
+
* - `temp` — what `replaceFileAtomic` leaves when a process dies between the open and the rename.
|
|
373
|
+
*/
|
|
374
|
+
type SessionArtifact = "transcript" | "writer-lock" | "lock-directory" | "temp";
|
|
375
|
+
/**
|
|
376
|
+
* U-1 — what is this entry, if it is one of ours?
|
|
377
|
+
*
|
|
378
|
+
* The SDK writes four kinds of file into a project directory and reasons about none of them
|
|
379
|
+
* afterwards: there is no retention, no collector, and there was no way even to ask what an entry
|
|
380
|
+
* IS. A consumer wanting to reclaim disk had to re-derive the suffixes by reading this source — and
|
|
381
|
+
* one did, which means a suffix changing here would have left its classifier silently mislabelling
|
|
382
|
+
* files on a path that deletes them.
|
|
383
|
+
*
|
|
384
|
+
* This is deliberately NOT a garbage collector. Retention is policy — how many days, how many to
|
|
385
|
+
* keep, which session is live, whether to delete at all — and policy belongs to the application,
|
|
386
|
+
* which is the only one that can know. What belongs here is the half only the SDK can answer.
|
|
387
|
+
*
|
|
388
|
+
* `undefined` means "not written by this SDK", and it is the answer that matters most: a caller
|
|
389
|
+
* deleting what it does not recognise is how an editor's swap file gets collected. The `temp` case
|
|
390
|
+
* defers to {@link atomicWriteTempTarget} rather than matching `.tmp`, for exactly that reason.
|
|
391
|
+
*/
|
|
392
|
+
declare function classifySessionArtifact(name: string, isDirectory: boolean): SessionArtifact | undefined;
|
|
393
|
+
|
|
394
|
+
/** Claude Code path convention: cwd with every non-alphanumeric replaced by `-`. */
|
|
395
|
+
declare function encodeProjectDir(cwd: string): string;
|
|
396
|
+
/**
|
|
397
|
+
* Root of the transcript state.
|
|
398
|
+
*
|
|
399
|
+
* `THEOKIT_HOME` wins; the fallback is `~/.theokit`. Home-anchored on purpose, and NOT
|
|
400
|
+
* `paths.ts`'s `getTheokitHome(cwd)`: that one falls back to `<cwd>/.theokit`, and switching
|
|
401
|
+
* by it would move the transcript of everyone who does **not** set the variable. The sibling with the
|
|
402
|
+
* right shape is `catalog-source-models-dev.ts` (M94 ADR-2).
|
|
403
|
+
*
|
|
404
|
+
* Before M94 this function ignored the env var, so whoever set it had their state split in
|
|
405
|
+
* two silently — sessions stayed in `~/.theokit` while the rest of the SDK migrated.
|
|
406
|
+
*/
|
|
407
|
+
declare function transcriptRoot(): string;
|
|
408
|
+
/**
|
|
409
|
+
* The transcript filename for an agent id — always a UUID.
|
|
410
|
+
*
|
|
411
|
+
* #400: the Claude Code CLI resumes a transcript ONLY when its basename is a UUID. Measured against
|
|
412
|
+
* CLI 2.1.236: `<uuid>.jsonl` resumes; `agent-<uuid>.jsonl` and `billing-bot.jsonl` are both ignored,
|
|
413
|
+
* with no error — the session simply is not offered. So a session written under a human-readable
|
|
414
|
+
* agent id was invisible to `claude --continue`, which is the interoperability this project claims
|
|
415
|
+
* as its difference from a proprietary session store.
|
|
416
|
+
*
|
|
417
|
+
* An id that is ALREADY a UUID passes through verbatim, so a transcript Claude Code wrote keeps its
|
|
418
|
+
* own name and the two directions stay symmetric. Anything else is derived with a UUIDv8, which is
|
|
419
|
+
* deterministic: the same agent id always yields the same filename, so nothing has to be persisted
|
|
420
|
+
* to map one back to the other.
|
|
421
|
+
*
|
|
422
|
+
* A DERIVED name is version 8 — RFC 9562's slot for a UUID whose bits come from an
|
|
423
|
+
* implementation-defined scheme, which is exactly what this is. The obvious alternative was v5, but
|
|
424
|
+
* RFC 4122 fixes v5's hash to SHA-1, and reaching for SHA-1 in 2026 means either shipping a weak
|
|
425
|
+
* primitive or arguing with every scanner that flags it, forever. v8 lets the hash be SHA-256 and
|
|
426
|
+
* lets the version nibble say honestly that this is our scheme rather than a standard one.
|
|
427
|
+
*
|
|
428
|
+
* There is no security claim here either way — no secret, no signature, and nothing an attacker
|
|
429
|
+
* gains by colliding two agent ids inside one consumer's own directory. The hash is a naming
|
|
430
|
+
* function. The point of preferring SHA-256 is not that SHA-1 was exploitable here; it is that a
|
|
431
|
+
* weak primitive in the tree costs a permanent argument with every scanner that sees it.
|
|
432
|
+
*
|
|
433
|
+
* The version nibble was measured, not assumed: CLI 2.1.236 resumes a v8-named transcript exactly as
|
|
434
|
+
* it does a v4 one.
|
|
435
|
+
*/
|
|
436
|
+
declare function sessionUuidFor(id: string): string;
|
|
437
|
+
/** The `.jsonl` path for a session: `<baseDir>/projects/<encoded-cwd>/<session-uuid>.jsonl`. */
|
|
438
|
+
declare function transcriptPath(baseDir: string, cwd: string, sessionId: string): string;
|
|
439
|
+
/**
|
|
440
|
+
* The path this session used BEFORE #400 made transcript filenames UUIDs.
|
|
441
|
+
*
|
|
442
|
+
* Kept so an existing transcript is never orphaned: {@link FsSessionStore} prefers this file when it
|
|
443
|
+
* exists, so history written under the old scheme keeps accumulating in the same file instead of
|
|
444
|
+
* being silently abandoned for a fresh one under the new name.
|
|
445
|
+
*/
|
|
446
|
+
declare function legacyTranscriptPath(baseDir: string, cwd: string, sessionId: string): string;
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* M81 — single-writer lease for a session transcript.
|
|
450
|
+
*
|
|
451
|
+
* ## The problem
|
|
452
|
+
*
|
|
453
|
+
* Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec
|
|
454
|
+
* resume --last` can write into the TUI's live session. Two interleaved appends to an append-only
|
|
455
|
+
* file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing
|
|
456
|
+
* reports it, because every line parses.
|
|
457
|
+
*
|
|
458
|
+
* ## Why an exclusive lockfile rather than `withFileLock`
|
|
459
|
+
*
|
|
460
|
+
* The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a
|
|
461
|
+
* second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is
|
|
462
|
+
* scope-based — it holds the lock for the duration of a callback. A session lease is **held across
|
|
463
|
+
* turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the
|
|
464
|
+
* whole session lifetime in a callback would invert control of the entire agent loop.
|
|
465
|
+
*
|
|
466
|
+
* So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,
|
|
467
|
+
* `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —
|
|
468
|
+
* while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the
|
|
469
|
+
* plan's rationale (no second mechanism) still holds; only its shape assumption did not.
|
|
470
|
+
*
|
|
471
|
+
* ## Fail fast, never wait
|
|
472
|
+
*
|
|
473
|
+
* A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed
|
|
474
|
+
* error lets the caller choose: fork to a new id, or give up with a real diagnosis.
|
|
475
|
+
*
|
|
476
|
+
* @internal
|
|
477
|
+
*/
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* M81 — another process already holds the writer lease for this session.
|
|
481
|
+
*
|
|
482
|
+
* Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between
|
|
483
|
+
* forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough
|
|
484
|
+
* to act on).
|
|
485
|
+
*/
|
|
486
|
+
declare class SessionBusyError extends TheokitAgentError {
|
|
487
|
+
readonly sessionPath: string;
|
|
488
|
+
readonly name = "SessionBusyError";
|
|
489
|
+
constructor(sessionPath: string);
|
|
490
|
+
}
|
|
491
|
+
/** A held writer lease. `release()` is idempotent. */
|
|
492
|
+
interface SessionWriterLease {
|
|
493
|
+
readonly sessionPath: string;
|
|
494
|
+
release(): Promise<void>;
|
|
495
|
+
/**
|
|
496
|
+
* Re-stamp the ownership record, so a **live** owner never crosses the staleness window.
|
|
497
|
+
*
|
|
498
|
+
* Idempotent and cheap: one `write` of a ~80-byte JSON. Call it on the path that already writes to
|
|
499
|
+
* the session — appending a turn — and the cross-host window stops being a lie about liveness.
|
|
500
|
+
*
|
|
501
|
+
* A no-op after `release()`: renewing a lease you no longer hold would re-create the lock file and
|
|
502
|
+
* hand this process ownership it gave up. That is the one direction of this API that could
|
|
503
|
+
* *create* the double-writer it exists to prevent.
|
|
504
|
+
*/
|
|
505
|
+
renew(): void;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Does the session have a writer **right now**? A query that does NOT take the lease.
|
|
509
|
+
*
|
|
510
|
+
* M95 — it exists because asking by taking creates the very contention it meant to detect: two
|
|
511
|
+
* processes querying a **free** session at the same time made one of them lose, and the consumer
|
|
512
|
+
* forked for no reason. Measured in adversarial review: `RACE: spurious forks = 1`.
|
|
513
|
+
*
|
|
514
|
+
* It is a snapshot, not a guarantee: between the query and the real acquisition someone may take
|
|
515
|
+
* the session. Callers needing the guarantee use {@link acquireSessionWriter}; callers needing to
|
|
516
|
+
* **decide an id before opening anything** use this, and handle the race where it shows up.
|
|
517
|
+
*
|
|
518
|
+
*/
|
|
519
|
+
declare function sessionHasWriter(sessionPath: string): boolean;
|
|
520
|
+
/**
|
|
521
|
+
* Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.
|
|
522
|
+
*
|
|
523
|
+
* The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the
|
|
524
|
+
* SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across
|
|
525
|
+
* processes, not just across async tasks in one process.
|
|
526
|
+
*/
|
|
527
|
+
declare function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease>;
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Resilient SQLite open (plan m0-foundation-expose-primitives, M0-5).
|
|
531
|
+
*
|
|
532
|
+
* Generalizes the driver-load + WAL-apply + corruption-recovery logic that was
|
|
533
|
+
* duplicated (byte-identical) across `sdk/internal/memory/index-db.ts` and
|
|
534
|
+
* `sdk-memory/internal/index/index-db.ts`. Schema-agnostic: the caller applies
|
|
535
|
+
* its own PRAGMA/SCHEMA via the `onOpen` callback.
|
|
536
|
+
*
|
|
537
|
+
* Corruption recovery (EC-7): when opening fails with a "malformed" / "not a
|
|
538
|
+
* database" / "encrypted" error and `recoverCorrupt` is not false, the file is
|
|
539
|
+
* renamed aside to `<path>.corrupt-<ts>` (plus its WAL/SHM siblings) and a fresh
|
|
540
|
+
* DB is opened. The corrupt file is renamed, NOT backed up — the timestamped
|
|
541
|
+
* `.corrupt-*` file is kept for manual recovery.
|
|
542
|
+
*
|
|
543
|
+
* @internal — public via `@theokit/sdk/internal/persistence` (semver-exempt)
|
|
544
|
+
*/
|
|
545
|
+
/** Minimal SQLite handle surface every driver (`better-sqlite3`) exposes. */
|
|
546
|
+
interface ResilientSqliteDb {
|
|
547
|
+
/** SQLite `pragma()` access (used by `applyWalWithFallback`). */
|
|
548
|
+
pragma(statement: string, options?: {
|
|
549
|
+
simple?: boolean;
|
|
550
|
+
}): unknown;
|
|
551
|
+
exec(sql: string): void;
|
|
552
|
+
close(): void;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Input to {@link openSqliteResilient}.
|
|
556
|
+
*
|
|
557
|
+
* The trap is `recoverCorrupt`, whose default is ON. When the driver reports a corrupt or encrypted
|
|
558
|
+
* database, the file is renamed aside and a FRESH one is opened — so the call SUCCEEDS and hands
|
|
559
|
+
* back an EMPTY database. The old bytes survive on disk under the renamed path, but a caller that
|
|
560
|
+
* treats a resolved promise as "my data is here" is wrong exactly when it matters. Pass
|
|
561
|
+
* `recoverCorrupt: false` to get the corruption error thrown instead.
|
|
562
|
+
*
|
|
563
|
+
* Semver-exempt: reachable via the '@theokit/sdk/internal/persistence' sub-path, which the package
|
|
564
|
+
* declares in 'exports' but does NOT cover with its semver contract.
|
|
565
|
+
*
|
|
566
|
+
* @typeParam T - the concrete DB handle the driver returns; defaults to {@link ResilientSqliteDb}
|
|
567
|
+
*/
|
|
568
|
+
interface OpenSqliteResilientOptions<T extends ResilientSqliteDb> {
|
|
569
|
+
/** Absolute path to the SQLite file. Parent directories are created. */
|
|
570
|
+
filePath: string;
|
|
571
|
+
/**
|
|
572
|
+
* Called after the driver is open and WAL is applied, before the handle is
|
|
573
|
+
* returned. Apply PRAGMA/SCHEMA statements here. Errors propagate.
|
|
574
|
+
*/
|
|
575
|
+
onOpen?: (db: T) => void | Promise<void>;
|
|
576
|
+
/** Label used in the WAL-fallback warning and corruption-recovery log. Default "sqlite". */
|
|
577
|
+
label?: string;
|
|
578
|
+
/** When true (default) a corruption error renames the file aside and rebuilds. */
|
|
579
|
+
recoverCorrupt?: boolean;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Open a SQLite file with WAL (+ DELETE fallback) and corruption recovery.
|
|
583
|
+
*
|
|
584
|
+
* @typeParam T - the concrete DB handle type the driver returns (defaults to the
|
|
585
|
+
* minimal {@link ResilientSqliteDb} surface)
|
|
586
|
+
*/
|
|
587
|
+
declare function openSqliteResilient<T extends ResilientSqliteDb = ResilientSqliteDb>(options: OpenSqliteResilientOptions<T>): Promise<T>;
|
|
588
|
+
/** True when an open error indicates an unreadable / corrupt database file. */
|
|
589
|
+
declare function isCorruptionError(cause: unknown): boolean;
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* SQLite WAL mode helper with NFS/SMB/FUSE fallback to DELETE (ADR D63).
|
|
593
|
+
*
|
|
594
|
+
* WAL is faster (concurrent readers + one writer) but unsupported on some
|
|
595
|
+
* network/FUSE filesystems. Try WAL; if the pragma returns something else
|
|
596
|
+
* or throws, fall back to DELETE journal mode. Warn one time per label.
|
|
597
|
+
*
|
|
598
|
+
* This block is the MODULE header and must stay at offset 0. Sitting below the import it became a
|
|
599
|
+
* leading comment of the first declaration instead, which is how its tag deleted `PragmaCapable`
|
|
600
|
+
* from the emitted `.d.ts` while `applyWalWithFallback` — which names it — survived.
|
|
601
|
+
*/
|
|
602
|
+
/**
|
|
603
|
+
* The narrow slice of a SQLite handle this module needs: anything with a `pragma()` method, which
|
|
604
|
+
* `better-sqlite3` satisfies without being named here.
|
|
605
|
+
*
|
|
606
|
+
* Not exported, but it appears in the signature of `applyWalWithFallback`, so it is emitted into
|
|
607
|
+
* the published declarations and a consumer can structurally satisfy it.
|
|
608
|
+
*/
|
|
609
|
+
interface PragmaCapable {
|
|
610
|
+
pragma: (statement: string, options?: {
|
|
611
|
+
simple?: boolean;
|
|
612
|
+
}) => unknown;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* What journal mode a connection ended up in after `applyWalWithFallback`.
|
|
616
|
+
*
|
|
617
|
+
* `mode` is the mode actually in effect, never the one that was requested. `fellBack` is `true`
|
|
618
|
+
* only when WAL was attempted and refused — either the pragma threw or it reported a mode other
|
|
619
|
+
* than `wal` — and the connection was put into DELETE instead.
|
|
620
|
+
*
|
|
621
|
+
* `fellBack: true` is normal on NFS, SMB and FUSE, where WAL needs shared memory the filesystem
|
|
622
|
+
* does not provide. It is not an error and nothing further is required of the caller; the
|
|
623
|
+
* consequence is slower concurrent access, since DELETE mode does not allow readers alongside a
|
|
624
|
+
* writer. Treat it as a signal about the storage, not about the database.
|
|
625
|
+
*
|
|
626
|
+
* @public — re-exported from the semver-protected `@theokit/sdk/persistence` barrel, and (for
|
|
627
|
+
* back-compat) from the semver-exempt `@theokit/sdk/internal/persistence` alias.
|
|
628
|
+
*/
|
|
629
|
+
interface WalApplyResult {
|
|
630
|
+
/** Final journal_mode actually in effect. */
|
|
631
|
+
mode: "wal" | "delete";
|
|
632
|
+
/** True if we wanted WAL but the filesystem refused. */
|
|
633
|
+
fellBack: boolean;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Apply WAL mode with DELETE fallback. Idempotent — safe to call multiple
|
|
637
|
+
* times on the same connection.
|
|
638
|
+
*
|
|
639
|
+
* @param db any `pragma()`-capable SQLite handle (e.g., `better-sqlite3`)
|
|
640
|
+
* @param label short identifier used in the warning (e.g., "memory-index")
|
|
641
|
+
*
|
|
642
|
+
*/
|
|
643
|
+
declare function applyWalWithFallback(db: PragmaCapable, label: string): WalApplyResult;
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* M81 — transcript operations the consumer was doing by hand INSIDE the framework's own store.
|
|
647
|
+
*
|
|
648
|
+
* ## What this replaces
|
|
649
|
+
*
|
|
650
|
+
* `agents/lib/session/backtrack.ts:188` (agent-builder) wrote straight into the session store with a
|
|
651
|
+
* bare `writeFileSync` — no atomicity, no lock, no API. 243 lines re-implementing parse, cut and
|
|
652
|
+
* write for a format the framework owns. The consumer is not at fault: nothing here was reachable.
|
|
653
|
+
*
|
|
654
|
+
* ## The rule that travels WITH the operation
|
|
655
|
+
*
|
|
656
|
+
* `rules/audit-trail-rotation.md § Session transcripts (M60)` defines a NEVER-delete list — the live
|
|
657
|
+
* pointer, the most recent transcript, and any active registry entry. That rule lived in the
|
|
658
|
+
* CONSUMER. Moving the operation here without moving the rule would ship an API able to destroy
|
|
659
|
+
* exactly what the rule protects — the same shape of defect as M80's `reconcileUpdateGoalStatus`:
|
|
660
|
+
* critical knowledge outside the module that needs it, applied by convention.
|
|
661
|
+
*
|
|
662
|
+
* So `forkTranscript` takes `liveSessionPaths` and refuses, with a TYPED error, to write over any of
|
|
663
|
+
* them. The caller supplies the list because only the caller knows which session is live; the
|
|
664
|
+
* enforcement lives here because that is where the write happens.
|
|
665
|
+
*
|
|
666
|
+
* @internal
|
|
667
|
+
*/
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* M81 — the target is a protected session (live pointer / most-recent transcript / active entry).
|
|
671
|
+
*
|
|
672
|
+
* Typed rather than a bare `Error` because the caller must distinguish "this session is protected"
|
|
673
|
+
* from "the disk is full": the first is a correct refusal, the second is an incident.
|
|
674
|
+
*
|
|
675
|
+
* RENAMED from `LiveSessionError` (2026-09-01). `session-guard.ts` exports a DIFFERENT class of that
|
|
676
|
+
* name from the root barrel, with an incompatible shape — `(sessionId, reason)` and a `reason`
|
|
677
|
+
* field, against this one's `(path)` and `code: "live_session_protected"` — and `.` and
|
|
678
|
+
* `./persistence` are both declared subpaths, so one consumer can hold both. `instanceof` never
|
|
679
|
+
* crossed the pair, so a `catch` checking the root import silently missed this one and ran its
|
|
680
|
+
* fallback for a condition it believed it handled; `err.name` matched BOTH, so a name check looked
|
|
681
|
+
* right and then read `err.reason`, which only the other one has. The names now say what each
|
|
682
|
+
* refusal is about: destroying a SESSION, versus overwriting a TRANSCRIPT file.
|
|
683
|
+
*/
|
|
684
|
+
declare class LiveTranscriptError extends TheokitAgentError {
|
|
685
|
+
readonly path: string;
|
|
686
|
+
readonly name = "LiveTranscriptError";
|
|
687
|
+
constructor(path: string);
|
|
688
|
+
}
|
|
689
|
+
/** Options for {@link forkTranscript}. */
|
|
690
|
+
interface ForkTranscriptOptions {
|
|
691
|
+
/** Keep records `[0, beforeRecordIndex)`. Omit to copy the whole transcript. */
|
|
692
|
+
readonly beforeRecordIndex?: number;
|
|
693
|
+
/**
|
|
694
|
+
* Paths that must never be written over — the live pointer, the most recent transcript, any active
|
|
695
|
+
* registry entry. The caller supplies them because only the caller knows which session is live.
|
|
696
|
+
*/
|
|
697
|
+
readonly liveSessionPaths?: readonly string[];
|
|
698
|
+
/**
|
|
699
|
+
* M107 — permission bits for the created destination. Default: `0o600`.
|
|
700
|
+
*
|
|
701
|
+
* A transcript carries the conversation. Before M107 no mode was passed at all, so the file was
|
|
702
|
+
* born `0o666 & ~umask` — measured `0o664` (group-WRITABLE) on a `umask 002` machine, `0o644` on
|
|
703
|
+
* `umask 022`, `0o466` on `umask 0200`. This is a DEFAULT and not a required knob on purpose: a
|
|
704
|
+
* knob would reach zero consumers by omission, which is the failure mode that matters.
|
|
705
|
+
*
|
|
706
|
+
* As with any `open` mode, the `umask` may still CLEAR bits — under `umask 0200` the result is
|
|
707
|
+
* `0o400`. That is accepted: the invariant bought here is "neither group nor others", and `0o400`
|
|
708
|
+
* satisfies it more strictly. The SDK deliberately does not `fchmod` the default back, because
|
|
709
|
+
* that would hand back a bit the operator asked to remove.
|
|
710
|
+
*/
|
|
711
|
+
readonly mode?: number;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Copy `src` into `dst`, keeping the first `beforeRecordIndex` records. The SOURCE is never touched.
|
|
715
|
+
*
|
|
716
|
+
* Atomicity comes from `wx` (exclusive create): two concurrent forks to the same destination cannot
|
|
717
|
+
* both succeed — the loser gets `EEXIST` rather than writing over a half-written file. That is also
|
|
718
|
+
* why an existing destination is a refusal, not a silent overwrite: losing a transcript without an
|
|
719
|
+
* error is the worst failure mode for an operation that touches user sessions.
|
|
720
|
+
*/
|
|
721
|
+
declare function forkTranscript(src: string, dst: string, options?: ForkTranscriptOptions): void;
|
|
722
|
+
/** Options for {@link readJsonlTail}. */
|
|
723
|
+
interface ReadJsonlTailOptions {
|
|
724
|
+
/** Maximum records to return, counted from the END. */
|
|
725
|
+
readonly maxRecords?: number;
|
|
726
|
+
/**
|
|
727
|
+
* Start the window AFTER the last record whose `subtype` (or `type`) equals this.
|
|
728
|
+
*
|
|
729
|
+
* Matched STRUCTURALLY since T2.5. It used to be `line.includes(marker)`, so any message
|
|
730
|
+
* mentioning the marker in its text truncated the read — silently, with a successful return.
|
|
731
|
+
*/
|
|
732
|
+
readonly sinceMarker?: string;
|
|
733
|
+
/** Test-only: also report how many bytes were read, to prove the read is not whole-file. */
|
|
734
|
+
readonly _stats?: boolean;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Read the LAST records of a JSONL file without loading the whole thing.
|
|
738
|
+
*
|
|
739
|
+
* Reads fixed-size chunks backwards from EOF until enough newlines have been seen. A session
|
|
740
|
+
* transcript grows without bound; loading megabytes to show the last three turns is the cost this
|
|
741
|
+
* exists to avoid — and a `slice` over a full read would be that same cost with a better name.
|
|
742
|
+
*/
|
|
743
|
+
declare function readJsonlTail<T = Record<string, unknown>>(path: string, options?: ReadJsonlTailOptions): T[];
|
|
744
|
+
|
|
745
|
+
export { type AtomicWriteJsonOptions, type FileLockOptions, type ForkTranscriptOptions, JsonlParseError, type ListSessionsOptions, LiveTranscriptError as LiveSessionError, LiveTranscriptError, type OpenSqliteResilientOptions, PersistenceSchema, type ReadJsonlTailOptions, type ResilientSqliteDb, type SessionArtifact, SessionBusyError, type SessionIdSource, type SessionListing, type SessionWriterLease, type WalApplyResult, acquireSessionWriter, appendJsonl, applyWalWithFallback, atomicWriteJson, atomicWriteTempTarget, atomicWriteText, classifySessionArtifact, encodeProjectDir, forkTranscript, isCorruptionError, legacyTranscriptPath, listSessions, loadJsonl, openSqliteResilient, readJsonlIds, readJsonlTail, replaceFileAtomic, sanitizeFts5Query, sessionHasWriter, sessionUuidFor, transcriptPath, transcriptRoot, withFileLock };
|