@gaunt-sloth/core 2.0.0-alpha.6 → 2.0.0-alpha.8
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.
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/jsonc.d.ts +12 -0
- package/dist/config/jsonc.js +41 -0
- package/dist/config/jsonc.js.map +1 -0
- package/dist/config/loader.d.ts +21 -0
- package/dist/config/loader.js +62 -3
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +32 -0
- package/dist/config/schema.js +40 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/config/types.d.ts +21 -0
- package/dist/config/types.js.map +1 -1
- package/dist/constants.d.ts +18 -0
- package/dist/constants.js +18 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +25 -1
- package/dist/core/GthAbstractAgent.js +77 -0
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +23 -1
- package/dist/core/GthAgentRunner.js +52 -0
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.js +58 -5
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/runStats.d.ts +41 -0
- package/dist/core/runStats.js +73 -0
- package/dist/core/runStats.js.map +1 -0
- package/dist/core/shell/ShellCommandFailedError.d.ts +54 -0
- package/dist/core/shell/ShellCommandFailedError.js +68 -0
- package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
- package/dist/core/types.d.ts +29 -0
- package/dist/history/historyFormat.d.ts +19 -0
- package/dist/history/historyFormat.js +85 -0
- package/dist/history/historyFormat.js.map +1 -0
- package/dist/history/historyStore.d.ts +121 -0
- package/dist/history/historyStore.js +315 -0
- package/dist/history/historyStore.js.map +1 -0
- package/dist/history/recordSession.d.ts +28 -0
- package/dist/history/recordSession.js +29 -0
- package/dist/history/recordSession.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +4 -9
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/deepseek.d.ts +1 -1
- package/dist/providers/deepseek.js +4 -9
- package/dist/providers/deepseek.js.map +1 -1
- package/dist/providers/google-genai.d.ts +1 -1
- package/dist/providers/google-genai.js +4 -9
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/groq.d.ts +1 -1
- package/dist/providers/groq.js +4 -9
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/modelDiscovery.d.ts +54 -1
- package/dist/providers/modelDiscovery.js +106 -8
- package/dist/providers/modelDiscovery.js.map +1 -1
- package/dist/providers/ollama.d.ts +1 -1
- package/dist/providers/ollama.js +4 -14
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +4 -9
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +4 -9
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.d.ts +1 -1
- package/dist/providers/vertexai.js +4 -9
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +4 -9
- package/dist/providers/xai.js.map +1 -1
- package/dist/runtime/singleShot.js +31 -1
- package/dist/runtime/singleShot.js.map +1 -1
- package/package.json +2 -1
- package/schema/gsloth-config.schema.json +19 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/ShellCommandFailedError
|
|
3
|
+
*
|
|
4
|
+
* The typed error a `run_*` shell/dev tool throws when a command did NOT exit cleanly
|
|
5
|
+
* (non-zero exit code, or killed for exceeding the timeout). It carries the FULL model-facing
|
|
6
|
+
* body so a softening middleware can hand the model the exact observation it saw before — the
|
|
7
|
+
* only change being that the tool result's status flips to `'error'` (→ `isError` → the ✗ glyph).
|
|
8
|
+
*
|
|
9
|
+
* Canonical home is **core** so BOTH agents can recognise a shell failure without violating the
|
|
10
|
+
* `agent → core` dependency direction:
|
|
11
|
+
* - the deep agent (`code` mode, `@gaunt-sloth/agent` `GthDeepShellExitSoftening`) and
|
|
12
|
+
* - the lean agent (`exec` / `ask --write`, core `GthLangChainAgent` `GthLeanShellExitSoftening`).
|
|
13
|
+
*
|
|
14
|
+
* The throw site (`GthDevToolkit.executeCommand`) lives in the `agent` package and re-exports this
|
|
15
|
+
* class, so its `throw new ShellCommandFailedError(...)` is one and the same type both agents catch.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A `run_*` command that did NOT exit cleanly (non-zero exit code, or was killed for exceeding the
|
|
19
|
+
* timeout). Carries the FULL model-facing body text ({@link output}) so a softening middleware can
|
|
20
|
+
* hand the model the exact same observation it saw before — the only change is the tool result's
|
|
21
|
+
* status flips to `'error'`, which drives the ✗ (`isError`) glyph.
|
|
22
|
+
*
|
|
23
|
+
* `executeCommand` previously `resolve()`d on a non-zero exit, so the LangChain `ToolMessage` stayed
|
|
24
|
+
* `status: 'success'` and every failure rendered a ✓. Throwing this typed error instead lets each
|
|
25
|
+
* agent's softening middleware convert it into an error `ToolMessage`. A clean exit (`code === 0`)
|
|
26
|
+
* still `resolve()`s; a spawn-level `child.on('error')` still rejects with a plain `Error`.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ShellCommandFailedError extends Error {
|
|
29
|
+
/** The full model-facing body (command echo + `<COMMAND_OUTPUT>` + the failure/timeout tail). */
|
|
30
|
+
readonly output: string;
|
|
31
|
+
/** The process exit code; `null` when the command was killed (timeout) and never exited cleanly. */
|
|
32
|
+
readonly exitCode: number | null;
|
|
33
|
+
/** The exact command string that was executed. */
|
|
34
|
+
readonly command: string;
|
|
35
|
+
/** The run_* tool name that invoked the command (e.g. `run_tests`, `run_shell_command`). */
|
|
36
|
+
readonly toolName: string;
|
|
37
|
+
constructor(params: {
|
|
38
|
+
output: string;
|
|
39
|
+
exitCode: number | null;
|
|
40
|
+
command: string;
|
|
41
|
+
toolName: string;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Recognise a {@link ShellCommandFailedError} for the softening middleware in either agent.
|
|
46
|
+
*
|
|
47
|
+
* Prefers a plain `instanceof` (both agents share this one core module, so the class identity is
|
|
48
|
+
* the same), but falls back to a STRUCTURAL check keyed on `name === 'ShellCommandFailedError'`
|
|
49
|
+
* plus the carried fields. The structural arm is deliberate defence against a dual-package /
|
|
50
|
+
* realm-boundary hazard: if the error ever crossed a module boundary that broke `instanceof`, we
|
|
51
|
+
* would otherwise silently rethrow a real shell failure and regress the ✗ signal. Every field the
|
|
52
|
+
* softener reads (`output`) is asserted so a narrowed value is safe to use.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isShellCommandFailedError(e: unknown): e is ShellCommandFailedError;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/ShellCommandFailedError
|
|
3
|
+
*
|
|
4
|
+
* The typed error a `run_*` shell/dev tool throws when a command did NOT exit cleanly
|
|
5
|
+
* (non-zero exit code, or killed for exceeding the timeout). It carries the FULL model-facing
|
|
6
|
+
* body so a softening middleware can hand the model the exact observation it saw before — the
|
|
7
|
+
* only change being that the tool result's status flips to `'error'` (→ `isError` → the ✗ glyph).
|
|
8
|
+
*
|
|
9
|
+
* Canonical home is **core** so BOTH agents can recognise a shell failure without violating the
|
|
10
|
+
* `agent → core` dependency direction:
|
|
11
|
+
* - the deep agent (`code` mode, `@gaunt-sloth/agent` `GthDeepShellExitSoftening`) and
|
|
12
|
+
* - the lean agent (`exec` / `ask --write`, core `GthLangChainAgent` `GthLeanShellExitSoftening`).
|
|
13
|
+
*
|
|
14
|
+
* The throw site (`GthDevToolkit.executeCommand`) lives in the `agent` package and re-exports this
|
|
15
|
+
* class, so its `throw new ShellCommandFailedError(...)` is one and the same type both agents catch.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A `run_*` command that did NOT exit cleanly (non-zero exit code, or was killed for exceeding the
|
|
19
|
+
* timeout). Carries the FULL model-facing body text ({@link output}) so a softening middleware can
|
|
20
|
+
* hand the model the exact same observation it saw before — the only change is the tool result's
|
|
21
|
+
* status flips to `'error'`, which drives the ✗ (`isError`) glyph.
|
|
22
|
+
*
|
|
23
|
+
* `executeCommand` previously `resolve()`d on a non-zero exit, so the LangChain `ToolMessage` stayed
|
|
24
|
+
* `status: 'success'` and every failure rendered a ✓. Throwing this typed error instead lets each
|
|
25
|
+
* agent's softening middleware convert it into an error `ToolMessage`. A clean exit (`code === 0`)
|
|
26
|
+
* still `resolve()`s; a spawn-level `child.on('error')` still rejects with a plain `Error`.
|
|
27
|
+
*/
|
|
28
|
+
export class ShellCommandFailedError extends Error {
|
|
29
|
+
/** The full model-facing body (command echo + `<COMMAND_OUTPUT>` + the failure/timeout tail). */
|
|
30
|
+
output;
|
|
31
|
+
/** The process exit code; `null` when the command was killed (timeout) and never exited cleanly. */
|
|
32
|
+
exitCode;
|
|
33
|
+
/** The exact command string that was executed. */
|
|
34
|
+
command;
|
|
35
|
+
/** The run_* tool name that invoked the command (e.g. `run_tests`, `run_shell_command`). */
|
|
36
|
+
toolName;
|
|
37
|
+
constructor(params) {
|
|
38
|
+
// Use the full body as the Error message so any generic logger/handler still surfaces the
|
|
39
|
+
// real command output rather than an opaque wrapper string.
|
|
40
|
+
super(params.output);
|
|
41
|
+
this.name = 'ShellCommandFailedError';
|
|
42
|
+
this.output = params.output;
|
|
43
|
+
this.exitCode = params.exitCode;
|
|
44
|
+
this.command = params.command;
|
|
45
|
+
this.toolName = params.toolName;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Recognise a {@link ShellCommandFailedError} for the softening middleware in either agent.
|
|
50
|
+
*
|
|
51
|
+
* Prefers a plain `instanceof` (both agents share this one core module, so the class identity is
|
|
52
|
+
* the same), but falls back to a STRUCTURAL check keyed on `name === 'ShellCommandFailedError'`
|
|
53
|
+
* plus the carried fields. The structural arm is deliberate defence against a dual-package /
|
|
54
|
+
* realm-boundary hazard: if the error ever crossed a module boundary that broke `instanceof`, we
|
|
55
|
+
* would otherwise silently rethrow a real shell failure and regress the ✗ signal. Every field the
|
|
56
|
+
* softener reads (`output`) is asserted so a narrowed value is safe to use.
|
|
57
|
+
*/
|
|
58
|
+
export function isShellCommandFailedError(e) {
|
|
59
|
+
if (e instanceof ShellCommandFailedError)
|
|
60
|
+
return true;
|
|
61
|
+
return (typeof e === 'object' &&
|
|
62
|
+
e !== null &&
|
|
63
|
+
e.name === 'ShellCommandFailedError' &&
|
|
64
|
+
typeof e.output === 'string' &&
|
|
65
|
+
typeof e.command === 'string' &&
|
|
66
|
+
typeof e.toolName === 'string');
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=ShellCommandFailedError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShellCommandFailedError.js","sourceRoot":"","sources":["../../../src/core/shell/ShellCommandFailedError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD,iGAAiG;IACxF,MAAM,CAAS;IACxB,oGAAoG;IAC3F,QAAQ,CAAgB;IACjC,kDAAkD;IACzC,OAAO,CAAS;IACzB,4FAA4F;IACnF,QAAQ,CAAS;IAE1B,YAAY,MAKX;QACC,0FAA0F;QAC1F,4DAA4D;QAC5D,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IAClC,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CAAC,CAAU;IAClD,IAAI,CAAC,YAAY,uBAAuB;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,CACL,OAAO,CAAC,KAAK,QAAQ;QACrB,CAAC,KAAK,IAAI;QACT,CAAwB,CAAC,IAAI,KAAK,yBAAyB;QAC5D,OAAQ,CAA0B,CAAC,MAAM,KAAK,QAAQ;QACtD,OAAQ,CAA2B,CAAC,OAAO,KAAK,QAAQ;QACxD,OAAQ,CAA4B,CAAC,QAAQ,KAAK,QAAQ,CAC3D,CAAC;AACJ,CAAC"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -22,6 +22,23 @@ export declare enum StatusLevel {
|
|
|
22
22
|
STREAM = 6
|
|
23
23
|
}
|
|
24
24
|
export type GthCommand = 'ask' | 'pr' | 'review' | 'chat' | 'code' | 'api' | 'exec';
|
|
25
|
+
/**
|
|
26
|
+
* GS2-16 — per-run analytics harvested from a finished agent turn, threaded into the opt-in
|
|
27
|
+
* history recorder ({@link recordSessionSafe}) so `gth insights` reports real numbers instead of
|
|
28
|
+
* zeros. All fields are best-effort: token counts are only present when the provider actually
|
|
29
|
+
* reported `usage_metadata` (otherwise omitted so the recorder stores NULL and the insights
|
|
30
|
+
* formatter suppresses the misleading `0`), and `tools` lists the names of tools invoked during
|
|
31
|
+
* the run (deduplicated, order-insensitive). There is no `costUsd` — cost requires a reliable
|
|
32
|
+
* price table this project does not carry, so it is deliberately never invented here.
|
|
33
|
+
*/
|
|
34
|
+
export interface GthRunStats {
|
|
35
|
+
/** Total prompt/input tokens across the run's LLM calls, when the provider reported usage. */
|
|
36
|
+
tokensInput?: number;
|
|
37
|
+
/** Total completion/output tokens across the run's LLM calls, when the provider reported usage. */
|
|
38
|
+
tokensOutput?: number;
|
|
39
|
+
/** Names of tools invoked during the run (deduplicated); empty when no tools were used. */
|
|
40
|
+
tools: string[];
|
|
41
|
+
}
|
|
25
42
|
/**
|
|
26
43
|
* Typed events emitted by the agent's {@link GthAgentInterface#streamWithEvents} path.
|
|
27
44
|
* This is the renderer contract shared by every consumer of an agent run — the AG-UI
|
|
@@ -155,6 +172,18 @@ export interface GthAgentInterface {
|
|
|
155
172
|
* approve/reject confirmation loop.
|
|
156
173
|
*/
|
|
157
174
|
getPendingToolInterrupts?(runConfig: RunnableConfig): Promise<PendingToolInterrupt[]>;
|
|
175
|
+
/**
|
|
176
|
+
* GS2-16 — reset the per-run analytics accumulator so the NEXT turn's token/tool totals start
|
|
177
|
+
* from zero. Called by {@link GthAgentRunner} at each turn boundary (the runner is reused across
|
|
178
|
+
* turns in interactive sessions). Optional: agents that don't collect stats simply omit it.
|
|
179
|
+
*/
|
|
180
|
+
resetRunStats?(): void;
|
|
181
|
+
/**
|
|
182
|
+
* GS2-16 — the analytics harvested from the run(s) since the last {@link resetRunStats}. Used by
|
|
183
|
+
* the runner to thread token/tool data into the opt-in history recorder. Optional; when absent
|
|
184
|
+
* the runner records no analytics for that turn. Reading must never throw.
|
|
185
|
+
*/
|
|
186
|
+
getRunStats?(): GthRunStats;
|
|
158
187
|
cleanup?(): Promise<void>;
|
|
159
188
|
}
|
|
160
189
|
/**
|
|
@@ -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;
|