@gaunt-sloth/core 2.0.0-beta.4 → 2.0.0-beta.6

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 (94) hide show
  1. package/dist/config/schema.d.ts +4 -0
  2. package/dist/config/schema.js +64 -2
  3. package/dist/config/schema.js.map +1 -1
  4. package/dist/config/tokenBudget.d.ts +88 -0
  5. package/dist/config/tokenBudget.js +155 -0
  6. package/dist/config/tokenBudget.js.map +1 -0
  7. package/dist/config/types.d.ts +33 -4
  8. package/dist/config/types.js.map +1 -1
  9. package/dist/config.d.ts +1 -0
  10. package/dist/config.js +5 -0
  11. package/dist/config.js.map +1 -1
  12. package/dist/core/GthAbstractAgent.d.ts +24 -0
  13. package/dist/core/GthAbstractAgent.js +37 -1
  14. package/dist/core/GthAbstractAgent.js.map +1 -1
  15. package/dist/core/GthAgentRunner.d.ts +216 -1
  16. package/dist/core/GthAgentRunner.js +424 -3
  17. package/dist/core/GthAgentRunner.js.map +1 -1
  18. package/dist/core/GthLangChainAgent.d.ts +196 -0
  19. package/dist/core/GthLangChainAgent.js +392 -2
  20. package/dist/core/GthLangChainAgent.js.map +1 -1
  21. package/dist/core/approvals/approvalRequest.d.ts +142 -0
  22. package/dist/core/approvals/approvalRequest.js +198 -3
  23. package/dist/core/approvals/approvalRequest.js.map +1 -1
  24. package/dist/core/approvals/conversationGrants.d.ts +60 -0
  25. package/dist/core/approvals/conversationGrants.js +77 -0
  26. package/dist/core/approvals/conversationGrants.js.map +1 -0
  27. package/dist/core/approvals/grants.d.ts +16 -0
  28. package/dist/core/approvals/grants.js +20 -5
  29. package/dist/core/approvals/grants.js.map +1 -1
  30. package/dist/core/compaction.d.ts +181 -0
  31. package/dist/core/compaction.js +293 -0
  32. package/dist/core/compaction.js.map +1 -0
  33. package/dist/core/compactionThreshold.d.ts +158 -0
  34. package/dist/core/compactionThreshold.js +183 -0
  35. package/dist/core/compactionThreshold.js.map +1 -0
  36. package/dist/core/contextWindow.d.ts +146 -0
  37. package/dist/core/contextWindow.js +256 -0
  38. package/dist/core/contextWindow.js.map +1 -0
  39. package/dist/core/exitOutputChannel.d.ts +51 -0
  40. package/dist/core/exitOutputChannel.js +65 -0
  41. package/dist/core/exitOutputChannel.js.map +1 -0
  42. package/dist/core/refusal.d.ts +17 -2
  43. package/dist/core/refusal.js +80 -14
  44. package/dist/core/refusal.js.map +1 -1
  45. package/dist/core/runStats.d.ts +1 -1
  46. package/dist/core/terminationNotice.d.ts +8 -0
  47. package/dist/core/terminationNotice.js +10 -4
  48. package/dist/core/terminationNotice.js.map +1 -1
  49. package/dist/core/terminationReason.d.ts +28 -0
  50. package/dist/core/terminationReason.js +27 -0
  51. package/dist/core/terminationReason.js.map +1 -1
  52. package/dist/core/types.d.ts +35 -1
  53. package/dist/core/types.js.map +1 -1
  54. package/dist/history/checkpointRetention.d.ts +279 -0
  55. package/dist/history/checkpointRetention.js +567 -0
  56. package/dist/history/checkpointRetention.js.map +1 -0
  57. package/dist/history/checkpointSaver.d.ts +93 -0
  58. package/dist/history/checkpointSaver.js +464 -0
  59. package/dist/history/checkpointSaver.js.map +1 -0
  60. package/dist/history/historyEnabled.d.ts +27 -0
  61. package/dist/history/historyEnabled.js +23 -0
  62. package/dist/history/historyEnabled.js.map +1 -0
  63. package/dist/history/historyFormat.d.ts +27 -0
  64. package/dist/history/historyFormat.js +125 -2
  65. package/dist/history/historyFormat.js.map +1 -1
  66. package/dist/history/historyStore.d.ts +61 -0
  67. package/dist/history/historyStore.js +180 -7
  68. package/dist/history/historyStore.js.map +1 -1
  69. package/dist/history/recordSession.d.ts +84 -22
  70. package/dist/history/recordSession.js +187 -12
  71. package/dist/history/recordSession.js.map +1 -1
  72. package/dist/history/sessionCheckpointer.d.ts +48 -0
  73. package/dist/history/sessionCheckpointer.js +200 -0
  74. package/dist/history/sessionCheckpointer.js.map +1 -0
  75. package/dist/index.d.ts +6 -0
  76. package/dist/index.js +8 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/providers/modelCatalog.d.ts +14 -0
  79. package/dist/providers/modelCatalog.js +4 -0
  80. package/dist/providers/modelCatalog.js.map +1 -1
  81. package/dist/providers/modelDiscovery.d.ts +3 -1
  82. package/dist/providers/modelDiscovery.js +22 -8
  83. package/dist/providers/modelDiscovery.js.map +1 -1
  84. package/dist/providers/ollama.js +3 -19
  85. package/dist/providers/ollama.js.map +1 -1
  86. package/dist/runtime/conversation.js +7 -1
  87. package/dist/runtime/conversation.js.map +1 -1
  88. package/dist/runtime/singleShot.js +6 -1
  89. package/dist/runtime/singleShot.js.map +1 -1
  90. package/dist/utils/consoleUtils.d.ts +77 -0
  91. package/dist/utils/consoleUtils.js +81 -0
  92. package/dist/utils/consoleUtils.js.map +1 -1
  93. package/package.json +2 -2
  94. package/schema/gsloth-config.schema.json +33 -14
