@gaunt-sloth/core 2.0.0-alpha.4 → 2.0.0-alpha.40

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 (289) hide show
  1. package/README.md +71 -20
  2. package/dist/config/colour.d.ts +38 -0
  3. package/dist/config/colour.js +36 -0
  4. package/dist/config/colour.js.map +1 -0
  5. package/dist/config/configDiscovery.d.ts +79 -0
  6. package/dist/config/configDiscovery.js +80 -0
  7. package/dist/config/configDiscovery.js.map +1 -0
  8. package/dist/config/defaults.d.ts +21 -21
  9. package/dist/config/defaults.js +11 -9
  10. package/dist/config/defaults.js.map +1 -1
  11. package/dist/config/filesystem-tools.d.ts +41 -0
  12. package/dist/config/filesystem-tools.js +56 -0
  13. package/dist/config/filesystem-tools.js.map +1 -0
  14. package/dist/config/jsonc.d.ts +12 -0
  15. package/dist/config/jsonc.js +41 -0
  16. package/dist/config/jsonc.js.map +1 -0
  17. package/dist/config/loader.d.ts +165 -6
  18. package/dist/config/loader.js +963 -109
  19. package/dist/config/loader.js.map +1 -1
  20. package/dist/config/mouse.d.ts +50 -0
  21. package/dist/config/mouse.js +44 -0
  22. package/dist/config/mouse.js.map +1 -0
  23. package/dist/config/profiles.d.ts +68 -0
  24. package/dist/config/profiles.js +93 -0
  25. package/dist/config/profiles.js.map +1 -0
  26. package/dist/config/providerKeys.d.ts +69 -0
  27. package/dist/config/providerKeys.js +69 -0
  28. package/dist/config/providerKeys.js.map +1 -0
  29. package/dist/config/schema.d.ts +2695 -130
  30. package/dist/config/schema.js +1385 -68
  31. package/dist/config/schema.js.map +1 -1
  32. package/dist/config/shell-policy.d.ts +899 -111
  33. package/dist/config/shell-policy.js +800 -70
  34. package/dist/config/shell-policy.js.map +1 -1
  35. package/dist/config/tool-descriptions.d.ts +211 -0
  36. package/dist/config/tool-descriptions.js +272 -0
  37. package/dist/config/tool-descriptions.js.map +1 -0
  38. package/dist/config/types.d.ts +372 -34
  39. package/dist/config/types.js +1 -0
  40. package/dist/config/types.js.map +1 -1
  41. package/dist/config.d.ts +35 -1
  42. package/dist/config.js +16 -1
  43. package/dist/config.js.map +1 -1
  44. package/dist/constants.d.ts +45 -0
  45. package/dist/constants.js +45 -0
  46. package/dist/constants.js.map +1 -1
  47. package/dist/core/GthAbstractAgent.d.ts +190 -11
  48. package/dist/core/GthAbstractAgent.js +551 -35
  49. package/dist/core/GthAbstractAgent.js.map +1 -1
  50. package/dist/core/GthAgentRunner.d.ts +565 -57
  51. package/dist/core/GthAgentRunner.js +1546 -140
  52. package/dist/core/GthAgentRunner.js.map +1 -1
  53. package/dist/core/GthLangChainAgent.d.ts +117 -2
  54. package/dist/core/GthLangChainAgent.js +644 -18
  55. package/dist/core/GthLangChainAgent.js.map +1 -1
  56. package/dist/core/approvals/annotations.d.ts +122 -0
  57. package/dist/core/approvals/annotations.js +137 -0
  58. package/dist/core/approvals/annotations.js.map +1 -0
  59. package/dist/core/approvals/grants.d.ts +216 -0
  60. package/dist/core/approvals/grants.js +469 -0
  61. package/dist/core/approvals/grants.js.map +1 -0
  62. package/dist/core/approvals/matcher.d.ts +202 -0
  63. package/dist/core/approvals/matcher.js +267 -0
  64. package/dist/core/approvals/matcher.js.map +1 -0
  65. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  66. package/dist/core/approvals/mcpSubjects.js +99 -0
  67. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  68. package/dist/core/approvals/promptHeader.d.ts +28 -0
  69. package/dist/core/approvals/promptHeader.js +62 -0
  70. package/dist/core/approvals/promptHeader.js.map +1 -0
  71. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  72. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  73. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  74. package/dist/core/approvals/toolHost.d.ts +46 -0
  75. package/dist/core/approvals/toolHost.js +108 -0
  76. package/dist/core/approvals/toolHost.js.map +1 -0
  77. package/dist/core/debugCapture.d.ts +74 -0
  78. package/dist/core/debugCapture.js +100 -0
  79. package/dist/core/debugCapture.js.map +1 -0
  80. package/dist/core/gthLeanAgentFactory.d.ts +9 -0
  81. package/dist/core/gthLeanAgentFactory.js +10 -0
  82. package/dist/core/gthLeanAgentFactory.js.map +1 -0
  83. package/dist/core/launchBanner.d.ts +127 -0
  84. package/dist/core/launchBanner.js +414 -0
  85. package/dist/core/launchBanner.js.map +1 -0
  86. package/dist/core/modelLabel.d.ts +19 -0
  87. package/dist/core/modelLabel.js +26 -0
  88. package/dist/core/modelLabel.js.map +1 -0
  89. package/dist/core/plainToolIndication.d.ts +15 -0
  90. package/dist/core/plainToolIndication.js +174 -0
  91. package/dist/core/plainToolIndication.js.map +1 -0
  92. package/dist/core/reasoningBlocks.d.ts +65 -0
  93. package/dist/core/reasoningBlocks.js +103 -0
  94. package/dist/core/reasoningBlocks.js.map +1 -0
  95. package/dist/core/refusal.d.ts +53 -0
  96. package/dist/core/refusal.js +133 -0
  97. package/dist/core/refusal.js.map +1 -0
  98. package/dist/core/runHeader.d.ts +38 -0
  99. package/dist/core/runHeader.js +42 -0
  100. package/dist/core/runHeader.js.map +1 -0
  101. package/dist/core/runStats.d.ts +52 -0
  102. package/dist/core/runStats.js +118 -0
  103. package/dist/core/runStats.js.map +1 -0
  104. package/dist/core/shell/ShellCommandFailedError.d.ts +53 -0
  105. package/dist/core/shell/ShellCommandFailedError.js +67 -0
  106. package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
  107. package/dist/core/shell/abstention.d.ts +88 -0
  108. package/dist/core/shell/abstention.js +184 -0
  109. package/dist/core/shell/abstention.js.map +1 -0
  110. package/dist/core/shell/approvalCapture.d.ts +271 -0
  111. package/dist/core/shell/approvalCapture.js +108 -0
  112. package/dist/core/shell/approvalCapture.js.map +1 -0
  113. package/dist/core/shell/approvalStop.d.ts +123 -0
  114. package/dist/core/shell/approvalStop.js +269 -0
  115. package/dist/core/shell/approvalStop.js.map +1 -0
  116. package/dist/core/shell/arity.d.ts +6 -0
  117. package/dist/core/shell/arity.js +20 -6
  118. package/dist/core/shell/arity.js.map +1 -1
  119. package/dist/core/shell/denylist.d.ts +11 -0
  120. package/dist/core/shell/denylist.js +37 -0
  121. package/dist/core/shell/denylist.js.map +1 -0
  122. package/dist/core/shell/escalationSeverity.d.ts +141 -0
  123. package/dist/core/shell/escalationSeverity.js +89 -0
  124. package/dist/core/shell/escalationSeverity.js.map +1 -0
  125. package/dist/core/shell/framing.d.ts +190 -0
  126. package/dist/core/shell/framing.js +633 -0
  127. package/dist/core/shell/framing.js.map +1 -0
  128. package/dist/core/shell/hardline.d.ts +103 -0
  129. package/dist/core/shell/hardline.js +780 -0
  130. package/dist/core/shell/hardline.js.map +1 -0
  131. package/dist/core/shell/negotiation.d.ts +328 -0
  132. package/dist/core/shell/negotiation.js +488 -0
  133. package/dist/core/shell/negotiation.js.map +1 -0
  134. package/dist/core/shell/normalize.d.ts +44 -4
  135. package/dist/core/shell/normalize.js +61 -7
  136. package/dist/core/shell/normalize.js.map +1 -1
  137. package/dist/core/shell/openWorld.d.ts +263 -0
  138. package/dist/core/shell/openWorld.js +1188 -0
  139. package/dist/core/shell/openWorld.js.map +1 -0
  140. package/dist/core/shell/rater.d.ts +873 -0
  141. package/dist/core/shell/rater.js +1454 -0
  142. package/dist/core/shell/rater.js.map +1 -0
  143. package/dist/core/shell/raterModel.d.ts +41 -0
  144. package/dist/core/shell/raterModel.js +51 -0
  145. package/dist/core/shell/raterModel.js.map +1 -0
  146. package/dist/core/shell/raterVocabulary.d.ts +121 -0
  147. package/dist/core/shell/raterVocabulary.js +116 -0
  148. package/dist/core/shell/raterVocabulary.js.map +1 -0
  149. package/dist/core/shell/rejection.d.ts +69 -0
  150. package/dist/core/shell/rejection.js +38 -0
  151. package/dist/core/shell/rejection.js.map +1 -0
  152. package/dist/core/toolCallRepair/grammar.d.ts +41 -0
  153. package/dist/core/toolCallRepair/grammar.js +116 -0
  154. package/dist/core/toolCallRepair/grammar.js.map +1 -0
  155. package/dist/core/toolCallRepair/index.d.ts +2 -0
  156. package/dist/core/toolCallRepair/index.js +7 -0
  157. package/dist/core/toolCallRepair/index.js.map +1 -0
  158. package/dist/core/toolCallRepair/payload.d.ts +36 -0
  159. package/dist/core/toolCallRepair/payload.js +341 -0
  160. package/dist/core/toolCallRepair/payload.js.map +1 -0
  161. package/dist/core/toolCallRepair/promote.d.ts +45 -0
  162. package/dist/core/toolCallRepair/promote.js +90 -0
  163. package/dist/core/toolCallRepair/promote.js.map +1 -0
  164. package/dist/core/toolDisplay.d.ts +123 -0
  165. package/dist/core/toolDisplay.js +451 -0
  166. package/dist/core/toolDisplay.js.map +1 -0
  167. package/dist/core/toolOutputChannel.d.ts +95 -0
  168. package/dist/core/toolOutputChannel.js +165 -0
  169. package/dist/core/toolOutputChannel.js.map +1 -0
  170. package/dist/core/types.d.ts +378 -16
  171. package/dist/core/types.js.map +1 -1
  172. package/dist/history/historyFormat.d.ts +28 -0
  173. package/dist/history/historyFormat.js +127 -0
  174. package/dist/history/historyFormat.js.map +1 -0
  175. package/dist/history/historyStore.d.ts +198 -0
  176. package/dist/history/historyStore.js +482 -0
  177. package/dist/history/historyStore.js.map +1 -0
  178. package/dist/history/recordSession.d.ts +37 -0
  179. package/dist/history/recordSession.js +56 -0
  180. package/dist/history/recordSession.js.map +1 -0
  181. package/dist/index.d.ts +4 -0
  182. package/dist/index.js +4 -0
  183. package/dist/index.js.map +1 -1
  184. package/dist/providers/anthropic.d.ts +1 -1
  185. package/dist/providers/anthropic.js +17 -10
  186. package/dist/providers/anthropic.js.map +1 -1
  187. package/dist/providers/configurationPassthrough.d.ts +107 -0
  188. package/dist/providers/configurationPassthrough.js +148 -0
  189. package/dist/providers/configurationPassthrough.js.map +1 -0
  190. package/dist/providers/deepseek.d.ts +1 -1
  191. package/dist/providers/deepseek.js +5 -10
  192. package/dist/providers/deepseek.js.map +1 -1
  193. package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
  194. package/dist/providers/geminiSchemaSanitizer.js +347 -0
  195. package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
  196. package/dist/providers/geminiThinking.d.ts +60 -0
  197. package/dist/providers/geminiThinking.js +92 -0
  198. package/dist/providers/geminiThinking.js.map +1 -0
  199. package/dist/providers/google-genai.d.ts +1 -1
  200. package/dist/providers/google-genai.js +23 -11
  201. package/dist/providers/google-genai.js.map +1 -1
  202. package/dist/providers/groq.d.ts +1 -1
  203. package/dist/providers/groq.js +17 -10
  204. package/dist/providers/groq.js.map +1 -1
  205. package/dist/providers/huggingface.d.ts +25 -0
  206. package/dist/providers/huggingface.js +69 -0
  207. package/dist/providers/huggingface.js.map +1 -0
  208. package/dist/providers/modelCatalog.d.ts +109 -0
  209. package/dist/providers/modelCatalog.js +245 -0
  210. package/dist/providers/modelCatalog.js.map +1 -0
  211. package/dist/providers/modelDiscovery.d.ts +153 -6
  212. package/dist/providers/modelDiscovery.js +295 -41
  213. package/dist/providers/modelDiscovery.js.map +1 -1
  214. package/dist/providers/ollama.d.ts +19 -5
  215. package/dist/providers/ollama.js +72 -52
  216. package/dist/providers/ollama.js.map +1 -1
  217. package/dist/providers/openai.d.ts +1 -1
  218. package/dist/providers/openai.js +39 -10
  219. package/dist/providers/openai.js.map +1 -1
  220. package/dist/providers/openrouter.d.ts +27 -5
  221. package/dist/providers/openrouter.js +88 -36
  222. package/dist/providers/openrouter.js.map +1 -1
  223. package/dist/providers/vertexai.d.ts +1 -1
  224. package/dist/providers/vertexai.js +24 -11
  225. package/dist/providers/vertexai.js.map +1 -1
  226. package/dist/providers/xai.d.ts +1 -1
  227. package/dist/providers/xai.js +25 -10
  228. package/dist/providers/xai.js.map +1 -1
  229. package/dist/runtime/askStructured.d.ts +105 -0
  230. package/dist/runtime/askStructured.js +120 -0
  231. package/dist/runtime/askStructured.js.map +1 -0
  232. package/dist/runtime/conversation.d.ts +64 -0
  233. package/dist/runtime/conversation.js +171 -0
  234. package/dist/runtime/conversation.js.map +1 -0
  235. package/dist/runtime/singleShot.d.ts +39 -5
  236. package/dist/runtime/singleShot.js +115 -36
  237. package/dist/runtime/singleShot.js.map +1 -1
  238. package/dist/runtime/structuredOutput.d.ts +104 -0
  239. package/dist/runtime/structuredOutput.js +393 -0
  240. package/dist/runtime/structuredOutput.js.map +1 -0
  241. package/dist/utils/ProgressIndicator.d.ts +21 -0
  242. package/dist/utils/ProgressIndicator.js +30 -3
  243. package/dist/utils/ProgressIndicator.js.map +1 -1
  244. package/dist/utils/aiignoreUtils.js.map +1 -1
  245. package/dist/utils/binaryOutputUtils.js.map +1 -1
  246. package/dist/utils/consoleUtils.d.ts +95 -0
  247. package/dist/utils/consoleUtils.js +112 -2
  248. package/dist/utils/consoleUtils.js.map +1 -1
  249. package/dist/utils/crashHandler.d.ts +87 -0
  250. package/dist/utils/crashHandler.js +128 -0
  251. package/dist/utils/crashHandler.js.map +1 -0
  252. package/dist/utils/debugDump.d.ts +134 -0
  253. package/dist/utils/debugDump.js +381 -0
  254. package/dist/utils/debugDump.js.map +1 -0
  255. package/dist/utils/debugUtils.d.ts +13 -4
  256. package/dist/utils/debugUtils.js +36 -13
  257. package/dist/utils/debugUtils.js.map +1 -1
  258. package/dist/utils/displayWidth.d.ts +53 -0
  259. package/dist/utils/displayWidth.js +195 -0
  260. package/dist/utils/displayWidth.js.map +1 -0
  261. package/dist/utils/fileUtils.d.ts +20 -1
  262. package/dist/utils/fileUtils.js +35 -2
  263. package/dist/utils/fileUtils.js.map +1 -1
  264. package/dist/utils/llmUtils.d.ts +39 -8
  265. package/dist/utils/llmUtils.js +76 -8
  266. package/dist/utils/llmUtils.js.map +1 -1
  267. package/dist/utils/redactSecrets.d.ts +63 -0
  268. package/dist/utils/redactSecrets.js +286 -0
  269. package/dist/utils/redactSecrets.js.map +1 -0
  270. package/dist/utils/systemPromptNotes.d.ts +222 -0
  271. package/dist/utils/systemPromptNotes.js +338 -0
  272. package/dist/utils/systemPromptNotes.js.map +1 -0
  273. package/dist/utils/systemUtils.d.ts +18 -1
  274. package/dist/utils/systemUtils.js +38 -3
  275. package/dist/utils/systemUtils.js.map +1 -1
  276. package/dist/utils/toolMatching.d.ts +30 -0
  277. package/dist/utils/toolMatching.js +44 -0
  278. package/dist/utils/toolMatching.js.map +1 -0
  279. package/dist/utils/untrustedText.d.ts +86 -0
  280. package/dist/utils/untrustedText.js +101 -0
  281. package/dist/utils/untrustedText.js.map +1 -0
  282. package/package.json +21 -6
  283. package/schema/gsloth-config.schema.json +1979 -353
  284. package/dist/core/shell/allowlist.d.ts +0 -75
  285. package/dist/core/shell/allowlist.js +0 -187
  286. package/dist/core/shell/allowlist.js.map +0 -1
  287. package/dist/core/shell/judge.d.ts +0 -161
  288. package/dist/core/shell/judge.js +0 -261
  289. package/dist/core/shell/judge.js.map +0 -1
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @module runtime/askStructured
3
+ *
4
+ * A reusable, non-agentic "ask the LLM and get a schema-validated object back" primitive — the
5
+ * structured-output half the (later) `gth workflow` host calls. Deliberately mirrors the
6
+ * *mechanism* of the structured-evaluation calls ({@link judgeEvalCase} — the EVAL GRADER in
7
+ * `@gaunt-sloth/batch`'s `judge.ts` — and its in-core sibling {@link rateShellCommand}, the
8
+ * approvals AI rater in `core/shell/rater.ts`):
9
+ * `model.withStructuredOutput(schema)` for a single structured call, `.invoke([SystemMessage,
10
+ * HumanMessage])` raced against a wall-clock timeout via `Promise.race`, a defensive `safeParse`
11
+ * re-validation, `clearTimeout` in `finally`, and — crucially — it **never throws**, returning a
12
+ * failure object instead.
13
+ *
14
+ * Differences from those two: this one is **generic** over the Zod schema and takes the
15
+ * system/user strings from the caller (they hard-code a schema and build a rubric/safety
16
+ * prompt), and it reads the model from `config.llm` (like `runSingleShot`/`rateShellCommand`),
17
+ * so the workflow host can hand it the resolved {@link GthConfig} directly. `judgeEvalCase` could
18
+ * later be refactored to delegate to this primitive — out of scope here.
19
+ */
20
+ import * as z from 'zod';
21
+ import type { GthConfig } from '#src/config.js';
22
+ /**
23
+ * Default wall-clock budget (ms) for the structured LLM call — same value as their
24
+ * `EVAL_JUDGE_DEFAULT_TIMEOUT_MS` / `JUDGE_DEFAULT_TIMEOUT_MS`, kept as this module's own constant
25
+ * since the primitive is conceptually independent of them.
26
+ */
27
+ export declare const ASK_STRUCTURED_DEFAULT_TIMEOUT_MS = 30000;
28
+ /**
29
+ * The exact `error` text {@link askStructured} returns when its wall-clock budget fires — i.e. the
30
+ * provider produced **no answer at all** within the budget.
31
+ *
32
+ * Exported as a builder rather than left as an inline literal because the three failure classes here
33
+ * (no response · an answer that does not fit the schema · a call that failed outright) are the
34
+ * question a reader of a red run has to answer first, and they are only distinguishable by this text.
35
+ * A caller that re-types the sentence to recognise it stops recognising it the day the wording moves,
36
+ * and silently re-files a stall as a rejection — which is precisely the confusion this exists to end.
37
+ */
38
+ export declare function structuredCallTimedOutError(timeoutMs: number): string;
39
+ /**
40
+ * The exact `error` text {@link askStructured} returns when the provider **did** answer and the
41
+ * answer failed the caller's own schema. The counterpart of {@link structuredCallTimedOutError};
42
+ * see that doc for why both are exported rather than written inline.
43
+ */
44
+ export declare const STRUCTURED_CALL_UNPARSEABLE_ERROR = "Model returned unparseable output.";
45
+ /**
46
+ * Which of {@link askStructured}'s three failure paths a result came from.
47
+ *
48
+ * - `timeout` — the budget fired and the provider had returned nothing at all.
49
+ * - `unparseable` — the provider answered, and the answer failed the caller's schema.
50
+ * - `call-failed` — the call threw before any answer arrived: a provider rejection (OpenAI's 400 on
51
+ * a strict `json_schema` is the canonical one), an auth failure, a transport failure.
52
+ */
53
+ export type StructuredFailureKind = 'timeout' | 'unparseable' | 'call-failed';
54
+ /**
55
+ * Name which failure happened, given the `error` string and the budget the call was made with.
56
+ *
57
+ * The single place the three classes are told apart. A stall and a rejection are the two failures a
58
+ * reader has to distinguish first and the two that look most alike from the outside — a red that
59
+ * cannot say which happened sends the reader to re-run a real defect, or to investigate a provider
60
+ * hiccup. Every caller that wants to say which one it was calls this rather than matching the prose
61
+ * itself, so the wording can only be got wrong in one place, and that place has a test.
62
+ *
63
+ * @param error The `error` from a `{ ok: false }` {@link AskStructuredResult}.
64
+ * @param timeoutMs The budget passed to that same {@link askStructured} call — the timeout text
65
+ * carries it, so a different value here reports a genuine timeout as `call-failed`.
66
+ */
67
+ export declare function classifyStructuredFailure(error: string, timeoutMs: number): StructuredFailureKind;
68
+ /** Inputs to {@link askStructured}. The caller supplies the model (via config), the two message
69
+ * texts, and an optional timeout — the Zod schema is a separate positional argument so `<T>` can
70
+ * be inferred from it. */
71
+ export interface AskStructuredOptions {
72
+ config: GthConfig;
73
+ /** System-message text (instructions). May be empty. */
74
+ system: string;
75
+ /** Human-message text (the actual content/question). */
76
+ user: string;
77
+ /** Wall-clock budget in ms. Default {@link ASK_STRUCTURED_DEFAULT_TIMEOUT_MS} (30_000). */
78
+ timeoutMs?: number;
79
+ }
80
+ /** Discriminated result of {@link askStructured}: the parsed value on success, an error string on
81
+ * any failure (unusable model, timeout, unparseable output, or a thrown error). Never throws. */
82
+ export type AskStructuredResult<T> = {
83
+ ok: true;
84
+ value: T;
85
+ } | {
86
+ ok: false;
87
+ error: string;
88
+ };
89
+ /**
90
+ * Ask the configured model for a single schema-validated object — a non-agentic structured-output
91
+ * call that mirrors their mechanism (see the module doc) and never throws.
92
+ *
93
+ * - No usable model (`config.llm` missing or lacking `withStructuredOutput`) →
94
+ * `{ ok: false, error: 'No usable model configured.' }`.
95
+ * - Timeout → `ok: false` with the error {@link structuredCallTimedOutError} builds for that budget.
96
+ * - Output that fails `schema.safeParse` → `{ ok: false, error: {@link STRUCTURED_CALL_UNPARSEABLE_ERROR} }`.
97
+ * - Any thrown error → `{ ok: false, error: <message> }` — a provider rejection (e.g. OpenAI's 400 on
98
+ * a strict `json_schema`), an auth or transport failure. Distinct from the two above BY CODE PATH,
99
+ * so a caller can classify a failure by comparing against those two exported texts.
100
+ * - Success → `{ ok: true, value }` with the parsed data.
101
+ *
102
+ * @param schema The Zod schema the model output must satisfy; `<T>` is inferred from it.
103
+ * @param opts The model (via `config.llm`), the system/user message texts, and an optional timeout.
104
+ */
105
+ export declare function askStructured<T>(schema: z.ZodType<T>, opts: AskStructuredOptions): Promise<AskStructuredResult<T>>;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @module runtime/askStructured
3
+ *
4
+ * A reusable, non-agentic "ask the LLM and get a schema-validated object back" primitive — the
5
+ * structured-output half the (later) `gth workflow` host calls. Deliberately mirrors the
6
+ * *mechanism* of the structured-evaluation calls ({@link judgeEvalCase} — the EVAL GRADER in
7
+ * `@gaunt-sloth/batch`'s `judge.ts` — and its in-core sibling {@link rateShellCommand}, the
8
+ * approvals AI rater in `core/shell/rater.ts`):
9
+ * `model.withStructuredOutput(schema)` for a single structured call, `.invoke([SystemMessage,
10
+ * HumanMessage])` raced against a wall-clock timeout via `Promise.race`, a defensive `safeParse`
11
+ * re-validation, `clearTimeout` in `finally`, and — crucially — it **never throws**, returning a
12
+ * failure object instead.
13
+ *
14
+ * Differences from those two: this one is **generic** over the Zod schema and takes the
15
+ * system/user strings from the caller (they hard-code a schema and build a rubric/safety
16
+ * prompt), and it reads the model from `config.llm` (like `runSingleShot`/`rateShellCommand`),
17
+ * so the workflow host can hand it the resolved {@link GthConfig} directly. `judgeEvalCase` could
18
+ * later be refactored to delegate to this primitive — out of scope here.
19
+ */
20
+ import { HumanMessage, SystemMessage } from '@langchain/core/messages';
21
+ import { structuredOutputBoundary } from '#src/runtime/structuredOutput.js';
22
+ /**
23
+ * Default wall-clock budget (ms) for the structured LLM call — same value as their
24
+ * `EVAL_JUDGE_DEFAULT_TIMEOUT_MS` / `JUDGE_DEFAULT_TIMEOUT_MS`, kept as this module's own constant
25
+ * since the primitive is conceptually independent of them.
26
+ */
27
+ export const ASK_STRUCTURED_DEFAULT_TIMEOUT_MS = 30_000;
28
+ /**
29
+ * The exact `error` text {@link askStructured} returns when its wall-clock budget fires — i.e. the
30
+ * provider produced **no answer at all** within the budget.
31
+ *
32
+ * Exported as a builder rather than left as an inline literal because the three failure classes here
33
+ * (no response · an answer that does not fit the schema · a call that failed outright) are the
34
+ * question a reader of a red run has to answer first, and they are only distinguishable by this text.
35
+ * A caller that re-types the sentence to recognise it stops recognising it the day the wording moves,
36
+ * and silently re-files a stall as a rejection — which is precisely the confusion this exists to end.
37
+ */
38
+ export function structuredCallTimedOutError(timeoutMs) {
39
+ return `Structured call timed out after ${timeoutMs}ms with no response from the provider.`;
40
+ }
41
+ /**
42
+ * The exact `error` text {@link askStructured} returns when the provider **did** answer and the
43
+ * answer failed the caller's own schema. The counterpart of {@link structuredCallTimedOutError};
44
+ * see that doc for why both are exported rather than written inline.
45
+ */
46
+ export const STRUCTURED_CALL_UNPARSEABLE_ERROR = 'Model returned unparseable output.';
47
+ /**
48
+ * Name which failure happened, given the `error` string and the budget the call was made with.
49
+ *
50
+ * The single place the three classes are told apart. A stall and a rejection are the two failures a
51
+ * reader has to distinguish first and the two that look most alike from the outside — a red that
52
+ * cannot say which happened sends the reader to re-run a real defect, or to investigate a provider
53
+ * hiccup. Every caller that wants to say which one it was calls this rather than matching the prose
54
+ * itself, so the wording can only be got wrong in one place, and that place has a test.
55
+ *
56
+ * @param error The `error` from a `{ ok: false }` {@link AskStructuredResult}.
57
+ * @param timeoutMs The budget passed to that same {@link askStructured} call — the timeout text
58
+ * carries it, so a different value here reports a genuine timeout as `call-failed`.
59
+ */
60
+ export function classifyStructuredFailure(error, timeoutMs) {
61
+ if (error === structuredCallTimedOutError(timeoutMs))
62
+ return 'timeout';
63
+ if (error === STRUCTURED_CALL_UNPARSEABLE_ERROR)
64
+ return 'unparseable';
65
+ return 'call-failed';
66
+ }
67
+ /**
68
+ * Ask the configured model for a single schema-validated object — a non-agentic structured-output
69
+ * call that mirrors their mechanism (see the module doc) and never throws.
70
+ *
71
+ * - No usable model (`config.llm` missing or lacking `withStructuredOutput`) →
72
+ * `{ ok: false, error: 'No usable model configured.' }`.
73
+ * - Timeout → `ok: false` with the error {@link structuredCallTimedOutError} builds for that budget.
74
+ * - Output that fails `schema.safeParse` → `{ ok: false, error: {@link STRUCTURED_CALL_UNPARSEABLE_ERROR} }`.
75
+ * - Any thrown error → `{ ok: false, error: <message> }` — a provider rejection (e.g. OpenAI's 400 on
76
+ * a strict `json_schema`), an auth or transport failure. Distinct from the two above BY CODE PATH,
77
+ * so a caller can classify a failure by comparing against those two exported texts.
78
+ * - Success → `{ ok: true, value }` with the parsed data.
79
+ *
80
+ * @param schema The Zod schema the model output must satisfy; `<T>` is inferred from it.
81
+ * @param opts The model (via `config.llm`), the system/user message texts, and an optional timeout.
82
+ */
83
+ export async function askStructured(schema, opts) {
84
+ const { config, system, user } = opts;
85
+ const timeoutMs = opts.timeoutMs ?? ASK_STRUCTURED_DEFAULT_TIMEOUT_MS;
86
+ const model = config.llm;
87
+ if (!model || typeof model.withStructuredOutput !== 'function') {
88
+ return { ok: false, error: 'No usable model configured.' };
89
+ }
90
+ let timer;
91
+ try {
92
+ // EXT-88 — caller schemas are arbitrary, so this is the call site with the widest exposure to
93
+ // the optional-field problem the boundary exists for; it also does the defensive re-validation
94
+ // below, against the caller's own schema, so `<T>` is exactly what the caller declared.
95
+ const boundary = structuredOutputBoundary(schema);
96
+ const structured = model.withStructuredOutput(boundary.wireSchema);
97
+ const invokePromise = structured.invoke([new SystemMessage(system), new HumanMessage(user)]);
98
+ const TIMEOUT = Symbol('ask-structured-timeout');
99
+ const timeoutPromise = new Promise((resolve) => {
100
+ timer = setTimeout(() => resolve(TIMEOUT), timeoutMs);
101
+ });
102
+ const raced = await Promise.race([invokePromise, timeoutPromise]);
103
+ if (raced === TIMEOUT) {
104
+ return { ok: false, error: structuredCallTimedOutError(timeoutMs) };
105
+ }
106
+ const parsed = boundary.safeParse(raced);
107
+ if (!parsed.success) {
108
+ return { ok: false, error: STRUCTURED_CALL_UNPARSEABLE_ERROR };
109
+ }
110
+ return { ok: true, value: parsed.data };
111
+ }
112
+ catch (error) {
113
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
114
+ }
115
+ finally {
116
+ if (timer)
117
+ clearTimeout(timer);
118
+ }
119
+ }
120
+ //# sourceMappingURL=askStructured.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"askStructured.js","sourceRoot":"","sources":["../../src/runtime/askStructured.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAIvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;GAIG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,MAAM,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,UAAU,2BAA2B,CAAC,SAAiB;IAC3D,OAAO,mCAAmC,SAAS,wCAAwC,CAAC;AAC9F,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,oCAAoC,CAAC;AAYtF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAa,EAAE,SAAiB;IACxE,IAAI,KAAK,KAAK,2BAA2B,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACvE,IAAI,KAAK,KAAK,iCAAiC;QAAE,OAAO,aAAa,CAAC;IACtE,OAAO,aAAa,CAAC;AACvB,CAAC;AAmBD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAoB,EACpB,IAA0B;IAE1B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,iCAAiC,CAAC;IAEtE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC;IACzB,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,oBAAoB,KAAK,UAAU,EAAE,CAAC;QAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC;IAC7D,CAAC;IAED,IAAI,KAAgD,CAAC;IACrD,IAAI,CAAC;QACH,8FAA8F;QAC9F,+FAA+F;QAC/F,wFAAwF;QACxF,MAAM,QAAQ,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,UAAU,GAAG,KAAK,CAAC,oBAAoB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACnE,MAAM,aAAa,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAE7F,MAAM,OAAO,GAAG,MAAM,CAAC,wBAAwB,CAAC,CAAC;QACjD,MAAM,cAAc,GAAG,IAAI,OAAO,CAAiB,CAAC,OAAO,EAAE,EAAE;YAC7D,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC,CAAC;QAClE,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,2BAA2B,CAAC,SAAS,CAAC,EAAE,CAAC;QACtE,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC;QACjE,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACtF,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;AACH,CAAC"}
@@ -0,0 +1,64 @@
1
+ import type { GthConfig } from '#src/config.js';
2
+ import type { AgentResolvers, GthAgentFactory, GthCommand } from '#src/core/types.js';
3
+ import type { SingleShotOptions } from '#src/runtime/singleShot.js';
4
+ import type { GthRunStats } from '#src/core/types.js';
5
+ /**
6
+ * One turn's result inside a {@link runConversation} run: the per-turn `ok`/`answer` plus that
7
+ * turn's run stats (GS2-16 {@link GthRunStats} — token usage + invoked tools), captured PER TURN (a
8
+ * per-invoke delta, not the cumulative conversation total). `ok` is `false` when that turn's agent
9
+ * invocation failed (`error` set, `answer` empty). Extends `GthRunStats` rather than restating its
10
+ * fields — same shape {@link ../runtime/singleShot.js SingleShotResult} uses.
11
+ */
12
+ export interface ConversationTurnResult extends GthRunStats {
13
+ /** `true` when this turn completed without error, `false` when it failed. */
14
+ ok: boolean;
15
+ /** This turn's full answer text (`runner.processMessages()`'s return value). Empty on failure. */
16
+ answer: string;
17
+ /** Set when `ok` is `false`: why this turn failed. */
18
+ error?: string;
19
+ }
20
+ /**
21
+ * Run a scripted MULTI-TURN conversation and return one {@link ConversationTurnResult} per turn.
22
+ *
23
+ * This is the **conversational** counterpart to {@link ../runtime/singleShot.js runSingleShot}
24
+ * (which is stateless — a fresh agent per call). It builds the agent + resolves tools ONCE, then
25
+ * runs each turn against the ACCUMULATED message history so cross-turn "memory" / identity behaviour
26
+ * is real, and cleans up ONCE at the end (reusing runSingleShot's cleanup discipline — the resolvers
27
+ * are the caller's to tear down, exactly as with runSingleShot).
28
+ *
29
+ * **History mechanism = stateless replay of the growing message array.** Messages accumulate as
30
+ * `[user1, ai1, user2, ai2, …]`: per turn a `HumanMessage(user)` is appended, the agent runs on the
31
+ * WHOLE array, and its answer is appended as an `AIMessage` so the next turn sees it. The system
32
+ * prompt is NOT seeded here — the agent composes it via `createAgent({ systemPrompt })` (BATCH-13;
33
+ * see `runSingleShot`), so the replayed array carries only human/assistant turns. Before each
34
+ * turn the runner's thread is rotated ({@link GthAgentRunner.resetThread}) so the checkpointer starts
35
+ * empty and the replayed array is the sole history (no `add_messages` double-append). This mirrors
36
+ * the AG-UI server's "client is the source of truth for history — it sends the full message list
37
+ * every turn" model and reuses the existing `processMessages` + `resetThread` machinery with no new
38
+ * agent surface. **Known limitation (unverified pending a live pass):** replay carries prior
39
+ * *answers* (as `AIMessage` text) but NOT prior tool-call / tool-result messages — a checkpointer-
40
+ * thread approach (send only the new message, let `add_messages` accumulate) would preserve those.
41
+ *
42
+ * **Per-turn tool capture (GS2-16):** `processMessages` resets the analytics tally at its top, so
43
+ * `getRunStats()` read right after each turn returns THAT turn's tool/token delta (not cumulative).
44
+ *
45
+ * A turn that fails is recorded (`ok:false`, `error`) and the conversation STOPS (later turns depend
46
+ * on the broken context), so the returned array may be shorter than `userMessages` — the caller
47
+ * (`gth eval`'s runner) fails the un-run turns.
48
+ *
49
+ * @param source - The source label (used for output/session-file naming), e.g. `EVAL-<cellId>`.
50
+ * @param _preamble - Deprecated/ignored (BATCH-13): the agent composes the system prompt itself (via
51
+ * `createAgent({ systemPrompt })`); seeding it here too produced a second system message that
52
+ * `@langchain/anthropic` rejects. Retained positionally so existing callers need no change.
53
+ * @param userMessages - The ordered user turns to send (one conversation).
54
+ * @param config - The resolved config.
55
+ * @param resolvers - Optional agent resolvers (tools/middleware); the caller owns their cleanup.
56
+ * @param command - The originating command (defaults to `ask`); selects the agent mode prompt.
57
+ * @param agentFactory - Optional backend factory (B5); omitted = the runner's lean default, which
58
+ * is also what the shared backend seam resolves to.
59
+ * @param options - GS2-95: `displayCommand` names the run in the header when the caller's own name
60
+ * differs from the `command` it runs under (`gth eval` runs its conversations in `ask` mode).
61
+ * Header only — it never reaches the mode prompt.
62
+ * @returns One {@link ConversationTurnResult} per turn attempted, in turn order.
63
+ */
64
+ export declare function runConversation(source: string, _preamble: string, userMessages: string[], config: GthConfig, resolvers?: AgentResolvers, command?: GthCommand, agentFactory?: GthAgentFactory, options?: SingleShotOptions): Promise<ConversationTurnResult[]>;
@@ -0,0 +1,171 @@
1
+ import { defaultStatusCallback, display, displayError, displaySuccess, flushSessionLog, initSessionLogging, stopSessionLogging, } from '#src/utils/consoleUtils.js';
2
+ import { getCommandOutputFilePath } from '#src/utils/fileUtils.js';
3
+ import { GthAgentRunner } from '#src/core/GthAgentRunner.js';
4
+ import { MemorySaver } from '@langchain/langgraph';
5
+ import { AIMessage, HumanMessage } from '@langchain/core/messages';
6
+ import { ProgressIndicator } from '#src/utils/ProgressIndicator.js';
7
+ import { recordSessionSafe } from '#src/history/recordSession.js';
8
+ import { getProjectDir, stdout } from '#src/utils/systemUtils.js';
9
+ import { ApprovalStopError, approvalStopRows } from '#src/core/shell/approvalStop.js';
10
+ /**
11
+ * Run a scripted MULTI-TURN conversation and return one {@link ConversationTurnResult} per turn.
12
+ *
13
+ * This is the **conversational** counterpart to {@link ../runtime/singleShot.js runSingleShot}
14
+ * (which is stateless — a fresh agent per call). It builds the agent + resolves tools ONCE, then
15
+ * runs each turn against the ACCUMULATED message history so cross-turn "memory" / identity behaviour
16
+ * is real, and cleans up ONCE at the end (reusing runSingleShot's cleanup discipline — the resolvers
17
+ * are the caller's to tear down, exactly as with runSingleShot).
18
+ *
19
+ * **History mechanism = stateless replay of the growing message array.** Messages accumulate as
20
+ * `[user1, ai1, user2, ai2, …]`: per turn a `HumanMessage(user)` is appended, the agent runs on the
21
+ * WHOLE array, and its answer is appended as an `AIMessage` so the next turn sees it. The system
22
+ * prompt is NOT seeded here — the agent composes it via `createAgent({ systemPrompt })` (BATCH-13;
23
+ * see `runSingleShot`), so the replayed array carries only human/assistant turns. Before each
24
+ * turn the runner's thread is rotated ({@link GthAgentRunner.resetThread}) so the checkpointer starts
25
+ * empty and the replayed array is the sole history (no `add_messages` double-append). This mirrors
26
+ * the AG-UI server's "client is the source of truth for history — it sends the full message list
27
+ * every turn" model and reuses the existing `processMessages` + `resetThread` machinery with no new
28
+ * agent surface. **Known limitation (unverified pending a live pass):** replay carries prior
29
+ * *answers* (as `AIMessage` text) but NOT prior tool-call / tool-result messages — a checkpointer-
30
+ * thread approach (send only the new message, let `add_messages` accumulate) would preserve those.
31
+ *
32
+ * **Per-turn tool capture (GS2-16):** `processMessages` resets the analytics tally at its top, so
33
+ * `getRunStats()` read right after each turn returns THAT turn's tool/token delta (not cumulative).
34
+ *
35
+ * A turn that fails is recorded (`ok:false`, `error`) and the conversation STOPS (later turns depend
36
+ * on the broken context), so the returned array may be shorter than `userMessages` — the caller
37
+ * (`gth eval`'s runner) fails the un-run turns.
38
+ *
39
+ * @param source - The source label (used for output/session-file naming), e.g. `EVAL-<cellId>`.
40
+ * @param _preamble - Deprecated/ignored (BATCH-13): the agent composes the system prompt itself (via
41
+ * `createAgent({ systemPrompt })`); seeding it here too produced a second system message that
42
+ * `@langchain/anthropic` rejects. Retained positionally so existing callers need no change.
43
+ * @param userMessages - The ordered user turns to send (one conversation).
44
+ * @param config - The resolved config.
45
+ * @param resolvers - Optional agent resolvers (tools/middleware); the caller owns their cleanup.
46
+ * @param command - The originating command (defaults to `ask`); selects the agent mode prompt.
47
+ * @param agentFactory - Optional backend factory (B5); omitted = the runner's lean default, which
48
+ * is also what the shared backend seam resolves to.
49
+ * @param options - GS2-95: `displayCommand` names the run in the header when the caller's own name
50
+ * differs from the `command` it runs under (`gth eval` runs its conversations in `ask` mode).
51
+ * Header only — it never reaches the mode prompt.
52
+ * @returns One {@link ConversationTurnResult} per turn attempted, in turn order.
53
+ */
54
+ export async function runConversation(source, _preamble, userMessages, config, resolvers, command = 'ask', agentFactory, options) {
55
+ const progressIndicator = config.streamOutput ? undefined : new ProgressIndicator('Thinking.');
56
+ try {
57
+ // Resolve output path and initialize session logging if enabled (same discipline as
58
+ // runSingleShot; a no-op when `writeOutputToFile` is off, as `gth eval` forces it —
59
+ // getCommandOutputFilePath null).
60
+ const filePath = getCommandOutputFilePath(config, source);
61
+ if (filePath) {
62
+ initSessionLogging(filePath, config.streamSessionInferenceLog);
63
+ }
64
+ // Build the agent + resolve tools ONCE for the whole conversation (the MCP connection / any
65
+ // OAuth / the toolset must persist across turns so cross-turn memory is real). Cleaned up once,
66
+ // in finally.
67
+ const runner = new GthAgentRunner(defaultStatusCallback, resolvers, agentFactory);
68
+ const results = [];
69
+ // The accumulated conversation: [user1, ai1, user2, ai2, …]. Each turn replays the whole array
70
+ // against a freshly-rotated thread (see the doc block). BATCH-13: NO leading SystemMessage — the
71
+ // agent composes the system prompt via `createAgent({ systemPrompt })` (same as runSingleShot);
72
+ // seeding a preamble SystemMessage here too made two system messages, which Anthropic rejects.
73
+ const messages = [];
74
+ try {
75
+ await runner.init(command, config, new MemorySaver(), {
76
+ displayCommand: options?.displayCommand,
77
+ });
78
+ for (const userMessage of userMessages) {
79
+ // Rotate to a fresh (empty) checkpointer thread so this turn's replay of the full `messages`
80
+ // array is the sole history the agent sees — no double-append from a prior checkpoint.
81
+ runner.resetThread();
82
+ messages.push(new HumanMessage(userMessage));
83
+ const startedAt = Date.now();
84
+ let answer = '';
85
+ let ok = true;
86
+ let error;
87
+ try {
88
+ answer = await runner.processMessages(messages);
89
+ // Append this turn's answer so the NEXT turn's replay includes it (cross-turn memory).
90
+ messages.push(new AIMessage(answer));
91
+ }
92
+ catch (err) {
93
+ ok = false;
94
+ error = err instanceof Error ? err.message : String(err);
95
+ // [[TUI-C71]] — the SAME branch `runSingleShot` carries, and for a sharper reason: this
96
+ // surface wires no tool-approval callback either, so every escalation it meets is a
97
+ // §6.2 `NonInteractiveEscalationError` — the error class that carries the command, the
98
+ // rating, the rater's reason AND the whole negotiation transcript. The hostile path is
99
+ // not an edge case here, it is the only path. `error` above keeps the neutralised
100
+ // message for the turn record, which is a data consumer and wants the string.
101
+ if (err instanceof ApprovalStopError) {
102
+ displayError('Failed to get answer:');
103
+ for (const row of approvalStopRows(err.parts, { columns: stdout.columns })) {
104
+ displayError(row);
105
+ }
106
+ }
107
+ else {
108
+ displayError(`Failed to get answer: ${error}`);
109
+ }
110
+ }
111
+ // GS2-16: read this turn's token/tool delta from the live agent (before cleanup). Fail-soft
112
+ // — analytics must never affect the run. `processMessages` reset the tally at its top, so
113
+ // this is THIS turn's usage, not the conversation's cumulative total.
114
+ let runStats = { tools: [] };
115
+ try {
116
+ const s = runner.getRunStats?.();
117
+ if (s)
118
+ runStats = s;
119
+ }
120
+ catch {
121
+ /* fail-soft */
122
+ }
123
+ // GS2-7 (B20): opt-in, fail-soft per-turn session history. A no-op unless `history.enabled`.
124
+ recordSessionSafe(config, {
125
+ command,
126
+ project: getProjectDir(),
127
+ model: config.modelDisplayName,
128
+ prompt: userMessage,
129
+ response: answer,
130
+ tokensInput: runStats.tokensInput,
131
+ tokensOutput: runStats.tokensOutput,
132
+ tools: runStats.tools.length > 0 ? runStats.tools : undefined,
133
+ durationMs: Date.now() - startedAt,
134
+ });
135
+ results.push({ ok, answer, error, ...runStats });
136
+ // A failed turn breaks the conversation's context — stop rather than run later turns on it.
137
+ if (!ok)
138
+ break;
139
+ }
140
+ }
141
+ finally {
142
+ await runner.cleanup();
143
+ }
144
+ progressIndicator?.stop();
145
+ if (config.writeOutputToFile === false) {
146
+ display('\n'); // something going on in some terminals, they swallow last line of output
147
+ }
148
+ if (filePath) {
149
+ try {
150
+ flushSessionLog();
151
+ stopSessionLogging();
152
+ displaySuccess(`\n\nThis report can be found in ${filePath}`);
153
+ }
154
+ catch (err) {
155
+ displayError(`Failed to write answer to file: ${filePath}`);
156
+ displayError(err instanceof Error ? err.message : String(err));
157
+ }
158
+ }
159
+ return results;
160
+ }
161
+ finally {
162
+ // EXT-53: the indicator owns a 1s setInterval — an active libuv handle that keeps Node's event
163
+ // loop from ever draining, so leaking it hangs the CLI forever after the work is done. The
164
+ // `stop()` above sits where it does for output ordering (before the trailing newline / the
165
+ // "report can be found in …" line); this `finally` guarantees the handle is also released when
166
+ // anything above throws — notably `runner.init()`, which has no catch, and `runner.cleanup()`.
167
+ // `stop()` is idempotent, so the normal path's second call is a no-op.
168
+ progressIndicator?.stop();
169
+ }
170
+ }
171
+ //# sourceMappingURL=conversation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation.js","sourceRoot":"","sources":["../../src/runtime/conversation.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,30 @@
1
1
  import type { GthConfig } from '#src/config.js';
