@gaunt-sloth/core 2.0.0-alpha.2 → 2.0.0-alpha.21

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 (158) hide show
  1. package/.gsloth.code.md +10 -0
  2. package/README.md +6 -6
  3. package/dist/config/defaults.d.ts +85 -0
  4. package/dist/config/defaults.js +103 -0
  5. package/dist/config/defaults.js.map +1 -0
  6. package/dist/config/jsonc.d.ts +12 -0
  7. package/dist/config/jsonc.js +41 -0
  8. package/dist/config/jsonc.js.map +1 -0
  9. package/dist/config/loader.d.ts +169 -0
  10. package/dist/config/loader.js +872 -0
  11. package/dist/config/loader.js.map +1 -0
  12. package/dist/config/schema.d.ts +673 -0
  13. package/dist/config/schema.js +524 -0
  14. package/dist/config/schema.js.map +1 -0
  15. package/dist/config/shell-policy.d.ts +331 -0
  16. package/dist/config/shell-policy.js +238 -0
  17. package/dist/config/shell-policy.js.map +1 -0
  18. package/dist/config/types.d.ts +596 -0
  19. package/dist/config/types.js +13 -0
  20. package/dist/config/types.js.map +1 -0
  21. package/dist/config.d.ts +18 -647
  22. package/dist/config.js +15 -516
  23. package/dist/config.js.map +1 -1
  24. package/dist/constants.d.ts +42 -0
  25. package/dist/constants.js +42 -0
  26. package/dist/constants.js.map +1 -1
  27. package/dist/core/GthAbstractAgent.d.ts +85 -2
  28. package/dist/core/GthAbstractAgent.js +398 -28
  29. package/dist/core/GthAbstractAgent.js.map +1 -1
  30. package/dist/core/GthAgentRunner.d.ts +158 -1
  31. package/dist/core/GthAgentRunner.js +367 -4
  32. package/dist/core/GthAgentRunner.js.map +1 -1
  33. package/dist/core/GthLangChainAgent.js +193 -16
  34. package/dist/core/GthLangChainAgent.js.map +1 -1
  35. package/dist/core/debugCapture.d.ts +59 -0
  36. package/dist/core/debugCapture.js +101 -0
  37. package/dist/core/debugCapture.js.map +1 -0
  38. package/dist/core/gthLeanAgentFactory.d.ts +9 -0
  39. package/dist/core/gthLeanAgentFactory.js +10 -0
  40. package/dist/core/gthLeanAgentFactory.js.map +1 -0
  41. package/dist/core/plainToolIndication.d.ts +15 -0
  42. package/dist/core/plainToolIndication.js +148 -0
  43. package/dist/core/plainToolIndication.js.map +1 -0
  44. package/dist/core/runStats.d.ts +41 -0
  45. package/dist/core/runStats.js +73 -0
  46. package/dist/core/runStats.js.map +1 -0
  47. package/dist/core/shell/ShellCommandFailedError.d.ts +54 -0
  48. package/dist/core/shell/ShellCommandFailedError.js +68 -0
  49. package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
  50. package/dist/core/shell/allowlist.d.ts +75 -0
  51. package/dist/core/shell/allowlist.js +187 -0
  52. package/dist/core/shell/allowlist.js.map +1 -0
  53. package/dist/core/shell/arity.d.ts +75 -0
  54. package/dist/core/shell/arity.js +313 -0
  55. package/dist/core/shell/arity.js.map +1 -0
  56. package/dist/core/shell/judge.d.ts +161 -0
  57. package/dist/core/shell/judge.js +261 -0
  58. package/dist/core/shell/judge.js.map +1 -0
  59. package/dist/core/shell/normalize.d.ts +27 -0
  60. package/dist/core/shell/normalize.js +53 -0
  61. package/dist/core/shell/normalize.js.map +1 -0
  62. package/dist/core/toolDisplay.d.ts +101 -0
  63. package/dist/core/toolDisplay.js +374 -0
  64. package/dist/core/toolDisplay.js.map +1 -0
  65. package/dist/core/toolOutputChannel.d.ts +68 -0
  66. package/dist/core/toolOutputChannel.js +112 -0
  67. package/dist/core/toolOutputChannel.js.map +1 -0
  68. package/dist/core/types.d.ts +173 -0
  69. package/dist/core/types.js.map +1 -1
  70. package/dist/history/historyFormat.d.ts +28 -0
  71. package/dist/history/historyFormat.js +127 -0
  72. package/dist/history/historyFormat.js.map +1 -0
  73. package/dist/history/historyStore.d.ts +198 -0
  74. package/dist/history/historyStore.js +482 -0
  75. package/dist/history/historyStore.js.map +1 -0
  76. package/dist/history/recordSession.d.ts +37 -0
  77. package/dist/history/recordSession.js +56 -0
  78. package/dist/history/recordSession.js.map +1 -0
  79. package/dist/index.d.ts +4 -0
  80. package/dist/index.js +4 -0
  81. package/dist/index.js.map +1 -1
  82. package/dist/providers/anthropic.d.ts +1 -1
  83. package/dist/providers/anthropic.js +5 -10
  84. package/dist/providers/anthropic.js.map +1 -1
  85. package/dist/providers/deepseek.d.ts +1 -1
  86. package/dist/providers/deepseek.js +5 -10
  87. package/dist/providers/deepseek.js.map +1 -1
  88. package/dist/providers/geminiSchemaSanitizer.d.ts +52 -0
  89. package/dist/providers/geminiSchemaSanitizer.js +201 -0
  90. package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
  91. package/dist/providers/google-genai.d.ts +1 -1
  92. package/dist/providers/google-genai.js +9 -11
  93. package/dist/providers/google-genai.js.map +1 -1
  94. package/dist/providers/groq.d.ts +1 -1
  95. package/dist/providers/groq.js +5 -10
  96. package/dist/providers/groq.js.map +1 -1
  97. package/dist/providers/huggingface.d.ts +25 -0
  98. package/dist/providers/huggingface.js +69 -0
  99. package/dist/providers/huggingface.js.map +1 -0
  100. package/dist/providers/modelCatalog.d.ts +109 -0
  101. package/dist/providers/modelCatalog.js +245 -0
  102. package/dist/providers/modelCatalog.js.map +1 -0
  103. package/dist/providers/modelDiscovery.d.ts +111 -3
  104. package/dist/providers/modelDiscovery.js +183 -27
  105. package/dist/providers/modelDiscovery.js.map +1 -1
  106. package/dist/providers/ollama.d.ts +19 -5
  107. package/dist/providers/ollama.js +60 -52
  108. package/dist/providers/ollama.js.map +1 -1
  109. package/dist/providers/openai.d.ts +1 -1
  110. package/dist/providers/openai.js +5 -10
  111. package/dist/providers/openai.js.map +1 -1
  112. package/dist/providers/openrouter.d.ts +1 -1
  113. package/dist/providers/openrouter.js +12 -12
  114. package/dist/providers/openrouter.js.map +1 -1
  115. package/dist/providers/vertexai.d.ts +1 -1
  116. package/dist/providers/vertexai.js +9 -11
  117. package/dist/providers/vertexai.js.map +1 -1
  118. package/dist/providers/xai.d.ts +1 -1
  119. package/dist/providers/xai.js +5 -10
  120. package/dist/providers/xai.js.map +1 -1
  121. package/dist/runtime/askStructured.d.ts +62 -0
  122. package/dist/runtime/askStructured.js +76 -0
  123. package/dist/runtime/askStructured.js.map +1 -0
  124. package/dist/runtime/conversation.d.ts +59 -0
  125. package/dist/runtime/conversation.js +137 -0
  126. package/dist/runtime/conversation.js.map +1 -0
  127. package/dist/runtime/singleShot.d.ts +24 -5
  128. package/dist/runtime/singleShot.js +56 -9
  129. package/dist/runtime/singleShot.js.map +1 -1
  130. package/dist/utils/aiignoreUtils.js.map +1 -1
  131. package/dist/utils/binaryOutputUtils.js.map +1 -1
  132. package/dist/utils/consoleUtils.d.ts +22 -0
  133. package/dist/utils/consoleUtils.js +45 -0
  134. package/dist/utils/consoleUtils.js.map +1 -1
  135. package/dist/utils/debugDump.d.ts +57 -0
  136. package/dist/utils/debugDump.js +236 -0
  137. package/dist/utils/debugDump.js.map +1 -0
  138. package/dist/utils/debugUtils.d.ts +13 -4
  139. package/dist/utils/debugUtils.js +36 -13
  140. package/dist/utils/debugUtils.js.map +1 -1
  141. package/dist/utils/fileUtils.d.ts +24 -2
  142. package/dist/utils/fileUtils.js +54 -12
  143. package/dist/utils/fileUtils.js.map +1 -1
  144. package/dist/utils/llmUtils.js.map +1 -1
  145. package/dist/utils/redactSecrets.d.ts +63 -0
  146. package/dist/utils/redactSecrets.js +238 -0
  147. package/dist/utils/redactSecrets.js.map +1 -0
  148. package/dist/utils/systemPromptNotes.d.ts +181 -0
  149. package/dist/utils/systemPromptNotes.js +298 -0
  150. package/dist/utils/systemPromptNotes.js.map +1 -0
  151. package/dist/utils/systemUtils.d.ts +32 -1
  152. package/dist/utils/systemUtils.js +50 -3
  153. package/dist/utils/systemUtils.js.map +1 -1
  154. package/dist/utils/toolMatching.d.ts +30 -0
  155. package/dist/utils/toolMatching.js +44 -0
  156. package/dist/utils/toolMatching.js.map +1 -0
  157. package/package.json +17 -8
  158. package/schema/gsloth-config.schema.json +2111 -0
