@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
@@ -1,4 +1,5 @@
1
1
  import type { GthConfig } from '#src/config.js';
2
+ import type { ShellSafetyVerdict } from '#src/core/shell/judge.js';
2
3
  import type { BaseMessage } from '@langchain/core/messages';
3
4
  import type { RunnableConfig } from '@langchain/core/runnables';
4
5
  import type { StructuredToolInterface } from '@langchain/core/tools';
@@ -21,6 +22,23 @@ export declare enum StatusLevel {
21
22
  STREAM = 6
22
23
  }
23
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
+ }
24
42
  /**
25
43
  * Typed events emitted by the agent's {@link GthAgentInterface#streamWithEvents} path.
26
44
  * This is the renderer contract shared by every consumer of an agent run — the AG-UI
@@ -48,10 +66,45 @@ export type AgentStreamEvent = {
48
66
  } | {
49
67
  type: 'tool_end';
50
68
  id: string;
69
+ } | {
70
+ /**
71
+ * TUI-C17 — one live output chunk from an EXECUTING tool (a custom/dev toolkit child
72
+ * process's stdout/stderr, or its "Executing …" announcement), surfaced through the managed
73
+ * event stream instead of raw `process.stdout` so a renderer (the Ink TUI) can fold it into
74
+ * its view-model. Emitted by the tool-output channel merge
75
+ * (see `core/toolOutputChannel.js#mergeToolOutputIntoEvents`), NOT by `processEventStream`
76
+ * itself — consumers that don't opt into the merge (e.g. the AG-UI SSE encoder) never see it
77
+ * and the toolkits keep writing to stdout for them (today's headless behaviour).
78
+ */
79
+ type: 'tool_output';
80
+ /**
81
+ * The tool call this chunk belongs to (LangChain's `ToolRunnableConfig.toolCall.id`,
82
+ * threaded through the toolkits), so a renderer can nest output under the exact call —
83
+ * TUI-C30 consumes this for per-call output previews. Optional only defensively: absent
84
+ * when the executing framework did not supply a tool call, in which case consumers should
85
+ * fall back to `name` attribution.
86
+ */
87
+ id?: string;
88
+ /** The gth tool name (e.g. `run_shell_command`, a custom tool's name). Always known. */
89
+ name: string;
90
+ /** One verbatim streamed chunk of the child's stdout/stderr (or the notice text). */
91
+ chunk: string;
92
+ /**
93
+ * True when this chunk is the "🔧 Executing …" announcement rather than child output, so
94
+ * a richer renderer (TUI-C30) can style or strip it when previewing raw output lines.
95
+ */
96
+ isNotice?: boolean;
51
97
  } | {
52
98
  type: 'tool_result';
53
99
  id: string;
54
100
  content: string;
101
+ /**
102
+ * True when the underlying `ToolMessage.status` is `'error'` (LangChain's real
103
+ * tool-result error signal). Absent/undefined means success — consumers must not
104
+ * sniff the result text to infer failure. Optional for backward compatibility with
105
+ * producers that predate the field.
106
+ */
107
+ isError?: boolean;
55
108
  };
56
109
  /**
57
110
  * The minimal structural surface of a compiled LangGraph agent that the shared agent
@@ -66,7 +119,61 @@ export interface GthCompiledGraph {
66
119
  messages: BaseMessage[];
67
120
  }>;
68
121
  stream(input: any, config?: any): Promise<IterableReadableStream<any>>;
122
+ /**
123
+ * Read the checkpointed graph state for a thread. Present on LangGraph compiled graphs
124
+ * (both `createAgent` and `createDeepAgent`); used to detect a graph suspended on a
125
+ * human-in-the-loop `interrupt()` (its pending {@link PendingToolInterrupt} lives in
126
+ * `state.tasks[].interrupts[].value`). Optional because the structural surface predates it.
127
+ */
128
+ getState?(config: RunnableConfig): Promise<any>;
129
+ }
130
+ /**
131
+ * A single tool call a human-in-the-loop interrupt is waiting on, surfaced from the
132
+ * suspended graph state so a consumer (the interactive session) can render an approve/reject
133
+ * prompt. Mirrors LangChain's HITL `ActionRequest` (tool name + the args it would run with).
134
+ */
135
+ export interface PendingToolInterrupt {
136
+ name: string;
137
+ args: Record<string, unknown>;
138
+ /**
139
+ * EXT-10 — when the LLM-as-judge safety gate escalated this `run_shell_command` to the human
140
+ * (rather than auto-approving it), the judge's verdict is attached here so the approval surface
141
+ * can show a "safety judge flagged: <reason>" notice. Absent when the judge is disabled (the
142
+ * default) or when the command reached the human without going through the judge.
143
+ */
144
+ safetyVerdict?: ShellSafetyVerdict;
69
145
  }
