@theokit/sdk 4.31.1 → 4.33.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.
Files changed (113) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/{agent-BKNJIOEY.cjs → agent-2STDGKNI.cjs} +11 -10
  3. package/dist/{agent-BKNJIOEY.cjs.map → agent-2STDGKNI.cjs.map} +1 -1
  4. package/dist/{agent-CbZPGFfM.d.cts → agent-DGv1-d81.d.cts} +51 -1
  5. package/dist/{agent-HW7OY2W3.js → agent-LFSVFAUI.js} +10 -9
  6. package/dist/{agent-HW7OY2W3.js.map → agent-LFSVFAUI.js.map} +1 -1
  7. package/dist/{agent-BJbGi8-R.d.ts → agent-SmxntivO.d.ts} +51 -1
  8. package/dist/{chunk-U2FO3MRG.cjs → chunk-2DYI7MIF.cjs} +2 -2
  9. package/dist/chunk-2DYI7MIF.cjs.map +1 -0
  10. package/dist/{chunk-OIYQBUW4.cjs → chunk-6M6A6WWW.cjs} +2 -2
  11. package/dist/{chunk-OIYQBUW4.cjs.map → chunk-6M6A6WWW.cjs.map} +1 -1
  12. package/dist/chunk-B3HUL2OQ.js +78 -0
  13. package/dist/chunk-B3HUL2OQ.js.map +1 -0
  14. package/dist/{chunk-E2YEDATX.cjs → chunk-E33A7YIR.cjs} +28 -6
  15. package/dist/chunk-E33A7YIR.cjs.map +1 -0
  16. package/dist/chunk-FER7UMIC.cjs +75 -0
  17. package/dist/chunk-FER7UMIC.cjs.map +1 -0
  18. package/dist/{chunk-NZCFLEHO.cjs → chunk-FTRNHGAD.cjs} +11 -11
  19. package/dist/{chunk-NZCFLEHO.cjs.map → chunk-FTRNHGAD.cjs.map} +1 -1
  20. package/dist/{chunk-EVADEGM7.cjs → chunk-FX2DAR5R.cjs} +7 -5
  21. package/dist/chunk-FX2DAR5R.cjs.map +1 -0
  22. package/dist/{chunk-WA3FO4R7.js → chunk-GBCQ5742.js} +7 -6
  23. package/dist/chunk-GBCQ5742.js.map +1 -0
  24. package/dist/{chunk-S6F3FKFR.cjs → chunk-HTO4C32Q.cjs} +67 -51
  25. package/dist/chunk-HTO4C32Q.cjs.map +1 -0
  26. package/dist/{chunk-XDNGF5BY.js → chunk-KT6SWUY6.js} +4 -4
  27. package/dist/{chunk-XDNGF5BY.js.map → chunk-KT6SWUY6.js.map} +1 -1
  28. package/dist/chunk-LDDUIXWS.cjs +81 -0
  29. package/dist/chunk-LDDUIXWS.cjs.map +1 -0
  30. package/dist/chunk-OMDRRHRF.js +73 -0
  31. package/dist/chunk-OMDRRHRF.js.map +1 -0
  32. package/dist/{chunk-GQBXBHZ2.cjs → chunk-PZ4Q5K7O.cjs} +5 -5
  33. package/dist/{chunk-GQBXBHZ2.cjs.map → chunk-PZ4Q5K7O.cjs.map} +1 -1
  34. package/dist/{chunk-VGUOUAM2.js → chunk-Q56IYTBZ.js} +2 -2
  35. package/dist/chunk-Q56IYTBZ.js.map +1 -0
  36. package/dist/{chunk-SJLOIZ4Z.js → chunk-SDYJESYZ.js} +27 -6
  37. package/dist/chunk-SDYJESYZ.js.map +1 -0
  38. package/dist/{chunk-CCRWSZ2F.js → chunk-TKE4RTYA.js} +31 -15
  39. package/dist/chunk-TKE4RTYA.js.map +1 -0
  40. package/dist/{chunk-WYPHQ7NM.js → chunk-WBGCDZDV.js} +3 -3
  41. package/dist/{chunk-WYPHQ7NM.js.map → chunk-WBGCDZDV.js.map} +1 -1
  42. package/dist/{chunk-DGRZNMTU.js → chunk-XV2RBL6D.js} +2 -2
  43. package/dist/{chunk-DGRZNMTU.js.map → chunk-XV2RBL6D.js.map} +1 -1
  44. package/dist/{compact-session-42BK7RE3.cjs → compact-session-24PK2G4M.cjs} +13 -13
  45. package/dist/{compact-session-42BK7RE3.cjs.map → compact-session-24PK2G4M.cjs.map} +1 -1
  46. package/dist/{compact-session-QMBOCFVQ.js → compact-session-J7VNCICZ.js} +5 -5
  47. package/dist/{compact-session-QMBOCFVQ.js.map → compact-session-J7VNCICZ.js.map} +1 -1
  48. package/dist/compaction.cjs +17 -13
  49. package/dist/compaction.d.cts +8 -0
  50. package/dist/compaction.d.ts +8 -0
  51. package/dist/compaction.js +1 -1
  52. package/dist/{cron-BwojPwEs.d.cts → cron-BiK_aNJI.d.cts} +1 -1
  53. package/dist/{cron-BU9qD5B6.d.ts → cron-Dx7B-3WB.d.ts} +1 -1
  54. package/dist/cron.cjs +10 -9
  55. package/dist/cron.d.cts +2 -2
  56. package/dist/cron.d.ts +2 -2
  57. package/dist/cron.js +9 -8
  58. package/dist/eval.cjs +9 -8
  59. package/dist/eval.cjs.map +1 -1
  60. package/dist/eval.js +8 -7
  61. package/dist/eval.js.map +1 -1
  62. package/dist/fs-session-store-MZ4GD2C6.js +10 -0
  63. package/dist/{fs-session-store-T7YPW5A4.js.map → fs-session-store-MZ4GD2C6.js.map} +1 -1
  64. package/dist/fs-session-store-YMBSJ5Y7.cjs +19 -0
  65. package/dist/{fs-session-store-IIP7ZBYE.cjs.map → fs-session-store-YMBSJ5Y7.cjs.map} +1 -1
  66. package/dist/index.cjs +52 -32
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.cts +19 -4
  69. package/dist/index.d.ts +19 -4
  70. package/dist/index.js +32 -12
  71. package/dist/index.js.map +1 -1
  72. package/dist/{inject-session-6ZMCA2NE.cjs → inject-session-343WIDCM.cjs} +6 -6
  73. package/dist/{inject-session-6ZMCA2NE.cjs.map → inject-session-343WIDCM.cjs.map} +1 -1
  74. package/dist/{inject-session-YA3GQE4G.js → inject-session-RZEDWO53.js} +4 -4
  75. package/dist/{inject-session-YA3GQE4G.js.map → inject-session-RZEDWO53.js.map} +1 -1
  76. package/dist/internal/local-agent/local-agent.d.ts +93 -1
  77. package/dist/internal/persistence/fs-session-store.d.cts +16 -0
  78. package/dist/internal/persistence/fs-session-store.d.ts +16 -0
  79. package/dist/internal/persistence/index.cjs +7 -7
  80. package/dist/internal/persistence/index.js +1 -1
  81. package/dist/internal/persistence/paths.d.cts +11 -1
  82. package/dist/internal/persistence/paths.d.ts +11 -1
  83. package/dist/internal/persistence/session-writer.d.cts +8 -0
  84. package/dist/internal/persistence/session-writer.d.ts +8 -0
  85. package/dist/internal/session/agent-session.d.ts +22 -0
  86. package/dist/persistence.cjs +13 -39
  87. package/dist/persistence.cjs.map +1 -1
  88. package/dist/persistence.d.cts +1 -0
  89. package/dist/persistence.d.ts +1 -0
  90. package/dist/persistence.js +4 -36
  91. package/dist/persistence.js.map +1 -1
  92. package/dist/{session-transcript-5VZUWEJ5.js → session-transcript-F2ANTA2M.js} +3 -3
  93. package/dist/{session-transcript-5VZUWEJ5.js.map → session-transcript-F2ANTA2M.js.map} +1 -1
  94. package/dist/{session-transcript-T4SB66I7.cjs → session-transcript-JXFMPLOT.cjs} +13 -13
  95. package/dist/{session-transcript-T4SB66I7.cjs.map → session-transcript-JXFMPLOT.cjs.map} +1 -1
  96. package/dist/subagents-loader.d.cts +1 -1
  97. package/dist/subagents-loader.d.ts +1 -1
  98. package/dist/types/session-record.d.ts +51 -1
  99. package/package.json +1 -1
  100. package/dist/chunk-CCRWSZ2F.js.map +0 -1
  101. package/dist/chunk-E2YEDATX.cjs.map +0 -1
  102. package/dist/chunk-EVADEGM7.cjs.map +0 -1
  103. package/dist/chunk-IOOSAZZH.cjs +0 -30
  104. package/dist/chunk-IOOSAZZH.cjs.map +0 -1
  105. package/dist/chunk-RPKTXBPK.js +0 -28
  106. package/dist/chunk-RPKTXBPK.js.map +0 -1
  107. package/dist/chunk-S6F3FKFR.cjs.map +0 -1
  108. package/dist/chunk-SJLOIZ4Z.js.map +0 -1
  109. package/dist/chunk-U2FO3MRG.cjs.map +0 -1
  110. package/dist/chunk-VGUOUAM2.js.map +0 -1
  111. package/dist/chunk-WA3FO4R7.js.map +0 -1
  112. package/dist/fs-session-store-IIP7ZBYE.cjs +0 -17
  113. package/dist/fs-session-store-T7YPW5A4.js +0 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.33.0