@@ -1,25 +1,20 @@
1
1
  /**
2
2
  * @packageDocumentation
3
- * GS2-7 (B20) — the opt-in bridge from a finished run to the {@link HistoryStore}.
3
+ * GS2-7 (B20) — the bridge from a finished run to the {@link HistoryStore}.
4
4
  *
5
- * {@link recordSessionSafe} is the ONE entry point the run path calls. It is a no-op unless
6
- * `history.enabled` is true, and it swallows every error, so:
7
- * - a **default run** (history absent/false) opens nothing, writes nothing, and behaves exactly
8
- * as before the stateless identity is preserved; and
9
- * - even with history **on**, a DB problem (locked/corrupt/read-only fs) can never abort or alter
5
+ * {@link recordSessionSafe} is the ONE entry point the run path calls. It writes unless
6
+ * `history.enabled` is `false`, and it swallows every error, so:
7
+ * - a **default run** records its turns locally, under the user's own `~/.gsloth` dir, and
8
+ * `history.enabled: false` is the opt-out that restores the stateless identity; and
9
+ * - with history on, a DB problem (locked/corrupt/read-only fs) can never abort or alter
10
10
  * the run: the worst case is that one session isn't recorded.
11
11
  */
12
- import type { ConversationMeta, SessionRecord } from '#src/history/historyStore.js';
13
- /** The subset of the resolved config the recorder reads (structural, to avoid a hard type dep). */
14
- export interface HistoryConfigView {
15
- history?: {
16
- enabled?: boolean;
17
- dbPath?: string;
18
- };
19
- }
12
+ import type { ConversationMeta, ConversationSummary, SessionRecord } from '#src/history/historyStore.js';
13
+ export type { HistoryConfigView } from '#src/history/historyEnabled.js';
14
+ import type { HistoryConfigView } from '#src/history/historyEnabled.js';
20
15
  /**
21
- * Record one finished session IFF `history.enabled` is true. Returns the new row id, or `null`
22
- * when history is disabled or anything went wrong (both are non-events for the caller).
16
+ * Record one finished session unless `history.enabled` is `false`. Returns the new row id, or
17
+ * `null` when history is turned off or anything went wrong (both are non-events for the caller).
23
18
  *
24
19
  * Deliberately fully guarded: opening the store, ensuring the global dir, and the insert all run
25
20
  * behind a single try/catch, and the store itself is fail-soft. Callers put this in a `finally`
@@ -27,11 +22,78 @@ export interface HistoryConfigView {
27
22
  */
28
23
  export declare function recordSessionSafe(config: HistoryConfigView, record: SessionRecord): number | null;
29
24
  /**
30
- * GS2-19 — open one conversation for an interactive session IFF `history.enabled`, returning its id
31
- * (or `null` when disabled / anything failed). The session passes that id on every
32
- * {@link recordSessionSafe} so all its turns group under one conversation. Same guarantees as the
33
- * recorder: a no-op by default, fully fail-soft, never throws. When it returns `null` under an
34
- * enabled store (a rare open failure), turns simply fall back to per-turn 1-turn conversations —
35
- * grouping is lost but nothing is dropped or crashed.
25
+ * GS2-19 — open one conversation for an interactive session unless `history.enabled` is `false`,
26
+ * returning its id (or `null` when turned off / anything failed). The session passes that id on
27
+ * every {@link recordSessionSafe} so all its turns group under one conversation. Same guarantees as
28
+ * the recorder: fully fail-soft, never throws. When it returns `null` under an enabled store (a
29
+ * rare open failure), turns simply fall back to per-turn 1-turn conversations — grouping is lost
30
+ * but nothing is dropped or crashed.
31
+ *
32
+ * GS2-20 — `meta.threadId` is what makes the conversation resumable: it names the LangGraph thread
33
+ * whose durable checkpoint holds the session's graph state.
36
34
  */
37
35
  export declare function openConversationSafe(config: HistoryConfigView, meta: ConversationMeta): number | null;
