@gaunt-sloth/core 2.0.0-alpha.0 → 2.0.0-alpha.10

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 (114) hide show
  1. package/.gsloth.code.md +10 -0
  2. package/LICENSE +7 -0
  3. package/README.md +3 -4
  4. package/dist/config/defaults.d.ts +85 -0
  5. package/dist/config/defaults.js +101 -0
  6. package/dist/config/defaults.js.map +1 -0
  7. package/dist/config/jsonc.d.ts +12 -0
  8. package/dist/config/jsonc.js +41 -0
  9. package/dist/config/jsonc.js.map +1 -0
  10. package/dist/config/loader.d.ts +109 -0
  11. package/dist/config/loader.js +663 -0
  12. package/dist/config/loader.js.map +1 -0
  13. package/dist/config/schema.d.ts +509 -0
  14. package/dist/config/schema.js +349 -0
  15. package/dist/config/schema.js.map +1 -0
  16. package/dist/config/shell-policy.d.ts +212 -0
  17. package/dist/config/shell-policy.js +142 -0
  18. package/dist/config/shell-policy.js.map +1 -0
  19. package/dist/config/types.d.ts +490 -0
  20. package/dist/config/types.js +12 -0
  21. package/dist/config/types.js.map +1 -0
  22. package/dist/config.d.ts +18 -647
  23. package/dist/config.js +15 -516
  24. package/dist/config.js.map +1 -1
  25. package/dist/constants.d.ts +24 -0
  26. package/dist/constants.js +24 -0
  27. package/dist/constants.js.map +1 -1
  28. package/dist/core/GthAbstractAgent.d.ts +57 -1
  29. package/dist/core/GthAbstractAgent.js +171 -4
  30. package/dist/core/GthAbstractAgent.js.map +1 -1
  31. package/dist/core/GthAgentRunner.d.ts +168 -1
  32. package/dist/core/GthAgentRunner.js +380 -4
  33. package/dist/core/GthAgentRunner.js.map +1 -1
  34. package/dist/core/GthLangChainAgent.js +94 -5
  35. package/dist/core/GthLangChainAgent.js.map +1 -1
  36. package/dist/core/debugCapture.d.ts +59 -0
  37. package/dist/core/debugCapture.js +101 -0
  38. package/dist/core/debugCapture.js.map +1 -0
  39. package/dist/core/gthLeanAgentFactory.d.ts +9 -0
  40. package/dist/core/gthLeanAgentFactory.js +10 -0
  41. package/dist/core/gthLeanAgentFactory.js.map +1 -0
  42. package/dist/core/runStats.d.ts +41 -0
  43. package/dist/core/runStats.js +73 -0
  44. package/dist/core/runStats.js.map +1 -0
  45. package/dist/core/shell/ShellCommandFailedError.d.ts +54 -0
  46. package/dist/core/shell/ShellCommandFailedError.js +68 -0
  47. package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
  48. package/dist/core/shell/allowlist.d.ts +75 -0
  49. package/dist/core/shell/allowlist.js +187 -0
  50. package/dist/core/shell/allowlist.js.map +1 -0
  51. package/dist/core/shell/arity.d.ts +75 -0
  52. package/dist/core/shell/arity.js +313 -0
  53. package/dist/core/shell/arity.js.map +1 -0
  54. package/dist/core/shell/judge.d.ts +161 -0
  55. package/dist/core/shell/judge.js +261 -0
  56. package/dist/core/shell/judge.js.map +1 -0
  57. package/dist/core/shell/normalize.d.ts +27 -0
  58. package/dist/core/shell/normalize.js +53 -0
  59. package/dist/core/shell/normalize.js.map +1 -0
  60. package/dist/core/types.d.ts +104 -0
  61. package/dist/core/types.js.map +1 -1
  62. package/dist/history/historyFormat.d.ts +19 -0
  63. package/dist/history/historyFormat.js +85 -0
  64. package/dist/history/historyFormat.js.map +1 -0
  65. package/dist/history/historyStore.d.ts +121 -0
  66. package/dist/history/historyStore.js +315 -0
  67. package/dist/history/historyStore.js.map +1 -0
  68. package/dist/history/recordSession.d.ts +28 -0
  69. package/dist/history/recordSession.js +29 -0
  70. package/dist/history/recordSession.js.map +1 -0
  71. package/dist/index.d.ts +4 -0
  72. package/dist/index.js +4 -0
  73. package/dist/index.js.map +1 -1
  74. package/dist/providers/anthropic.d.ts +1 -1
  75. package/dist/providers/anthropic.js +5 -10
  76. package/dist/providers/anthropic.js.map +1 -1
  77. package/dist/providers/deepseek.d.ts +1 -1
  78. package/dist/providers/deepseek.js +5 -10
  79. package/dist/providers/deepseek.js.map +1 -1
  80. package/dist/providers/google-genai.d.ts +1 -1
  81. package/dist/providers/google-genai.js +5 -10
  82. package/dist/providers/google-genai.js.map +1 -1
  83. package/dist/providers/groq.d.ts +1 -1
  84. package/dist/providers/groq.js +5 -10
  85. package/dist/providers/groq.js.map +1 -1
  86. package/dist/providers/modelDiscovery.d.ts +54 -1
  87. package/dist/providers/modelDiscovery.js +106 -8
  88. package/dist/providers/modelDiscovery.js.map +1 -1
  89. package/dist/providers/ollama.d.ts +1 -1
  90. package/dist/providers/ollama.js +5 -15
  91. package/dist/providers/ollama.js.map +1 -1
  92. package/dist/providers/openai.d.ts +1 -1
  93. package/dist/providers/openai.js +5 -10
  94. package/dist/providers/openai.js.map +1 -1
  95. package/dist/providers/openrouter.d.ts +1 -1
  96. package/dist/providers/openrouter.js +7 -12
  97. package/dist/providers/openrouter.js.map +1 -1
  98. package/dist/providers/vertexai.d.ts +1 -1
  99. package/dist/providers/vertexai.js +5 -10
  100. package/dist/providers/vertexai.js.map +1 -1
  101. package/dist/providers/xai.d.ts +1 -1
  102. package/dist/providers/xai.js +5 -10
  103. package/dist/providers/xai.js.map +1 -1
  104. package/dist/runtime/singleShot.d.ts +5 -2
  105. package/dist/runtime/singleShot.js +36 -3
  106. package/dist/runtime/singleShot.js.map +1 -1
  107. package/dist/utils/fileUtils.d.ts +24 -2
  108. package/dist/utils/fileUtils.js +54 -12
  109. package/dist/utils/fileUtils.js.map +1 -1
  110. package/dist/utils/systemUtils.d.ts +31 -0
  111. package/dist/utils/systemUtils.js +38 -0
  112. package/dist/utils/systemUtils.js.map +1 -1
  113. package/package.json +16 -11
  114. package/schema/gsloth-config.schema.json +1579 -0
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,MAAM,CAAN,IAAY,WAQX;AARD,WAAY,WAAW;IACrB,+CAAS,CAAA;IACT,6CAAQ,CAAA;IACR,mDAAW,CAAA;IACX,mDAAW,CAAA;IACX,mDAAW,CAAA;IACX,+CAAS,CAAA;IACT,iDAAU,CAAA;AACZ,CAAC,EARW,WAAW,KAAX,WAAW,QAQtB"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,MAAM,CAAN,IAAY,WAQX;AARD,WAAY,WAAW;IACrB,+CAAS,CAAA;IACT,6CAAQ,CAAA;IACR,mDAAW,CAAA;IACX,mDAAW,CAAA;IACX,mDAAW,CAAA;IACX,+CAAS,CAAA;IACT,iDAAU,CAAA;AACZ,CAAC,EARW,WAAW,KAAX,WAAW,QAQtB"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * GS2-7 (B20) — pure, presentation-only formatters for the history surfaces.
4
+ *
5
+ * Shared by the `gth history` / `gth insights` CLI commands AND the `/history` `/search`
6
+ * `/insights` TUI slash commands so both render identically and both are unit-testable without a
7
+ * DB or a terminal. Every function is a pure `data -> string[]` transform (one display line per
8
+ * element); no I/O, no colour codes.
9
+ */
10
+ import type { HistoryInsights, SessionSearchResult } from '#src/history/historyStore.js';
11
+ /**
12
+ * Render FTS search hits: a header line per hit plus its snippet (or a prompt preview when the
13
+ * snippet is empty). Returns a friendly single line when there are none.
14
+ */
15
+ export declare function formatSearchResults(results: SessionSearchResult[]): string[];
16
+ /** Render a recent-sessions listing: one header + prompt-preview pair per session. */
17
+ export declare function formatHistoryList(results: SessionSearchResult[]): string[];
18
+ /** Render the analytics summary: totals, top tools, per-command breakdown. */
19
+ export declare function formatInsightsSummary(insights: HistoryInsights): string[];
@@ -0,0 +1,85 @@
1
+ /** Collapse whitespace and clip to `max` chars with an ellipsis, for one-line previews. */
2
+ function oneLine(text, max = 80) {
3
+ const s = (text ?? '').replace(/\s+/g, ' ').trim();
4
+ return s.length > max ? s.slice(0, max - 1) + '…' : s;
5
+ }
6
+ /** A compact `#id <ts> [command] model` header line for one session. */
7
+ function headerLine(r) {
8
+ const parts = [`#${r.id}`, r.ts];
9
+ if (r.command)
10
+ parts.push(`[${r.command}]`);
11
+ if (r.model)
12
+ parts.push(r.model);
13
+ return parts.join(' ');
14
+ }
15
+ /**
16
+ * Render FTS search hits: a header line per hit plus its snippet (or a prompt preview when the
17
+ * snippet is empty). Returns a friendly single line when there are none.
18
+ */
19
+ export function formatSearchResults(results) {
20
+ if (results.length === 0)
21
+ return ['No matching sessions found.'];
22
+ const lines = [];
23
+ for (const r of results) {
24
+ lines.push(headerLine(r));
25
+ const detail = r.snippet && r.snippet.trim().length > 0 ? r.snippet : r.prompt;
26
+ const preview = oneLine(detail, 100);
27
+ if (preview)
28
+ lines.push(` ${preview}`);
29
+ }
30
+ return lines;
31
+ }
32
+ /** Render a recent-sessions listing: one header + prompt-preview pair per session. */
33
+ export function formatHistoryList(results) {
34
+ if (results.length === 0) {
35
+ return [
36
+ 'No sessions recorded yet. Enable history with `history.enabled: true` in your config.',
37
+ ];
38
+ }
39
+ const lines = [];
40
+ for (const r of results) {
41
+ lines.push(headerLine(r));
42
+ const preview = oneLine(r.prompt, 100);
43
+ if (preview)
44
+ lines.push(` ${preview}`);
45
+ }
46
+ return lines;
47
+ }
48
+ /** Render the analytics summary: totals, top tools, per-command breakdown. */
49
+ export function formatInsightsSummary(insights) {
50
+ if (insights.sessionCount === 0) {
51
+ return [
52
+ 'No sessions recorded yet. Enable history with `history.enabled: true` in your config.',
53
+ ];
54
+ }
55
+ const lines = [];
56
+ lines.push(`Sessions: ${insights.sessionCount}`);
57
+ if (insights.firstTs && insights.lastTs) {
58
+ lines.push(`Span: ${insights.firstTs} → ${insights.lastTs}`);
59
+ }
60
+ // GS2-16: only surface the token/cost/top-tool lines when there is real data behind them.
61
+ // Older records (and providers that report no usage) leave these zero/empty; printing
62
+ // `Tokens: 0` / `$0.0000` / `(none recorded)` reads as "the run used nothing", which is
63
+ // misleading, so omit the line entirely instead. Sessions / Span / By-command always show.
64
+ if (insights.totalTokens > 0) {
65
+ lines.push(`Tokens: ${insights.totalTokens} total ` +
66
+ `(${insights.totalTokensInput} in / ${insights.totalTokensOutput} out)`);
67
+ }
68
+ // Cost is only ever recorded when a reliable price was available (the recorder never invents
69
+ // one), so a positive total is the signal that a cost line is meaningful.
70
+ if (insights.totalCostUsd > 0) {
71
+ lines.push(`Estimated cost: $${insights.totalCostUsd.toFixed(4)}`);
72
+ }
73
+ if (insights.perCommand.length > 0) {
74
+ lines.push('By command:');
75
+ for (const c of insights.perCommand)
76
+ lines.push(` ${c.command}: ${c.count}`);
77
+ }
78
+ if (insights.topTools.length > 0) {
79
+ lines.push('Top tools:');
80
+ for (const t of insights.topTools)
81
+ lines.push(` ${t.tool}: ${t.count}`);
82
+ }
83
+ return lines;
84
+ }
85
+ //# sourceMappingURL=historyFormat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"historyFormat.js","sourceRoot":"","sources":["../../src/history/historyFormat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /** Filename of the global history DB inside `~/.gsloth`. */
2
+ export declare const HISTORY_DB_FILENAME = "history.db";
3
+ /** A single persisted session record (all analytics fields optional; populated when available). */
4
+ export interface SessionRecord {
5
+ /** ISO-8601 timestamp; defaults to now when omitted. */
6
+ ts?: string;
7
+ /** Project / working directory the run happened in. */
8
+ project?: string;
9
+ /** Originating command (ask/chat/code/exec/…). */
10
+ command?: string;
11
+ /** Human-readable model/provider label. */
12
+ model?: string;
13
+ /** The user prompt / source that started the run (full-text indexed). */
14
+ prompt?: string;
15
+ /** The final assistant response text (full-text indexed). */
16
+ response?: string;
17
+ /** Prompt/input token count, when known. */
18
+ tokensInput?: number;
19
+ /** Completion/output token count, when known. */
20
+ tokensOutput?: number;
21
+ /** Estimated cost in USD, when known. */
22
+ costUsd?: number;
23
+ /** Names of tools invoked during the run, when known. */
24
+ tools?: string[];
25
+ /** Wall-clock duration of the run in milliseconds, when known. */
26
+ durationMs?: number;
27
+ }
28
+ /** A search hit: the stored record plus its id and a highlighted snippet. */
29
+ export interface SessionSearchResult extends SessionRecord {
30
+ id: number;
31
+ ts: string;
32
+ /** FTS5 snippet around the match (may be empty). */
33
+ snippet: string;
34
+ }
35
+ /** Aggregate analytics over the whole store (local only). */
36
+ export interface HistoryInsights {
37
+ sessionCount: number;
38
+ totalTokensInput: number;
39
+ totalTokensOutput: number;
40
+ totalTokens: number;
41
+ totalCostUsd: number;
42
+ /** Tool-name → invocation count, most-used first. */
43
+ topTools: {
44
+ tool: string;
45
+ count: number;
46
+ }[];
47
+ /** Command → run count, most-used first. */
48
+ perCommand: {
49
+ command: string;
50
+ count: number;
51
+ }[];
52
+ firstTs?: string;
53
+ lastTs?: string;
54
+ }
55
+ /** Options for opening a store. */
56
+ export interface OpenHistoryStoreOptions {
57
+ /**
58
+ * When false (the default for read-only callers), a missing DB file yields `null` instead of
59
+ * creating an empty database. Read commands pass `create: false` so `gth insights` never
60
+ * materialises a DB as a side effect; the recorder passes `create: true`.
61
+ */
62
+ create?: boolean;
63
+ }
64
+ /**
65
+ * Turn arbitrary user text into a safe FTS5 MATCH expression: each whitespace-separated token is
66
+ * wrapped as a quoted string and AND-ed together. This avoids FTS5 syntax errors from stray
67
+ * operators (`AND`, `*`, `:`, parentheses, unbalanced quotes) in a user's query while still
68
+ * matching all of their words.
69
+ */
70
+ export declare function toFtsMatchQuery(query: string): string;
71
+ /**
72
+ * A thin, fail-soft wrapper over a `node:sqlite` connection holding the session history.
73
+ *
74
+ * Obtain one via {@link openHistoryStore} (which returns `null` if the DB can't be opened). Every
75
+ * method is defensive: on any SQLite error it returns a safe empty/zero result rather than
76
+ * throwing, so callers on a run's hot path never have to guard.
77
+ */
78
+ export declare class HistoryStore {
79
+ private db;
80
+ private constructor();
81
+ /**
82
+ * Open (and lazily initialise) the store at `dbPath`. Returns `null` on any failure — a missing
83
+ * file when `create` is false, an unopenable/locked/corrupt DB, or a schema-init error — so the
84
+ * caller can simply skip history without a try/catch.
85
+ */
86
+ static open(dbPath: string, options?: OpenHistoryStoreOptions): HistoryStore | null;
87
+ private initSchema;
88
+ /**
89
+ * Persist one session and its full-text index entry. Returns the new row id, or `null` on any
90
+ * error (the run continues regardless). The two inserts run in a transaction so a failure can't
91
+ * leave the FTS index out of sync with the base table.
92
+ */
93
+ record(rec: SessionRecord): number | null;
94
+ /**
95
+ * Full-text search over prompt/response/command/project, best match first (FTS5 `rank`). User
96
+ * text is sanitised via {@link toFtsMatchQuery}; an empty or all-punctuation query returns `[]`.
97
+ * Any SQLite error yields `[]` (fail-soft).
98
+ */
99
+ search(query: string, limit?: number): SessionSearchResult[];
100
+ /** Most recent sessions, newest first. Fail-soft ([] on error). */
101
+ listRecent(limit?: number): SessionSearchResult[];
102
+ /**
103
+ * Aggregate token/cost totals, a top-tool tally, and a per-command breakdown over the whole
104
+ * store. Tool tallying reads each row's JSON `tools` array in JS (robust to nulls). Fail-soft:
105
+ * returns a zeroed summary on any error.
106
+ */
107
+ insights(topN?: number): HistoryInsights;
108
+ /** Close the underlying connection (fail-soft). */
109
+ close(): void;
110
+ }
111
+ /**
112
+ * Resolve the on-disk path of the history DB. Honors an explicit `dbPath` (from `history.dbPath`
113
+ * or a `--db` flag); otherwise the global `~/.gsloth/history.db`. When `dbPath` is omitted and
114
+ * `ensureDir` is true, the global dir is created so the recorder can write.
115
+ */
116
+ export declare function resolveHistoryDbPath(dbPath?: string, ensureDir?: boolean): string;
117
+ /**
118
+ * Fail-soft open of the history store. Returns `null` (never throws) when the DB can't be opened
119
+ * or, for read-only callers (`create: false`, the default), when the file does not yet exist.
120
+ */
121
+ export declare function openHistoryStore(dbPath: string, options?: OpenHistoryStoreOptions): HistoryStore | null;
@@ -0,0 +1,315 @@
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
+ try {
66
+ const db = new DatabaseSync(dbPath);
67
+ const store = new HistoryStore(db);
68
+ store.initSchema();
69
+ return store;
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ initSchema() {
76
+ this.db.exec(`
77
+ CREATE TABLE IF NOT EXISTS sessions (
78
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
79
+ ts TEXT NOT NULL,
80
+ project TEXT,
81
+ command TEXT,
82
+ model TEXT,
83
+ prompt TEXT,
84
+ response TEXT,
85
+ tokens_input INTEGER,
86
+ tokens_output INTEGER,
87
+ cost_usd REAL,
88
+ tools TEXT,
89
+ duration_ms INTEGER
90
+ );
91
+ CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts USING fts5(
92
+ prompt, response, command, project
93
+ );
94
+ `);
95
+ }
96
+ /**
97
+ * Persist one session and its full-text index entry. Returns the new row id, or `null` on any
98
+ * error (the run continues regardless). The two inserts run in a transaction so a failure can't
99
+ * leave the FTS index out of sync with the base table.
100
+ */
101
+ record(rec) {
102
+ try {
103
+ const ts = rec.ts ?? new Date().toISOString();
104
+ const tools = rec.tools && rec.tools.length > 0 ? JSON.stringify(rec.tools) : null;
105
+ this.db.exec('BEGIN');
106
+ try {
107
+ const insert = this.db.prepare(`INSERT INTO sessions
108
+ (ts, project, command, model, prompt, response,
109
+ tokens_input, tokens_output, cost_usd, tools, duration_ms)
110
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
111
+ 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);
112
+ const id = Number(info.lastInsertRowid);
113
+ this.db
114
+ .prepare(`INSERT INTO sessions_fts (rowid, prompt, response, command, project)
115
+ VALUES (?, ?, ?, ?, ?)`)
116
+ .run(id, rec.prompt ?? '', rec.response ?? '', rec.command ?? '', rec.project ?? '');
117
+ this.db.exec('COMMIT');
118
+ return id;
119
+ }
120
+ catch (e) {
121
+ this.db.exec('ROLLBACK');
122
+ throw e;
123
+ }
124
+ }
125
+ catch {
126
+ return null;
127
+ }
128
+ }
129
+ /**
130
+ * Full-text search over prompt/response/command/project, best match first (FTS5 `rank`). User
131
+ * text is sanitised via {@link toFtsMatchQuery}; an empty or all-punctuation query returns `[]`.
132
+ * Any SQLite error yields `[]` (fail-soft).
133
+ */
134
+ search(query, limit = 20) {
135
+ const match = toFtsMatchQuery(query);
136
+ if (!match)
137
+ return [];
138
+ try {
139
+ const rows = this.db
140
+ .prepare(`SELECT s.id AS id, s.ts AS ts, s.project AS project, s.command AS command,
141
+ s.model AS model, s.prompt AS prompt, s.response AS response,
142
+ s.tokens_input AS tokens_input, s.tokens_output AS tokens_output,
143
+ s.cost_usd AS cost_usd, s.tools AS tools, s.duration_ms AS duration_ms,
144
+ snippet(sessions_fts, 0, '[', ']', '…', 12) AS snippet
145
+ FROM sessions_fts f
146
+ JOIN sessions s ON s.id = f.rowid
147
+ WHERE sessions_fts MATCH ?
148
+ ORDER BY rank
149
+ LIMIT ?`)
150
+ .all(match, limit);
151
+ return rows.map((r) => ({
152
+ ...rowToRecord(r),
153
+ id: Number(r.id),
154
+ ts: String(r.ts),
155
+ snippet: String(r.snippet ?? ''),
156
+ }));
157
+ }
158
+ catch {
159
+ return [];
160
+ }
161
+ }
162
+ /** Most recent sessions, newest first. Fail-soft ([] on error). */
163
+ listRecent(limit = 20) {
164
+ try {
165
+ const rows = this.db
166
+ .prepare(`SELECT id, ts, project, command, model, prompt, response,
167
+ tokens_input, tokens_output, cost_usd, tools, duration_ms
168
+ FROM sessions
169
+ ORDER BY id DESC
170
+ LIMIT ?`)
171
+ .all(limit);
172
+ return rows.map((r) => ({
173
+ ...rowToRecord(r),
174
+ id: Number(r.id),
175
+ ts: String(r.ts),
176
+ snippet: '',
177
+ }));
178
+ }
179
+ catch {
180
+ return [];
181
+ }
182
+ }
183
+ /**
184
+ * Aggregate token/cost totals, a top-tool tally, and a per-command breakdown over the whole
185
+ * store. Tool tallying reads each row's JSON `tools` array in JS (robust to nulls). Fail-soft:
186
+ * returns a zeroed summary on any error.
187
+ */
188
+ insights(topN = 10) {
189
+ const empty = {
190
+ sessionCount: 0,
191
+ totalTokensInput: 0,
192
+ totalTokensOutput: 0,
193
+ totalTokens: 0,
194
+ totalCostUsd: 0,
195
+ topTools: [],
196
+ perCommand: [],
197
+ };
198
+ try {
199
+ const agg = this.db
200
+ .prepare(`SELECT COUNT(*) AS n,
201
+ COALESCE(SUM(tokens_input), 0) AS ti,
202
+ COALESCE(SUM(tokens_output), 0) AS to_,
203
+ COALESCE(SUM(cost_usd), 0) AS cost,
204
+ MIN(ts) AS first_ts,
205
+ MAX(ts) AS last_ts
206
+ FROM sessions`)
207
+ .get();
208
+ const perCommandRows = this.db
209
+ .prepare(`SELECT command, COUNT(*) AS n
210
+ FROM sessions
211
+ WHERE command IS NOT NULL AND command <> ''
212
+ GROUP BY command
213
+ ORDER BY n DESC, command ASC`)
214
+ .all();
215
+ const toolRows = this.db
216
+ .prepare(`SELECT tools FROM sessions WHERE tools IS NOT NULL AND tools <> ''`)
217
+ .all();
218
+ const toolCounts = new Map();
219
+ for (const row of toolRows) {
220
+ let names;
221
+ try {
222
+ names = JSON.parse(String(row.tools));
223
+ }
224
+ catch {
225
+ continue;
226
+ }
227
+ if (!Array.isArray(names))
228
+ continue;
229
+ for (const name of names) {
230
+ if (typeof name !== 'string' || name.length === 0)
231
+ continue;
232
+ toolCounts.set(name, (toolCounts.get(name) ?? 0) + 1);
233
+ }
234
+ }
235
+ const topTools = [...toolCounts.entries()]
236
+ .map(([tool, count]) => ({ tool, count }))
237
+ .sort((a, b) => b.count - a.count || a.tool.localeCompare(b.tool))
238
+ .slice(0, topN);
239
+ const ti = Number(agg.ti ?? 0);
240
+ const to = Number(agg.to_ ?? 0);
241
+ return {
242
+ sessionCount: Number(agg.n ?? 0),
243
+ totalTokensInput: ti,
244
+ totalTokensOutput: to,
245
+ totalTokens: ti + to,
246
+ totalCostUsd: Number(agg.cost ?? 0),
247
+ topTools,
248
+ perCommand: perCommandRows.map((r) => ({
249
+ command: String(r.command),
250
+ count: Number(r.n),
251
+ })),
252
+ firstTs: agg.first_ts ? String(agg.first_ts) : undefined,
253
+ lastTs: agg.last_ts ? String(agg.last_ts) : undefined,
254
+ };
255
+ }
256
+ catch {
257
+ return empty;
258
+ }
259
+ }
260
+ /** Close the underlying connection (fail-soft). */
261
+ close() {
262
+ try {
263
+ this.db.close();
264
+ }
265
+ catch {
266
+ /* ignore */
267
+ }
268
+ }
269
+ }
270
+ /** Map a raw DB row (snake_case columns) to a {@link SessionRecord}. */
271
+ function rowToRecord(r) {
272
+ let tools;
273
+ if (r.tools != null && r.tools !== '') {
274
+ try {
275
+ const parsed = JSON.parse(String(r.tools));
276
+ if (Array.isArray(parsed))
277
+ tools = parsed.filter((t) => typeof t === 'string');
278
+ }
279
+ catch {
280
+ /* ignore malformed tools JSON */
281
+ }
282
+ }
283
+ return {
284
+ ts: r.ts != null ? String(r.ts) : undefined,
285
+ project: r.project != null ? String(r.project) : undefined,
286
+ command: r.command != null ? String(r.command) : undefined,
287
+ model: r.model != null ? String(r.model) : undefined,
288
+ prompt: r.prompt != null ? String(r.prompt) : undefined,
289
+ response: r.response != null ? String(r.response) : undefined,
290
+ tokensInput: r.tokens_input != null ? Number(r.tokens_input) : undefined,
291
+ tokensOutput: r.tokens_output != null ? Number(r.tokens_output) : undefined,
292
+ costUsd: r.cost_usd != null ? Number(r.cost_usd) : undefined,
293
+ tools,
294
+ durationMs: r.duration_ms != null ? Number(r.duration_ms) : undefined,
295
+ };
296
+ }
297
+ /**
298
+ * Resolve the on-disk path of the history DB. Honors an explicit `dbPath` (from `history.dbPath`
299
+ * or a `--db` flag); otherwise the global `~/.gsloth/history.db`. When `dbPath` is omitted and
300
+ * `ensureDir` is true, the global dir is created so the recorder can write.
301
+ */
302
+ export function resolveHistoryDbPath(dbPath, ensureDir = false) {
303
+ if (dbPath && dbPath.trim().length > 0)
304
+ return dbPath;
305
+ const dir = ensureDir ? ensureGlobalGslothDir() : getGlobalGslothDir();
306
+ return resolve(dir, HISTORY_DB_FILENAME);
307
+ }
308
+ /**
309
+ * Fail-soft open of the history store. Returns `null` (never throws) when the DB can't be opened
310
+ * or, for read-only callers (`create: false`, the default), when the file does not yet exist.
311
+ */
312
+ export function openHistoryStore(dbPath, options = {}) {
313
+ return HistoryStore.open(dbPath, options);
314
+ }
315
+ //# 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,28 @@
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 { 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;
@@ -0,0 +1,29 @@
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
+ //# 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"}
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"}