@theokit/sdk 4.32.0 → 4.33.1

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.
Files changed (102) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/{agent-FBFPSBTX.js → agent-LIVADAZ4.js} +10 -9
  3. package/dist/{agent-FBFPSBTX.js.map → agent-LIVADAZ4.js.map} +1 -1
  4. package/dist/{agent-ES57DGAT.cjs → agent-T2AIB3EV.cjs} +11 -10
  5. package/dist/{agent-ES57DGAT.cjs.map → agent-T2AIB3EV.cjs.map} +1 -1
  6. package/dist/chunk-2BYBFBNH.cjs +75 -0
  7. package/dist/chunk-2BYBFBNH.cjs.map +1 -0
  8. package/dist/{chunk-DQSILMWU.cjs → chunk-2DYI7MIF.cjs} +2 -2
  9. package/dist/chunk-2DYI7MIF.cjs.map +1 -0
  10. package/dist/{chunk-HBTEK5LK.js → chunk-3CMIY3N7.js} +3 -3
  11. package/dist/{chunk-HBTEK5LK.js.map → chunk-3CMIY3N7.js.map} +1 -1
  12. package/dist/{chunk-NI4PAMJQ.js → chunk-4LXCZSIC.js} +18 -12
  13. package/dist/chunk-4LXCZSIC.js.map +1 -0
  14. package/dist/{chunk-OIYQBUW4.cjs → chunk-6M6A6WWW.cjs} +2 -2
  15. package/dist/{chunk-OIYQBUW4.cjs.map → chunk-6M6A6WWW.cjs.map} +1 -1
  16. package/dist/{chunk-I4QDIQ45.cjs → chunk-E33A7YIR.cjs} +28 -6
  17. package/dist/chunk-E33A7YIR.cjs.map +1 -0
  18. package/dist/chunk-F5RLC6ZH.js +73 -0
  19. package/dist/chunk-F5RLC6ZH.js.map +1 -0
  20. package/dist/{chunk-CVSNCUBU.cjs → chunk-FTRNHGAD.cjs} +11 -11
  21. package/dist/{chunk-CVSNCUBU.cjs.map → chunk-FTRNHGAD.cjs.map} +1 -1
  22. package/dist/{chunk-EVADEGM7.cjs → chunk-FX2DAR5R.cjs} +7 -5
  23. package/dist/chunk-FX2DAR5R.cjs.map +1 -0
  24. package/dist/{chunk-WA3FO4R7.js → chunk-GBCQ5742.js} +7 -6
  25. package/dist/chunk-GBCQ5742.js.map +1 -0
  26. package/dist/{chunk-SQVQH4EK.js → chunk-KT6SWUY6.js} +4 -4
  27. package/dist/{chunk-SQVQH4EK.js.map → chunk-KT6SWUY6.js.map} +1 -1
  28. package/dist/chunk-NEPJ3TC4.cjs +82 -0
  29. package/dist/chunk-NEPJ3TC4.cjs.map +1 -0
  30. package/dist/{chunk-WPBTPSKQ.js → chunk-Q56IYTBZ.js} +2 -2
  31. package/dist/chunk-Q56IYTBZ.js.map +1 -0
  32. package/dist/{chunk-334WDVFZ.js → chunk-SDYJESYZ.js} +27 -6
  33. package/dist/chunk-SDYJESYZ.js.map +1 -0
  34. package/dist/{chunk-JPG7NMNH.cjs → chunk-SIJZVUUD.cjs} +54 -48
  35. package/dist/chunk-SIJZVUUD.cjs.map +1 -0
  36. package/dist/chunk-TOVGIP4L.js +79 -0
  37. package/dist/chunk-TOVGIP4L.js.map +1 -0
  38. package/dist/{chunk-OQX2DG36.cjs → chunk-WKSVAP6H.cjs} +5 -5
  39. package/dist/{chunk-OQX2DG36.cjs.map → chunk-WKSVAP6H.cjs.map} +1 -1
  40. package/dist/{chunk-DGRZNMTU.js → chunk-XV2RBL6D.js} +2 -2
  41. package/dist/{chunk-DGRZNMTU.js.map → chunk-XV2RBL6D.js.map} +1 -1
  42. package/dist/{compact-session-OKXLKJIS.cjs → compact-session-24PK2G4M.cjs} +13 -13
  43. package/dist/{compact-session-OKXLKJIS.cjs.map → compact-session-24PK2G4M.cjs.map} +1 -1
  44. package/dist/{compact-session-UHGMWPMW.js → compact-session-J7VNCICZ.js} +5 -5
  45. package/dist/{compact-session-UHGMWPMW.js.map → compact-session-J7VNCICZ.js.map} +1 -1
  46. package/dist/compaction.cjs +17 -13
  47. package/dist/compaction.d.cts +8 -0
  48. package/dist/compaction.d.ts +8 -0
  49. package/dist/compaction.js +1 -1
  50. package/dist/cron.cjs +10 -9
  51. package/dist/cron.js +9 -8
  52. package/dist/eval.cjs +9 -8
  53. package/dist/eval.cjs.map +1 -1
  54. package/dist/eval.js +8 -7
  55. package/dist/eval.js.map +1 -1
  56. package/dist/fs-session-store-ENQIIMIR.cjs +19 -0
  57. package/dist/{fs-session-store-DNAJ6RQG.cjs.map → fs-session-store-ENQIIMIR.cjs.map} +1 -1
  58. package/dist/fs-session-store-RKLORRCG.js +10 -0
  59. package/dist/{fs-session-store-2J5JOXC2.js.map → fs-session-store-RKLORRCG.js.map} +1 -1
  60. package/dist/index.cjs +35 -35
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.js +15 -15
  63. package/dist/index.js.map +1 -1
  64. package/dist/{inject-session-24FJ65LP.cjs → inject-session-343WIDCM.cjs} +6 -6
  65. package/dist/{inject-session-24FJ65LP.cjs.map → inject-session-343WIDCM.cjs.map} +1 -1
  66. package/dist/{inject-session-JXQO33T3.js → inject-session-RZEDWO53.js} +4 -4
  67. package/dist/{inject-session-JXQO33T3.js.map → inject-session-RZEDWO53.js.map} +1 -1
  68. package/dist/internal/local-agent/local-agent.d.ts +93 -1
  69. package/dist/internal/persistence/fs-session-store.d.cts +16 -0
  70. package/dist/internal/persistence/fs-session-store.d.ts +16 -0
  71. package/dist/internal/persistence/index.cjs +7 -7
  72. package/dist/internal/persistence/index.js +1 -1
  73. package/dist/internal/persistence/paths.d.cts +11 -1
  74. package/dist/internal/persistence/paths.d.ts +11 -1
  75. package/dist/internal/persistence/session-writer.d.cts +8 -0
  76. package/dist/internal/persistence/session-writer.d.ts +8 -0
  77. package/dist/internal/session/agent-session.d.ts +22 -0
  78. package/dist/persistence.cjs +13 -39
  79. package/dist/persistence.cjs.map +1 -1
  80. package/dist/persistence.d.cts +1 -1
  81. package/dist/persistence.d.ts +1 -1
  82. package/dist/persistence.js +4 -36
  83. package/dist/persistence.js.map +1 -1
  84. package/dist/{session-transcript-QS6CXHHD.js → session-transcript-F2ANTA2M.js} +3 -3
  85. package/dist/{session-transcript-QS6CXHHD.js.map → session-transcript-F2ANTA2M.js.map} +1 -1
  86. package/dist/{session-transcript-F5OXXO6G.cjs → session-transcript-JXFMPLOT.cjs} +13 -13
  87. package/dist/{session-transcript-F5OXXO6G.cjs.map → session-transcript-JXFMPLOT.cjs.map} +1 -1
  88. package/package.json +1 -1
  89. package/dist/chunk-334WDVFZ.js.map +0 -1
  90. package/dist/chunk-DQSILMWU.cjs.map +0 -1
  91. package/dist/chunk-EIPSZ4GA.js +0 -28
  92. package/dist/chunk-EIPSZ4GA.js.map +0 -1
  93. package/dist/chunk-EVADEGM7.cjs.map +0 -1
  94. package/dist/chunk-HCXDDR6P.cjs +0 -30
  95. package/dist/chunk-HCXDDR6P.cjs.map +0 -1
  96. package/dist/chunk-I4QDIQ45.cjs.map +0 -1
  97. package/dist/chunk-JPG7NMNH.cjs.map +0 -1
  98. package/dist/chunk-NI4PAMJQ.js.map +0 -1
  99. package/dist/chunk-WA3FO4R7.js.map +0 -1
  100. package/dist/chunk-WPBTPSKQ.js.map +0 -1
  101. package/dist/fs-session-store-2J5JOXC2.js +0 -8
  102. package/dist/fs-session-store-DNAJ6RQG.cjs +0 -17
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ var chunkA44COQJJ_cjs = require('./chunk-A44COQJJ.cjs');
4
+ var fs = require('fs');
5
+ var os = require('os');
6
+
7
+ var SessionBusyError = class extends chunkA44COQJJ_cjs.TheokitAgentError {
8
+ constructor(sessionPath) {
9
+ super(
10
+ `another process is already writing this session: ${sessionPath}. Fork it to a new id instead of appending \u2014 two writers interleave lines into a sequence that parses but is not what either process wrote.`,
11
+ { code: "session_busy", isRetryable: false }
12
+ );
13
+ this.sessionPath = sessionPath;
14
+ }
15
+ sessionPath;
16
+ name = "SessionBusyError";
17
+ };
18
+ var JANELA_DE_HEARTBEAT_MS = 3e4;
19
+ function lerDono(lockPath) {
20
+ let bruto;
21
+ try {
22
+ bruto = fs.readFileSync(lockPath, "utf8");
23
+ } catch {
24
+ return void 0;
25
+ }
26
+ try {
27
+ const d = JSON.parse(bruto);
28
+ if (typeof d.pid !== "number" || typeof d.hostname !== "string" || typeof d.mtime !== "number") {
29
+ return void 0;
30
+ }
31
+ return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };
32
+ } catch {
33
+ return void 0;
34
+ }
35
+ }
36
+ function processoVivo(pid) {
37
+ try {
38
+ process.kill(pid, 0);
39
+ return true;
40
+ } catch (err) {
41
+ return err.code === "EPERM";
42
+ }
43
+ }
44
+ function reclamavel(dono) {
45
+ if (dono === void 0) return true;
46
+ if (dono.hostname !== os.hostname()) {
47
+ return Date.now() - dono.mtime > JANELA_DE_HEARTBEAT_MS;
48
+ }
49
+ return !processoVivo(dono.pid);
50
+ }
51
+ async function acquireSessionWriter(sessionPath) {
52
+ const lockPath = `${sessionPath}.writer.lock`;
53
+ const meu = { pid: process.pid, hostname: os.hostname(), mtime: Date.now() };
54
+ let fd;
55
+ try {
56
+ fd = fs.openSync(lockPath, "wx");
57
+ } catch (err) {
58
+ if (err.code !== "EEXIST") throw err;
59
+ if (!reclamavel(lerDono(lockPath))) throw new SessionBusyError(sessionPath);
60
+ fs.writeFileSync(lockPath, JSON.stringify(meu));
61
+ return criarLease(sessionPath, lockPath);
62
+ }
63
+ fs.closeSync(fd);
64
+ fs.writeFileSync(lockPath, JSON.stringify(meu));
65
+ return criarLease(sessionPath, lockPath);
66
+ }
67
+ function criarLease(sessionPath, lockPath) {
68
+ let released = false;
69
+ return {
70
+ sessionPath,
71
+ release: async () => {
72
+ if (released) return;
73
+ released = true;
74
+ fs.rmSync(lockPath, { force: true });
75
+ }
76
+ };
77
+ }
78
+
79
+ exports.SessionBusyError = SessionBusyError;
80
+ exports.acquireSessionWriter = acquireSessionWriter;
81
+ //# sourceMappingURL=chunk-NEPJ3TC4.cjs.map
82
+ //# sourceMappingURL=chunk-NEPJ3TC4.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/persistence/session-writer.ts"],"names":["TheokitAgentError","readFileSync","hostname","openSync","writeFileSync","closeSync","rmSync"],"mappings":";;;;;;AA2CO,IAAM,gBAAA,GAAN,cAA+BA,mCAAA,CAAkB;AAAA,EAGtD,YAAqB,WAAA,EAAqB;AACxC,IAAA,KAAA;AAAA,MACE,oDAAoD,WAAW,CAAA,gJAAA,CAAA;AAAA,MAG/D,EAAE,IAAA,EAAM,cAAA,EAAgB,WAAA,EAAa,KAAA;AAAM,KAC7C;AANmB,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA,EAOrB;AAAA,EAPqB,WAAA;AAAA,EAFH,IAAA,GAAO,kBAAA;AAU3B;AAsBO,IAAM,sBAAA,GAAyB,GAAA;AAUtC,SAAS,QAAQ,QAAA,EAA0C;AACzD,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI;AACF,IAAA,KAAA,GAAQC,eAAA,CAAa,UAAU,MAAM,CAAA;AAAA,EACvC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AAC1B,IAAA,IACE,OAAO,CAAA,CAAE,GAAA,KAAQ,QAAA,IACjB,OAAO,CAAA,CAAE,QAAA,KAAa,QAAA,IACtB,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,EACnB;AACA,MAAA,OAAO,KAAA,CAAA;AAAA,IACT;AACA,IAAA,OAAO,EAAE,KAAK,CAAA,CAAE,GAAA,EAAK,UAAU,CAAA,CAAE,QAAA,EAAU,KAAA,EAAO,CAAA,CAAE,KAAA,EAAM;AAAA,EAC5D,CAAA,CAAA,MAAQ;AAGN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAGA,SAAS,aAAa,GAAA,EAAsB;AAC1C,EAAA,IAAI;AACF,IAAA,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAC,CAAA;AACnB,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,GAAA,EAAK;AAEZ,IAAA,OAAQ,IAA8B,IAAA,KAAS,OAAA;AAAA,EACjD;AACF;AAwBA,SAAS,WAAW,IAAA,EAAuC;AACzD,EAAA,IAAI,IAAA,KAAS,QAAW,OAAO,IAAA;AAC/B,EAAA,IAAI,IAAA,CAAK,QAAA,KAAaC,WAAA,EAAS,EAAG;AAChC,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,KAAA,GAAQ,sBAAA;AAAA,EACnC;AACA,EAAA,OAAO,CAAC,YAAA,CAAa,IAAA,CAAK,GAAG,CAAA;AAC/B;AAEA,eAAsB,qBAAqB,WAAA,EAAkD;AAU3F,EAAA,MAAM,QAAA,GAAW,GAAG,WAAW,CAAA,YAAA,CAAA;AAC/B,EAAA,MAAM,GAAA,GAAkB,EAAE,GAAA,EAAK,OAAA,CAAQ,GAAA,EAAK,QAAA,EAAUA,WAAA,EAAS,EAAG,KAAA,EAAO,IAAA,CAAK,GAAA,EAAI,EAAE;AACpF,EAAA,IAAI,EAAA;AACJ,EAAA,IAAI;AACF,IAAA,EAAA,GAAKC,WAAA,CAAS,UAAU,IAAI,CAAA;AAAA,EAC9B,SAAS,GAAA,EAAK;AACZ,IAAA,IAAK,GAAA,CAA8B,IAAA,KAAS,QAAA,EAAU,MAAM,GAAA;AAI5D,IAAA,IAAI,CAAC,WAAW,OAAA,CAAQ,QAAQ,CAAC,CAAA,EAAG,MAAM,IAAI,gBAAA,CAAiB,WAAW,CAAA;AAC1E,IAAAC,gBAAA,CAAc,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,GAAG,CAAC,CAAA;AAC3C,IAAA,OAAO,UAAA,CAAW,aAAa,QAAQ,CAAA;AAAA,EACzC;AACA,EAAAC,YAAA,CAAU,EAAE,CAAA;AACZ,EAAAD,gBAAA,CAAc,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,GAAG,CAAC,CAAA;AAE3C,EAAA,OAAO,UAAA,CAAW,aAAa,QAAQ,CAAA;AACzC;AAGA,SAAS,UAAA,CAAW,aAAqB,QAAA,EAAsC;AAC7E,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,OAAO;AAAA,IACL,WAAA;AAAA,IACA,SAAS,YAA2B;AAClC,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,QAAA,GAAW,IAAA;AACX,MAAAE,SAAA,CAAO,QAAA,EAAU,EAAE,KAAA,EAAO,IAAA,EAAM,CAAA;AAAA,IAClC;AAAA,GACF;AACF","file":"chunk-NEPJ3TC4.cjs","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport { closeSync, openSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\n/**\n * Janela de heartbeat: um lock mais velho que isto é considerado obsoleto.\n *\n * 30 s. Curta o bastante para não trancar o usuário por minutos depois de um crash; longa o\n * bastante para não reclamar o lock de um processo vivo que só está lento em I/O. O dono toca o\n * arquivo a cada aquisição, então um processo ativo nunca cruza a janela.\n */\nexport const JANELA_DE_HEARTBEAT_MS = 30_000;\n\n/** Quem detém o lock. Gravado como JSON no `.lock`. */\ninterface DonoDoLock {\n pid: number;\n hostname: string;\n mtime: number;\n}\n\n/** Lê o dono do lock. `undefined` quando o arquivo sumiu ou o conteúdo é ilegível. */\nfunction lerDono(lockPath: string): DonoDoLock | undefined {\n let bruto: string;\n try {\n bruto = readFileSync(lockPath, \"utf8\");\n } catch {\n return undefined; // sumiu entre o EEXIST e a leitura — corrida benigna, trate como livre\n }\n try {\n const d = JSON.parse(bruto) as Partial<DonoDoLock>;\n if (\n typeof d.pid !== \"number\" ||\n typeof d.hostname !== \"string\" ||\n typeof d.mtime !== \"number\"\n ) {\n return undefined;\n }\n return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };\n } catch {\n // JSON ilegível: um lock que ninguém consegue interpretar não pode trancar a sessão para\n // sempre. Tratar como obsoleto é a escolha recuperável; o custo é o mesmo de um lock velho.\n return undefined;\n }\n}\n\n/** O processo existe? `signal 0` não envia nada — só consulta permissão/existência. */\nfunction processoVivo(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM significa que ele EXISTE e é de outro usuário.\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * O lock pode ser tomado de quem o detém?\n *\n * ADR-2 do plano: reclamar por `pid` sozinho tem falso positivo entre máquinas — o mesmo número\n * existe noutro host, apontando para um processo sem relação. Então:\n *\n * - **mesmo host:** o `pid` é autoritativo, e **só** ele. Processo morto ⇒ reclamável na hora;\n * processo vivo ⇒ nunca, por mais velho que o lock esteja.\n * - **outro host:** o `pid` não diz nada aqui. Só a janela de heartbeat vale, porque é o único\n * sinal que não mente entre máquinas.\n *\n * ## Por que a idade NÃO conta no mesmo host\n *\n * A primeira versão fazia `velho || !processoVivo(pid)`, e isso era um defeito grave: o `mtime` é\n * gravado na **aquisição** e não é tocado a cada append, então qualquer sessão que durasse mais que\n * a janela — isto é, **toda sessão real** — passava a ser roubável por outro processo. Dois\n * escritores no mesmo transcript é exatamente o que o lease existe para impedir.\n *\n * No mesmo host a pergunta \"o dono ainda existe?\" tem resposta exata, e a idade não acrescenta\n * informação nenhuma a ela — só um modo de errar. Manter a janela ali seria heurística por cima de\n * um fato.\n */\nfunction reclamavel(dono: DonoDoLock | undefined): boolean {\n if (dono === undefined) return true; // ilegível ou sumido\n if (dono.hostname !== hostname()) {\n return Date.now() - dono.mtime > JANELA_DE_HEARTBEAT_MS;\n }\n return !processoVivo(dono.pid);\n}\n\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n // M95 — `.writer.lock`, NÃO `.lock`.\n //\n // `withFileLock(path, fn)` já usa `<path>.lock` como companheiro. Enquanto o lease não era\n // chamado de lugar nenhum (o defeito que este milestone corrige) a colisão era teórica; ligá-lo\n // ao mesmo arquivo faria o lease de vida longa bloquear toda seção crítica curta do mesmo path.\n //\n // Dois arquivos porque são duas coisas: `withFileLock` protege uma seção com início e fim; o\n // lease é POSSE, mantida através de turnos, com `release()` explícito — a distinção que o\n // docstring do M81 acima já explica.\n const lockPath = `${sessionPath}.writer.lock`;\n const meu: DonoDoLock = { pid: process.pid, hostname: hostname(), mtime: Date.now() };\n let fd: number;\n try {\n fd = openSync(lockPath, \"wx\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n // M95 — o lock existe. Até aqui isso bastava para recusar, e era o defeito: uma TUI morta por\n // SIGKILL trancava o usuário para fora da própria sessão PERMANENTEMENTE, sem caminho de\n // recuperação documentado. Agora o lock diz quem é o dono, e um dono morto cede o lugar.\n if (!reclamavel(lerDono(lockPath))) throw new SessionBusyError(sessionPath);\n writeFileSync(lockPath, JSON.stringify(meu));\n return criarLease(sessionPath, lockPath);\n }\n closeSync(fd);\n writeFileSync(lockPath, JSON.stringify(meu));\n\n return criarLease(sessionPath, lockPath);\n}\n\n/** O lease em si — `release()` idempotente. Extraído porque a aquisição tem dois caminhos de saída. */\nfunction criarLease(sessionPath: string, lockPath: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n };\n}\n"]}
@@ -231,5 +231,5 @@ async function readTranscript(path) {
231
231
  }