4
+
5
+ ### Minor Changes
6
+
7
+ - M95 — o escritor único passa a existir de fato, e as caches de sessão ganham dono.
8
+
9
+ - **A garantia de escritor único está ligada.** Ela existia como função desde a versão que a introduziu e não tinha **nenhum** chamador: o transcript nunca foi protegido. Agora o store a adquire na primeira gravação e a solta ao encerrar.
10
+ - **Um processo que morreu deixa de trancar a sessão para sempre.** O lock não registrava dono, então uma interface encerrada abruptamente bloqueava o usuário fora da própria sessão sem caminho de recuperação. O lock passa a gravar `{pid, hostname, mtime}`, e um dono morto — ou um lock mais velho que a janela de heartbeat — cede o lugar. Entre máquinas diferentes só a janela vale, porque um número de processo não significa nada fora do host onde nasceu.
11
+ - **As caches de sessão em memória param de crescer sem limite.** Duas das quatro nunca eram apagadas por sessão; agora todas caem no encerramento do agente, e a conversa em cache respeita um teto com descarte da menos recente.
12
+
13
+ ### Corrigido
14
+
15
+ - **Uma janela de contexto declarada absurdamente alta volta a ser limitada mesmo sem entrada de catálogo.** O limite só existia quando havia catálogo — e a razão de ser da declaração é justamente o modelo que não tem. Um zero a mais na configuração fazia o agente nunca compactar até o provider recusar o turno.
16
+ - **A resolução de provider a partir do id de modelo passa a usar o parser canônico**, então aliases, maiúsculas e espaços resolvem como em todo o resto do SDK. A versão anterior refazia a separação à mão e não reconhecia sete formas válidas.
17
+
18
+ ## 4.32.0
19
+
20
+ ### Minor Changes
21
+
22
+ - M94 — publica quatro resolvedores que o SDK já conhecia internamente.
23
+
24
+ - **`transcriptRoot()` é exportado e honra `THEOKIT_HOME`.** Antes, a raiz do transcript ignorava a variável enquanto os stores irmãos a respeitavam: quem a definia tinha o estado partido em dois em silêncio, e as sessões antigas sumiam da listagem sem erro. O fallback continua sendo `~/.theokit` — deliberadamente **não** o resolvedor cwd-ancorado de `paths.ts`, cuja troca moveria o transcript de quem **não** define a variável.
25
+ - **`ModelSelection.contextWindow` atravessa até o orçamento de compactação.** O resolvedor aceitava um `override` desde a versão anterior e nenhum caminho de produção o passava: um modelo de 400k sem entrada de catálogo era orçado contra o piso de 128k e compactava cerca de três vezes mais do que precisava. Um valor acima do que o catálogo conhece continua sendo limitado.
26
+ - **`SessionRecord.message` deixa de ser `Record<string, unknown>`.** O novo `TranscriptMessage` descreve a forma que o escritor sempre produziu. **Não** se chama `SessionMessage`: esse nome já existe com forma incompatível, e reaproveitá-lo repetiria uma quebra silenciosa anterior. A leitura do disco segue tolerante — o que muda é o tipo.
27
+ - **`Provider.forModel(modelId)`** dá um dono único à gramática `provider/modelo`. Um id sem barra devolve `undefined` em vez de casar parcialmente, para que quem chama possa distinguir "modelo não-roteável" de "caminho default".
28
+
29
+ Nota de rótulo: `4.31.1` já continha, por engano, parte destas adições — foi publicado como patch quando o correto era minor. Nada quebra (adições são compatíveis), e esta versão declara a superfície corretamente.
30
+
3
31
  ## 4.31.1
4
32
 
5
33
  ### Patch Changes
@@ -1,18 +1,18 @@
1
1
  'use strict';
2
2
 