146
+ /**
147
+ * Persistence scope for an `approve` decision (EXT-9 Tier-2 allow-list ergonomics):
148
+ * - `once` — run this single invocation only; remember nothing (the default).
149
+ * - `session` — remember the command's classified prefix for the life of this runner
150
+ * instance, so flag-variants of the same operation auto-approve without re-prompting.
151
+ * - `always` — additionally persist the prefix to the project allow-list
152
+ * (`.gsloth/.gsloth-settings/shell-allowlist.json`) so it survives across runs.
153
+ */
154
+ export type ToolApprovalScope = 'once' | 'session' | 'always';
155
+ /**
156
+ * A consumer-supplied decision on a {@link PendingToolInterrupt}: approve runs the tool,
157
+ * reject feeds the model a tool-rejected message (with the optional reason).
158
+ *
159
+ * `approve` carries an optional {@link ToolApprovalScope}; when absent it means `once`
160
+ * (backward compatible — a bare `{ type: 'approve' }` still type-checks and behaves as
161
+ * a single-shot approval that persists nothing).
162
+ */
163
+ export type ToolApprovalDecision = {
164
+ type: 'approve';
165
+ scope?: ToolApprovalScope;
166
+ } | {
167
+ type: 'reject';
168
+ message?: string;
169
+ };
170
+ /**
171
+ * Callback the {@link GthAgentRunner} invokes when a run suspends on a tool-approval
172
+ * interrupt, once per pending tool call. Returns the human's decision. When no handler is
173
+ * wired (e.g. a non-interactive run), the runner defaults to reject so a run can never
174
+ * silently hang or auto-approve.
175
+ */
176
+ export type ToolApprovalCallback = (pending: PendingToolInterrupt) => Promise<ToolApprovalDecision> | ToolApprovalDecision;
70
177
  export interface GthAgentInterface {
71
178
  init(command: GthCommand | undefined, configIn: GthConfig, checkpointSaver?: BaseCheckpointSaver | undefined): Promise<void>;
72
179
  invoke(messages: Message[], runConfig: RunnableConfig): Promise<string>;
@@ -80,6 +187,31 @@ export interface GthAgentInterface {
80
187
  streamWithEvents(messages: Message[], runConfig: RunnableConfig, signal?: AbortSignal): AsyncGenerator<AgentStreamEvent>;
81
188
  /** Resume a graph suspended via `interrupt()` with the supplied value. */
82
189
  streamWithEventsResume(resumeValue: unknown, runConfig: RunnableConfig, queuedMessages?: BaseMessage[], signal?: AbortSignal): AsyncGenerator<AgentStreamEvent>;
190
+ /**
191
+ * Resume a graph suspended on a human-in-the-loop `interrupt()` and stream the continuation
192
+ * as text (the string counterpart to {@link streamWithEventsResume}, for the readline path).
193
+ * Optional: only implemented by agents that support tool-approval interrupts.
194
+ */
195
+ streamResume?(resumeValue: unknown, runConfig: RunnableConfig): Promise<IterableReadableStream<string>>;
196
+ /**
197
+ * Inspect the checkpointed state for the thread and return any tool calls currently pending
198
+ * human approval (empty when the run completed normally). Optional: only implemented by
199
+ * agents whose graph exposes `getState`. Used by {@link GthAgentRunner} to drive the
200
+ * approve/reject confirmation loop.
201
+ */
202
+ getPendingToolInterrupts?(runConfig: RunnableConfig): Promise<PendingToolInterrupt[]>;
203
+ /**
204
+ * GS2-16 — reset the per-run analytics accumulator so the NEXT turn's token/tool totals start
205
+ * from zero. Called by {@link GthAgentRunner} at each turn boundary (the runner is reused across
206
+ * turns in interactive sessions). Optional: agents that don't collect stats simply omit it.
207
+ */
208
+ resetRunStats?(): void;
209
+ /**
210
+ * GS2-16 — the analytics harvested from the run(s) since the last {@link resetRunStats}. Used by
211
+ * the runner to thread token/tool data into the opt-in history recorder. Optional; when absent
212
+ * the runner records no analytics for that turn. Reading must never throw.
213
+ */
214
+ getRunStats?(): GthRunStats;
83
215
  cleanup?(): Promise<void>;
84
216
  }
85
217
  /**
@@ -93,9 +225,50 @@ export type ToolsResolver = (config: GthConfig, command?: GthCommand) => Promise
93
225
  export type ToolsCleanup = () => Promise<void>;
94
226
  export type MiddlewareResolver = (middleware: any[] | undefined, config: GthConfig) => Promise<any[]>;
95
227
  export type MiddlewareCleanup = () => Promise<void>;
228
+ /**
229
+ * EXT-32 — one connected MCP server's discovery `instructions` string (from its MCP `initialize`
230
+ * handshake), paired with the server name it came from. Captured once during tool resolution and
231
+ * reused: injected (fenced + per-server-labelled) into the composed system prompt, and available
232
+ * for [[TUI-C20]]'s MCP debug tab to render the same captured text. Only servers that actually
233
+ * supplied non-empty instructions appear here.
234
+ */
235
+ export interface McpServerInstruction {
236
+ /** The configured MCP server name (the key under `config.mcpServers`). */
237
+ server: string;
238
+ /** The server-provided instructions text (trimmed, non-empty). */
239
+ instructions: string;
240
+ }
241
+ /**
242
+ * A per-server MCP connection failure captured during the most recent {@link ToolsResolver} call.
243
+ * Recorded when a configured MCP server can't be reached (connection/handshake/auth error), so the
244
+ * failure — otherwise a transient `displayWarning` that scrolls away the moment the Ink TUI takes
245
+ * over the screen — can be re-surfaced persistently in the chrome AND named in the /debug MCP tab
246
+ * (which renders per configured server and would otherwise show only a bare "no tools" line, with
247
+ * no hint that the server never connected). Mirrors {@link McpServerInstruction}.
248
+ */
249
+ export interface McpConnectionFailure {
250
+ /** The configured MCP server name (the key under `config.mcpServers`). */
251
+ server: string;
252
+ /** A concise, human-readable reason (the underlying connection error's message). */
253
+ reason: string;
254
+ }
96
255
  export interface AgentResolvers {
97
256
  resolveTools?: ToolsResolver;
98
257
  cleanupTools?: ToolsCleanup;
99
258
  resolveMiddleware?: MiddlewareResolver;
100
259
  cleanupMiddleware?: MiddlewareCleanup;
260
+ /**
261
+ * EXT-32 — the per-server MCP discovery instructions captured during the most recent
262
+ * {@link ToolsResolver} call (empty when no MCP servers are configured or none supplied
263
+ * instructions). Optional: resolvers without MCP support simply omit it, and the prompt
264
+ * composition treats an absent accessor as "no instructions" (no MCP section is emitted).
265
+ */
266
+ getMcpServerInstructions?(): McpServerInstruction[];
267
+ /**
268
+ * The per-server MCP connection failures captured during the most recent {@link ToolsResolver}
269
+ * call (empty when every configured server connected, or none is configured). Optional: resolvers
270
+ * without MCP support omit it, and callers treat an absent accessor as "no failures". Read by the
271
+ * TUI to surface a persistent notice and to annotate the /debug MCP tab.
272
+ */
273
+ getMcpConnectionFailures?(): McpConnectionFailure[];
101
274
  }
@@ -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,28 @@
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 { ConversationSummary, HistoryInsights, SessionRecord, 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
+ /**
17
+ * GS2-19 — render a conversation-grained listing: one header + last-turn preview per conversation.
18
+ * The header carries the count / timespan / last message that make the conversation the top-level
19
+ * unit (`gth history list`), replacing the old flat per-turn list.
20
+ */
21
+ export declare function formatConversationList(conversations: ConversationSummary[]): string[];
22
+ /**
23
+ * GS2-19 — render one conversation's full thread (all turns in order) for `gth history show <id>`.
24
+ * Each turn shows its prompt and response preview so a search hit can be expanded into context.
25
+ */
26
+ export declare function formatConversationThread(turns: SessionRecord[]): string[];
27
+ /** Render the analytics summary: totals, top tools, per-command breakdown. */
28
+ export declare function formatInsightsSummary(insights: HistoryInsights): string[];
@@ -0,0 +1,127 @@
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 (conversation #cid)` header line for one turn. */
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
+ // GS2-19: a search hit resolves to the conversation it belongs to, so the reader can pull up the
14
+ // whole thread (`gth history show <cid>`). Older rows migrated from GS2-7 always have one now.
15
+ if (r.conversationId != null)
16
+ parts.push(`(conversation #${r.conversationId})`);
17
+ return parts.join(' ');
18
+ }
19
+ /**
20
+ * Render FTS search hits: a header line per hit plus its snippet (or a prompt preview when the
21
+ * snippet is empty). Returns a friendly single line when there are none.
22
+ */
23
+ export function formatSearchResults(results) {
24
+ if (results.length === 0)
25
+ return ['No matching sessions found.'];
26
+ const lines = [];
27
+ for (const r of results) {
28
+ lines.push(headerLine(r));
29
+ const detail = r.snippet && r.snippet.trim().length > 0 ? r.snippet : r.prompt;
30
+ const preview = oneLine(detail, 100);
31
+ if (preview)
32
+ lines.push(` ${preview}`);
33
+ }
34
+ return lines;
35
+ }
36
+ /**
37
+ * GS2-19 — render a conversation-grained listing: one header + last-turn preview per conversation.
38
+ * The header carries the count / timespan / last message that make the conversation the top-level
39
+ * unit (`gth history list`), replacing the old flat per-turn list.
40
+ */
41
+ export function formatConversationList(conversations) {
42
+ if (conversations.length === 0) {
43
+ return [
44
+ 'No conversations recorded yet. Enable history with `history.enabled: true` in your config.',
45
+ ];
46
+ }
47
+ const lines = [];
48
+ for (const c of conversations) {
49
+ const parts = [`#${c.id}`];
50
+ // Timespan across the conversation's turns; a 1-turn (or not-yet-started) conversation collapses
51
+ // to a single instant, so show one timestamp rather than an `a → a` range.
52
+ if (c.firstTs && c.lastTs && c.firstTs !== c.lastTs) {
53
+ parts.push(`${c.firstTs} → ${c.lastTs}`);
54
+ }
55
+ else {
56
+ parts.push(c.lastTs ?? c.firstTs ?? c.startedTs);
57
+ }
58
+ if (c.command)
59
+ parts.push(`[${c.command}]`);
60
+ if (c.model)
61
+ parts.push(c.model);
62
+ parts.push(`(${c.turnCount} ${c.turnCount === 1 ? 'turn' : 'turns'})`);
63
+ lines.push(parts.join(' '));
64
+ const preview = oneLine(c.lastPrompt, 100);
65
+ if (preview)
66
+ lines.push(` ${preview}`);
67
+ }
68
+ return lines;
69
+ }
70
+ /**
71
+ * GS2-19 — render one conversation's full thread (all turns in order) for `gth history show <id>`.
72
+ * Each turn shows its prompt and response preview so a search hit can be expanded into context.
73
+ */
74
+ export function formatConversationThread(turns) {
75
+ if (turns.length === 0)
76
+ return ['No turns found for that conversation.'];
77
+ const lines = [];
78
+ turns.forEach((t, i) => {
79
+ const header = t.ts ? `Turn ${i + 1} ${t.ts}` : `Turn ${i + 1}`;
80
+ lines.push(header);
81
+ const prompt = oneLine(t.prompt, 200);
82
+ if (prompt)
83
+ lines.push(` > ${prompt}`);
84
+ const response = oneLine(t.response, 200);
85
+ if (response)
86
+ lines.push(` ${response}`);
87
+ });
88
+ return lines;
89
+ }
90
+ /** Render the analytics summary: totals, top tools, per-command breakdown. */
91
+ export function formatInsightsSummary(insights) {
92
+ if (insights.sessionCount === 0) {
93
+ return [
94
+ 'No sessions recorded yet. Enable history with `history.enabled: true` in your config.',
95
+ ];
96
+ }
97
+ const lines = [];
98
+ lines.push(`Sessions: ${insights.sessionCount}`);
99
+ if (insights.firstTs && insights.lastTs) {
100
+ lines.push(`Span: ${insights.firstTs} → ${insights.lastTs}`);
101
+ }
102
+ // GS2-16: only surface the token/cost/top-tool lines when there is real data behind them.
103
+ // Older records (and providers that report no usage) leave these zero/empty; printing
104
+ // `Tokens: 0` / `$0.0000` / `(none recorded)` reads as "the run used nothing", which is
105
+ // misleading, so omit the line entirely instead. Sessions / Span / By-command always show.
106
+ if (insights.totalTokens > 0) {
107
+ lines.push(`Tokens: ${insights.totalTokens} total ` +
108
+ `(${insights.totalTokensInput} in / ${insights.totalTokensOutput} out)`);
109
+ }
110
+ // Cost is only ever recorded when a reliable price was available (the recorder never invents
111
+ // one), so a positive total is the signal that a cost line is meaningful.
112
+ if (insights.totalCostUsd > 0) {
113
+ lines.push(`Estimated cost: $${insights.totalCostUsd.toFixed(4)}`);
114
+ }
115
+ if (insights.perCommand.length > 0) {
116
+ lines.push('By command:');
117
+ for (const c of insights.perCommand)
118
+ lines.push(` ${c.command}: ${c.count}`);
119
+ }
120
+ if (insights.topTools.length > 0) {
121
+ lines.push('Top tools:');
122
+ for (const t of insights.topTools)
123
+ lines.push(` ${t.tool}: ${t.count}`);
124
+ }
125
+ return lines;
126
+ }
127
+ //# 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,198 @@
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
+ /**
6
+ * GS2-19 — the parent conversation this turn belongs to. When omitted, {@link HistoryStore.record}
7
+ * opens a fresh single-turn conversation for the row (so a bare single-shot run = a 1-turn
8
+ * conversation). Interactive sessions {@link HistoryStore.openConversation | open} one conversation
9
+ * up-front and pass its id here on every turn, grouping the whole chat under one conversation.
10
+ */
11
+ conversationId?: number;
12
+ /** ISO-8601 timestamp; defaults to now when omitted. */
13
+ ts?: string;
14
+ /** Project / working directory the run happened in. */
15
+ project?: string;
16
+ /** Originating command (ask/chat/code/exec/…). */
17
+ command?: string;
18
+ /** Human-readable model/provider label. */
19
+ model?: string;
20
+ /** The user prompt / source that started the run (full-text indexed). */
21
+ prompt?: string;
22
+ /** The final assistant response text (full-text indexed). */
23
+ response?: string;
24
+ /** Prompt/input token count, when known. */
25
+ tokensInput?: number;
26
+ /** Completion/output token count, when known. */
27
+ tokensOutput?: number;
28
+ /** Estimated cost in USD, when known. */
29
+ costUsd?: number;
30
+ /** Names of tools invoked during the run, when known. */
31
+ tools?: string[];
32
+ /** Wall-clock duration of the run in milliseconds, when known. */
33
+ durationMs?: number;
34
+ }
35
+ /** A search hit: the stored record plus its id and a highlighted snippet. */
36
+ export interface SessionSearchResult extends SessionRecord {
37
+ id: number;
38
+ ts: string;
39
+ /** FTS5 snippet around the match (may be empty). */
40
+ snippet: string;
41
+ }
42
+ /**
43
+ * GS2-19 — metadata for a conversation (a group of turns). Passed to
44
+ * {@link HistoryStore.openConversation} at interactive-session start.
45
+ */
46
+ export interface ConversationMeta {
47
+ /** ISO-8601 start timestamp; defaults to now when omitted. */
48
+ ts?: string;
49
+ /** Project / working directory the conversation happened in. */
50
+ project?: string;
51
+ /** Originating command (chat/code/ask/exec/…). */
52
+ command?: string;
53
+ /** Human-readable model/provider label. */
54
+ model?: string;
55
+ }
56
+ /**
57
+ * GS2-19 — a conversation-grained listing row: the conversation's metadata plus the aggregate of
58
+ * its turns (how many, the timespan they cover, and a preview of the last one). This is the
59
+ * top-level unit `gth history list` shows, in place of isolated per-turn rows.
60
+ */
61
+ export interface ConversationSummary {
62
+ id: number;
63
+ /** When the conversation was opened. */
64
+ startedTs: string;
65
+ project?: string;
66
+ command?: string;
67
+ model?: string;
68
+ /** Number of turns recorded under this conversation (0 for a conversation with no turns). */
69
+ turnCount: number;
70
+ /** Timestamp of the first / last turn, when any turns exist. */
71
+ firstTs?: string;
72
+ lastTs?: string;
73
+ /** Prompt / response of the most recent turn (a one-line preview source). */
74
+ lastPrompt?: string;
75
+ lastResponse?: string;
76
+ }
77
+ /** Aggregate analytics over the whole store (local only). */
78
+ export interface HistoryInsights {
79
+ sessionCount: number;
80
+ totalTokensInput: number;
81
+ totalTokensOutput: number;
82
+ totalTokens: number;
83
+ totalCostUsd: number;
84
+ /** Tool-name → invocation count, most-used first. */
85
+ topTools: {
86
+ tool: string;
87
+ count: number;
88
+ }[];
89
+ /** Command → run count, most-used first. */
90
+ perCommand: {
91
+ command: string;
92
+ count: number;
93
+ }[];
94
+ firstTs?: string;
95
+ lastTs?: string;
96
+ }
97
+ /** Options for opening a store. */
98
+ export interface OpenHistoryStoreOptions {
99
+ /**
100
+ * When false (the default for read-only callers), a missing DB file yields `null` instead of
101
+ * creating an empty database. Read commands pass `create: false` so `gth insights` never
102
+ * materialises a DB as a side effect; the recorder passes `create: true`.
103
+ */
104
+ create?: boolean;
105
+ }
106
+ /**
107
+ * Turn arbitrary user text into a safe FTS5 MATCH expression: each whitespace-separated token is
108
+ * wrapped as a quoted string and AND-ed together. This avoids FTS5 syntax errors from stray
109
+ * operators (`AND`, `*`, `:`, parentheses, unbalanced quotes) in a user's query while still
110
+ * matching all of their words.
111
+ */
112
+ export declare function toFtsMatchQuery(query: string): string;
113
+ /**
114
+ * A thin, fail-soft wrapper over a `node:sqlite` connection holding the session history.
115
+ *
116
+ * Obtain one via {@link openHistoryStore} (which returns `null` if the DB can't be opened). Every
117
+ * method is defensive: on any SQLite error it returns a safe empty/zero result rather than
118
+ * throwing, so callers on a run's hot path never have to guard.
119
+ */
120
+ export declare class HistoryStore {
121
+ private db;
122
+ private constructor();
123
+ /**
124
+ * Open (and lazily initialise) the store at `dbPath`. Returns `null` on any failure — a missing
125
+ * file when `create` is false, an unopenable/locked/corrupt DB, or a schema-init error — so the
126
+ * caller can simply skip history without a try/catch.
127
+ */
128
+ static open(dbPath: string, options?: OpenHistoryStoreOptions): HistoryStore | null;
129
+ private initSchema;
130
+ /**
131
+ * GS2-19 — in-place, idempotent upgrade of a pre-existing GS2-7 DB (flat `sessions` rows, no
132
+ * grouping) to the conversation-grained model. Runs on every open (including read-only `list` /
133
+ * `search` opens, which open the file read-write): it adds the `conversation_id` column if an
134
+ * older `sessions` table lacks it, then back-fills each ungrouped turn into its own 1-turn
135
+ * conversation. After the first pass there are no ungrouped rows, so it is a cheap no-op.
136
+ *
137
+ * Fully fail-soft: a migration hiccup is swallowed here rather than nulling the store, so reads
138
+ * still work against whatever is already there and new turns just fall back to fresh 1-turn
139
+ * conversations. Never throws.
140
+ */
141
+ private migrate;
142
+ /**
143
+ * GS2-19 — open a new conversation and return its id (or `null` on any error). Interactive
144
+ * sessions call this once at start, then pass the id on every {@link record} so all the session's
145
+ * turns group under it. A single-shot run does not need this: {@link record} opens a 1-turn
146
+ * conversation itself when no `conversationId` is supplied.
147
+ */
148
+ openConversation(meta?: ConversationMeta): number | null;
149
+ /**
150
+ * Persist one session and its full-text index entry. Returns the new row id, or `null` on any
151
+ * error (the run continues regardless). The two inserts run in a transaction so a failure can't
152
+ * leave the FTS index out of sync with the base table.
153
+ */
154
+ record(rec: SessionRecord): number | null;
155
+ /**
156
+ * Full-text search over prompt/response/command/project, best match first (FTS5 `rank`). User
157
+ * text is sanitised via {@link toFtsMatchQuery}; an empty or all-punctuation query returns `[]`.
158
+ * Any SQLite error yields `[]` (fail-soft).
159
+ */
160
+ search(query: string, limit?: number): SessionSearchResult[];
161
+ /** Most recent sessions, newest first. Fail-soft ([] on error). */
162
+ listRecent(limit?: number): SessionSearchResult[];
163
+ /**
164
+ * GS2-19 — conversations newest-first, each with its turn count, timespan, and a preview of the
165
+ * last turn. This is the top-level unit for `gth history list` (the turn-grained {@link listRecent}
166
+ * remains for callers that want raw turns). Fail-soft ([] on error).
167
+ *
168
+ * Empty conversations are **excluded** (`HAVING COUNT(s.id) > 0`): a session opens its conversation
169
+ * at start (before any turn), so one that exits with zero turns would otherwise show as a
170
+ * contentless `turnCount: 0` row. The LEFT JOIN still keeps every conversation that has ≥1 turn —
171
+ * including back-filled 1-turn conversations, whose single turn satisfies the HAVING.
172
+ */
173
+ listConversations(limit?: number): ConversationSummary[];
174
+ /**
175
+ * GS2-19 — all turns of one conversation in chronological (insert) order, so a search hit can be
176
+ * expanded into the whole thread it belonged to. Fail-soft ([] on error / unknown id).
177
+ */
178
+ getConversationThread(conversationId: number): SessionRecord[];
179
+ /**
180
+ * Aggregate token/cost totals, a top-tool tally, and a per-command breakdown over the whole
181
+ * store. Tool tallying reads each row's JSON `tools` array in JS (robust to nulls). Fail-soft:
182
+ * returns a zeroed summary on any error.
183
+ */
184
+ insights(topN?: number): HistoryInsights;
185
+ /** Close the underlying connection (fail-soft). */
186
+ close(): void;
187
+ }
188
+ /**
189
+ * Resolve the on-disk path of the history DB. Honors an explicit `dbPath` (from `history.dbPath`
190
+ * or a `--db` flag); otherwise the global `~/.gsloth/history.db`. When `dbPath` is omitted and
191
+ * `ensureDir` is true, the global dir is created so the recorder can write.
192
+ */
193
+ export declare function resolveHistoryDbPath(dbPath?: string, ensureDir?: boolean): string;
194
+ /**
195
+ * Fail-soft open of the history store. Returns `null` (never throws) when the DB can't be opened
196
+ * or, for read-only callers (`create: false`, the default), when the file does not yet exist.
197
+ */
198
+ export declare function openHistoryStore(dbPath: string, options?: OpenHistoryStoreOptions): HistoryStore | null;