232
232
 
233
233
  export { SessionTranscript, defaultBaseDir, encodeProjectDir, expandTilde, readTranscript, reconstructMessages, safeSessionId, transcriptPath, transcriptRoot, writeTranscript };
234
- //# sourceMappingURL=chunk-WPBTPSKQ.js.map
235
- //# sourceMappingURL=chunk-WPBTPSKQ.js.map
234
+ //# sourceMappingURL=chunk-Q56IYTBZ.js.map
235
+ //# sourceMappingURL=chunk-Q56IYTBZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/persistence/session-transcript.ts"],"names":[],"mappings":"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","file":"chunk-Q56IYTBZ.js","sourcesContent":["/**\n * SE40 — native session transcript (Claude-Code-compatible on-disk shape, theokit-native names).\n *\n * The theokit session format IS the Claude Code record shape (a `uuid`/`parentUuid` DAG of records with\n * structured `text`/`tool_use`/`tool_result`/`thinking` blocks) — so the ecosystem's read-side tools\n * parse our sessions AND, pointed at `~/.claude`, the Claude Code CLI can `--continue` them. This module\n * is the PURE core: append-per-turn record builders + the DAG reader that rebuilds `LlmMessage[]` for\n * resume. File I/O + loop wiring + append-only compaction live in the FS store.\n *\n * Reader mirrors `claude-code-log` dag.py: leaf = a uuid never used as a parentUuid; walk parentUuid to a\n * root (cycle-break); dedup a uuid across sessions keeping the earliest; a `compact_boundary` root\n * terminates the walk so a resume-after-compaction replays only the post-boundary continuation.\n *\n * referencia: knowledge-base/references/claude-code-log/claude_code_log/dag.py + models.py\n */\nimport { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport type { SessionRecord, TranscriptBlock } from \"../../types/session-record.js\";\nimport type { LlmContentPart, LlmMessage, LlmToolResultPart } from \"../llm/types.js\";\nimport { redactSecrets } from \"../security/redact.js\";\nimport { replaceFileAtomic } from \"./atomic-write.js\";\n\n// The `SessionRecord` contract now lives in the domain `types/` layer (SE46 DIP\n// direction). Re-exported here so existing importers of this DAG-core module\n// keep resolving the same name.\nexport type { SessionRecord } from \"../../types/session-record.js\";\n\nexport interface SessionTranscriptOptions {\n cwd: string;\n sessionId: string;\n model: string;\n version?: string;\n}\n\nexport interface AssistantTurn {\n text?: string;\n thinking?: string;\n toolCalls?: Array<{ id: string; name: string; input: Record<string, unknown> }>;\n}\n\n/** Claude Code path convention: cwd with every non-alphanumeric replaced by `-`. */\nexport function encodeProjectDir(cwd: string): string {\n return cwd.replace(/[^a-zA-Z0-9]/g, \"-\");\n}\n\n/** Make a caller-supplied session id safe as a path component (no traversal). */\nexport function safeSessionId(id: string): string {\n return id.replace(/[^a-zA-Z0-9_-]/g, \"-\");\n}\n\nconst red = (s: string): string => redactSecrets(s);\n\n/** Deep-redact a structured value (tool input) preserving object shape. */\nfunction redactValue(value: unknown): unknown {\n try {\n return JSON.parse(redactSecrets(JSON.stringify(value ?? null)));\n } catch {\n return redactSecrets(String(value));\n }\n}\n\n// M94 — era `Record<string, unknown>`. O tipo publicado descreve a mesma forma que este arquivo\n// sempre produziu; o alias local passa a apontar para ele para que escritor e leitor não possam\n// divergir em silêncio.\ntype Block = TranscriptBlock;\n\n/**\n * Builds a session transcript append-by-append, maintaining the `parentUuid` chain. Each turn mints a\n * fresh `uuid` and parents on the previous record; `appendCompactBoundary` starts a NEW ROOT.\n */\nexport class SessionTranscript {\n #records: SessionRecord[] = [];\n #last: string | null = null;\n #opts: SessionTranscriptOptions;\n\n constructor(opts: SessionTranscriptOptions) {\n this.#opts = opts;\n }\n\n /**\n * Seed a transcript from records already on disk so a subsequent append parents\n * on the existing DAG leaf (the last record in file order). Prior records are\n * re-emitted verbatim by {@link records}, so the whole file rewrites atomically\n * as one append-only line set — never shrinking (SE40 compaction is append-only).\n */\n static fromRecords(\n prior: readonly SessionRecord[],\n opts: SessionTranscriptOptions,\n ): SessionTranscript {\n const t = new SessionTranscript(opts);\n t.#records = [...prior];\n t.#last = prior.length > 0 ? (prior[prior.length - 1]?.uuid ?? null) : null;\n return t;\n }\n\n #push(\n rec: Omit<SessionRecord, \"uuid\" | \"parentUuid\" | \"sessionId\" | \"timestamp\">,\n ): SessionRecord {\n const full: SessionRecord = {\n ...rec,\n uuid: randomUUID(),\n parentUuid: this.#last,\n sessionId: this.#opts.sessionId,\n timestamp: new Date().toISOString(),\n isSidechain: false,\n userType: \"external\",\n cwd: this.#opts.cwd,\n version: this.#opts.version ?? \"1.0.0-theokit\",\n };\n this.#records.push(full);\n this.#last = full.uuid;\n return full;\n }\n\n appendUserTurn(text: string): SessionRecord {\n return this.#push({\n type: \"user\",\n message: { role: \"user\", content: [{ type: \"text\", text: red(text) }] },\n });\n }\n\n appendAssistantTurn(turn: AssistantTurn): SessionRecord {\n const content: Block[] = [];\n if (turn.thinking) content.push({ type: \"thinking\", thinking: red(turn.thinking) });\n if (turn.text) content.push({ type: \"text\", text: red(turn.text) });\n for (const c of turn.toolCalls ?? []) {\n content.push({ type: \"tool_use\", id: c.id, name: c.name, input: redactValue(c.input) });\n }\n return this.#push({\n type: \"assistant\",\n message: {\n id: randomUUID(),\n type: \"message\",\n role: \"assistant\",\n model: this.#opts.model,\n content,\n },\n });\n }\n\n appendToolResults(\n results: Array<{ toolUseId: string; content: string; isError?: boolean }>,\n ): SessionRecord {\n const content: Block[] = results.map((r) => ({\n type: \"tool_result\",\n tool_use_id: r.toolUseId,\n content: red(r.content),\n is_error: r.isError === true,\n }));\n return this.#push({ type: \"user\", message: { role: \"user\", content } });\n }\n\n /** Append-only compaction: a `compact_boundary` system record becomes a NEW ROOT (parentUuid null). */\n appendCompactBoundary(meta: { preTokens: number; trigger: string }): SessionRecord {\n const rec: SessionRecord = {\n type: \"system\",\n uuid: randomUUID(),\n parentUuid: null,\n sessionId: this.#opts.sessionId,\n timestamp: new Date().toISOString(),\n subtype: \"compact_boundary\",\n compactMetadata: meta,\n isSidechain: false,\n userType: \"external\",\n cwd: this.#opts.cwd,\n version: this.#opts.version ?? \"1.0.0-theokit\",\n };\n this.#records.push(rec);\n this.#last = rec.uuid;\n return rec;\n }\n\n records(): readonly SessionRecord[] {\n return this.#records;\n }\n}\n\n// ─── Reader (DAG → LlmMessage[]) ──────────────────────────────────────────────────────────────────\n\n/** Earliest timestamp seen per session id. */\nfunction sessionFirstTimestamps(records: readonly SessionRecord[]): Map<string, string> {\n const first = new Map<string, string>();\n for (const r of records) {\n const cur = first.get(r.sessionId);\n if (cur === undefined || r.timestamp < cur) first.set(r.sessionId, r.timestamp);\n }\n return first;\n}\n\n/** Dedup a uuid across sessions: keep the record whose session's earliest timestamp is smallest. */\nfunction dedupByUuid(records: readonly SessionRecord[]): Map<string, SessionRecord> {\n const firstTs = sessionFirstTimestamps(records);\n const byUuid = new Map<string, SessionRecord>();\n for (const r of records) {\n const existing = byUuid.get(r.uuid);\n const wins =\n existing === undefined ||\n (firstTs.get(r.sessionId) ?? \"\") < (firstTs.get(existing.sessionId) ?? \"\");\n if (wins) byUuid.set(r.uuid, r);\n }\n return byUuid;\n}\n\n/** Map one structured block to an LlmContentPart (`undefined` for thinking — dropped on read, SE42/#122). */\nfunction blockToPart(b: Block): LlmContentPart | undefined {\n if (b.type === \"text\") return { type: \"text\", text: String(b.text ?? \"\") };\n if (b.type === \"tool_use\") {\n return {\n type: \"tool_use\",\n id: String(b.id),\n name: String(b.name),\n input: (b.input as Record<string, unknown>) ?? {},\n };\n }\n if (b.type === \"tool_result\") {\n const tr: LlmToolResultPart = {\n type: \"tool_result\",\n toolUseId: String(b.tool_use_id),\n content: typeof b.content === \"string\" ? b.content : JSON.stringify(b.content),\n isError: b.is_error === true,\n };\n return tr;\n }\n return undefined;\n}\n\n/** Map one record to an LlmMessage; `undefined` for non-conversational (system) records. */\nfunction recordToMessage(rec: SessionRecord): LlmMessage | undefined {\n if (rec.type !== \"user\" && rec.type !== \"assistant\") return undefined;\n const blocks = rec.message?.content ?? [];\n const parts = blocks.map(blockToPart).filter((p): p is LlmContentPart => p !== undefined);\n return { role: rec.type, content: parts };\n}\n\n/** The most-recent leaf: the LAST record (in input order) whose uuid is never used as a parentUuid. */\nfunction findLatestLeaf(\n records: readonly SessionRecord[],\n byUuid: Map<string, SessionRecord>,\n parents: Set<string>,\n): SessionRecord | undefined {\n let leaf: SessionRecord | undefined;\n for (const r of records) {\n if (byUuid.get(r.uuid) === r && !parents.has(r.uuid)) leaf = r;\n }\n return leaf;\n}\n\n/** Walk parentUuid from a leaf to a root (cycle-safe), returning root→leaf order. */\nfunction walkToRoot(leaf: SessionRecord, byUuid: Map<string, SessionRecord>): SessionRecord[] {\n const chain: SessionRecord[] = [];\n const seen = new Set<string>();\n let cur: SessionRecord | undefined = leaf;\n while (cur !== undefined && !seen.has(cur.uuid)) {\n seen.add(cur.uuid);\n chain.push(cur);\n cur = cur.parentUuid !== null ? byUuid.get(cur.parentUuid) : undefined;\n }\n return chain.reverse();\n}\n\n/**\n * Rebuild the resumable `LlmMessage[]` from transcript records: dedup → find the most-recent leaf →\n * walk `parentUuid` to a root (a `compact_boundary` root terminates the walk) → map to LlmMessages.\n * Returns them in root→leaf (chronological replay) order.\n */\nexport function reconstructMessages(records: readonly SessionRecord[]): LlmMessage[] {\n const byUuid = dedupByUuid(records);\n const parents = new Set<string>();\n for (const r of byUuid.values()) if (r.parentUuid) parents.add(r.parentUuid);\n const leaf = findLatestLeaf(records, byUuid, parents);\n if (leaf === undefined) return [];\n\n const msgs: LlmMessage[] = [];\n for (const rec of walkToRoot(leaf, byUuid)) {\n const m = recordToMessage(rec);\n if (m !== undefined) msgs.push(m);\n }\n return msgs;\n}\n\n// ─── File I/O (the on-disk Claude layout) ─────────────────────────────────────────────────────────\n\n/**\n * Raiz do estado de transcript.\n *\n * `THEOKIT_HOME` vence; o fallback é `~/.theokit`. Home-ancorado de propósito, e NÃO\n * `getTheokitHome(cwd)` de `paths.ts`: aquele faz fallback para `<cwd>/.theokit`, e trocar\n * por ele moveria o transcript de todo mundo que **não** define a variável. O irmão com a\n * forma certa é `catalog-source-models-dev.ts` (M94 ADR-2).\n *\n * Antes do M94 esta função ignorava a env, então quem a definia tinha o estado partido em\n * dois em silêncio — as sessões continuavam em `~/.theokit` enquanto o resto do SDK migrava.\n */\nexport function transcriptRoot(): string {\n const override = process.env.THEOKIT_HOME?.trim();\n if (override !== undefined && override.length > 0) return override;\n return join(homedir(), \".theokit\");\n}\n\n/** @deprecated Use {@link transcriptRoot}. Mantido como alias — mesmo valor. */\nexport function defaultBaseDir(): string {\n return transcriptRoot();\n}\n\n/**\n * Expand a leading `~` / `~/` to the user's home dir. `node:path` does NOT expand `~`, so a\n * `baseDir: \"~/.claude\"` (the documented Claude Code CLI interop path) would otherwise resolve to a\n * literal `./~/.claude` under cwd and the CLI would never find the session. Absolute or relative\n * paths without a leading tilde pass through untouched.\n */\nexport function expandTilde(p: string): string {\n if (p === \"~\") return homedir();\n if (p.startsWith(\"~/\")) return join(homedir(), p.slice(2));\n return p;\n}\n\n/** The `.jsonl` path for a session: `<baseDir>/projects/<encoded-cwd>/<safe-sessionId>.jsonl`. */\nexport function transcriptPath(baseDir: string, cwd: string, sessionId: string): string {\n return join(baseDir, \"projects\", encodeProjectDir(cwd), `${safeSessionId(sessionId)}.jsonl`);\n}\n\n/** Write records as one JSONL line each (atomic; parent dir created). Returns the path. */\nexport async function writeTranscript(\n path: string,\n records: readonly SessionRecord[],\n): Promise<string> {\n await mkdir(dirname(path), { recursive: true });\n const body = records.map((r) => JSON.stringify(r)).join(\"\\n\");\n await replaceFileAtomic(path, records.length > 0 ? `${body}\\n` : \"\");\n return path;\n}\n\n/** Parse one JSONL line to a record; `undefined` for blank/torn/invalid lines (skipped on read). */\nfunction parseRecordLine(line: string): SessionRecord | undefined {\n const trimmed = line.trim();\n if (trimmed.length === 0) return undefined;\n try {\n const rec = JSON.parse(trimmed) as SessionRecord;\n return typeof rec.uuid === \"string\" && typeof rec.type === \"string\" ? rec : undefined;\n } catch {\n return undefined; // a torn last line (crash mid-write) — the rest of the DAG still reconstructs\n }\n}\n\n/** Parse a transcript `.jsonl` into records; a missing file yields `[]`; malformed lines are skipped. */\nexport async function readTranscript(path: string): Promise<SessionRecord[]> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (cause) {\n if ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw cause;\n }\n return raw\n .split(\"\\n\")\n .map(parseRecordLine)\n .filter((r): r is SessionRecord => r !== undefined);\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { sessions, transcriptKey, hydratedKeys } from './chunk-EEZ7CFMM.js';
2
- import { reconstructMessages, SessionTranscript } from './chunk-WPBTPSKQ.js';
1
+ import { sessions, hydratedKeys, transcriptKey } from './chunk-EEZ7CFMM.js';
2
+ import { reconstructMessages, SessionTranscript } from './chunk-Q56IYTBZ.js';
3
3
 