2
- import type { AgentResolvers, GthCommand } from '#src/core/types.js';
2
+ import type { AgentResolvers, GthAgentFactory, GthCommand } from '#src/core/types.js';
3
+ import type { GthRunStats } from '#src/core/types.js';
4
+ /**
5
+ * Result of a {@link runSingleShot} run: the pass/fail contract callers such as `ask`/`exec` have
6
+ * always used (`ok`), plus the SUT's answer text and run stats (GS2-16's {@link GthRunStats}) that
7
+ * were already computed internally but previously discarded. Extends `GthRunStats` rather than
8
+ * restating `tokensInput`/`tokensOutput`/`tools` as parallel fields.
9
+ */
10
+ export interface SingleShotResult extends GthRunStats {
11
+ /** `true` when the run completed without error, `false` when it failed. */
12
+ ok: boolean;
13
+ /** The SUT's full answer text (`runner.processMessages()`'s return value). Empty on failure. */
14
+ answer: string;
15
+ }
16
+ /** Options that qualify a {@link runSingleShot} run without changing how it behaves. */
17
+ export interface SingleShotOptions {
18
+ /**
19
+ * GS2-95 — the name of the command the USER typed, for the run header (`eval`, `batch`,
20
+ * `workflow`, `gth-batch`). Omit it and the header names `command`, which is right for every
21
+ * caller whose verb IS its name (`ask`, `exec`).
22
+ *
23
+ * It is deliberately NOT `command`: that argument selects the agent's mode prompt, so a caller
24
+ * that renamed itself through it would change which system prompt its runs execute under.
25
+ */
26
+ displayCommand?: string;
27
+ }
3
28
  /**
4
29
  * Ask a question and get an answer from the LLM.
5
30
  *
@@ -8,12 +33,21 @@ import type { AgentResolvers, GthCommand } from '#src/core/types.js';
8
33
  * is forwarded to the agent so it can pick the right mode prompt (e.g. exec-mode for `exec`).
9
34
  *
10
35
  * @param source - The source of the question (used for file naming)
11
- * @param preamble - The preamble to send to the LLM
36
+ * @param _preamble - Deprecated/ignored (BATCH-13): the agent composes the system prompt itself;
37
+ * see the body comment. Retained positionally so existing callers need no change.
12
38
  * @param content - The content of the question
13
39
  * @param config - The resolved config
14
40
  * @param resolvers - Optional agent resolvers (tools/middleware)
15
41
  * @param command - The originating command (defaults to `ask`); selects the agent mode prompt
16
- * @returns `true` when the run completed without error, `false` when it failed (so callers
17
- * such as `exec` can set a non-zero exit code).
42
+ * @param agentFactory - Optional backend factory (B5). When omitted the runner uses its built-in
43
+ * lean {@link GthLangChainAgent} default. The app layer passes `resolveAgentFactory(config,
44
+ * 'lean')`, which resolves to the same agent through the shared backend seam.
45
+ * @param options - GS2-95: `displayCommand` names the run in the header when the caller's own name
46
+ * differs from the `command` it runs under (`gth eval` runs cases in `ask` mode). Header only —
47
+ * it never reaches the mode prompt.
48
+ * @returns A {@link SingleShotResult}: `ok` is `true` when the run completed without error, `false`
49
+ * when it failed (so callers such as `exec` can set a non-zero exit code); `answer`/`tokensInput`/
50
+ * `tokensOutput`/`tools` carry the SUT's answer text and run stats for callers that need them
51
+ * (e.g. `gth batch`/`gth eval`).
18
52
  */
19
- export declare function runSingleShot(source: string, preamble: string, content: string, config: GthConfig, resolvers?: AgentResolvers, command?: GthCommand): Promise<boolean>;
53
+ export declare function runSingleShot(source: string, _preamble: string, content: string, config: GthConfig, resolvers?: AgentResolvers, command?: GthCommand, agentFactory?: GthAgentFactory, options?: SingleShotOptions): Promise<SingleShotResult>;