3
- var chunkS6F3FKFR_cjs = require('./chunk-S6F3FKFR.cjs');
3
+ var chunkHTO4C32Q_cjs = require('./chunk-HTO4C32Q.cjs');
4
4
  require('./chunk-BV2MWEMV.cjs');
5
5
  require('./chunk-MZ5ZRORA.cjs');
6
- require('./chunk-E2YEDATX.cjs');
6
+ require('./chunk-E33A7YIR.cjs');
7
7
  require('./chunk-BUUUWQMB.cjs');
8
- require('./chunk-IOOSAZZH.cjs');
9
- require('./chunk-NZCFLEHO.cjs');
8
+ require('./chunk-FER7UMIC.cjs');
9
+ require('./chunk-FTRNHGAD.cjs');
10
10
  require('./chunk-LUSXDZPV.cjs');
11
11
  require('./chunk-IT3PWBW4.cjs');
12
12
  require('./chunk-JLMXKTBX.cjs');
13
13
  require('./chunk-N24XBYNX.cjs');
14
14
  require('./chunk-WPZTYOUX.cjs');
15
- require('./chunk-OIYQBUW4.cjs');
15
+ require('./chunk-6M6A6WWW.cjs');
16
16
  require('./chunk-MUUQ2WFJ.cjs');
17
17
  require('./chunk-6JSIEDKL.cjs');
18
18
  require('./chunk-G2F67XUR.cjs');
@@ -21,15 +21,16 @@ require('./chunk-YPEPCZHF.cjs');
21
21
  require('./chunk-VJ26EKGJ.cjs');
22
22
  require('./chunk-GIW4L6HY.cjs');
23
23
  require('./chunk-7S7KKUO6.cjs');
24
+ require('./chunk-LDDUIXWS.cjs');
24
25
  require('./chunk-CJVMDRQB.cjs');
25
26
  require('./chunk-JD74LN7R.cjs');
26
27
  require('./chunk-YLQQX5W2.cjs');
27
- require('./chunk-U2FO3MRG.cjs');
28
+ require('./chunk-2DYI7MIF.cjs');
28
29
  require('./chunk-ANIZEWTU.cjs');
29
30
  require('./chunk-WJVHMTKB.cjs');
30
31
  require('./chunk-TIZ4TS5V.cjs');
31
32
  require('./chunk-RZQ6E5YZ.cjs');
32
- require('./chunk-EVADEGM7.cjs');
33
+ require('./chunk-FX2DAR5R.cjs');
33
34
  require('./chunk-VYHJZVL5.cjs');
34
35
  require('./chunk-D42XHI4O.cjs');
35
36
  require('./chunk-HFPGOTVF.cjs');
@@ -44,7 +45,7 @@ require('./chunk-BEURCLDV.cjs');
44
45
 
45
46
  Object.defineProperty(exports, "Agent", {
46
47
  enumerable: true,
47
- get: function () { return chunkS6F3FKFR_cjs.Agent; }
48
+ get: function () { return chunkHTO4C32Q_cjs.Agent; }
48
49
  });
49
- //# sourceMappingURL=agent-BKNJIOEY.cjs.map
50
- //# sourceMappingURL=agent-BKNJIOEY.cjs.map
50
+ //# sourceMappingURL=agent-2STDGKNI.cjs.map
51
+ //# sourceMappingURL=agent-2STDGKNI.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-BKNJIOEY.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-2STDGKNI.cjs"}
@@ -16,6 +16,49 @@ import * as zod from 'zod';
16
16
  * @public
17
17
  */
18
18
  /** One transcript record (one JSONL line). `message` absent on `system` (compact_boundary) records. */
19
+ /** Um bloco de conteúdo dentro de {@link TranscriptMessage}. */
20
+ type TranscriptBlock = {
21
+ type: "text";
22
+ text: string;
23
+ } | {
24
+ type: "thinking";
25
+ thinking?: string;
26
+ signature?: string;
27
+ } | {
28
+ type: "tool_use";
29
+ id: string;
30
+ name: string;
31
+ input: unknown;
32
+ } | {
33
+ type: "tool_result";
34
+ tool_use_id: string;
35
+ content: unknown;
36
+ is_error?: boolean;
37
+ };
38
+ /**
39
+ * O corpo de mensagem de um {@link SessionRecord}.
40
+ *
41
+ * **NÃO se chama `SessionMessage`** — esse nome já existe em `internal/session/session-types.ts` com
42
+ * a forma `{role, text}`, incompatível com esta. Repropor um nome exportado com forma nova é
43
+ * precisamente a quebra silenciosa que o M91 custou dois patches para desfazer.
44
+ *
45
+ * O parse do disco continua **tolerante** (`readTranscript` pula linha malformada): o que muda aqui
46
+ * é o **tipo**, não a leniência — registros gravados por versões anteriores continuam legíveis.
47
+ *
48
+ * @public
49
+ */
50
+ interface TranscriptMessage {
51
+ role: "user" | "assistant";
52
+ content: TranscriptBlock[];
53
+ /**
54
+ * Os três campos abaixo aparecem **só** no registro de assistant, e vêm do escritor
55
+ * (`SessionTranscript.appendAssistant`). Declarados opcionais porque o registro de user não os
56
+ * tem — medido no próprio escritor, não presumido.
57
+ */
58
+ id?: string;
59
+ type?: "message";
60
+ model?: string;
61
+ }
19
62
  interface SessionRecord {
20
63
  type: "user" | "assistant" | "system";
21
64
  uuid: string;
@@ -31,7 +74,14 @@ interface SessionRecord {
31
74
  preTokens: number;
32
75
  trigger: string;
33
76
  };
34
- message?: Record<string, unknown>;
77
+ /**
78
+ * O corpo da mensagem, na forma que o escritor de transcript de fato grava.
79
+ *
80
+ * M94 — era `Record<string, unknown>`, e o consumidor recuperava o tipo com cast a cada leitura.
81
+ * A forma **sempre foi fixa** (`SessionTranscript.appendToolResults` / `#push` a produzem); só não
82
+ * estava declarada. Segue opcional porque registros de sistema não carregam mensagem.
83
+ */
84
+ message?: TranscriptMessage;
35
85
  }
36
86
 