36
+ /**
37
+ * GS2-20 — record, ON DISK, that a conversation can no longer be resumed, by clearing its thread
38
+ * link. Called once per session, the first time a checkpoint write fails after the store opened.
39
+ *
40
+ * The session itself keeps running and the user is told; this is the half that has to outlive the
41
+ * process. A checkpoint chain that stopped growing mid-conversation is still a well-formed chain,
42
+ * so nothing a later `--resume` could inspect would reveal that it is truncated — the refusal has
43
+ * to be written down at the moment the writing broke.
44
+ *
45
+ * Fail-soft, like its neighbours: the session is already degraded, and a failure here must not turn
46
+ * that into a second error.
47
+ */
48
+ export declare function markConversationUnresumableSafe(config: HistoryConfigView, conversationId: number): void;
49
+ /**
50
+ * GS2-20 — the reverse trip: from a conversation id (what `gth history list` prints and a user
51
+ * types) to the LangGraph thread whose checkpoint holds its state, or `null` when there isn't one.
52
+ *
53
+ * `null` covers every way this can come up empty — history turned off, no store, an id that names
54
+ * nothing, or a conversation recorded without a checkpointer — and never a neighbouring
55
+ * conversation. A caller resuming on this must treat `null` as a refusal to resume and say so;
56
+ * quietly starting a fresh session, or picking the most recent conversation instead, would hand
57
+ * someone another conversation's state under the id they typed.
58
+ */
59
+ export declare function lookupConversationThreadSafe(config: HistoryConfigView, conversationId: number): string | null;
60
+ /** GS2-20 — what a resume needs to know about a stored conversation before re-entering it. */
61
+ export interface StoredConversation {
62
+ /** The listing row: when it started, which command and model recorded it, its workspace. */
63
+ summary: ConversationSummary;
64
+ /** Its recorded turns, oldest first — what the surface replays as restored turns. */
65
+ turns: SessionRecord[];
66
+ }
67
+ /**
68
+ * GS2-20 — the conversation row and its recorded turns, or `null` when history is off, the store
69
+ * cannot be opened, or no conversation has that id.
70
+ *
71
+ * This is the *display and policy* half of a resume — the workspace the conversation belongs to,
72
+ * the command it was recorded under, the turns to show — and deliberately not the thread decision:
73
+ * whether the conversation can be re-entered is answered by {@link lookupConversationThreadSafe},
74
+ * whose exact-match-never-fallback contract is the one a resume rests on. Fail-soft, never throws.
75
+ */
76
+ export declare function lookupConversationSafe(config: HistoryConfigView, conversationId: number): StoredConversation | null;
77
+ /**
78
+ * GS2-20 — the conversations a `/resume` picker may offer: the most recent ones that carry a thread
79
+ * (so a resume could actually re-enter them), minus the one the session is already in. `[]` when
80
+ * history is off or the store cannot be opened, which the caller renders as "nothing to resume".
81
+ * Fail-soft, never throws.
82
+ */
83
+ export declare function listResumableConversationsSafe(config: HistoryConfigView, options?: {
84
+ limit?: number;
85
+ exclude?: number;
86
+ }): ConversationSummary[];
87
+ /**
88
+ * GS2-20 — the stored approval-grants document of one conversation, as the opaque JSON string the
89
+ * approvals layer wrote (`core/approvals/conversationGrants.ts` owns the format), or `null` when
90
+ * there is none. Governed by the same switch as everything else here. Fail-soft, never throws.
91
+ */
92
+ export declare function readConversationGrantsSafe(config: HistoryConfigView, conversationId: number): string | null;
93
+ /**
94
+ * GS2-20 — replace one conversation's stored approval-grants document. Called whenever the session's
95
+ * grants change, so the row always holds the current set and a resume restores exactly what the
96
+ * conversation had. Returns whether the row was written. Fail-soft, never throws — a grant that
97
+ * could not be recorded costs one re-prompt after a resume, and must never cost the turn.
98
+ */
99
+ export declare function writeConversationGrantsSafe(config: HistoryConfigView, conversationId: number, grantsJson: string | null): boolean;
@@ -1,7 +1,8 @@
1
1
  import { openHistoryStore, resolveHistoryDbPath } from '#src/history/historyStore.js';
2
+ import { isHistoryEnabled } from '#src/history/historyEnabled.js';
2
3
  /**
3
- * Record one finished session IFF `history.enabled` is true. Returns the new row id, or `null`
4
- * when history is disabled or anything went wrong (both are non-events for the caller).
4
+ * Record one finished session unless `history.enabled` is `false`. Returns the new row id, or
5
+ * `null` when history is turned off or anything went wrong (both are non-events for the caller).
5
6
  *
6
7
  * Deliberately fully guarded: opening the store, ensuring the global dir, and the insert all run
7
8
  * behind a single try/catch, and the store itself is fail-soft. Callers put this in a `finally`
@@ -9,9 +10,9 @@ import { openHistoryStore, resolveHistoryDbPath } from '#src/history/historyStor
9
10
  */