4
4
  // src/internal/session/agent-session-store.ts
5
5
  function seedTranscript(prior, opts) {
@@ -74,7 +74,28 @@ var recordCounts = /* @__PURE__ */ new Map();
74
74
  function appendSessionMessage(agentId, message) {
75
75
  const existing = sessions.get(agentId) ?? [];
76
76
  existing.push(message);
77
+ sessions.delete(agentId);
77
78
  sessions.set(agentId, existing);
79
+ aplicarTeto();
80
+ }
81
+ var TETO_DE_SESSOES_EM_CACHE = 32;
82
+ function aplicarTeto() {
83
+ while (sessions.size > TETO_DE_SESSOES_EM_CACHE) {
84
+ const maisAntiga = sessions.keys().next().value;
85
+ if (maisAntiga === void 0) return;
86
+ sessions.delete(maisAntiga);
87
+ hydratedKeys.delete(maisAntiga);
88
+ pendingWrites.delete(maisAntiga);
89
+ recordCounts.delete(maisAntiga);
90
+ }
91
+ }
92
+ function descartarSessao(cwd, agentId) {
93
+ const chave = transcriptKey(cwd, agentId);
94
+ let removidas = 0;
95
+ if (hydratedKeys.delete(chave)) removidas++;
96
+ if (pendingWrites.delete(agentId)) removidas++;
97
+ if (recordCounts.delete(agentId)) removidas++;
98
+ return removidas;
78
99
  }
79
100
  function getSessionMessages(agentId) {
80
101
  return sessions.get(agentId) ?? [];
@@ -87,7 +108,7 @@ function persistTurnToTranscript(store, loc, sessionId, turn, onCompact) {
87
108
  const count = (recordCounts.get(key) ?? 0) + 1;
88
109
  recordCounts.set(key, count);
89
110
  if (turn.autoCompact !== void 0) {
90
- const { autoCompactIfNeeded } = await import('./compact-session-UHGMWPMW.js');
111
+ const { autoCompactIfNeeded } = await import('./compact-session-J7VNCICZ.js');
91
112
  const fired = await autoCompactIfNeeded({
92
113
  store,
93
114
  loc,
@@ -147,6 +168,6 @@ function enqueueSessionWrite(cwd, agentId, fn) {
147
168
  return result;
148
169
  }
149
170
 
150
- export { appendSessionMessage, clearSession, enqueueSessionWrite, flushSessionWrites, getSessionMessages, hydrateSession, persistTurnToTranscript };
151
- //# sourceMappingURL=chunk-334WDVFZ.js.map
152
- //# sourceMappingURL=chunk-334WDVFZ.js.map
171
+ export { appendSessionMessage, clearSession, descartarSessao, enqueueSessionWrite, flushSessionWrites, getSessionMessages, hydrateSession, persistTurnToTranscript };
172
+ //# sourceMappingURL=chunk-SDYJESYZ.js.map
173
+ //# sourceMappingURL=chunk-SDYJESYZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/session/agent-session-store.ts","../src/internal/session/agent-session.ts"],"names":[],"mappings":"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","file":"chunk-SDYJESYZ.js","sourcesContent":["/**\n * SE40 — native session store over the Claude-shaped `.jsonl` transcript.\n *\n * The theokit session format IS the Claude Code record shape (see\n * `../../persistence/session-transcript.ts`). This module owns the FS lifecycle:\n * read existing records, seed a {@link SessionTranscript}'s parent chain, append\n * the new turn (user + assistant/tool blocks), and write append-only. Compaction\n * appends a `compact_boundary` record (a new root) — it NEVER shrinks the line\n * set (that would break the DAG). Reads reconstruct `LlmMessage[]` via\n * {@link reconstructMessages}, narrowed to the in-memory {@link SessionMessage}.\n *\n * @internal\n */\n\nimport type { ConversationStep, ConversationTurn } from \"../../types/conversation.js\";\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport {\n type AssistantTurn,\n reconstructMessages,\n type SessionRecord,\n SessionTranscript,\n} from \"../persistence/session-transcript.js\";\nimport type { SessionMessage } from \"./session-types.js\";\n\n/**\n * Seed a {@link SessionTranscript} from the already-on-disk records so a new\n * append parents correctly on the existing DAG leaf. The transcript re-emits the\n * seeded records verbatim before the new turn, so the whole file rewrites atomically\n * as one append-only line set. Returns the transcript primed with the prior records.\n */\nfunction seedTranscript(\n prior: readonly SessionRecord[],\n opts: { cwd: string; sessionId: string; model: string },\n): SessionTranscript {\n return SessionTranscript.fromRecords(prior, opts);\n}\n\n/** The turn to persist: the user's text plus the rich per-turn conversation view. */\nexport interface PersistTurnInput {\n userText: string;\n conversation: readonly ConversationTurn[];\n /**\n * M50 — when supplied, size-driven auto-compaction runs in the SAME write chain after the turn\n * persists (usage real vs the model's context window; summarizer injected by the caller).\n */\n autoCompact?: {\n usageTotal?: number | undefined;\n contextWindow?: number | undefined;\n summarize: (\n messages: readonly import(\"../../compaction.js\").CompressibleMessage[],\n ) => Promise<string>;\n };\n}\n\ninterface MappedTurn {\n assistant: AssistantTurn;\n toolResults: Array<{ toolUseId: string; content: string; isError?: boolean }>;\n}\n\n/** Fold one agent turn's steps into an assistant record + paired tool results. */\nfunction mapAgentTurn(steps: readonly ConversationStep[]): MappedTurn {\n const assistant: AssistantTurn = {};\n const toolResults: MappedTurn[\"toolResults\"] = [];\n const toolCalls: NonNullable<AssistantTurn[\"toolCalls\"]> = [];\n for (const step of steps) {\n if (step.type === \"thinkingMessage\") assistant.thinking = step.message.text;\n else if (step.type === \"assistantMessage\") assistant.text = step.message.text;\n else if (step.type === \"toolCall\")\n toolCalls.push({\n id: step.message.callId,\n name: step.message.name,\n input: (step.message.args as Record<string, unknown> | undefined) ?? {},\n });\n else\n toolResults.push({\n toolUseId: step.message.callId,\n content: step.message.result,\n isError: step.message.isError,\n });\n }\n if (toolCalls.length > 0) assistant.toolCalls = toolCalls;\n return { assistant, toolResults };\n}\n\n/** True when the assistant record carries any content worth emitting. */\nfunction hasAssistantContent(a: AssistantTurn): boolean {\n return a.text !== undefined || a.thinking !== undefined || (a.toolCalls?.length ?? 0) > 0;\n}\n\n/**\n * Map the rich `ConversationTurn[]` (from `run.conversation()`) into the native\n * assistant/tool records. One transcript turn is: an `assistant` record carrying\n * thinking + text + tool_use blocks, then (when present) a `user` record carrying\n * the paired tool_result blocks. Tool results pair with tool_use by `callId`.\n */\nfunction appendConversation(\n transcript: SessionTranscript,\n conversation: readonly ConversationTurn[],\n): void {\n for (const ct of conversation) {\n if (ct.type !== \"agentConversationTurn\") continue;\n const { assistant, toolResults } = mapAgentTurn(ct.turn.steps);\n if (hasAssistantContent(assistant)) transcript.appendAssistantTurn(assistant);\n if (toolResults.length > 0) transcript.appendToolResults(toolResults);\n }\n}\n\n/**\n * The per-agent transcript metadata used to seed a {@link SessionTranscript}. The\n * actual record I/O goes through the injected {@link SessionStore} (SE41), so this\n * no longer carries a `baseDir` — the store is already bound to its location.\n */\nexport interface TranscriptLocation {\n cwd: string;\n agentId: string;\n model: string;\n}\n\n/**\n * Read the session's records via the {@link SessionStore} and reconstruct the\n * resumable history, narrowed to the in-memory {@link SessionMessage} shape\n * (user/assistant text). Tool turns fold into assistant-role context so resume\n * keeps tool history. Works identically for the FS default and an external store.\n */\nexport async function readSessionMessages(\n store: SessionStore,\n agentId: string,\n): Promise<SessionMessage[]> {\n const records = await store.readRecords(agentId);\n return reconstructMessages(records).map(narrowToSessionMessage);\n}\n\ntype NarrowPart = { type: string; text?: string; content?: unknown; name?: string };\n\n/** Render one reconstructed content part as folded session text. */\nfunction partToText(p: NarrowPart): string {\n if (p.type === \"text\") return p.text ?? \"\";\n if (p.type === \"tool_use\") return `[tool call] ${p.name ?? \"\"}`;\n if (p.type === \"tool_result\") {\n const body = typeof p.content === \"string\" ? p.content : JSON.stringify(p.content);\n return `[tool result] ${body}`;\n }\n return \"\";\n}\n\n/** Flatten a reconstructed `LlmMessage` to the narrowed in-memory `SessionMessage`. */\nfunction narrowToSessionMessage(m: {\n role: \"system\" | \"user\" | \"assistant\";\n content: NarrowPart[];\n}): SessionMessage {\n const role = m.role === \"user\" ? \"user\" : \"assistant\";\n const text = m.content\n .map(partToText)\n .filter((s) => s.length > 0)\n .join(\"\\n\");\n return { role, text };\n}\n\n/** The records appended by the last turn (the delta beyond the seeded prior). */\nfunction deltaRecords(\n transcript: SessionTranscript,\n priorLength: number,\n): readonly SessionRecord[] {\n return transcript.records().slice(priorLength);\n}\n\n/**\n * Persist one conversation turn (user + assistant/tool records) to the session\n * store, append-only. Reads the prior records to seed the parent chain, folds the\n * new turn, and appends ONLY the delta via {@link SessionStore.appendRecords}\n * (the store owns the append atomicity — the FS default serializes under a file\n * lock). Within one process, turns for an agent are already chained upstream, so\n * the read→append is ordered; cross-host ordering is the external store's contract.\n */\nexport async function persistTurn(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n turn: PersistTurnInput,\n): Promise<void> {\n const prior = await store.readRecords(loc.agentId);\n const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });\n transcript.appendUserTurn(turn.userText);\n appendConversation(transcript, turn.conversation);\n await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));\n}\n\n/**\n * Append-only compaction: add a `compact_boundary` system record (a new root) so\n * resume replays only the post-boundary continuation. Appends only the boundary\n * record via the store — NEVER rewrites or shrinks the prior records.\n */\nexport async function appendCompactBoundaryRecord(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n meta: { preTokens: number; trigger: string },\n): Promise<void> {\n const prior = await store.readRecords(loc.agentId);\n const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });\n transcript.appendCompactBoundary(meta);\n await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));\n}\n","import type { SessionStore } from \"../../types/session-store.js\";\nimport {\n type PersistTurnInput,\n persistTurn,\n readSessionMessages,\n type TranscriptLocation,\n} from \"./agent-session-store.js\";\n\n/**\n * Per-agent conversation history kept across runs (and across `Agent.resume()`\n * within the same process). Lets the fixture responder recall prior facts when\n * the user asks a follow-up question.\n *\n * SE40 — disk persistence IS the native Claude-shaped `.jsonl` transcript. The\n * in-memory cache holds the narrowed `SessionMessage[]` (user/assistant text) so\n * a send can read `priorMessages` synchronously; the whole rich turn (user +\n * assistant + tool blocks) lands on disk once per send via {@link persistTurnToTranscript}\n * (fed by `run.conversation()`). Hydration on resume reconstructs the session by\n * walking the transcript DAG (`readSessionMessages`).\n *\n * @internal\n */\n\n// `SessionMessage` lives in `./session-types.ts` (leaf types file). Re-exported\n// for back-compat with downstream importers that pulled it from here.\nexport type { SessionMessage } from \"./session-types.js\";\n\nimport type { SessionMessage } from \"./session-types.js\";\n\n// M75 — o cache mora numa folha; ver session-cache.ts para a razao (quebra de ciclo por extracao).\nexport {\n hydratedKeys,\n invalidateSessionCache,\n sessions,\n transcriptKey,\n} from \"./session-cache.js\";\n\nimport { hydratedKeys, sessions, transcriptKey } from \"./session-cache.js\";\n\nconst pendingWrites = new Map<string, Promise<void>>();\nconst recordCounts = new Map<string, number>();\n\n/**\n * Append a session message to the in-memory cache only. Disk persistence for the\n * whole turn happens once per send via {@link persistTurnToTranscript}; the cache\n * feeds `priorMessages` / `onBeforeSend.previousMessageCount` synchronously.\n *\n * @internal\n */\nexport function appendSessionMessage(agentId: string, message: SessionMessage): void {\n const existing = sessions.get(agentId) ?? [];\n existing.push(message);\n // `delete` + `set` reinsere no FIM: `Map` do JS preserva ordem de inserção, então a primeira\n // chave é sempre a menos recentemente tocada. É o LRU inteiro, sem estrutura nova (rung 2/5 da\n // parcimônia — a ordenação que precisamos já é garantia da linguagem).\n sessions.delete(agentId);\n sessions.set(agentId, existing);\n aplicarTeto();\n}\n\n/**\n * Teto de sessões mantidas em memória.\n *\n * O runtime só lê a sessão **ativa**; o resto é cache puro, reconstruível do transcript em disco.\n * 32 é folgado de propósito — o caminho primário de remoção é o `descartarSessao()` explícito no\n * fim da vida do agente, e este teto é rede de segurança contra um processo de vida longa que roda\n * centenas de sessões (risco #2 do plano: um teto apertado poderia evictar sessão ainda referenciada\n * por um fluxo assíncrono em voo).\n */\nexport const TETO_DE_SESSOES_EM_CACHE = 32;\n\nfunction aplicarTeto(): void {\n while (sessions.size > TETO_DE_SESSOES_EM_CACHE) {\n const maisAntiga = sessions.keys().next().value;\n if (maisAntiga === undefined) return;\n sessions.delete(maisAntiga);\n hydratedKeys.delete(maisAntiga);\n pendingWrites.delete(maisAntiga);\n recordCounts.delete(maisAntiga);\n }\n}\n\n/**\n * Apaga a escrituração de módulo do agente e devolve quantas entradas saíram.\n *\n * M95 — `invalidateSessionCache` limpava **dois** dos quatro mapas (`sessions`, `hydratedKeys`);\n * `pendingWrites` e `recordCounts` nunca eram tocados por id, então cresciam pela vida do processo.\n * Nenhum dos dois é grande por entrada — o vazamento é de contagem, não de volume — mas cache sem\n * dono da remoção é cache que só cresce.\n *\n * Devolve a contagem para que o chamador possa provar a remoção; um segundo descarte devolve 0,\n * que é o que torna o teste de idempotência possível sem expor os mapas.\n */\nexport function descartarSessao(cwd: string, agentId: string): number {\n const chave = transcriptKey(cwd, agentId);\n let removidas = 0;\n // `sessions` NÃO é apagado aqui — e a distinção é medida, não estética. Ele é a conversa\n // legível, e há leitor legítimo DEPOIS do dispose: o golden `two-concurrent-sends-serialize`\n // chama `getSessionMessages(agentId)` após `agent.dispose()`. Apagá-lo aqui devolvia lista vazia\n // e quebrava dois goldens. Quem o limita é o teto LRU acima; os três abaixo são escrituração\n // pura, sem leitor pós-dispose.\n if (hydratedKeys.delete(chave)) removidas++;\n if (pendingWrites.delete(agentId)) removidas++;\n if (recordCounts.delete(agentId)) removidas++;\n return removidas;\n}\n\nexport function getSessionMessages(agentId: string): SessionMessage[] {\n return sessions.get(agentId) ?? [];\n}\n\n/**\n * Persist a full conversation turn (user + assistant + tool blocks) to the native\n * transcript. Chained per-(agent, transcript) so on-disk order matches send order,\n * and fire-and-forget so `send()` is not blocked by disk I/O. Every\n * M50 — when the caller supplies `turn.autoCompact`, size-driven auto-compaction (usage real vs\n * the model's context window) runs in this same chain, surfaced via the optional `onCompact` observer.\n *\n * @internal\n */\nexport function persistTurnToTranscript(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n turn: PersistTurnInput,\n onCompact?: () => void,\n): void {\n const key = transcriptKey(loc.cwd, loc.agentId);\n // Divida PRE-EXISTENTE, exposta quando o M75 consertou a config Biome que abortava antes\n // de varrer estes arquivos (raiz aninhada em refactor/). Nao e codigo novo e nao foi tocado\n // pelo M75; refatorar internals do SDK sem revisao trocaria um problema visivel por um diff\n // arriscado. Rastreado em usetheodev/theokit-sdk#151.\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: ver a razao logo acima\n const chained = (pendingWrites.get(key) ?? Promise.resolve()).then(async () => {\n try {\n await persistTurn(store, loc, sessionId, turn);\n const count = (recordCounts.get(key) ?? 0) + 1;\n recordCounts.set(key, count);\n // M50 — the 50-turn no-summary boundary stub is GONE (it silently amnesia'd the session).\n // Auto-compaction is now size-driven with a real summary: see `maybeAutoCompact` below,\n // invoked in this same write chain by the post-run lifecycle when usage is known.\n if (turn.autoCompact !== undefined) {\n const { autoCompactIfNeeded } = await import(\"./compact-session.js\");\n const fired = await autoCompactIfNeeded({\n store,\n loc,\n sessionId,\n usageTotal: turn.autoCompact.usageTotal,\n contextWindow: turn.autoCompact.contextWindow,\n turnCount: count,\n summarize: turn.autoCompact.summarize,\n });\n if (fired) onCompact?.();\n }\n } catch (cause) {\n const msg = cause instanceof Error ? cause.message : String(cause);\n process.stderr.write(\n `[theokit-sdk] session transcript write failed (${loc.agentId}): ${msg}\\n`,\n );\n }\n });\n pendingWrites.set(\n key,\n chained.then(\n () => undefined,\n () => undefined,\n ),\n );\n}\n\n/**\n * Load the persisted transcript into the in-memory cache. Idempotent per\n * (baseDir, cwd, agentId). Call once per agent lifecycle before the first read.\n *\n * @internal\n */\nexport async function hydrateSession(\n agentId: string,\n loc: { store: SessionStore; cwd: string },\n): Promise<void> {\n const key = transcriptKey(loc.cwd, agentId);\n if (hydratedKeys.has(key)) return;\n hydratedKeys.add(key);\n\n const persisted = await readSessionMessages(loc.store, agentId);\n if (persisted.length === 0) return;\n // M51 review F4 — the DISK is the source of truth at hydration time: after an invalidation\n // (compact/inject), an in-flight turn may have repopulated the cache with a SINGLE message before\n // this hydrate ran; the old \"skip when non-empty\" guard then pinned the parent to a 1-message\n // context (history + injected pair lost until restart). The persist chain serializes writes, so\n // the disk already contains that in-flight turn — replacing is always correct.\n sessions.set(agentId, persisted);\n}\n\n/**\n * Wait for all pending transcript writes to settle. Used by tests and by the\n * agent dispose path so on-disk state matches in-memory before the caller proceeds.\n *\n * @internal\n */\nexport async function flushSessionWrites(): Promise<void> {\n while (pendingWrites.size > 0) {\n const all = Array.from(pendingWrites.values());\n pendingWrites.clear();\n await Promise.all(all);\n }\n}\n\nexport function clearSession(agentId: string): void {\n sessions.delete(agentId);\n}\n\n/**\n * M50 review F1 — drop BOTH the message cache and the hydration marker so the NEXT send re-hydrates\n * from disk. Without this, a compaction only helps after a process restart: the live process keeps\n * sending the full pre-compact history (the cache is read synchronously by every send).\n */\n/**\n * M50 review F5 — run `fn` serialized on the SAME per-(cwd,agentId) write chain the per-turn\n * persistence uses, so a manual `Agent.compact` can never interleave with an in-flight turn's\n * writes (boundary landing mid-turn would orphan the turn from the replay).\n */\nexport function enqueueSessionWrite<T>(\n cwd: string,\n agentId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n const key = transcriptKey(cwd, agentId);\n const prior = pendingWrites.get(key) ?? Promise.resolve();\n const result = prior.then(fn);\n pendingWrites.set(\n key,\n result.then(\n () => undefined,\n () => undefined,\n ),\n );\n return result;\n}\n\n/** Test-only: drop every cached session and hydration marker. @internal */\nexport function clearAllSessions(): void {\n sessions.clear();\n hydratedKeys.clear();\n recordCounts.clear();\n // M50 review F11 — the auto-compact attempt marks live on globalThis; tests reset them here.\n const g = globalThis as unknown as Record<symbol, Map<string, number>>;\n const sym = Symbol.for(\"theokit-sdk.session.auto-compact-attempts\");\n g[sym]?.clear();\n}\n"]}