37
87
  /**
@@ -1,16 +1,16 @@
1
- export { Agent } from './chunk-CCRWSZ2F.js';
1
+ export { Agent } from './chunk-TKE4RTYA.js';
2
2
  import './chunk-2SFBB54R.js';
3
3
  import './chunk-HRA3CPGG.js';
4
- import './chunk-SJLOIZ4Z.js';
4
+ import './chunk-SDYJESYZ.js';
5
5
  import './chunk-K2BQQ445.js';
6
- import './chunk-RPKTXBPK.js';
7
- import './chunk-XDNGF5BY.js';
6
+ import './chunk-OMDRRHRF.js';
7
+ import './chunk-KT6SWUY6.js';
8
8
  import './chunk-6CLRANZA.js';
9
9
  import './chunk-EEZ7CFMM.js';
10
10
  import './chunk-XAUBQMQU.js';
11
11
  import './chunk-XIG2IE3L.js';
12
12
  import './chunk-7GFX45DD.js';
13
- import './chunk-DGRZNMTU.js';
13
+ import './chunk-XV2RBL6D.js';
14
14
  import './chunk-FD2UT76F.js';
15
15
  import './chunk-U54XCYG3.js';
16
16
  import './chunk-FV3YEMQ7.js';
@@ -19,15 +19,16 @@ import './chunk-UJ7IQJAG.js';
19
19
  import './chunk-7AD3ZTZL.js';
20
20
  import './chunk-V4XRLPEF.js';
21
21
  import './chunk-5PZMMF4H.js';
22
+ import './chunk-B3HUL2OQ.js';
22
23
  import './chunk-UAYYNWYJ.js';
23
24
  import './chunk-77FOZCOT.js';
24
25
  import './chunk-2XLKLVVR.js';
25
- import './chunk-VGUOUAM2.js';
26
+ import './chunk-Q56IYTBZ.js';
26
27
  import './chunk-BXV2QUIC.js';
27
28
  import './chunk-4VPXM6UU.js';
28
29
  import './chunk-S2BBRVQX.js';
29
30
  import './chunk-BNIFFFH3.js';
30
- import './chunk-WA3FO4R7.js';
31
+ import './chunk-GBCQ5742.js';
31
32
  import './chunk-X2FR4OIT.js';
32
33
  import './chunk-KAP4GJHO.js';
33
34
  import './chunk-LU7VALW5.js';
@@ -37,5 +38,5 @@ import './chunk-POQT2OIF.js';
37
38
  import './chunk-FKCC2MED.js';
38
39
  import './chunk-WLKFWQSJ.js';
39
40
  import './chunk-JTFYYKDT.js';
40
- //# sourceMappingURL=agent-HW7OY2W3.js.map
41
- //# sourceMappingURL=agent-HW7OY2W3.js.map
41
+ //# sourceMappingURL=agent-LFSVFAUI.js.map
42
+ //# sourceMappingURL=agent-LFSVFAUI.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-HW7OY2W3.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-LFSVFAUI.js"}
@@ -16,6 +16,49 @@ import * as zod from 'zod';
16
16
  * @public
17
17
  */
18
18
  /** One transcript record (one JSONL line). `message` absent on `system` (compact_boundary) records. */
19
+ /** Um bloco de conteúdo dentro de {@link TranscriptMessage}. */
20
+ type TranscriptBlock = {
21
+ type: "text";
22
+ text: string;
23
+ } | {
24
+ type: "thinking";
25
+ thinking?: string;
26
+ signature?: string;
27
+ } | {
28
+ type: "tool_use";
29
+ id: string;
30
+ name: string;
31
+ input: unknown;
32
+ } | {
33
+ type: "tool_result";
34
+ tool_use_id: string;
35
+ content: unknown;
36
+ is_error?: boolean;
37
+ };
38
+ /**
39
+ * O corpo de mensagem de um {@link SessionRecord}.
40
+ *
41
+ * **NÃO se chama `SessionMessage`** — esse nome já existe em `internal/session/session-types.ts` com
42
+ * a forma `{role, text}`, incompatível com esta. Repropor um nome exportado com forma nova é
43
+ * precisamente a quebra silenciosa que o M91 custou dois patches para desfazer.
44
+ *
45
+ * O parse do disco continua **tolerante** (`readTranscript` pula linha malformada): o que muda aqui
46
+ * é o **tipo**, não a leniência — registros gravados por versões anteriores continuam legíveis.
47
+ *
48
+ * @public
49
+ */
50
+ interface TranscriptMessage {
51
+ role: "user" | "assistant";
52
+ content: TranscriptBlock[];
53
+ /**
54
+ * Os três campos abaixo aparecem **só** no registro de assistant, e vêm do escritor
55
+ * (`SessionTranscript.appendAssistant`). Declarados opcionais porque o registro de user não os
56
+ * tem — medido no próprio escritor, não presumido.
57
+ */
58
+ id?: string;
59
+ type?: "message";
60
+ model?: string;
61
+ }
19
62
  interface SessionRecord {
20
63
  type: "user" | "assistant" | "system";
21
64
  uuid: string;
@@ -31,7 +74,14 @@ interface SessionRecord {
31
74
  preTokens: number;
32
75
  trigger: string;
33
76
  };
34
- message?: Record<string, unknown>;
77
+ /**
78
+ * O corpo da mensagem, na forma que o escritor de transcript de fato grava.
79
+ *
80
+ * M94 — era `Record<string, unknown>`, e o consumidor recuperava o tipo com cast a cada leitura.
81
+ * A forma **sempre foi fixa** (`SessionTranscript.appendToolResults` / `#push` a produzem); só não
82
+ * estava declarada. Segue opcional porque registros de sistema não carregam mensagem.
83
+ */
84
+ message?: TranscriptMessage;
35
85
  }
36
86
 
