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

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.
@@ -442,7 +442,7 @@ export interface LLMConfig extends Record<string, unknown> {
442
442
  configuration: Record<string, unknown>;
443
443
  apiKeyEnvironmentVariable?: string;
444
444
  }
445
- export declare const availableDefaultConfigs: readonly ["vertexai", "anthropic", "groq", "deepseek", "openai", "google-genai", "xai", "openrouter", "ollama"];
445
+ export declare const availableDefaultConfigs: readonly ["vertexai", "anthropic", "groq", "deepseek", "openai", "google-genai", "xai", "openrouter", "huggingface", "ollama"];
446
446
  export type ConfigType = (typeof availableDefaultConfigs)[number];
447
447
  export interface CommandLineConfigOverrides {
448
448
  /**
@@ -7,6 +7,7 @@ export const availableDefaultConfigs = [
7
7
  'google-genai',
8
8
  'xai',
9
9
  'openrouter',
10
+ 'huggingface',
10
11
  'ollama',
11
12
  ];
12
13
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AA2cA,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,UAAU;IACV,WAAW;IACX,MAAM;IACN,UAAU;IACV,QAAQ;IACR,cAAc;IACd,KAAK;IACL,YAAY;IACZ,QAAQ;CACA,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AA2cA,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,UAAU;IACV,WAAW;IACX,MAAM;IACN,UAAU;IACV,QAAQ;IACR,cAAc;IACd,KAAK;IACL,YAAY;IACZ,aAAa;IACb,QAAQ;CACA,CAAC"}
@@ -36,3 +36,11 @@ export declare const CONFIG_SCHEMA_POINTER = "https://gauntsloth.app/schema/v2/g
36
36
  * Lives under `.gsloth/.gsloth-settings/` like other project settings.
37
37
  */
38
38
  export declare const SHELL_ALLOWLIST_FILE = "shell-allowlist.json";
39
+ /**
40
+ * The `additionalToolNamePrefix` handed to `MultiServerMCPClient` so every MCP tool name is
41
+ * namespaced. Combined with `prefixToolNameWithServerName`, the adapter emits tool names shaped
42
+ * `${MCP_TOOL_NAME_PREFIX}__<serverName>__<toolName>` (the `__` separator is the mcp-adapters
43
+ * convention). Single-sourced here so the resolver that sets the prefix (EXT-32) and any consumer
44
+ * that groups tools back by server (the [[TUI-C20]] MCP debug tab) can never silently drift apart.
45
+ */
46
+ export declare const MCP_TOOL_NAME_PREFIX = "mcp";
package/dist/constants.js CHANGED
@@ -36,4 +36,12 @@ export const CONFIG_SCHEMA_POINTER = 'https://gauntsloth.app/schema/v2/gsloth-co
36
36
  * Lives under `.gsloth/.gsloth-settings/` like other project settings.
37
37
  */
38
38
  export const SHELL_ALLOWLIST_FILE = 'shell-allowlist.json';
39
+ /**
40
+ * The `additionalToolNamePrefix` handed to `MultiServerMCPClient` so every MCP tool name is
41
+ * namespaced. Combined with `prefixToolNameWithServerName`, the adapter emits tool names shaped
42
+ * `${MCP_TOOL_NAME_PREFIX}__<serverName>__<toolName>` (the `__` separator is the mcp-adapters
43
+ * convention). Single-sourced here so the resolver that sets the prefix (EXT-32) and any consumer
44
+ * that groups tools back by server (the [[TUI-C20]] MCP debug tab) can never silently drift apart.
45
+ */
46
+ export const MCP_TOOL_NAME_PREFIX = 'mcp';
39
47
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC;AACpC,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC;AAC1C,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAC1D,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAC;AAC9D,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAC5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAC1D,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AACvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAC1D,MAAM,CAAC,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;AACxD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AACpD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AACpD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AACpD,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAC;AACzC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,4DAA4D,CAAC;AAClG;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC;AACpC,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC;AAC1C,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAC1D,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAC;AAC9D,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAC5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAC1D,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AACvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAC1D,MAAM,CAAC,MAAM,2BAA2B,GAAG,mBAAmB,CAAC;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;AACxD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AACpD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AACpD,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AACpD,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAC;AACzC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,4DAA4D,CAAC;AAClG;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC"}
@@ -3,6 +3,7 @@ import { GthAbstractAgent } from '#src/core/GthAbstractAgent.js';
3
3
  import { debugLog, debugLogObject } from '#src/utils/debugUtils.js';
4
4
  import { buildSystemMessages, formatToolCalls, readChatPrompt, readCodePrompt, readExecPrompt, } from '#src/utils/llmUtils.js';
5
5
  import { getCurrentWorkDir } from '#src/utils/systemUtils.js';
6
+ import { appendOsShellNote, appendCwdNote, appendMcpServerInstructionsNote, } from '#src/utils/systemPromptNotes.js';
6
7
  import { isShellCommandFailedError } from '#src/core/shell/ShellCommandFailedError.js';
7
8
  import { extractDebugRequestExtras } from '#src/core/debugCapture.js';
8
9
  import { AIMessage, ToolMessage } from '@langchain/core/messages';
@@ -176,7 +177,30 @@ export class GthLangChainAgent extends GthAbstractAgent {
176
177
  ? readExecPrompt(this.config)
177
178
  : readChatPrompt(this.config);
178
179
  const systemMessages = buildSystemMessages(this.config, modePrompt);
179
- const systemPrompt = typeof systemMessages[0]?.content === 'string' ? systemMessages[0].content : undefined;
180
+ const baseSystemPrompt = typeof systemMessages[0]?.content === 'string' ? systemMessages[0].content : undefined;
181
+ // GS2-27: in `code` mode append the SHARED code-mode notes the deep backend has always carried
182
+ // — the real-cwd / path-model note (EXT-13) and the OS + shell-dialect note (EXT-26). Both are
183
+ // backend-agnostic (the lean backend also exposes `run_shell_command` and runs on the real-fs
184
+ // cwd), so composing them here closes the deep-only drift that left a lean code session with no
185
+ // cwd value and no shell-dialect guidance (e.g. on Windows). The deepagents virtual-fs-namespace
186
+ // notes stay deep-only (lean never runs virtualMode). Same order the deep backend's real-path
187
+ // branch uses: cwd note first, OS/shell note last. `getCurrentWorkDir()` is already read above
188
+ // for the status line, so the value is free.
189
+ const codeNotesPrompt = this.command === 'code'
190
+ ? appendOsShellNote(appendCwdNote(baseSystemPrompt, getCurrentWorkDir()))
191
+ : baseSystemPrompt;
192
+ // EXT-32: inject each connected MCP server's discovery `instructions` (captured during tool
193
+ // resolution) into the prompt — fenced + per-server-labelled as untrusted server-provided
194
+ // context. Mode-independent: MCP tools load in every mode, so their usage guidance applies in
195
+ // every mode (not just `code`). Empty/absent capture (or a resolver without the accessor) adds
196
+ // nothing. Composed through this shared path so it reaches the lean AND deep backends alike.
197
+ // When tools are disabled, resolveTools is skipped entirely (no MCP contact), so a REUSED
198
+ // resolver could still hold a prior run's capture — gate on toolsDisabled so no stale
199
+ // instructions leak into a tools-disabled session.
200
+ const mcpInstructions = toolsDisabled
201
+ ? []
202
+ : (this.resolvers?.getMcpServerInstructions?.() ?? []);
203
+ const systemPrompt = appendMcpServerInstructionsNote(codeNotesPrompt, mcpInstructions);
180
204
  // Create agent with configured middleware. Only pass systemPrompt when non-empty so we never
181
205
  // hand createAgent an empty system message.
182
206
  this.agent = createAgent({
@@ -1 +1 @@
1
- {"version":3,"file":"GthLangChainAgent.js","sourceRoot":"","sources":["../../src/core/GthLangChainAgent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GthLangChainAgent.js","sourceRoot":"","sources":["../../src/core/GthLangChainAgent.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,+BAA+B,GAChC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAM1D;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,gBAAgB;IACrD,KAAK,CAAC,IAAI,CACR,OAA+B,EAC/B,QAAmB,EACnB,YAA8C;QAE9C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,QAAQ,CAAC,+CAA+C,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;QAEhF,uDAAuD;QACvD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACzD,cAAc,CAAC,kBAAkB,EAAE;YACjC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU;YAClC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;YACtC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;YACtC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;SAC/B,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,YAAY,iBAAiB,EAAE,EAAE,CAAC,CAAC;QAEvE,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC;YACjC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,UAAU,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,uFAAuF;QACvF,4EAA4E;QAC5E,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC;QAC9C,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC;QAC/E,IAAI,aAAa,EAAE,CAAC;YAClB,IAAI,CAAC,YAAY,CACf,WAAW,CAAC,IAAI,EAChB,uHAAuH,CACxH,CAAC;QACJ,CAAC;QAED,+DAA+D;QAC/D,QAAQ,CAAC,oBAAoB,CAAC,CAAC;QAC/B,MAAM,aAAa,GACjB,CAAC,aAAa,IAAI,IAAI,CAAC,SAAS,EAAE,YAAY;YAC5C,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;YACzD,CAAC,CAAC,EAAE,CAAC;QACT,QAAQ,CAAC,0BAA0B,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC;QAE3D,wBAAwB;QACxB,MAAM,oBAAoB,GAAG,aAAa;YACxC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACzD,QAAQ,CAAC,6BAA6B,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC;QAErE,kFAAkF;QAClF,IAAI,KAAK,GAAG,CAAC,GAAG,aAAa,EAAE,GAAG,oBAAoB,CAAC,CAAC;QACxD,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YAChC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;YACtC,0FAA0F;YAC1F,sFAAsF;YACtF,wFAAwF;YACxF,wFAAwF;YACxF,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACvE,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,SAAS,GAAG,KAAK;iBACpB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;iBACxB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;iBACtB,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,iBAAiB,SAAS,EAAE,CAAC,CAAC;YAClE,QAAQ,CAAC,0BAA0B,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YACnD,cAAc,CAAC,WAAW,EAAE,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACrD,CAAC;QAED,yBAAyB;QACzB,QAAQ,CAAC,yBAAyB,CAAC,CAAC;QAEpC,wDAAwD;QACxD,MAAM,oBAAoB,GAAG,IAAI,CAAC,SAAS,EAAE,iBAAiB;YAC5D,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,iBAAiB;YACpC,8DAA8D;YAC9D,IAAI,CAAC,MAAM,CAAC,UAA+B,EAC3C,IAAI,CAAC,MAAM,CACZ;YACH,CAAC,CAAC,EAAE,CAAC;QAEP,yCAAyC;QACzC,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACvC,MAAM,wBAAwB,GAAG,gBAAgB,CAAC;YAChD,IAAI,EAAE,mCAAmC;YACzC,8DAA8D;YAC9D,UAAU,EAAE,CAAC,KAAU,EAAE,EAAE;gBACzB,cAAc,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;gBACzC,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC9D,IACE,SAAS,CAAC,UAAU,CAAC,WAAW,CAAC;oBACjC,WAAW,CAAC,UAAU;oBACtB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,CAAC,EAClC,CAAC;oBACD,YAAY,CACV,WAAW,CAAC,IAAI,EAChB,sBAAsB,eAAe,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAClE,CAAC;gBACJ,CAAC;gBACD,OAAO,KAAK,CAAC;YACf,CAAC;SACF,CAAC,CAAC;QAEH,6FAA6F;QAC7F,4FAA4F;QAC5F,+FAA+F;QAC/F,iGAAiG;QACjG,gGAAgG;QAChG,0FAA0F;QAC1F,4FAA4F;QAC5F,iGAAiG;QACjG,gGAAgG;QAChG,gGAAgG;QAChG,+FAA+F;QAC/F,gEAAgE;QAChE,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;YAC1C,IAAI,EAAE,2BAA2B;YACjC,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;gBACvC,IAAI,CAAC;oBACH,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;gBAChC,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,IAAI,yBAAyB,CAAC,CAAC,CAAC,EAAE,CAAC;wBACjC,QAAQ,CACN,4CAA4C,CAAC,CAAC,QAAQ,IAAI,SAAS,YAAY;4BAC7E,0BAA0B,CAAC,CAAC,OAAO,GAAG,CACzC,CAAC;wBACF,OAAO,IAAI,WAAW,CAAC;4BACrB,OAAO,EAAE,CAAC,CAAC,MAAM;4BACjB,8DAA8D;4BAC9D,YAAY,EAAG,OAAO,CAAC,QAAgB,EAAE,EAAE,IAAI,EAAE;4BACjD,MAAM,EAAE,OAAO;yBAChB,CAAC,CAAC;oBACL,CAAC;oBACD,MAAM,CAAC,CAAC;gBACV,CAAC;YACH,CAAC;SACF,CAAC,CAAC;QAEH,2FAA2F;QAC3F,gGAAgG;QAChG,8FAA8F;QAC9F,yFAAyF;QACzF,+FAA+F;QAC/F,qDAAqD;QACrD,MAAM,eAAe,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC;QAChD,MAAM,sBAAsB,GAAG,gBAAgB,CAAC;YAC9C,IAAI,EAAE,2BAA2B;YACjC,8DAA8D;YAC9D,aAAa,EAAE,KAAK,EAAE,OAAY,EAAE,OAAY,EAAE,EAAE;gBAClD,MAAM,OAAO,GAAG,eAAe,EAAE,CAAC;gBAClC,IAAI,CAAC,OAAO;oBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;gBACtC,IAAI,CAAC;oBACH,OAAO,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC5E,CAAC;gBAAC,MAAM,CAAC;oBACP,2CAA2C;gBAC7C,CAAC;gBACD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;gBACxC,IAAI,CAAC;oBACH,OAAO,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,CAAC;gBACjC,CAAC;gBAAC,MAAM,CAAC;oBACP,2CAA2C;gBAC7C,CAAC;gBACD,OAAO,QAAQ,CAAC;YAClB,CAAC;SACF,CAAC,CAAC;QAEH,qFAAqF;QACrF,0FAA0F;QAC1F,MAAM,UAAU,GAAG;YACjB,kBAAkB;YAClB,GAAG,oBAAoB;YACvB,wBAAwB;YACxB,sBAAsB;SACvB,CAAC;QAEF,IAAI,CAAC,YAAY,CACf,WAAW,CAAC,IAAI,EAChB,sBAAsB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACjE,CAAC;QAEF,6FAA6F;QAC7F,2FAA2F;QAC3F,6FAA6F;QAC7F,8FAA8F;QAC9F,yFAAyF;QACzF,0FAA0F;QAC1F,oFAAoF;QACpF,+FAA+F;QAC/F,iFAAiF;QACjF,MAAM,UAAU,GACd,IAAI,CAAC,OAAO,KAAK,MAAM;YACrB,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC;YAC7B,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,MAAM;gBACvB,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC7B,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,cAAc,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACpE,MAAM,gBAAgB,GACpB,OAAO,cAAc,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QAEzF,+FAA+F;QAC/F,+FAA+F;QAC/F,8FAA8F;QAC9F,gGAAgG;QAChG,iGAAiG;QACjG,8FAA8F;QAC9F,+FAA+F;QAC/F,6CAA6C;QAC7C,MAAM,eAAe,GACnB,IAAI,CAAC,OAAO,KAAK,MAAM;YACrB,CAAC,CAAC,iBAAiB,CAAC,aAAa,CAAC,gBAAgB,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACzE,CAAC,CAAC,gBAAgB,CAAC;QAEvB,4FAA4F;QAC5F,0FAA0F;QAC1F,8FAA8F;QAC9F,+FAA+F;QAC/F,6FAA6F;QAC7F,0FAA0F;QAC1F,sFAAsF;QACtF,mDAAmD;QACnD,MAAM,eAAe,GAAG,aAAa;YACnC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,MAAM,YAAY,GAAG,+BAA+B,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;QAEvF,6FAA6F;QAC7F,4CAA4C;QAC5C,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC;YACvB,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG;YACtB,KAAK;YACL,UAAU;YACV,YAAY;YACZ,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,CAAC,CAAC;QACH,QAAQ,CAAC,kCAAkC,CAAC,CAAC;IAC/C,CAAC;CACF"}
@@ -197,9 +197,29 @@ export type ToolsResolver = (config: GthConfig, command?: GthCommand) => Promise
197
197
  export type ToolsCleanup = () => Promise<void>;
198
198
  export type MiddlewareResolver = (middleware: any[] | undefined, config: GthConfig) => Promise<any[]>;
199
199
  export type MiddlewareCleanup = () => Promise<void>;
200
+ /**
201
+ * EXT-32 — one connected MCP server's discovery `instructions` string (from its MCP `initialize`
202
+ * handshake), paired with the server name it came from. Captured once during tool resolution and
203
+ * reused: injected (fenced + per-server-labelled) into the composed system prompt, and available
204
+ * for [[TUI-C20]]'s MCP debug tab to render the same captured text. Only servers that actually
205
+ * supplied non-empty instructions appear here.
206
+ */
207
+ export interface McpServerInstruction {
208
+ /** The configured MCP server name (the key under `config.mcpServers`). */
209
+ server: string;
210
+ /** The server-provided instructions text (trimmed, non-empty). */
211
+ instructions: string;
212
+ }
200
213
  export interface AgentResolvers {
201
214
  resolveTools?: ToolsResolver;
202
215
  cleanupTools?: ToolsCleanup;
203
216
  resolveMiddleware?: MiddlewareResolver;
204
217
  cleanupMiddleware?: MiddlewareCleanup;
218
+ /**
219
+ * EXT-32 — the per-server MCP discovery instructions captured during the most recent
220
+ * {@link ToolsResolver} call (empty when no MCP servers are configured or none supplied
221
+ * instructions). Optional: resolvers without MCP support simply omit it, and the prompt
222
+ * composition treats an absent accessor as "no instructions" (no MCP section is emitted).
223
+ */
224
+ getMcpServerInstructions?(): McpServerInstruction[];
205
225
  }
@@ -7,13 +7,22 @@
7
7
  * DB or a terminal. Every function is a pure `data -> string[]` transform (one display line per
8
8
  * element); no I/O, no colour codes.
9
9
  */
10
- import type { HistoryInsights, SessionSearchResult } from '#src/history/historyStore.js';
10
+ import type { ConversationSummary, HistoryInsights, SessionRecord, SessionSearchResult } from '#src/history/historyStore.js';
11
11
  /**
12
12
  * Render FTS search hits: a header line per hit plus its snippet (or a prompt preview when the
13
13
  * snippet is empty). Returns a friendly single line when there are none.
14
14
  */
15
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[];
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[];
18
27
  /** Render the analytics summary: totals, top tools, per-command breakdown. */
19
28
  export declare function formatInsightsSummary(insights: HistoryInsights): string[];
@@ -3,13 +3,17 @@ function oneLine(text, max = 80) {
3
3
  const s = (text ?? '').replace(/\s+/g, ' ').trim();
4
4
  return s.length > max ? s.slice(0, max - 1) + '…' : s;
5
5
  }
6
- /** A compact `#id <ts> [command] model` header line for one session. */
6
+ /** A compact `#id <ts> [command] model (conversation #cid)` header line for one turn. */
7
7
  function headerLine(r) {
8
8
  const parts = [`#${r.id}`, r.ts];
9
9
  if (r.command)
10
10
  parts.push(`[${r.command}]`);
11
11
  if (r.model)
12
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})`);
13
17
  return parts.join(' ');
14
18
  }
15
19
  /**
@@ -29,22 +33,60 @@ export function formatSearchResults(results) {
29
33
  }
30
34
  return lines;
31
35
  }
32
- /** Render a recent-sessions listing: one header + prompt-preview pair per session. */
33
- export function formatHistoryList(results) {
34
- if (results.length === 0) {
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) {
35
43
  return [
36
- 'No sessions recorded yet. Enable history with `history.enabled: true` in your config.',
44
+ 'No conversations recorded yet. Enable history with `history.enabled: true` in your config.',
37
45
  ];
38
46
  }
39
47
  const lines = [];
40
- for (const r of results) {
41
- lines.push(headerLine(r));
42
- const preview = oneLine(r.prompt, 100);
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);
43
65
  if (preview)
44
66
  lines.push(` ${preview}`);
45
67
  }
46
68
  return lines;
47
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
+ }
48
90
  /** Render the analytics summary: totals, top tools, per-command breakdown. */
49
91
  export function formatInsightsSummary(insights) {
50
92
  if (insights.sessionCount === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"historyFormat.js","sourceRoot":"","sources":["../../src/history/historyFormat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"historyFormat.js","sourceRoot":"","sources":["../../src/history/historyFormat.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,13 @@
2
2
  export declare const HISTORY_DB_FILENAME = "history.db";
3
3
  /** A single persisted session record (all analytics fields optional; populated when available). */
4
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;
5
12
  /** ISO-8601 timestamp; defaults to now when omitted. */
6
13
  ts?: string;
7
14
  /** Project / working directory the run happened in. */
@@ -32,6 +39,41 @@ export interface SessionSearchResult extends SessionRecord {
32
39
  /** FTS5 snippet around the match (may be empty). */
33
40
  snippet: string;
34
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
+ }
35
77
  /** Aggregate analytics over the whole store (local only). */
36
78
  export interface HistoryInsights {
37
79
  sessionCount: number;
@@ -85,6 +127,25 @@ export declare class HistoryStore {
85
127
  */
86
128
  static open(dbPath: string, options?: OpenHistoryStoreOptions): HistoryStore | null;
87
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;
88
149
  /**
89
150
  * Persist one session and its full-text index entry. Returns the new row id, or `null` on any
90
151
  * error (the run continues regardless). The two inserts run in a transaction so a failure can't
@@ -99,6 +160,22 @@ export declare class HistoryStore {
99
160
  search(query: string, limit?: number): SessionSearchResult[];
100
161
  /** Most recent sessions, newest first. Fail-soft ([] on error). */
101
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[];
102
179
  /**
103
180
  * Aggregate token/cost totals, a top-tool tally, and a per-command breakdown over the whole
104
181
  * store. Tool tallying reads each row's JSON `tools` array in JS (robust to nulls). Fail-soft:
@@ -73,7 +73,20 @@ export class HistoryStore {
73
73
  }
74
74
  }
75
75
  initSchema() {
76
+ // GS2-19: the `sessions` table is turn-grained (its rows are prompt→response turns, despite the
77
+ // name). A `conversations` table groups the turns of one interactive session; each turn carries
78
+ // a `conversation_id`. `conversation_id` is a plain INTEGER join key, not an enforced FK — SQLite
79
+ // leaves `foreign_keys` off by default and `ALTER TABLE ADD COLUMN` can't add a REFERENCES clause,
80
+ // so declaring one here would only make the fresh schema drift from the migrated one. The column
81
+ // definition is kept byte-identical between this fresh path and {@link migrate}'s ALTER.
76
82
  this.db.exec(`
83
+ CREATE TABLE IF NOT EXISTS conversations (
84
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
85
+ started_ts TEXT NOT NULL,
86
+ project TEXT,
87
+ command TEXT,
88
+ model TEXT
89
+ );
77
90
  CREATE TABLE IF NOT EXISTS sessions (
78
91
  id INTEGER PRIMARY KEY AUTOINCREMENT,
79
92
  ts TEXT NOT NULL,
@@ -86,12 +99,77 @@ export class HistoryStore {
86
99
  tokens_output INTEGER,
87
100
  cost_usd REAL,
88
101
  tools TEXT,
89
- duration_ms INTEGER
102
+ duration_ms INTEGER,
103
+ conversation_id INTEGER
90
104
  );
91
105
  CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts USING fts5(
92
106
  prompt, response, command, project
93
107
  );
94
108
  `);
109
+ this.migrate();
110
+ }
111
+ /**
112
+ * GS2-19 — in-place, idempotent upgrade of a pre-existing GS2-7 DB (flat `sessions` rows, no
113
+ * grouping) to the conversation-grained model. Runs on every open (including read-only `list` /
114
+ * `search` opens, which open the file read-write): it adds the `conversation_id` column if an
115
+ * older `sessions` table lacks it, then back-fills each ungrouped turn into its own 1-turn
116
+ * conversation. After the first pass there are no ungrouped rows, so it is a cheap no-op.
117
+ *
118
+ * Fully fail-soft: a migration hiccup is swallowed here rather than nulling the store, so reads
119
+ * still work against whatever is already there and new turns just fall back to fresh 1-turn
120
+ * conversations. Never throws.
121
+ */
122
+ migrate() {
123
+ try {
124
+ const cols = this.db.prepare(`PRAGMA table_info(sessions)`).all();
125
+ const hasConversationId = cols.some((c) => c.name === 'conversation_id');
126
+ if (!hasConversationId) {
127
+ this.db.exec(`ALTER TABLE sessions ADD COLUMN conversation_id INTEGER`);
128
+ }
129
+ const orphans = this.db
130
+ .prepare(`SELECT id, ts, project, command, model
131
+ FROM sessions
132
+ WHERE conversation_id IS NULL
133
+ ORDER BY id`)
134
+ .all();
135
+ if (orphans.length === 0)
136
+ return;
137
+ this.db.exec('BEGIN');
138
+ try {
139
+ const insertConversation = this.db.prepare(`INSERT INTO conversations (started_ts, project, command, model) VALUES (?, ?, ?, ?)`);
140
+ const stampTurn = this.db.prepare(`UPDATE sessions SET conversation_id = ? WHERE id = ?`);
141
+ for (const row of orphans) {
142
+ const info = insertConversation.run(row.ts != null ? String(row.ts) : new Date().toISOString(), row.project != null ? String(row.project) : null, row.command != null ? String(row.command) : null, row.model != null ? String(row.model) : null);
143
+ stampTurn.run(Number(info.lastInsertRowid), Number(row.id));
144
+ }
145
+ this.db.exec('COMMIT');
146
+ }
147
+ catch (e) {
148
+ this.db.exec('ROLLBACK');
149
+ throw e;
150
+ }
151
+ }
152
+ catch {
153
+ /* fail-soft: a migration failure must not break opening the store or a run */
154
+ }
155
+ }
156
+ /**
157
+ * GS2-19 — open a new conversation and return its id (or `null` on any error). Interactive
158
+ * sessions call this once at start, then pass the id on every {@link record} so all the session's
159
+ * turns group under it. A single-shot run does not need this: {@link record} opens a 1-turn
160
+ * conversation itself when no `conversationId` is supplied.
161
+ */
162
+ openConversation(meta = {}) {
163
+ try {
164
+ const ts = meta.ts ?? new Date().toISOString();
165
+ const info = this.db
166
+ .prepare(`INSERT INTO conversations (started_ts, project, command, model) VALUES (?, ?, ?, ?)`)
167
+ .run(ts, meta.project ?? null, meta.command ?? null, meta.model ?? null);
168
+ return Number(info.lastInsertRowid);
169
+ }
170
+ catch {
171
+ return null;
172
+ }
95
173
  }
96
174
  /**
97
175
  * Persist one session and its full-text index entry. Returns the new row id, or `null` on any
@@ -104,11 +182,21 @@ export class HistoryStore {
104
182
  const tools = rec.tools && rec.tools.length > 0 ? JSON.stringify(rec.tools) : null;
105
183
  this.db.exec('BEGIN');
106
184
  try {
185
+ // GS2-19: every turn belongs to a conversation. When the caller opened one up-front
186
+ // (interactive sessions), stamp it; otherwise open a fresh 1-turn conversation for this row
187
+ // (single-shot runs / bare record() calls) so the turn is never left ungrouped.
188
+ let conversationId = rec.conversationId ?? null;
189
+ if (conversationId == null) {
190
+ const cinfo = this.db
191
+ .prepare(`INSERT INTO conversations (started_ts, project, command, model) VALUES (?, ?, ?, ?)`)
192
+ .run(ts, rec.project ?? null, rec.command ?? null, rec.model ?? null);
193
+ conversationId = Number(cinfo.lastInsertRowid);
194
+ }
107
195
  const insert = this.db.prepare(`INSERT INTO sessions
108
196
  (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);
197
+ tokens_input, tokens_output, cost_usd, tools, duration_ms, conversation_id)
198
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
199
+ const info = insert.run(ts, rec.project ?? null, rec.command ?? null, rec.model ?? null, rec.prompt ?? null, rec.response ?? null, rec.tokensInput ?? null, rec.tokensOutput ?? null, rec.costUsd ?? null, tools, rec.durationMs ?? null, conversationId);
112
200
  const id = Number(info.lastInsertRowid);
113
201
  this.db
114
202
  .prepare(`INSERT INTO sessions_fts (rowid, prompt, response, command, project)
@@ -141,6 +229,7 @@ export class HistoryStore {
141
229
  s.model AS model, s.prompt AS prompt, s.response AS response,
142
230
  s.tokens_input AS tokens_input, s.tokens_output AS tokens_output,
143
231
  s.cost_usd AS cost_usd, s.tools AS tools, s.duration_ms AS duration_ms,
232
+ s.conversation_id AS conversation_id,
144
233
  snippet(sessions_fts, 0, '[', ']', '…', 12) AS snippet
145
234
  FROM sessions_fts f
146
235
  JOIN sessions s ON s.id = f.rowid
@@ -164,7 +253,7 @@ export class HistoryStore {
164
253
  try {
165
254
  const rows = this.db
166
255
  .prepare(`SELECT id, ts, project, command, model, prompt, response,
167
- tokens_input, tokens_output, cost_usd, tools, duration_ms
256
+ tokens_input, tokens_output, cost_usd, tools, duration_ms, conversation_id
168
257
  FROM sessions
169
258
  ORDER BY id DESC
170
259
  LIMIT ?`)
@@ -180,6 +269,70 @@ export class HistoryStore {
180
269
  return [];
181
270
  }
182
271
  }
272
+ /**
273
+ * GS2-19 — conversations newest-first, each with its turn count, timespan, and a preview of the
274
+ * last turn. This is the top-level unit for `gth history list` (the turn-grained {@link listRecent}
275
+ * remains for callers that want raw turns). Fail-soft ([] on error).
276
+ *
277
+ * Empty conversations are **excluded** (`HAVING COUNT(s.id) > 0`): a session opens its conversation
278
+ * at start (before any turn), so one that exits with zero turns would otherwise show as a
279
+ * contentless `turnCount: 0` row. The LEFT JOIN still keeps every conversation that has ≥1 turn —
280
+ * including back-filled 1-turn conversations, whose single turn satisfies the HAVING.
281
+ */
282
+ listConversations(limit = 20) {
283
+ try {
284
+ const rows = this.db
285
+ .prepare(`SELECT c.id AS id, c.started_ts AS started_ts, c.project AS project,
286
+ c.command AS command, c.model AS model,
287
+ COUNT(s.id) AS turn_count, MIN(s.ts) AS first_ts, MAX(s.ts) AS last_ts
288
+ FROM conversations c
289
+ LEFT JOIN sessions s ON s.conversation_id = c.id
290
+ GROUP BY c.id
291
+ HAVING COUNT(s.id) > 0
292
+ ORDER BY c.id DESC
293
+ LIMIT ?`)
294
+ .all(limit);
295
+ const lastTurn = this.db.prepare(`SELECT prompt, response FROM sessions
296
+ WHERE conversation_id = ? ORDER BY id DESC LIMIT 1`);
297
+ return rows.map((r) => {
298
+ const last = lastTurn.get(Number(r.id));
299
+ return {
300
+ id: Number(r.id),
301
+ startedTs: String(r.started_ts),
302
+ project: r.project != null ? String(r.project) : undefined,
303
+ command: r.command != null ? String(r.command) : undefined,
304
+ model: r.model != null ? String(r.model) : undefined,
305
+ turnCount: Number(r.turn_count ?? 0),
306
+ firstTs: r.first_ts != null ? String(r.first_ts) : undefined,
307
+ lastTs: r.last_ts != null ? String(r.last_ts) : undefined,
308
+ lastPrompt: last?.prompt != null ? String(last.prompt) : undefined,
309
+ lastResponse: last?.response != null ? String(last.response) : undefined,
310
+ };
311
+ });
312
+ }
313
+ catch {
314
+ return [];
315
+ }
316
+ }
317
+ /**
318
+ * GS2-19 — all turns of one conversation in chronological (insert) order, so a search hit can be
319
+ * expanded into the whole thread it belonged to. Fail-soft ([] on error / unknown id).
320
+ */
321
+ getConversationThread(conversationId) {
322
+ try {
323
+ const rows = this.db
324
+ .prepare(`SELECT id, ts, project, command, model, prompt, response,
325
+ tokens_input, tokens_output, cost_usd, tools, duration_ms, conversation_id
326
+ FROM sessions
327
+ WHERE conversation_id = ?
328
+ ORDER BY id ASC`)
329
+ .all(conversationId);
330
+ return rows.map((r) => rowToRecord(r));
331
+ }
332
+ catch {
333
+ return [];
334
+ }
335
+ }
183
336
  /**
184
337
  * Aggregate token/cost totals, a top-tool tally, and a per-command breakdown over the whole
185
338
  * store. Tool tallying reads each row's JSON `tools` array in JS (robust to nulls). Fail-soft:
@@ -281,6 +434,7 @@ function rowToRecord(r) {
281
434
  }
282
435
  }
283
436
  return {
437
+ conversationId: r.conversation_id != null ? Number(r.conversation_id) : undefined,
284
438
  ts: r.ts != null ? String(r.ts) : undefined,
285
439
  project: r.project != null ? String(r.project) : undefined,
286
440
  command: r.command != null ? String(r.command) : undefined,