10
11
  export function recordSessionSafe(config, record) {
11
12
  try {
12
- if (!config?.history?.enabled)
13
+ if (!isHistoryEnabled(config))
13
14
  return null;
14
- const dbPath = resolveHistoryDbPath(config.history.dbPath, /* ensureDir */ true);
15
+ const dbPath = resolveHistoryDbPath(config.history?.dbPath, /* ensureDir */ true);
15
16
  const store = openHistoryStore(dbPath, { create: true });
16
17
  if (!store)
17
18
  return null;
@@ -27,18 +28,21 @@ export function recordSessionSafe(config, record) {
27
28
  }
28
29
  }
29
30
  /**
30
- * GS2-19 — open one conversation for an interactive session IFF `history.enabled`, returning its id
31
- * (or `null` when disabled / anything failed). The session passes that id on every
32
- * {@link recordSessionSafe} so all its turns group under one conversation. Same guarantees as the
33
- * recorder: a no-op by default, fully fail-soft, never throws. When it returns `null` under an
34
- * enabled store (a rare open failure), turns simply fall back to per-turn 1-turn conversations —
35
- * grouping is lost but nothing is dropped or crashed.
31
+ * GS2-19 — open one conversation for an interactive session unless `history.enabled` is `false`,
32
+ * returning its id (or `null` when turned off / anything failed). The session passes that id on
33
+ * every {@link recordSessionSafe} so all its turns group under one conversation. Same guarantees as
34
+ * the recorder: fully fail-soft, never throws. When it returns `null` under an enabled store (a
35
+ * rare open failure), turns simply fall back to per-turn 1-turn conversations — grouping is lost
36
+ * but nothing is dropped or crashed.
37
+ *
38
+ * GS2-20 — `meta.threadId` is what makes the conversation resumable: it names the LangGraph thread
39
+ * whose durable checkpoint holds the session's graph state.
36
40
  */
37
41
  export function openConversationSafe(config, meta) {
38
42
  try {
39
- if (!config?.history?.enabled)
43
+ if (!isHistoryEnabled(config))
40
44
  return null;
41
- const dbPath = resolveHistoryDbPath(config.history.dbPath, /* ensureDir */ true);
45
+ const dbPath = resolveHistoryDbPath(config.history?.dbPath, /* ensureDir */ true);
42
46
  const store = openHistoryStore(dbPath, { create: true });
43
47
  if (!store)
44
48
  return null;
@@ -53,4 +57,175 @@ export function openConversationSafe(config, meta) {
53
57
  return null;
54
58
  }
55
59
  }
60
+ /**
61
+ * GS2-20 — record, ON DISK, that a conversation can no longer be resumed, by clearing its thread
62
+ * link. Called once per session, the first time a checkpoint write fails after the store opened.
63
+ *
64
+ * The session itself keeps running and the user is told; this is the half that has to outlive the
65
+ * process. A checkpoint chain that stopped growing mid-conversation is still a well-formed chain,
66
+ * so nothing a later `--resume` could inspect would reveal that it is truncated — the refusal has
67
+ * to be written down at the moment the writing broke.
68
+ *
69
+ * Fail-soft, like its neighbours: the session is already degraded, and a failure here must not turn
70
+ * that into a second error.
71
+ */
72
+ export function markConversationUnresumableSafe(config, conversationId) {
73
+ try {
74
+ if (!isHistoryEnabled(config))
75
+ return;
76
+ const dbPath = resolveHistoryDbPath(config.history?.dbPath);
77
+ const store = openHistoryStore(dbPath, { create: false });
78
+ if (!store)
79
+ return;
80
+ try {
81
+ store.clearConversationThread(conversationId);
82
+ }
83
+ finally {
84
+ store.close();
85
+ }
86
+ }
87
+ catch {
88
+ /* ignore */
89
+ }
90
+ }
91
+ /**
92
+ * GS2-20 — the reverse trip: from a conversation id (what `gth history list` prints and a user
93
+ * types) to the LangGraph thread whose checkpoint holds its state, or `null` when there isn't one.
94
+ *
95
+ * `null` covers every way this can come up empty — history turned off, no store, an id that names
96
+ * nothing, or a conversation recorded without a checkpointer — and never a neighbouring
97
+ * conversation. A caller resuming on this must treat `null` as a refusal to resume and say so;
98
+ * quietly starting a fresh session, or picking the most recent conversation instead, would hand
99
+ * someone another conversation's state under the id they typed.
100
+ */
101
+ export function lookupConversationThreadSafe(config, conversationId) {
102
+ try {
103
+ if (!isHistoryEnabled(config))
104
+ return null;
105
+ const dbPath = resolveHistoryDbPath(config.history?.dbPath);
106
+ const store = openHistoryStore(dbPath, { create: false });
107
+ if (!store)
108
+ return null;
109
+ try {
110
+ return store.getConversationThreadId(conversationId);
111
+ }
112
+ finally {
113
+ store.close();
114
+ }
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
120
+ /**
121
+ * GS2-20 — the conversation row and its recorded turns, or `null` when history is off, the store
122
+ * cannot be opened, or no conversation has that id.
123
+ *
124
+ * This is the *display and policy* half of a resume — the workspace the conversation belongs to,
125
+ * the command it was recorded under, the turns to show — and deliberately not the thread decision:
126
+ * whether the conversation can be re-entered is answered by {@link lookupConversationThreadSafe},
127
+ * whose exact-match-never-fallback contract is the one a resume rests on. Fail-soft, never throws.
128
+ */
129
+ export function lookupConversationSafe(config, conversationId) {
130
+ try {
131
+ if (!isHistoryEnabled(config))
132
+ return null;
133
+ const dbPath = resolveHistoryDbPath(config.history?.dbPath);
134
+ const store = openHistoryStore(dbPath, { create: false });
135
+ if (!store)
136
+ return null;
137
+ try {
138
+ const summary = store.getConversation(conversationId);
139
+ if (!summary)
140
+ return null;
141
+ return { summary, turns: store.getConversationThread(conversationId) };
142
+ }
143
+ finally {
144
+ store.close();
145
+ }
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
151
+ /**
152
+ * GS2-20 — the conversations a `/resume` picker may offer: the most recent ones that carry a thread
153
+ * (so a resume could actually re-enter them), minus the one the session is already in. `[]` when
154
+ * history is off or the store cannot be opened, which the caller renders as "nothing to resume".
155
+ * Fail-soft, never throws.
156
+ */
157
+ export function listResumableConversationsSafe(config, options = {}) {
158
+ try {
159
+ if (!isHistoryEnabled(config))
160
+ return [];
161
+ const dbPath = resolveHistoryDbPath(config.history?.dbPath);
162
+ const store = openHistoryStore(dbPath, { create: false });
163
+ if (!store)
164
+ return [];
165
+ try {
166
+ // Over-fetch so the filter below still yields up to `limit` rows when recent conversations
167
+ // are single-shot runs (no thread) or the excluded one.
168
+ const limit = options.limit ?? 20;
169
+ return store
170
+ .listConversations(limit * 3)
171
+ .filter((c) => c.threadId !== undefined && c.id !== options.exclude)
172
+ .slice(0, limit);
173
+ }
174
+ finally {
175
+ store.close();
176
+ }
177
+ }
178
+ catch {
179
+ return [];
180
+ }
181
+ }
182
+ /**
183
+ * GS2-20 — the stored approval-grants document of one conversation, as the opaque JSON string the
184
+ * approvals layer wrote (`core/approvals/conversationGrants.ts` owns the format), or `null` when
185
+ * there is none. Governed by the same switch as everything else here. Fail-soft, never throws.
186
+ */
187
+ export function readConversationGrantsSafe(config, conversationId) {
188
+ try {
189
+ if (!isHistoryEnabled(config))
190
+ return null;
191
+ const dbPath = resolveHistoryDbPath(config.history?.dbPath);
192
+ const store = openHistoryStore(dbPath, { create: false });
193
+ if (!store)
194
+ return null;
195
+ try {
196
+ return store.getConversationGrants(conversationId);
197
+ }
198
+ finally {
199
+ store.close();
200
+ }
201
+ }
202
+ catch {
203
+ return null;
204
+ }
205
+ }
206
+ /**
207
+ * GS2-20 — replace one conversation's stored approval-grants document. Called whenever the session's
208
+ * grants change, so the row always holds the current set and a resume restores exactly what the
209
+ * conversation had. Returns whether the row was written. Fail-soft, never throws — a grant that
210
+ * could not be recorded costs one re-prompt after a resume, and must never cost the turn.
211
+ */
212
+ export function writeConversationGrantsSafe(config, conversationId, grantsJson) {
213
+ try {
214
+ if (!isHistoryEnabled(config))
215
+ return false;
216
+ const dbPath = resolveHistoryDbPath(config.history?.dbPath);
217
+ const store = openHistoryStore(dbPath, { create: false });
218
+ if (!store)
219
+ return false;
220
+ try {
221
+ return store.setConversationGrants(conversationId, grantsJson);
222
+ }
223
+ finally {
224
+ store.close();
225
+ }
226
+ }
227
+ catch {
228
+ return false;
229
+ }
230
+ }
56
231
  //# sourceMappingURL=recordSession.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"recordSession.js","sourceRoot":"","sources":["../../src/history/recordSession.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAOtF;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAyB,EAAE,MAAqB;IAChF,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;QACjF,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAyB,EACzB,IAAsB;IAEtB,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;QACjF,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"recordSession.js","sourceRoot":"","sources":["../../src/history/recordSession.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAKlE;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAyB,EAAE,MAAqB;IAChF,IAAI,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;QAClF,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAyB,EACzB,IAAsB;IAEtB,IAAI,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;QAClF,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,+BAA+B,CAC7C,MAAyB,EACzB,cAAsB;IAEtB,IAAI,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,OAAO;QACtC,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,CAAC;YACH,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,CAAC;QAChD,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAAyB,EACzB,cAAsB;IAEtB,IAAI,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,KAAK,CAAC,uBAAuB,CAAC,cAAc,CAAC,CAAC;QACvD,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAUD;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAyB,EACzB,cAAsB;IAEtB,IAAI,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,KAAK,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;YACtD,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YAC1B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,qBAAqB,CAAC,cAAc,CAAC,EAAE,CAAC;QACzE,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B,CAC5C,MAAyB,EACzB,OAAO,GAAyC,EAAE;IAElD,IAAI,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,2FAA2F;YAC3F,wDAAwD;YACxD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YAClC,OAAO,KAAK;iBACT,iBAAiB,CAAC,KAAK,GAAG,CAAC,CAAC;iBAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC;iBACnE,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAAyB,EACzB,cAAsB;IAEtB,IAAI,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,KAAK,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC;QACrD,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAAyB,EACzB,cAAsB,EACtB,UAAyB;IAEzB,IAAI,CAAC;QACH,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QAC5C,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,IAAI,CAAC;YACH,OAAO,KAAK,CAAC,qBAAqB,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;QACjE,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,48 @@
1
+ import { BaseCheckpointSaver } from '@langchain/langgraph';
2
+ import { type HistoryConfigView } from '#src/history/historyEnabled.js';
3
+ /** The checkpointer a session drives, plus what the session needs to know about it. */
4
+ export interface SessionCheckpointer {
5
+ /** The saver to hand to `GthAgentRunner.init` / `GthAgentInterface.init`. Never null. */
6
+ saver: BaseCheckpointSaver;
7
+ /**
8
+ * True when checkpoints are reaching disk — i.e. when this session can be resumed later. False
9
+ * for a `MemorySaver`, whether that is the deliberate opt-out or the fallback.
10
+ */
11
+ durable: boolean;
12
+ /**
13
+ * The LangGraph thread this session's state is stored under, minted here so the conversation row
14
+ * can record it BEFORE the runner is initialised — `init` can throw partway, and a conversation
15
+ * whose thread id was never written is a listing entry that can never be resumed.
16
+ */
17
+ threadId: string;
18
+ /**
19
+ * Name the conversation row this session's turns group under, so a later checkpoint-write failure
20
+ * can mark it unresumable ON DISK. Call once per conversation the session records under:
21
+ * immediately after `openConversationSafe`, and again when a mid-session `/resume` moves the
22
+ * session onto another conversation — the saver is the same, so the row it would mark has to
23
+ * follow the session. Passing `undefined` (history off, or the row could not be opened) is fine
24
+ * and simply leaves nothing to mark. Safe to call before or after a failure has already happened
25
+ * — a failure that arrives first is applied here instead, and a rebind after one marks the new
26
+ * row too, which is correct: a saver that has stopped writing is truncating whichever
27
+ * conversation it is now on. Never throws.
28
+ *
29
+ * **Optional on the interface, and called with `?.`**, because a dozen-odd specs stub
30
+ * `openSessionCheckpointerSafe` with a plain object literal to keep a session off the real
31
+ * database. Requiring it would break every one of them at runtime for no gain: a stub that omits
32
+ * it simply has no conversation to mark, which is the correct behaviour for a test that is not
33
+ * about this.
34
+ */
35
+ bindConversation?(conversationId: number | undefined): void;
36
+ /** Release the underlying connection (a no-op when the saver is in memory). Never throws. */
37
+ close(): void;
38
+ }
39
+ export interface OpenSessionCheckpointerOptions {
40
+ /** Drive a specific thread instead of a fresh one — how a resume re-enters a stored thread. */
41
+ threadId?: string;
42
+ /**
43
+ * Where the fallback notice goes. Defaults to {@link displayWarning}; injectable so a caller on a
44
+ * managed surface can route it, and so a test can read it without mocking the console.
45
+ */
46
+ notify?: (message: string) => void;
47
+ }
48
+ export declare function openSessionCheckpointerSafe(config: HistoryConfigView, options?: OpenSessionCheckpointerOptions): SessionCheckpointer;
@@ -0,0 +1,200 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * GS2-20 — the bridge from a session's config to the checkpointer it drives, and the counterpart to
4
+ * the recorder bridge in `recordSession.ts`: one call, never throws, always returns a usable saver.
5
+ *
6
+ * **The fallback is a `MemorySaver`, never the absence of one.** With a tool-approval interrupt
7
+ * installed and no checkpointer at all, the first gated tool call throws a LangGraph-internal
8
+ * `MISSING_CHECKPOINTER` in the middle of a turn — a failure with no relationship to what the user
9
+ * was doing. So a database this session cannot open costs exactly one thing, resumability, and the
10
+ * user is told that plainly rather than discovering it later.
11
+ *
12
+ * **A write that fails later costs the same one thing.** This module owns the degrade policy the
13
+ * saver reports into: one notice per session, and — the part that has to outlive the process — the
14
+ * conversation's thread link cleared on disk, so a later resume refuses it. A checkpoint chain that
15
+ * stopped growing mid-conversation is still well-formed, so without that mark the next `--resume`
16
+ * would load a truncated conversation and present it as complete.
17
+ */
18
+ import { randomUUID } from 'node:crypto';
19
+ import { MemorySaver } from '@langchain/langgraph';
20
+ import { openCheckpointSaver } from '#src/history/checkpointSaver.js';
21
+ import { isHistoryEnabled } from '#src/history/historyEnabled.js';
22
+ import { resolveHistoryDbPath } from '#src/history/historyStore.js';
23
+ import { markConversationUnresumableSafe } from '#src/history/recordSession.js';
24
+ import { displayWarning } from '#src/utils/consoleUtils.js';
25
+ /**
26
+ * Open the session's checkpointer. Returns a durable, SQLite-backed saver when history is on and
27
+ * the database opens; a `MemorySaver` otherwise. Never throws.
28
+ *
29
+ * The `MemorySaver` case is two different situations that must not be conflated:
30
+ * - `history.enabled: false` — the user turned persistence off. Silent: they asked for this.
31
+ * - the database would not open — the user asked for persistence and did not get it, so
32
+ * {@link OpenSessionCheckpointerOptions.notify} says so.
33
+ */
34
+ /**
35
+ * Every durable checkpointer currently open in this process, as its own close function.
36
+ *
37
+ * A session normally closes its connection in a `finally`, but a process killed by a signal never
38
+ * reaches one: `systemUtils` answers `SIGINT`/`SIGTERM` with `process.exit(0)`, which unwinds no
39
+ * `try`/`finally` at all, and a `SIGHUP` — a user closing the terminal window — terminates without
40
+ * running anything. Leaving the database open until the OS tears the process down is harmless on
41
+ * POSIX and not on win32, where a live handle blocks the file from being deleted or replaced.
42
+ */
43
+ const openSavers = new Set();
44
+ /** Installed at most once per process, on the first durable checkpointer. */
45
+ let terminationHooksInstalled = false;
46
+ /**
47
+ * Close every open checkpointer on the way out of the process.
48
+ *
49
+ * `exit` rather than a `SIGINT`/`SIGTERM` handler of our own, deliberately: `systemUtils` registers
50
+ * its signal handlers when it is first imported, which is long before any session starts, and they
51
+ * call `process.exit(0)`. Node runs signal listeners in registration order, so a later listener of
52
+ * ours would never be reached — but `process.exit()` does run `exit` listeners, so that one hook
53
+ * covers `SIGINT`, `SIGTERM`, an explicit exit and a normal return alike. `SIGHUP` has no such
54
+ * handler anywhere and so needs its own.
55
+ */
56
+ function ensureTerminationHooks() {
57
+ if (terminationHooksInstalled)
58
+ return;
59
+ terminationHooksInstalled = true;
60
+ const closeAll = () => {
61
+ for (const close of [...openSavers]) {
62
+ try {
63
+ close();
64
+ }
65
+ catch {
66
+ // Nothing useful can be done about a failed close while the process is already leaving.
67
+ }
68
+ }
69
+ openSavers.clear();
70
+ };
71
+ process.on('exit', closeAll);
72
+ const onHangup = () => {
73
+ // Remove ourselves first so the default disposition applies again and the process dies with the
74
+ // status it would have had. Swallowing a hangup would leave a session running invisibly.
75
+ process.off('SIGHUP', onHangup);
76
+ closeAll();
77
+ process.kill(process.pid, 'SIGHUP');
78
+ };
79
+ process.on('SIGHUP', onHangup);
80
+ }
81
+ export function openSessionCheckpointerSafe(config, options = {}) {
82
+ const threadId = options.threadId ?? randomUUID();
83
+ const notify = options.notify ?? displayWarning;
84
+ const inMemory = () => ({
85
+ saver: new MemorySaver(),
86
+ durable: false,
87
+ threadId,
88
+ bindConversation: () => { },
89
+ close: () => { },
90
+ });
91
+ // GS2-20 — the degrade path. A checkpoint write that fails mid-session drops the write and lets
92
+ // the turn continue; these two closures are the "loudly" half. They are declared before the saver
93
+ // because the saver is handed `degrade` at construction.
94
+ //
95
+ // The two facts are tracked separately on purpose. `degraded` is what happened; `conversationId`
96
+ // is where to write it down, and it is not known yet — the conversation row is opened by the
97
+ // caller AFTER this returns, because the row has to carry the thread id minted just above. So a
98
+ // failure can precede the binding, and `applyMark` is called from both sides to close that gap.
99
+ let degraded = false;
100
+ let conversationId;
101
+ const applyMark = () => {
102
+ if (!degraded || conversationId === undefined)
103
+ return;
104
+ markConversationUnresumableSafe(config, conversationId);
105
+ };
106
+ const degrade = () => {
107
+ // Once per session, not once per write: a full disk fails every super-step, and a notice per
108
+ // write would bury the turn the user is still having under its own error report.
109
+ if (degraded)
110
+ return;
111
+ degraded = true;
112
+ notify('Could not save this conversation to the conversation store, so it will not be resumable ' +
113
+ 'later — the rest of this session is unaffected and your work is not lost. The usual cause ' +
114
+ 'is a full or read-only disk. Set `history.enabled: false` in your config to stop trying.');
115
+ applyMark();
116
+ };
117
+ try {
118
+ if (!isHistoryEnabled(config))
119
+ return inMemory();
120
+ const dbPath = resolveHistoryDbPath(config.history?.dbPath, /* ensureDir */ true);
121
+ const saver = openCheckpointSaver(dbPath, { onWriteFailure: degrade });
122
+ if (!saver) {
123
+ notify(`Could not open the conversation store at ${dbPath}, so this session will not be ` +
124
+ 'resumable later. It runs normally otherwise; set `history.enabled: false` in your ' +
125
+ 'config to stop trying.');
126
+ return inMemory();
127
+ }
128
+ // GS2-20 — hold the connection open only as long as the session actually runs. `saver.close()`
129
+ // is itself fail-soft, so the guard is about not counting a closed saver as open rather than
130
+ // about a double close throwing.
131
+ let closed = false;
132
+ const closeOnce = () => {
133
+ if (closed)
134
+ return;
135
+ closed = true;
136
+ saver.close();
137
+ };
138
+ openSavers.add(closeOnce);
139
+ ensureTerminationHooks();
140
+ // GS2-107 — the automatic half of the retention policy runs HERE, on the orderly close of a
141
+ // session that actually ran, and nowhere else.
142
+ //
143
+ // **Why the close and not the open.** The connection is already there, so it adds no open of its
144
+ // own, and it lands after the person has stopped waiting on the tool rather than in front of
145
+ // their first prompt — a user with a large store must not pay for retention at startup.
146
+ //
147
+ // **Why not in `closeOnce`.** That closure is also what the process-exit and SIGHUP hooks call.
148
+ // A row delete while the process is already leaving is work nobody is waiting for and nothing
149
+ // can report, on a path that a signal can interrupt halfway.
150
+ //
151
+ // **Why the `bindConversation` gate.** Both surfaces bind straight after the resume checks pass,
152
+ // so a checkpointer closed before that point is one whose session never started — the
153
+ // `--resume` refusal path, which exits 1 promising that nothing was changed. Deleting rows there
154
+ // would make that sentence false.
155
+ //
156
+ // **What keeps this session's own thread safe.** Not the id minted above: the runner rotates
157
+ // threads and does not tell anyone — `resetThread()` on `/clear`, `resumeConversation` onto a
158
+ // stored thread — so after any rotation that id names a thread nobody wrote. The saver excludes
159
+ // the threads IT wrote instead, which is the same set by construction however often the session
160
+ // rotated, so nothing is passed here. What remains outside that set is a live session in
161
+ // ANOTHER process, and it is bounded only by the grace window; closing that gap needs shared
162
+ // cross-process session state, which this ticket does not add.
163
+ let served = false;
164
+ let reclaimed = false;
165
+ const reclaimOnClose = () => {
166
+ if (reclaimed || !served || closed)
167
+ return;
168
+ reclaimed = true;
169
+ try {
170
+ saver.reclaimUnresumableThreads();
171
+ }
172
+ catch {
173
+ /* retention is housekeeping: it must never be the reason a session fails to exit */
174
+ }
175
+ };
176
+ return {
177
+ saver,
178
+ durable: true,
179
+ threadId,
180
+ bindConversation: (id) => {
181
+ served = true;
182
+ conversationId = id;
183
+ applyMark();
184
+ },
185
+ close: () => {
186
+ reclaimOnClose();
187
+ openSavers.delete(closeOnce);
188
+ closeOnce();
189
+ },
190
+ };
191
+ }
192
+ catch {
193
+ // Reaching here means something outside the saver's own fail-soft open threw — resolving the
194
+ // path, or creating `~/.gsloth`. Same outcome, same sentence: the session runs, unresumable.
195
+ notify('Could not open the conversation store, so this session will not be resumable later. ' +
196
+ 'It runs normally otherwise; set `history.enabled: false` in your config to stop trying.');
197
+ return inMemory();
198
+ }
199
+ }
200
+ //# sourceMappingURL=sessionCheckpointer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionCheckpointer.js","sourceRoot":"","sources":["../../src/history/sessionCheckpointer.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  export * from '#src/constants.js';
2
2
  export * from '#src/core/types.js';
3
3
  export { gthLeanAgentFactory } from '#src/core/gthLeanAgentFactory.js';
4
+ export * from '#src/core/compaction.js';
5
+ export { AutocompactController } from '#src/core/compactionThreshold.js';
6
+ export type { AutocompactConfig, AutocompactControllerOptions, AutocompactStatus, AutocompactThresholdOrigin, ResolvedAutocompactConfig, } from '#src/core/compactionThreshold.js';
7
+ export type { ContextWindowOrigin, ContextWindowReading, ContextWindowResolutionOptions, ContextWindowSource, OllamaLikeModel, ResolvedContextWindow, } from '#src/core/contextWindow.js';
4
8
  export * from '#src/config.js';
5
9
  export * from '#src/providers/modelDiscovery.js';
10
+ export type { CatalogOptions, EnrichedModel, ModelCatalogEntry, ModelCost, ModelLimit, ModelModalities, ProviderCatalog, } from '#src/providers/modelCatalog.js';
6
11
  export * from '#src/history/historyStore.js';
7
12
  export * from '#src/history/recordSession.js';
8
13
  export * from '#src/history/historyFormat.js';
14
+ export type { CheckpointStoreStats, PrunableConversation, ReclaimSummary, ThreadUsage, } from '#src/history/checkpointRetention.js';