37
87
  /**
@@ -242,5 +242,5 @@ exports.safeSessionId = safeSessionId;
242
242
  exports.transcriptPath = transcriptPath;
243
243
  exports.transcriptRoot = transcriptRoot;
244
244
  exports.writeTranscript = writeTranscript;
245
- //# sourceMappingURL=chunk-U2FO3MRG.cjs.map
246
- //# sourceMappingURL=chunk-U2FO3MRG.cjs.map
245
+ //# sourceMappingURL=chunk-2DYI7MIF.cjs.map
246
+ //# sourceMappingURL=chunk-2DYI7MIF.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/persistence/session-transcript.ts"],"names":["redactSecrets","randomUUID","join","homedir","path","mkdir","dirname","replaceFileAtomic","readFile"],"mappings":"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","file":"chunk-2DYI7MIF.cjs","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"]}
@@ -107,5 +107,5 @@ exports.getTheokitHome = getTheokitHome;
107
107
  exports.migrateSchema = migrateSchema;
108
108
  exports.readVersionedJson = readVersionedJson;
109
109
  exports.writeVersionedJson = writeVersionedJson;
110
- //# sourceMappingURL=chunk-OIYQBUW4.cjs.map
111
- //# sourceMappingURL=chunk-OIYQBUW4.cjs.map
110
+ //# sourceMappingURL=chunk-6M6A6WWW.cjs.map
111
+ //# sourceMappingURL=chunk-6M6A6WWW.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/persistence/schema-version.ts","../src/internal/persistence/paths.ts"],"names":["readFile","rename","atomicWriteJson","join","homedir"],"mappings":"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","file":"chunk-OIYQBUW4.cjs","sourcesContent":["/**\n * Schema versioning helpers (ADR D62).\n *\n * Two parallel APIs:\n * - `migrateSchema` — SQLite via `PRAGMA user_version` + ordered forward-only migrations.\n * - `readVersionedJson` / `writeVersionedJson` — JSON files with `_schemaVersion` field.\n *\n * Forward-only: never deletes data, never downgrades. Migration callbacks\n * own the transformation; this module owns the bookkeeping.\n *\n * @internal\n */\n\nimport { readFile, rename } from \"node:fs/promises\";\n\nimport { atomicWriteJson } from \"./atomic-write.js\";\n\n// ────────────────────── SQLite migrations ──────────────────────\n\n/**\n * Minimal Database interface so this file does not hard-depend on\n * `better-sqlite3` at module load time. Compatible with the runtime\n * shape exposed by `better-sqlite3`.\n *\n * @internal\n */\nexport interface SqliteLike {\n pragma: (statement: string, options?: { simple?: boolean }) => unknown;\n exec: (sql: string) => void;\n transaction: <Args extends unknown[], R>(fn: (...args: Args) => R) => (...args: Args) => R;\n}\n\n/**\n * One forward migration step. `up` receives the DB inside a transaction;\n * `toVersion` is the value the pragma will be set to AFTER `up` returns.\n *\n * @internal\n */\nexport interface Migration {\n toVersion: number;\n up: (db: SqliteLike) => void;\n}\n\n/**\n * Options for `migrateSchema`.\n *\n * @internal\n */\nexport interface MigrateSchemaOptions {\n db: SqliteLike;\n currentVersion: number;\n migrations: ReadonlyArray<Migration>;\n /** For log/error context (e.g., \"memory-index\", \"registry\"). */\n label?: string;\n}\n\n/**\n * Result of `migrateSchema`.\n *\n * @internal\n */\nexport interface MigrateSchemaResult {\n from: number;\n to: number;\n ran: number;\n}\n\n/**\n * Run pending migrations to bring the DB from its current `user_version` up\n * to `currentVersion`. Migrations are sorted ascending by `toVersion` and\n * only those `> stored && <= currentVersion` execute. Each runs inside the\n * shared transaction.\n *\n * Throws if `stored > currentVersion` (downgrade attempt — forward-only).\n *\n * @internal\n */\nexport function migrateSchema(opts: MigrateSchemaOptions): MigrateSchemaResult {\n const { db, currentVersion, migrations, label = \"db\" } = opts;\n const storedRaw = db.pragma(\"user_version\", { simple: true });\n const stored = typeof storedRaw === \"number\" ? storedRaw : 0;\n\n if (stored > currentVersion) {\n throw new Error(\n `[${label}] schema version ${stored} > current ${currentVersion}; ` +\n \"did you downgrade the SDK? Forward-only migrations only.\",\n );\n }\n\n if (stored === currentVersion) {\n return { from: stored, to: stored, ran: 0 };\n }\n\n const pending = [...migrations]\n .sort((a, b) => a.toVersion - b.toVersion)\n .filter((m) => m.toVersion > stored && m.toVersion <= currentVersion);\n\n let ran = 0;\n let lastApplied = stored;\n\n db.transaction(() => {\n for (const m of pending) {\n m.up(db);\n db.pragma(`user_version = ${m.toVersion}`);\n lastApplied = m.toVersion;\n ran += 1;\n }\n })();\n\n return { from: stored, to: lastApplied, ran };\n}\n\n// ────────────────────── JSON versioned files ──────────────────────\n\n/**\n * Standard wrapper shape: `{ _schemaVersion: N, data: T }`. Use\n * `readVersionedJson` / `writeVersionedJson` for read/write.\n *\n * @internal\n */\nexport interface VersionedJsonFile<T> {\n _schemaVersion: number;\n data: T;\n}\n\n/**\n * Migration callback for `readVersionedJson`. Receives the FULL parsed\n * JSON object (not just `.data`), so legacy shapes without the\n * `_schemaVersion` / `data` wrapper can be migrated correctly (EC-2 fix).\n *\n * @internal\n */\nexport type VersionedJsonMigrate<T> = (parsed: unknown, fromVersion: number) => T;\n\n/**\n * Options for `readVersionedJson`.\n *\n * @internal\n */\nexport interface ReadVersionedJsonOptions<T> {\n path: string;\n currentVersion: number;\n migrate: VersionedJsonMigrate<T>;\n defaultValue: () => T;\n}\n\n/**\n * Read a versioned JSON file. Returns:\n * - file's `.data` when `_schemaVersion === currentVersion`\n * - migrated value (via `migrate(parsed, stored)`) when stored < current\n * - `defaultValue()` when file missing, corrupt, or stored > current\n *\n * Fail-soft: never throws. Corrupt or mismatched-newer files log a stderr\n * warning and fall back to `defaultValue()`.\n *\n * @internal\n */\nexport async function readVersionedJson<T>(opts: ReadVersionedJsonOptions<T>): Promise<T> {\n const { path, currentVersion, migrate, defaultValue } = opts;\n\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch {\n return defaultValue();\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // T5.10 — move corrupt file aside so (a) user can investigate and\n // (b) next run starts fresh instead of hitting the same warning loop.\n const asidePath = `${path}.corrupt.${Date.now()}`;\n try {\n await rename(path, asidePath);\n process.stderr.write(\n `[theokit-sdk] ${path} is corrupt; moved to ${asidePath}. Using default value.\\n`,\n );\n } catch {\n process.stderr.write(`[theokit-sdk] ${path} is corrupt; using default value.\\n`);\n }\n return defaultValue();\n }\n\n if (typeof parsed !== \"object\" || parsed === null) {\n return defaultValue();\n }\n\n const file = parsed as { _schemaVersion?: unknown; data?: unknown };\n const storedRaw = file._schemaVersion;\n const stored = typeof storedRaw === \"number\" ? storedRaw : 0;\n\n if (stored === currentVersion) {\n return file.data as T;\n }\n\n if (stored > currentVersion) {\n process.stderr.write(\n `[theokit-sdk] ${path} schema version ${stored} > current ${currentVersion}; ` +\n \"using default value (forward-only).\\n\",\n );\n return defaultValue();\n }\n\n // EC-2 fix: pass the FULL parsed object to migrate, not just `file.data`.\n // Legacy shapes (e.g., `{ schemaVersion: \"1.0\", agents: {...} }` without\n // a `data` field) need to inspect the whole thing.\n return migrate(parsed, stored);\n}\n\n/**\n * Write `data` as a versioned JSON file via atomic write.\n *\n * @internal\n */\nexport async function writeVersionedJson<T>(\n path: string,\n data: T,\n currentVersion: number,\n): Promise<void> {\n const file: VersionedJsonFile<T> = {\n _schemaVersion: currentVersion,\n data,\n };\n await atomicWriteJson(path, file);\n}\n","/**\n * Path resolution for SDK state files (ADR D60).\n *\n * Theokit anchors state at `<cwd>/.theokit/` by default (per-cwd). An\n * optional `THEOKIT_HOME` environment variable overrides this, enabling\n * test isolation, profile switching, and multi-tenant deployments.\n *\n * Rules:\n * - `getTheokitHome(cwd)` is the ONLY canonical resolver. Never hardcode\n * `path.join(cwd, \".theokit\")` in callers — use this function so tests\n * and overrides stay consistent.\n * - `getProfilesRoot()` is intentionally home-anchored (not affected by\n * `THEOKIT_HOME`) so `theokit profile list` discovers all profiles\n * regardless of which is active.\n * - `displayTheokitHome(cwd)` returns a human-readable path for logs.\n *\n * @internal\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst THEOKIT_DIR_NAME = \".theokit\";\n\n/**\n * Resolve the active Theokit state directory.\n *\n * Returns the value of `THEOKIT_HOME` env var if set (and non-empty after\n * trim); otherwise returns `<cwd>/.theokit`.\n *\n * @internal\n */\nexport function getTheokitHome(cwd: string): string {\n const override = process.env.THEOKIT_HOME?.trim();\n if (override !== undefined && override.length > 0) {\n return override;\n }\n return join(cwd, THEOKIT_DIR_NAME);\n}\n\n/**\n * Profiles root is ALWAYS at `~/.theokit/profiles/`, regardless of\n * `THEOKIT_HOME`. This lets `theokit profile list` see all profiles\n * regardless of which one is currently active.\n *\n * @internal\n */\nexport function getProfilesRoot(): string {\n return join(homedir(), THEOKIT_DIR_NAME, \"profiles\");\n}\n\n/**\n * Human-readable Theokit home for log/print output. Collapses `$HOME` to\n * `~` when applicable. NEVER used for `fs.*` calls — use `getTheokitHome`\n * for those.\n *\n * @internal\n */\nexport function displayTheokitHome(cwd: string): string {\n const resolved = getTheokitHome(cwd);\n const home = homedir();\n if (resolved === home) return \"~\";\n if (resolved.startsWith(`${home}/`)) {\n return `~${resolved.slice(home.length)}`;\n }\n return resolved;\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/persistence/schema-version.ts","../src/internal/persistence/paths.ts"],"names":["readFile","rename","atomicWriteJson","join","homedir"],"mappings":"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","file":"chunk-6M6A6WWW.cjs","sourcesContent":["/**\n * Schema versioning helpers (ADR D62).\n *\n * Two parallel APIs:\n * - `migrateSchema` — SQLite via `PRAGMA user_version` + ordered forward-only migrations.\n * - `readVersionedJson` / `writeVersionedJson` — JSON files with `_schemaVersion` field.\n *\n * Forward-only: never deletes data, never downgrades. Migration callbacks\n * own the transformation; this module owns the bookkeeping.\n *\n * @internal\n */\n\nimport { readFile, rename } from \"node:fs/promises\";\n\nimport { atomicWriteJson } from \"./atomic-write.js\";\n\n// ────────────────────── SQLite migrations ──────────────────────\n\n/**\n * Minimal Database interface so this file does not hard-depend on\n * `better-sqlite3` at module load time. Compatible with the runtime\n * shape exposed by `better-sqlite3`.\n *\n * @internal\n */\nexport interface SqliteLike {\n pragma: (statement: string, options?: { simple?: boolean }) => unknown;\n exec: (sql: string) => void;\n transaction: <Args extends unknown[], R>(fn: (...args: Args) => R) => (...args: Args) => R;\n}\n\n/**\n * One forward migration step. `up` receives the DB inside a transaction;\n * `toVersion` is the value the pragma will be set to AFTER `up` returns.\n *\n * @internal\n */\nexport interface Migration {\n toVersion: number;\n up: (db: SqliteLike) => void;\n}\n\n/**\n * Options for `migrateSchema`.\n *\n * @internal\n */\nexport interface MigrateSchemaOptions {\n db: SqliteLike;\n currentVersion: number;\n migrations: ReadonlyArray<Migration>;\n /** For log/error context (e.g., \"memory-index\", \"registry\"). */\n label?: string;\n}\n\n/**\n * Result of `migrateSchema`.\n *\n * @internal\n */\nexport interface MigrateSchemaResult {\n from: number;\n to: number;\n ran: number;\n}\n\n/**\n * Run pending migrations to bring the DB from its current `user_version` up\n * to `currentVersion`. Migrations are sorted ascending by `toVersion` and\n * only those `> stored && <= currentVersion` execute. Each runs inside the\n * shared transaction.\n *\n * Throws if `stored > currentVersion` (downgrade attempt — forward-only).\n *\n * @internal\n */\nexport function migrateSchema(opts: MigrateSchemaOptions): MigrateSchemaResult {\n const { db, currentVersion, migrations, label = \"db\" } = opts;\n const storedRaw = db.pragma(\"user_version\", { simple: true });\n const stored = typeof storedRaw === \"number\" ? storedRaw : 0;\n\n if (stored > currentVersion) {\n throw new Error(\n `[${label}] schema version ${stored} > current ${currentVersion}; ` +\n \"did you downgrade the SDK? Forward-only migrations only.\",\n );\n }\n\n if (stored === currentVersion) {\n return { from: stored, to: stored, ran: 0 };\n }\n\n const pending = [...migrations]\n .sort((a, b) => a.toVersion - b.toVersion)\n .filter((m) => m.toVersion > stored && m.toVersion <= currentVersion);\n\n let ran = 0;\n let lastApplied = stored;\n\n db.transaction(() => {\n for (const m of pending) {\n m.up(db);\n db.pragma(`user_version = ${m.toVersion}`);\n lastApplied = m.toVersion;\n ran += 1;\n }\n })();\n\n return { from: stored, to: lastApplied, ran };\n}\n\n// ────────────────────── JSON versioned files ──────────────────────\n\n/**\n * Standard wrapper shape: `{ _schemaVersion: N, data: T }`. Use\n * `readVersionedJson` / `writeVersionedJson` for read/write.\n *\n * @internal\n */\nexport interface VersionedJsonFile<T> {\n _schemaVersion: number;\n data: T;\n}\n\n/**\n * Migration callback for `readVersionedJson`. Receives the FULL parsed\n * JSON object (not just `.data`), so legacy shapes without the\n * `_schemaVersion` / `data` wrapper can be migrated correctly (EC-2 fix).\n *\n * @internal\n */\nexport type VersionedJsonMigrate<T> = (parsed: unknown, fromVersion: number) => T;\n\n/**\n * Options for `readVersionedJson`.\n *\n * @internal\n */\nexport interface ReadVersionedJsonOptions<T> {\n path: string;\n currentVersion: number;\n migrate: VersionedJsonMigrate<T>;\n defaultValue: () => T;\n}\n\n/**\n * Read a versioned JSON file. Returns:\n * - file's `.data` when `_schemaVersion === currentVersion`\n * - migrated value (via `migrate(parsed, stored)`) when stored < current\n * - `defaultValue()` when file missing, corrupt, or stored > current\n *\n * Fail-soft: never throws. Corrupt or mismatched-newer files log a stderr\n * warning and fall back to `defaultValue()`.\n *\n * @internal\n */\nexport async function readVersionedJson<T>(opts: ReadVersionedJsonOptions<T>): Promise<T> {\n const { path, currentVersion, migrate, defaultValue } = opts;\n\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch {\n return defaultValue();\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // T5.10 — move corrupt file aside so (a) user can investigate and\n // (b) next run starts fresh instead of hitting the same warning loop.\n const asidePath = `${path}.corrupt.${Date.now()}`;\n try {\n await rename(path, asidePath);\n process.stderr.write(\n `[theokit-sdk] ${path} is corrupt; moved to ${asidePath}. Using default value.\\n`,\n );\n } catch {\n process.stderr.write(`[theokit-sdk] ${path} is corrupt; using default value.\\n`);\n }\n return defaultValue();\n }\n\n if (typeof parsed !== \"object\" || parsed === null) {\n return defaultValue();\n }\n\n const file = parsed as { _schemaVersion?: unknown; data?: unknown };\n const storedRaw = file._schemaVersion;\n const stored = typeof storedRaw === \"number\" ? storedRaw : 0;\n\n if (stored === currentVersion) {\n return file.data as T;\n }\n\n if (stored > currentVersion) {\n process.stderr.write(\n `[theokit-sdk] ${path} schema version ${stored} > current ${currentVersion}; ` +\n \"using default value (forward-only).\\n\",\n );\n return defaultValue();\n }\n\n // EC-2 fix: pass the FULL parsed object to migrate, not just `file.data`.\n // Legacy shapes (e.g., `{ schemaVersion: \"1.0\", agents: {...} }` without\n // a `data` field) need to inspect the whole thing.\n return migrate(parsed, stored);\n}\n\n/**\n * Write `data` as a versioned JSON file via atomic write.\n *\n * @internal\n */\nexport async function writeVersionedJson<T>(\n path: string,\n data: T,\n currentVersion: number,\n): Promise<void> {\n const file: VersionedJsonFile<T> = {\n _schemaVersion: currentVersion,\n data,\n };\n await atomicWriteJson(path, file);\n}\n","/**\n * Path resolution for SDK state files (ADR D60).\n *\n * Theokit anchors state at `<cwd>/.theokit/` by default (per-cwd). An\n * optional `THEOKIT_HOME` environment variable overrides this, enabling\n * test isolation, profile switching, and multi-tenant deployments.\n *\n * Rules:\n * - `getTheokitHome(cwd)` is the canonical resolver **for cwd-anchored state**. Never hardcode\n * `path.join(cwd, \".theokit\")` in callers — use this function so tests\n * and overrides stay consistent.\n *\n * M94 — este comentário dizia \"the ONLY canonical resolver\", e deixou de ser verdade: o\n * transcript ganhou `transcriptRoot()`, que é **home-ancorado** (`~/.theokit`) com o mesmo\n * override por `THEOKIT_HOME`. Os dois defaults diferem de propósito — unificar moveria o\n * transcript de todo mundo que NÃO define a variável, que é uma migração de dados e não um\n * re-export.\n *\n * Consequência que merece estar escrita: **sem `THEOKIT_HOME` o estado segue partido em dois**\n * — registry em `<cwd>/.theokit`, transcript em `~/.theokit`. O M94 unifica só para quem define\n * a variável. Unificar os dois defaults é trabalho de outro milestone.\n * - `getProfilesRoot()` is intentionally home-anchored (not affected by\n * `THEOKIT_HOME`) so `theokit profile list` discovers all profiles\n * regardless of which is active.\n * - `displayTheokitHome(cwd)` returns a human-readable path for logs.\n *\n * @internal\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst THEOKIT_DIR_NAME = \".theokit\";\n\n/**\n * Resolve the active Theokit state directory.\n *\n * Returns the value of `THEOKIT_HOME` env var if set (and non-empty after\n * trim); otherwise returns `<cwd>/.theokit`.\n *\n * @internal\n */\nexport function getTheokitHome(cwd: string): string {\n const override = process.env.THEOKIT_HOME?.trim();\n if (override !== undefined && override.length > 0) {\n return override;\n }\n return join(cwd, THEOKIT_DIR_NAME);\n}\n\n/**\n * Profiles root is ALWAYS at `~/.theokit/profiles/`, regardless of\n * `THEOKIT_HOME`. This lets `theokit profile list` see all profiles\n * regardless of which one is currently active.\n *\n * @internal\n */\nexport function getProfilesRoot(): string {\n return join(homedir(), THEOKIT_DIR_NAME, \"profiles\");\n}\n\n/**\n * Human-readable Theokit home for log/print output. Collapses `$HOME` to\n * `~` when applicable. NEVER used for `fs.*` calls — use `getTheokitHome`\n * for those.\n *\n * @internal\n */\nexport function displayTheokitHome(cwd: string): string {\n const resolved = getTheokitHome(cwd);\n const home = homedir();\n if (resolved === home) return \"~\";\n if (resolved.startsWith(`${home}/`)) {\n return `~${resolved.slice(home.length)}`;\n }\n return resolved;\n}\n"]}
@@ -0,0 +1,78 @@
1
+ import { TheokitAgentError } from './chunk-WLKFWQSJ.js';
2
+ import { openSync, writeFileSync, closeSync, rmSync, readFileSync } from 'fs';
3
+ import { hostname } from 'os';
4
+
5
+ var SessionBusyError = class extends TheokitAgentError {
6
+ constructor(sessionPath) {
7
+ super(
8
+ `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.`,
9
+ { code: "session_busy", isRetryable: false }
10
+ );
11
+ this.sessionPath = sessionPath;
12
+ }
13
+ sessionPath;
14
+ name = "SessionBusyError";
15
+ };
16
+ var JANELA_DE_HEARTBEAT_MS = 3e4;
17
+ function lerDono(lockPath) {
18
+ let bruto;
19
+ try {
20
+ bruto = readFileSync(lockPath, "utf8");
21
+ } catch {
22
+ return void 0;
23
+ }
24
+ try {
25
+ const d = JSON.parse(bruto);
26
+ if (typeof d.pid !== "number" || typeof d.hostname !== "string" || typeof d.mtime !== "number") {
27
+ return void 0;
28
+ }
29
+ return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };
30
+ } catch {
31
+ return void 0;
32
+ }
33
+ }
34
+ function processoVivo(pid) {
35
+ try {
36
+ process.kill(pid, 0);
37
+ return true;
38
+ } catch (err) {
39
+ return err.code === "EPERM";
40
+ }
41
+ }
42
+ function reclamavel(dono) {
43
+ if (dono === void 0) return true;
44
+ const velho = Date.now() - dono.mtime > JANELA_DE_HEARTBEAT_MS;
45
+ if (dono.hostname !== hostname()) return velho;
46
+ return velho || !processoVivo(dono.pid);
47
+ }
48
+ async function acquireSessionWriter(sessionPath) {
49
+ const lockPath = `${sessionPath}.writer.lock`;
50
+ const meu = { pid: process.pid, hostname: hostname(), mtime: Date.now() };
51
+ let fd;
52
+ try {
53
+ fd = openSync(lockPath, "wx");
54
+ } catch (err) {
55
+ if (err.code !== "EEXIST") throw err;
56
+ if (!reclamavel(lerDono(lockPath))) throw new SessionBusyError(sessionPath);
57
+ writeFileSync(lockPath, JSON.stringify(meu));
58
+ return criarLease(sessionPath, lockPath);
59
+ }
60
+ closeSync(fd);
61
+ writeFileSync(lockPath, JSON.stringify(meu));
62
+ return criarLease(sessionPath, lockPath);
63
+ }
64
+ function criarLease(sessionPath, lockPath) {
65
+ let released = false;
66
+ return {
67
+ sessionPath,
68
+ release: async () => {
69
+ if (released) return;
70
+ released = true;
71
+ rmSync(lockPath, { force: true });
72
+ }
73
+ };
74
+ }
75
+
76
+ export { SessionBusyError, acquireSessionWriter };
77
+ //# sourceMappingURL=chunk-B3HUL2OQ.js.map
78
+ //# sourceMappingURL=chunk-B3HUL2OQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/persistence/session-writer.ts"],"names":[],"mappings":"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","file":"chunk-B3HUL2OQ.js","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. Processo morto ⇒ reclamável na hora.\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 */\nfunction reclamavel(dono: DonoDoLock | undefined): boolean {\n if (dono === undefined) return true; // ilegível ou sumido\n const velho = Date.now() - dono.mtime > JANELA_DE_HEARTBEAT_MS;\n if (dono.hostname !== hostname()) return velho;\n return velho || !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"]}
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkIT3PWBW4_cjs = require('./chunk-IT3PWBW4.cjs');
4
- var chunkU2FO3MRG_cjs = require('./chunk-U2FO3MRG.cjs');
4
+ var chunk2DYI7MIF_cjs = require('./chunk-2DYI7MIF.cjs');
5
5
 