@@ -0,0 +1,482 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * GS2-7 (B20) — local, opt-in session history store.
4
+ *
5
+ * A **local SQLite** store that persists a compact record of each run BESIDE the existing
6
+ * per-run `.md` logs, so `gth history search` / `gth insights` can look back over past sessions.
7
+ * It is a **side-benefit, never a critical path**:
8
+ *
9
+ * - **Local only.** Nothing leaves the machine. The DB lives under the user's global `~/.gsloth`
10
+ * dir (cross-project history), overridable via `history.dbPath`.
11
+ * - **Opt-in.** The recorder only writes when `history.enabled` is true (see
12
+ * {@link recordSessionSafe}); default runs persist nothing and behave exactly as before.
13
+ * - **Fail-soft.** {@link openHistoryStore} returns `null` if the DB can't be opened, and every
14
+ * {@link HistoryStore} method catches its own errors and returns a safe default. A malformed or
15
+ * locked DB therefore can never abort or alter a run — it just means no history for that run.
16
+ *
17
+ * Uses the built-in `node:sqlite` (Node ≥ 24) — zero native dependency, no build step — and its
18
+ * bundled **FTS5** extension for full-text search (verified available at build time via an
19
+ * `fts5` virtual table). No fallback path is needed on this runtime.
20
+ */
21
+ import { DatabaseSync } from 'node:sqlite';
22
+ import { existsSync } from 'node:fs';
23
+ import { resolve } from 'node:path';
24
+ import { getGlobalGslothDir, ensureGlobalGslothDir } from '#src/utils/globalConfigUtils.js';
25
+ /** Filename of the global history DB inside `~/.gsloth`. */
26
+ export const HISTORY_DB_FILENAME = 'history.db';
27
+ /** Escape a value for use inside an FTS5 double-quoted string token. */
28
+ function ftsQuote(term) {
29
+ return '"' + term.replace(/"/g, '""') + '"';
30
+ }
31
+ /**
32
+ * Turn arbitrary user text into a safe FTS5 MATCH expression: each whitespace-separated token is
33
+ * wrapped as a quoted string and AND-ed together. This avoids FTS5 syntax errors from stray
34
+ * operators (`AND`, `*`, `:`, parentheses, unbalanced quotes) in a user's query while still
35
+ * matching all of their words.
36
+ */
37
+ export function toFtsMatchQuery(query) {
38
+ const tokens = query.trim().split(/\s+/).filter(Boolean);
39
+ if (tokens.length === 0)
40
+ return '';
41
+ return tokens.map(ftsQuote).join(' AND ');
42
+ }
43
+ /**
44
+ * A thin, fail-soft wrapper over a `node:sqlite` connection holding the session history.
45
+ *
46
+ * Obtain one via {@link openHistoryStore} (which returns `null` if the DB can't be opened). Every
47
+ * method is defensive: on any SQLite error it returns a safe empty/zero result rather than
48
+ * throwing, so callers on a run's hot path never have to guard.
49
+ */
50
+ export class HistoryStore {
51
+ db;
52
+ constructor(db) {
53
+ this.db = db;
54
+ }
55
+ /**
56
+ * Open (and lazily initialise) the store at `dbPath`. Returns `null` on any failure — a missing
57
+ * file when `create` is false, an unopenable/locked/corrupt DB, or a schema-init error — so the
58
+ * caller can simply skip history without a try/catch.
59
+ */
60
+ static open(dbPath, options = {}) {
61
+ const create = options.create ?? false;
62
+ if (!create && dbPath !== ':memory:' && !existsSync(dbPath)) {
63
+ return null;
64
+ }
65
+ let db;
66
+ try {
67
+ db = new DatabaseSync(dbPath);
68
+ const store = new HistoryStore(db);
69
+ store.initSchema();
70
+ return store;
71
+ }
72
+ catch {
73
+ // GS2-42: initSchema() (or the DatabaseSync constructor itself) can throw after the OS-level
74
+ // file handle is already open (e.g. a corrupt/garbage DB file — SQLite doesn't validate the
75
+ // file header until the first statement executes). Fail-soft-close it here before returning
76
+ // null, mirroring close()'s own try/catch, since on win32 an unclosed handle blocks the file
77
+ // from being deleted/replaced/reopened until the process exits (POSIX allows unlinking an
78
+ // open fd, which is why this leak was invisible there).
79
+ try {
80
+ db?.close();
81
+ }
82
+ catch {
83
+ /* ignore: db may already be in a broken state */
84
+ }
85
+ return null;
86
+ }
87
+ }
88
+ initSchema() {
89
+ // GS2-19: the `sessions` table is turn-grained (its rows are prompt→response turns, despite the
90
+ // name). A `conversations` table groups the turns of one interactive session; each turn carries
91
+ // a `conversation_id`. `conversation_id` is a plain INTEGER join key, not an enforced FK — SQLite
92
+ // leaves `foreign_keys` off by default and `ALTER TABLE ADD COLUMN` can't add a REFERENCES clause,
93
+ // so declaring one here would only make the fresh schema drift from the migrated one. The column
94
+ // definition is kept byte-identical between this fresh path and {@link migrate}'s ALTER.
95
+ this.db.exec(`
96
+ CREATE TABLE IF NOT EXISTS conversations (
97
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
98
+ started_ts TEXT NOT NULL,
99
+ project TEXT,
100
+ command TEXT,
101
+ model TEXT
102
+ );
103
+ CREATE TABLE IF NOT EXISTS sessions (
104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
105
+ ts TEXT NOT NULL,
106
+ project TEXT,
107
+ command TEXT,
108
+ model TEXT,
109
+ prompt TEXT,
110
+ response TEXT,
111
+ tokens_input INTEGER,
112
+ tokens_output INTEGER,
113
+ cost_usd REAL,
114
+ tools TEXT,
115
+ duration_ms INTEGER,
116
+ conversation_id INTEGER
117
+ );
118
+ CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts USING fts5(
119
+ prompt, response, command, project
120
+ );
121
+ `);
122
+ this.migrate();
123
+ }
124
+ /**
125
+ * GS2-19 — in-place, idempotent upgrade of a pre-existing GS2-7 DB (flat `sessions` rows, no
126
+ * grouping) to the conversation-grained model. Runs on every open (including read-only `list` /
127
+ * `search` opens, which open the file read-write): it adds the `conversation_id` column if an
128
+ * older `sessions` table lacks it, then back-fills each ungrouped turn into its own 1-turn
129
+ * conversation. After the first pass there are no ungrouped rows, so it is a cheap no-op.
130
+ *
131
+ * Fully fail-soft: a migration hiccup is swallowed here rather than nulling the store, so reads
132
+ * still work against whatever is already there and new turns just fall back to fresh 1-turn
133
+ * conversations. Never throws.
134
+ */
135
+ migrate() {
136
+ try {
137
+ const cols = this.db.prepare(`PRAGMA table_info(sessions)`).all();
138
+ const hasConversationId = cols.some((c) => c.name === 'conversation_id');
139
+ if (!hasConversationId) {
140
+ this.db.exec(`ALTER TABLE sessions ADD COLUMN conversation_id INTEGER`);
141
+ }
142
+ const orphans = this.db
143
+ .prepare(`SELECT id, ts, project, command, model
144
+ FROM sessions
145
+ WHERE conversation_id IS NULL
146
+ ORDER BY id`)
147
+ .all();
148
+ if (orphans.length === 0)
149
+ return;
150
+ this.db.exec('BEGIN');
151
+ try {
152
+ const insertConversation = this.db.prepare(`INSERT INTO conversations (started_ts, project, command, model) VALUES (?, ?, ?, ?)`);
153
+ const stampTurn = this.db.prepare(`UPDATE sessions SET conversation_id = ? WHERE id = ?`);
154
+ for (const row of orphans) {
155
+ const info = insertConversation.run(row.ts != null ? String(row.ts) : new Date().toISOString(), row.project != null ? String(row.project) : null, row.command != null ? String(row.command) : null, row.model != null ? String(row.model) : null);
156
+ stampTurn.run(Number(info.lastInsertRowid), Number(row.id));
157
+ }
158
+ this.db.exec('COMMIT');
159
+ }
160
+ catch (e) {
161
+ this.db.exec('ROLLBACK');
162
+ throw e;
163
+ }
164
+ }
165
+ catch {
166
+ /* fail-soft: a migration failure must not break opening the store or a run */
167
+ }
168
+ }
169
+ /**
170
+ * GS2-19 — open a new conversation and return its id (or `null` on any error). Interactive
171
+ * sessions call this once at start, then pass the id on every {@link record} so all the session's
172
+ * turns group under it. A single-shot run does not need this: {@link record} opens a 1-turn
173
+ * conversation itself when no `conversationId` is supplied.
174
+ */
175
+ openConversation(meta = {}) {
176
+ try {
177
+ const ts = meta.ts ?? new Date().toISOString();
178
+ const info = this.db
179
+ .prepare(`INSERT INTO conversations (started_ts, project, command, model) VALUES (?, ?, ?, ?)`)
180
+ .run(ts, meta.project ?? null, meta.command ?? null, meta.model ?? null);
181
+ return Number(info.lastInsertRowid);
182
+ }
183
+ catch {
184
+ return null;
185
+ }
186
+ }
187
+ /**
188
+ * Persist one session and its full-text index entry. Returns the new row id, or `null` on any
189
+ * error (the run continues regardless). The two inserts run in a transaction so a failure can't
190
+ * leave the FTS index out of sync with the base table.
191
+ */
192
+ record(rec) {
193
+ try {
194
+ const ts = rec.ts ?? new Date().toISOString();
195
+ const tools = rec.tools && rec.tools.length > 0 ? JSON.stringify(rec.tools) : null;
196
+ this.db.exec('BEGIN');
197
+ try {
198
+ // GS2-19: every turn belongs to a conversation. When the caller opened one up-front
199
+ // (interactive sessions), stamp it; otherwise open a fresh 1-turn conversation for this row
200
+ // (single-shot runs / bare record() calls) so the turn is never left ungrouped.
201
+ let conversationId = rec.conversationId ?? null;
202
+ if (conversationId == null) {
203
+ const cinfo = this.db
204
+ .prepare(`INSERT INTO conversations (started_ts, project, command, model) VALUES (?, ?, ?, ?)`)
205
+ .run(ts, rec.project ?? null, rec.command ?? null, rec.model ?? null);
206
+ conversationId = Number(cinfo.lastInsertRowid);
207
+ }
208
+ const insert = this.db.prepare(`INSERT INTO sessions
209
+ (ts, project, command, model, prompt, response,
210
+ tokens_input, tokens_output, cost_usd, tools, duration_ms, conversation_id)
211
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
212
+ const info = insert.run(ts, rec.project ?? null, rec.command ?? null, rec.model ?? null, rec.prompt ?? null, rec.response ?? null, rec.tokensInput ?? null, rec.tokensOutput ?? null, rec.costUsd ?? null, tools, rec.durationMs ?? null, conversationId);
213
+ const id = Number(info.lastInsertRowid);
214
+ this.db
215
+ .prepare(`INSERT INTO sessions_fts (rowid, prompt, response, command, project)
216
+ VALUES (?, ?, ?, ?, ?)`)
217
+ .run(id, rec.prompt ?? '', rec.response ?? '', rec.command ?? '', rec.project ?? '');
218
+ this.db.exec('COMMIT');
219
+ return id;
220
+ }
221
+ catch (e) {
222
+ this.db.exec('ROLLBACK');
223
+ throw e;
224
+ }
225
+ }
226
+ catch {
227
+ return null;
228
+ }
229
+ }
230
+ /**
231
+ * Full-text search over prompt/response/command/project, best match first (FTS5 `rank`). User
232
+ * text is sanitised via {@link toFtsMatchQuery}; an empty or all-punctuation query returns `[]`.
233
+ * Any SQLite error yields `[]` (fail-soft).
234
+ */
235
+ search(query, limit = 20) {
236
+ const match = toFtsMatchQuery(query);
237
+ if (!match)
238
+ return [];
239
+ try {
240
+ const rows = this.db
241
+ .prepare(`SELECT s.id AS id, s.ts AS ts, s.project AS project, s.command AS command,
242
+ s.model AS model, s.prompt AS prompt, s.response AS response,
243
+ s.tokens_input AS tokens_input, s.tokens_output AS tokens_output,
244
+ s.cost_usd AS cost_usd, s.tools AS tools, s.duration_ms AS duration_ms,
245
+ s.conversation_id AS conversation_id,
246
+ snippet(sessions_fts, 0, '[', ']', '…', 12) AS snippet
247
+ FROM sessions_fts f
248
+ JOIN sessions s ON s.id = f.rowid
249
+ WHERE sessions_fts MATCH ?
250
+ ORDER BY rank
251
+ LIMIT ?`)
252
+ .all(match, limit);
253
+ return rows.map((r) => ({
254
+ ...rowToRecord(r),
255
+ id: Number(r.id),
256
+ ts: String(r.ts),
257
+ snippet: String(r.snippet ?? ''),
258
+ }));
259
+ }
260
+ catch {
261
+ return [];
262
+ }
263
+ }
264
+ /** Most recent sessions, newest first. Fail-soft ([] on error). */
265
+ listRecent(limit = 20) {
266
+ try {
267
+ const rows = this.db
268
+ .prepare(`SELECT id, ts, project, command, model, prompt, response,
269
+ tokens_input, tokens_output, cost_usd, tools, duration_ms, conversation_id
270
+ FROM sessions
271
+ ORDER BY id DESC
272
+ LIMIT ?`)
273
+ .all(limit);
274
+ return rows.map((r) => ({
275
+ ...rowToRecord(r),
276
+ id: Number(r.id),
277
+ ts: String(r.ts),
278
+ snippet: '',
279
+ }));
280
+ }
281
+ catch {
282
+ return [];
283
+ }
284
+ }
285
+ /**
286
+ * GS2-19 — conversations newest-first, each with its turn count, timespan, and a preview of the
287
+ * last turn. This is the top-level unit for `gth history list` (the turn-grained {@link listRecent}
288
+ * remains for callers that want raw turns). Fail-soft ([] on error).
289
+ *
290
+ * Empty conversations are **excluded** (`HAVING COUNT(s.id) > 0`): a session opens its conversation
291
+ * at start (before any turn), so one that exits with zero turns would otherwise show as a
292
+ * contentless `turnCount: 0` row. The LEFT JOIN still keeps every conversation that has ≥1 turn —
293
+ * including back-filled 1-turn conversations, whose single turn satisfies the HAVING.
294
+ */
295
+ listConversations(limit = 20) {
296
+ try {
297
+ const rows = this.db
298
+ .prepare(`SELECT c.id AS id, c.started_ts AS started_ts, c.project AS project,
299
+ c.command AS command, c.model AS model,
300
+ COUNT(s.id) AS turn_count, MIN(s.ts) AS first_ts, MAX(s.ts) AS last_ts
301
+ FROM conversations c
302
+ LEFT JOIN sessions s ON s.conversation_id = c.id
303
+ GROUP BY c.id
304
+ HAVING COUNT(s.id) > 0
305
+ ORDER BY c.id DESC
306
+ LIMIT ?`)
307
+ .all(limit);
308
+ const lastTurn = this.db.prepare(`SELECT prompt, response FROM sessions
309
+ WHERE conversation_id = ? ORDER BY id DESC LIMIT 1`);
310
+ return rows.map((r) => {
311
+ const last = lastTurn.get(Number(r.id));
312
+ return {
313
+ id: Number(r.id),
314
+ startedTs: String(r.started_ts),
315
+ project: r.project != null ? String(r.project) : undefined,
316
+ command: r.command != null ? String(r.command) : undefined,
317
+ model: r.model != null ? String(r.model) : undefined,
318
+ turnCount: Number(r.turn_count ?? 0),
319
+ firstTs: r.first_ts != null ? String(r.first_ts) : undefined,
320
+ lastTs: r.last_ts != null ? String(r.last_ts) : undefined,
321
+ lastPrompt: last?.prompt != null ? String(last.prompt) : undefined,
322
+ lastResponse: last?.response != null ? String(last.response) : undefined,
323
+ };
324
+ });
325
+ }
326
+ catch {
327
+ return [];
328
+ }
329
+ }
330
+ /**
331
+ * GS2-19 — all turns of one conversation in chronological (insert) order, so a search hit can be
332
+ * expanded into the whole thread it belonged to. Fail-soft ([] on error / unknown id).
333
+ */
334
+ getConversationThread(conversationId) {
335
+ try {
336
+ const rows = this.db
337
+ .prepare(`SELECT id, ts, project, command, model, prompt, response,
338
+ tokens_input, tokens_output, cost_usd, tools, duration_ms, conversation_id
339
+ FROM sessions
340
+ WHERE conversation_id = ?
341
+ ORDER BY id ASC`)
342
+ .all(conversationId);
343
+ return rows.map((r) => rowToRecord(r));
344
+ }
345
+ catch {
346
+ return [];
347
+ }
348
+ }
349
+ /**
350
+ * Aggregate token/cost totals, a top-tool tally, and a per-command breakdown over the whole
351
+ * store. Tool tallying reads each row's JSON `tools` array in JS (robust to nulls). Fail-soft:
352
+ * returns a zeroed summary on any error.
353
+ */
354
+ insights(topN = 10) {
355
+ const empty = {
356
+ sessionCount: 0,
357
+ totalTokensInput: 0,
358
+ totalTokensOutput: 0,
359
+ totalTokens: 0,
360
+ totalCostUsd: 0,
361
+ topTools: [],
362
+ perCommand: [],
363
+ };
364
+ try {
365
+ const agg = this.db
366
+ .prepare(`SELECT COUNT(*) AS n,
367
+ COALESCE(SUM(tokens_input), 0) AS ti,
368
+ COALESCE(SUM(tokens_output), 0) AS to_,
369
+ COALESCE(SUM(cost_usd), 0) AS cost,
370
+ MIN(ts) AS first_ts,
371
+ MAX(ts) AS last_ts
372
+ FROM sessions`)
373
+ .get();
374
+ const perCommandRows = this.db
375
+ .prepare(`SELECT command, COUNT(*) AS n
376
+ FROM sessions
377
+ WHERE command IS NOT NULL AND command <> ''
378
+ GROUP BY command
379
+ ORDER BY n DESC, command ASC`)
380
+ .all();
381
+ const toolRows = this.db
382
+ .prepare(`SELECT tools FROM sessions WHERE tools IS NOT NULL AND tools <> ''`)
383
+ .all();
384
+ const toolCounts = new Map();
385
+ for (const row of toolRows) {
386
+ let names;
387
+ try {
388
+ names = JSON.parse(String(row.tools));
389
+ }
390
+ catch {
391
+ continue;
392
+ }
393
+ if (!Array.isArray(names))
394
+ continue;
395
+ for (const name of names) {
396
+ if (typeof name !== 'string' || name.length === 0)
397
+ continue;
398
+ toolCounts.set(name, (toolCounts.get(name) ?? 0) + 1);
399
+ }
400
+ }
401
+ const topTools = [...toolCounts.entries()]
402
+ .map(([tool, count]) => ({ tool, count }))
403
+ .sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool))
404
+ .slice(0, topN);
405
+ const ti = Number(agg.ti ?? 0);
406
+ const to = Number(agg.to_ ?? 0);
407
+ return {
408
+ sessionCount: Number(agg.n ?? 0),
409
+ totalTokensInput: ti,
410
+ totalTokensOutput: to,
411
+ totalTokens: ti + to,
412
+ totalCostUsd: Number(agg.cost ?? 0),
413
+ topTools,
414
+ perCommand: perCommandRows.map((r) => ({
415
+ command: String(r.command),
416
+ count: Number(r.n),
417
+ })),
418
+ firstTs: agg.first_ts ? String(agg.first_ts) : undefined,
419
+ lastTs: agg.last_ts ? String(agg.last_ts) : undefined,
420
+ };
421
+ }
422
+ catch {
423
+ return empty;
424
+ }
425
+ }
426
+ /** Close the underlying connection (fail-soft). */
427
+ close() {
428
+ try {
429
+ this.db.close();
430
+ }
431
+ catch {
432
+ /* ignore */
433
+ }
434
+ }
435
+ }
436
+ /** Map a raw DB row (snake_case columns) to a {@link SessionRecord}. */
437
+ function rowToRecord(r) {
438
+ let tools;
439
+ if (r.tools != null && r.tools !== '') {
440
+ try {
441
+ const parsed = JSON.parse(String(r.tools));
442
+ if (Array.isArray(parsed))
443
+ tools = parsed.filter((t) => typeof t === 'string');
444
+ }
445
+ catch {
446
+ /* ignore malformed tools JSON */
447
+ }
448
+ }
449
+ return {
450
+ conversationId: r.conversation_id != null ? Number(r.conversation_id) : undefined,
451
+ ts: r.ts != null ? String(r.ts) : undefined,
452
+ project: r.project != null ? String(r.project) : undefined,
453
+ command: r.command != null ? String(r.command) : undefined,
454
+ model: r.model != null ? String(r.model) : undefined,
455
+ prompt: r.prompt != null ? String(r.prompt) : undefined,
456
+ response: r.response != null ? String(r.response) : undefined,
457
+ tokensInput: r.tokens_input != null ? Number(r.tokens_input) : undefined,
458
+ tokensOutput: r.tokens_output != null ? Number(r.tokens_output) : undefined,
459
+ costUsd: r.cost_usd != null ? Number(r.cost_usd) : undefined,
460
+ tools,
461
+ durationMs: r.duration_ms != null ? Number(r.duration_ms) : undefined,
462
+ };
463
+ }
464
+ /**
465
+ * Resolve the on-disk path of the history DB. Honors an explicit `dbPath` (from `history.dbPath`
466
+ * or a `--db` flag); otherwise the global `~/.gsloth/history.db`. When `dbPath` is omitted and
467
+ * `ensureDir` is true, the global dir is created so the recorder can write.
468
+ */
469
+ export function resolveHistoryDbPath(dbPath, ensureDir = false) {
470
+ if (dbPath && dbPath.trim().length > 0)
471
+ return dbPath;
472
+ const dir = ensureDir ? ensureGlobalGslothDir() : getGlobalGslothDir();
473
+ return resolve(dir, HISTORY_DB_FILENAME);
474
+ }
475
+ /**
476
+ * Fail-soft open of the history store. Returns `null` (never throws) when the DB can't be opened
477
+ * or, for read-only callers (`create: false`, the default), when the file does not yet exist.
478
+ */
479
+ export function openHistoryStore(dbPath, options = {}) {
480
+ return HistoryStore.open(dbPath, options);
481
+ }
482
+ //# sourceMappingURL=historyStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"historyStore.js","sourceRoot":"","sources":["../../src/history/historyStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * GS2-7 (B20) — the opt-in bridge from a finished run to the {@link HistoryStore}.
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
10
+ * the run: the worst case is that one session isn't recorded.
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
+ }
20
+ /**
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).
23
+ *
24
+ * Deliberately fully guarded: opening the store, ensuring the global dir, and the insert all run
25
+ * behind a single try/catch, and the store itself is fail-soft. Callers put this in a `finally`
26
+ * (or after a run) without a try/catch of their own.
27
+ */
28
+ export declare function recordSessionSafe(config: HistoryConfigView, record: SessionRecord): number | null;
29
+ /**
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.
36
+ */
37
+ export declare function openConversationSafe(config: HistoryConfigView, meta: ConversationMeta): number | null;
@@ -0,0 +1,56 @@
1
+ import { openHistoryStore, resolveHistoryDbPath } from '#src/history/historyStore.js';
2
+ /**
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).
5
+ *
6
+ * Deliberately fully guarded: opening the store, ensuring the global dir, and the insert all run
7
+ * behind a single try/catch, and the store itself is fail-soft. Callers put this in a `finally`
8
+ * (or after a run) without a try/catch of their own.
9
+ */
10
+ export function recordSessionSafe(config, record) {
11
+ try {
12
+ if (!config?.history?.enabled)
13
+ return null;
14
+ const dbPath = resolveHistoryDbPath(config.history.dbPath, /* ensureDir */ true);
15
+ const store = openHistoryStore(dbPath, { create: true });
16
+ if (!store)
17
+ return null;
18
+ try {
19
+ return store.record(record);
20
+ }
21
+ finally {
22
+ store.close();
23
+ }
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ /**
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.
36
+ */
37
+ export function openConversationSafe(config, meta) {
38
+ try {
39
+ if (!config?.history?.enabled)
40
+ return null;
41
+ const dbPath = resolveHistoryDbPath(config.history.dbPath, /* ensureDir */ true);
42
+ const store = openHistoryStore(dbPath, { create: true });
43
+ if (!store)
44
+ return null;
45
+ try {
46
+ return store.openConversation(meta);
47
+ }
48
+ finally {
49
+ store.close();
50
+ }
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ //# sourceMappingURL=recordSession.js.map
@@ -0,0 +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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  export * from '#src/constants.js';
2
2
  export * from '#src/core/types.js';
3
+ export { gthLeanAgentFactory } from '#src/core/gthLeanAgentFactory.js';
3
4
  export * from '#src/config.js';
4
5
  export * from '#src/providers/modelDiscovery.js';
6
+ export * from '#src/history/historyStore.js';
7
+ export * from '#src/history/recordSession.js';
8
+ export * from '#src/history/historyFormat.js';
package/dist/index.js CHANGED
@@ -1,5 +1,9 @@
1
1
  export * from '#src/constants.js';
2
2
  export * from '#src/core/types.js';
3
+ export { gthLeanAgentFactory } from '#src/core/gthLeanAgentFactory.js';
3
4
  export * from '#src/config.js';
4
5
  export * from '#src/providers/modelDiscovery.js';
6
+ export * from '#src/history/historyStore.js';
7
+ export * from '#src/history/recordSession.js';
8
+ export * from '#src/history/historyFormat.js';
5
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kCAAkC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC"}
@@ -4,4 +4,4 @@ import type { BaseChatModel, BaseChatModelParams } from '@langchain/core/languag
4
4
  * Function to process JSON config and create Anthropic LLM instance
5
5
  */
6
6
  export declare function processJsonConfig(llmConfig: AnthropicInput & BaseChatModelParams): Promise<BaseChatModel>;
7
- export declare function init(configFileName: string): void;
7
+ export declare function init(configFileName: string, force?: boolean, model?: string): void;