6
6
  // src/internal/session/agent-session-store.ts
7
7
  function seedTranscript(prior, opts) {
8
- return chunkU2FO3MRG_cjs.SessionTranscript.fromRecords(prior, opts);
8
+ return chunk2DYI7MIF_cjs.SessionTranscript.fromRecords(prior, opts);
9
9
  }
10
10
  function mapAgentTurn(steps) {
11
11
  const assistant = {};
@@ -43,7 +43,7 @@ function appendConversation(transcript, conversation) {
43
43
  }
44
44
  async function readSessionMessages(store, agentId) {
45
45
  const records = await store.readRecords(agentId);
46
- return chunkU2FO3MRG_cjs.reconstructMessages(records).map(narrowToSessionMessage);
46
+ return chunk2DYI7MIF_cjs.reconstructMessages(records).map(narrowToSessionMessage);
47
47
  }
48
48
  function partToText(p) {
49
49
  if (p.type === "text") return p.text ?? "";
@@ -76,7 +76,28 @@ var recordCounts = /* @__PURE__ */ new Map();
76
76
  function appendSessionMessage(agentId, message) {
77
77
  const existing = chunkIT3PWBW4_cjs.sessions.get(agentId) ?? [];
78
78
  existing.push(message);
79
+ chunkIT3PWBW4_cjs.sessions.delete(agentId);
79
80
  chunkIT3PWBW4_cjs.sessions.set(agentId, existing);
81
+ aplicarTeto();
82
+ }
83
+ var TETO_DE_SESSOES_EM_CACHE = 32;
84
+ function aplicarTeto() {
85
+ while (chunkIT3PWBW4_cjs.sessions.size > TETO_DE_SESSOES_EM_CACHE) {
86
+ const maisAntiga = chunkIT3PWBW4_cjs.sessions.keys().next().value;
87
+ if (maisAntiga === void 0) return;
88
+ chunkIT3PWBW4_cjs.sessions.delete(maisAntiga);
89
+ chunkIT3PWBW4_cjs.hydratedKeys.delete(maisAntiga);
90
+ pendingWrites.delete(maisAntiga);
91
+ recordCounts.delete(maisAntiga);
92
+ }
93
+ }
94
+ function descartarSessao(cwd, agentId) {
95
+ const chave = chunkIT3PWBW4_cjs.transcriptKey(cwd, agentId);
96
+ let removidas = 0;
97
+ if (chunkIT3PWBW4_cjs.hydratedKeys.delete(chave)) removidas++;
98
+ if (pendingWrites.delete(agentId)) removidas++;
99
+ if (recordCounts.delete(agentId)) removidas++;
100
+ return removidas;
80
101
  }
81
102
  function getSessionMessages(agentId) {
82
103
  return chunkIT3PWBW4_cjs.sessions.get(agentId) ?? [];
@@ -89,7 +110,7 @@ function persistTurnToTranscript(store, loc, sessionId, turn, onCompact) {
89
110
  const count = (recordCounts.get(key) ?? 0) + 1;
90
111
  recordCounts.set(key, count);
91
112
  if (turn.autoCompact !== void 0) {
92
- const { autoCompactIfNeeded } = await import('./compact-session-42BK7RE3.cjs');
113
+ const { autoCompactIfNeeded } = await import('./compact-session-24PK2G4M.cjs');
93
114
  const fired = await autoCompactIfNeeded({
94
115
  store,
95
116
  loc,
@@ -151,10 +172,11 @@ function enqueueSessionWrite(cwd, agentId, fn) {
151
172
 
152
173
  exports.appendSessionMessage = appendSessionMessage;
153
174
  exports.clearSession = clearSession;
175
+ exports.descartarSessao = descartarSessao;
154
176
  exports.enqueueSessionWrite = enqueueSessionWrite;
155
177
  exports.flushSessionWrites = flushSessionWrites;
156
178
  exports.getSessionMessages = getSessionMessages;
157
179
  exports.hydrateSession = hydrateSession;
158
180
  exports.persistTurnToTranscript = persistTurnToTranscript;
159
- //# sourceMappingURL=chunk-E2YEDATX.cjs.map
160
- //# sourceMappingURL=chunk-E2YEDATX.cjs.map
181
+ //# sourceMappingURL=chunk-E33A7YIR.cjs.map
182
+ //# sourceMappingURL=chunk-E33A7YIR.cjs.map