@hizliemre/horse-code 0.3.0 → 0.4.0

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 (32) hide show
  1. package/dist/{app-5FXHE7GX.js → app-KR7TAHUD.js} +82 -40
  2. package/dist/{chunk-LNW557IO.js → chunk-372X5HHU.js} +2 -2
  3. package/dist/{chunk-XEGQT5EN.js → chunk-4M6LXNG2.js} +1 -1
  4. package/dist/{chunk-6OSEQOYY.js → chunk-6S4WWQMN.js} +2 -2
  5. package/dist/chunk-ACTVFJRW.js +989 -0
  6. package/dist/{chunk-LLL7QWXB.js → chunk-BFIZMM4G.js} +6 -6
  7. package/dist/chunk-CYLPQWIF.js +214 -0
  8. package/dist/{chunk-AE36LLL2.js → chunk-JLWQCA7B.js} +2 -209
  9. package/dist/{chunk-XYZVZPAY.js → chunk-KXBYRU4W.js} +66 -31
  10. package/dist/chunk-MZM24M5M.js +251 -0
  11. package/dist/chunk-NBTH2VVI.js +1945 -0
  12. package/dist/chunk-PIG54WFU.js +2873 -0
  13. package/dist/{run-P6ZYL5JL.js → chunk-QJYVZPLG.js} +133 -389
  14. package/dist/{chunk-YPZP7LYL.js → chunk-UANNVVIU.js} +1 -1
  15. package/dist/{chunk-UGESK765.js → chunk-UTHLEW5V.js} +1 -1
  16. package/dist/{chunk-KAGKX2YT.js → chunk-ZPJP2VH5.js} +10 -1
  17. package/dist/cli.js +262 -69
  18. package/dist/{fix-ONLA45HD.js → fix-SSDUVV4T.js} +11 -8
  19. package/dist/{ongoing-WHYXPW24.js → ongoing-6NUSPSCV.js} +3 -2
  20. package/dist/{project-graph-5HNPRFQG.js → project-graph-OGIM2B33.js} +1 -1
  21. package/dist/research-MHBNZ6SA.js +111 -0
  22. package/dist/run-V5ZLZ3LS.js +274 -0
  23. package/dist/{save-skills-ZW5GY6KV.js → save-skills-NPKTYNAF.js} +2 -2
  24. package/dist/{trace-X6TU3AG6.js → trace-UVMZZRA5.js} +1 -1
  25. package/dist/{trace-adopt-URECQWJV.js → trace-adopt-7HWELJFE.js} +1 -1
  26. package/dist/{trace-run-7U4WJZ3V.js → trace-run-OBOD552Q.js} +9 -4
  27. package/dist/{triage-FCYHD2AQ.js → triage-IFCVL5MA.js} +7 -6
  28. package/dist/{verify-LC57A6H2.js → verify-2DTBG6RG.js} +26 -20
  29. package/package.json +1 -1
  30. package/dist/chunk-MRZVA5JB.js +0 -163
  31. package/dist/chunk-UEWVVN5L.js +0 -5691
  32. package/dist/{chunk-EAF22QIG.js → chunk-JR2JLRE3.js} +3 -3
@@ -3,20 +3,20 @@ import {
3
3
  loadMigratedSync,
4
4
  migratedNotice
5
5
  } from "./chunk-63E73TGI.js";
6
- import {
7
- telemetry
8
- } from "./chunk-AE36LLL2.js";
9
6
  import {
10
7
  readBriefSync
11
- } from "./chunk-6OSEQOYY.js";
8
+ } from "./chunk-6S4WWQMN.js";
9
+ import {
10
+ telemetry
11
+ } from "./chunk-JLWQCA7B.js";
12
12
  import {
13
13
  everTraceable,
14
14
  readTraceSync
15
- } from "./chunk-KAGKX2YT.js";
15
+ } from "./chunk-ZPJP2VH5.js";
16
16
  import {
17
17
  areaOf,
18
18
  loadGraphSync
19
- } from "./chunk-XEGQT5EN.js";
19
+ } from "./chunk-4M6LXNG2.js";
20
20
 
21
21
  // src/tools/read.ts
22
22
  import { readFile } from "fs/promises";
@@ -0,0 +1,214 @@
1
+ import {
2
+ runRoleAgent
3
+ } from "./chunk-JLWQCA7B.js";
4
+
5
+ // src/tools/registry.ts
6
+ import { z } from "zod";
7
+ var ToolRegistry = class {
8
+ tools = /* @__PURE__ */ new Map();
9
+ /**
10
+ * Registered and callable, but whose SCHEMA is withheld until something asks for it.
11
+ *
12
+ * A schema is paid for on every turn, whether or not the tool is ever used. Measured across twelve runs:
13
+ * 49 MCP tool schemas came to 86,620 characters (~21,655 tokens), 242 calls carried them, and that is
14
+ * ~5.2M of the 21.7M input tokens billed — 24% of everything — for FIVE tool calls, of two distinct tools.
15
+ * The catalogue that names them costs 900 characters (see MAX_TOOL_NOTE_CHARS); it is the schemas that are
16
+ * expensive, and a schema nobody is about to use buys nothing.
17
+ */
18
+ deferred = /* @__PURE__ */ new Set();
19
+ /** Bumped by anything that changes what `schemas()` would return, so the derivation can be cached. */
20
+ version = 0;
21
+ cached;
22
+ register(tool) {
23
+ this.tools.set(tool.name, tool);
24
+ this.deferred.delete(tool.name);
25
+ this.version++;
26
+ }
27
+ /** Callable by name from the moment it is registered; sent to the model only once {@link surface}d. */
28
+ registerDeferred(tool) {
29
+ this.tools.set(tool.name, tool);
30
+ this.deferred.add(tool.name);
31
+ this.version++;
32
+ }
33
+ /**
34
+ * Hands over the schemas for these names, from the next turn onward.
35
+ *
36
+ * Returns the ones that were actually withheld, so a caller can say what it just made available and stay
37
+ * quiet about what was already there.
38
+ */
39
+ surface(names) {
40
+ const opened = names.filter((n) => this.deferred.has(n));
41
+ for (const n of opened) this.deferred.delete(n);
42
+ if (opened.length) this.version++;
43
+ return opened;
44
+ }
45
+ /** Everything still withheld — what a search tool searches. */
46
+ deferredTools() {
47
+ return [...this.deferred].map((n) => this.tools.get(n)).filter((t) => t !== void 0);
48
+ }
49
+ /**
50
+ * A withheld tool is still CALLABLE.
51
+ *
52
+ * A model that reads the catalogue and calls the name straight off is right, and refusing it to enforce a
53
+ * search step would spend a turn teaching it a rule that exists for our benefit, not its.
54
+ */
55
+ get(name) {
56
+ return this.tools.get(name);
57
+ }
58
+ list() {
59
+ return [...this.tools.values()];
60
+ }
61
+ /**
62
+ * Tool schemas to send to the LLM: zod parameters → JSON Schema (zod 4 native). Withheld ones are omitted.
63
+ *
64
+ * …and so is a tool that has withdrawn itself. `Tool.broken` was documented as being read "where tools are
65
+ * OFFERED, so a broken one stops being handed to fresh agents" — and only `find_tool` ever read it, which
66
+ * covers the deferred tools and not the ones already on the list.
67
+ *
68
+ * Measured on one run: `mcp__angular-cli__list_projects` answered its first caller with a reply that failed
69
+ * its own declared output schema and withdrew itself. It was then offered to seventeen more agents, who
70
+ * called it twenty-eight more times. Every one of those was answered instantly, without touching the
71
+ * server — and still cost a whole model turn to learn what the run already knew.
72
+ */
73
+ schemas() {
74
+ const withdrawn = this.list().reduce((n, t) => n + (t.broken === void 0 ? 0 : 1), 0);
75
+ if (this.cached?.version === this.version && this.cached.withdrawn === withdrawn) return this.cached.schemas;
76
+ const schemas = this.list().filter((t) => !this.deferred.has(t.name) && t.broken === void 0).map((t) => ({
77
+ name: t.name,
78
+ description: t.description,
79
+ // MCP tools already carry a JSON Schema; everyone else derives it from their zod parameters.
80
+ parameters: t.rawSchema ?? z.toJSONSchema(t.parameters, { target: "draft-7" })
81
+ }));
82
+ this.cached = { version: this.version, withdrawn, schemas };
83
+ return schemas;
84
+ }
85
+ };
86
+
87
+ // src/core/types.ts
88
+ var DEADLINE_MESSAGE = "the model did not answer within its deadline";
89
+ var CHAIN_BUDGET_MESSAGE = "the chain's total budget ran out before this model was given a fair turn";
90
+
91
+ // src/agent/structured.ts
92
+ function valueAt(args, path) {
93
+ let cur = args;
94
+ for (const key of path) {
95
+ if (typeof cur !== "object" || cur === null) return void 0;
96
+ cur = cur[key];
97
+ }
98
+ return cur;
99
+ }
100
+ function whatWasWrong(issues, args) {
101
+ return issues.map((i) => {
102
+ const where = i.path.length ? i.path.join(".") : void 0;
103
+ const got = valueAt(args, i.path);
104
+ const shown = got === void 0 ? "nothing" : JSON.stringify(got);
105
+ const head = where ? `${where}: ${i.message}` : i.message;
106
+ if (got === void 0 && /received\s+(undefined|null|nothing)/i.test(i.message)) {
107
+ const parent = i.path.length > 1 ? valueAt(args, i.path.slice(0, -1)) : args;
108
+ const sent = parent && typeof parent === "object" ? Object.keys(parent) : [];
109
+ return sent.length ? `${head} \u2014 you sent only ${sent.map((k) => `\`${k}\``).join(", ")}` : head;
110
+ }
111
+ return `${head} \u2014 got ${shown.length > 120 ? `${shown.slice(0, 120)}\u2026` : shown}`;
112
+ }).join("; ");
113
+ }
114
+ function buildSubmitTool(schema) {
115
+ let box;
116
+ const tool = {
117
+ name: "submit",
118
+ description: "When you are done, submit your result in structured form with this tool.",
119
+ permissionLevel: "safe",
120
+ parameters: schema,
121
+ run: async (rawArgs) => {
122
+ const parsed = schema.safeParse(rawArgs);
123
+ if (!parsed.success) {
124
+ return { content: `submit: invalid output: ${whatWasWrong(parsed.error.issues, rawArgs)}`, isError: true };
125
+ }
126
+ box = { value: parsed.data };
127
+ return { content: "received", isError: false };
128
+ }
129
+ };
130
+ return { tool, result: () => box };
131
+ }
132
+ function extractStructured(text, schema) {
133
+ const trimmed = text.trim();
134
+ if (!trimmed) return void 0;
135
+ const candidates = [trimmed];
136
+ const block = trimmed.match(/\{[\s\S]*\}/);
137
+ if (block) candidates.push(block[0]);
138
+ for (const c of candidates) {
139
+ try {
140
+ const parsed = schema.safeParse(JSON.parse(c));
141
+ if (parsed.success) return parsed.data;
142
+ } catch {
143
+ }
144
+ }
145
+ return void 0;
146
+ }
147
+ var TURN_LIMIT_RE = /maximum turn count exceeded/i;
148
+ async function runStructuredRole(opts, schema, maxAttempts = 2) {
149
+ const handle = buildSubmitTool(schema);
150
+ const registry = new ToolRegistry();
151
+ for (const t of opts.tools.list()) registry.register(t);
152
+ registry.register(handle.tool);
153
+ const chain = [opts.model, ...opts.fallbacks ?? []];
154
+ const total = opts.totalMs ? AbortSignal.timeout(opts.totalMs) : void 0;
155
+ const outOfTime = () => total?.aborted === true;
156
+ const signalFor = () => {
157
+ const parts = [opts.signal];
158
+ if (total) parts.push(total);
159
+ if (opts.perAttemptMs) parts.push(AbortSignal.timeout(opts.perAttemptMs));
160
+ return parts.length === 1 ? opts.signal : AbortSignal.any(parts);
161
+ };
162
+ let lastError;
163
+ for (let ci = 0; ci < chain.length; ci++) {
164
+ const model = chain[ci];
165
+ if (ci > 0) opts.onFallback?.(chain[ci - 1], model, "structured: previous model returned no valid result");
166
+ const messages = [...opts.messages];
167
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
168
+ if (opts.signal.aborted) throw new Error("cancelled");
169
+ if (outOfTime()) break;
170
+ let lastText = "";
171
+ let errored;
172
+ for await (const ev of runRoleAgent({ ...opts, model, fallbacks: [], messages, tools: registry, signal: signalFor() })) {
173
+ if (ev.type === "error") {
174
+ errored = ev.message;
175
+ break;
176
+ }
177
+ if (ev.type === "abort") {
178
+ if (opts.signal.aborted) throw new Error("cancelled");
179
+ errored = total?.aborted ? CHAIN_BUDGET_MESSAGE : DEADLINE_MESSAGE;
180
+ break;
181
+ }
182
+ if (ev.type === "message.done") lastText = ev.message.content ?? lastText;
183
+ if (handle.result() !== void 0) break;
184
+ }
185
+ const r = handle.result();
186
+ if (r !== void 0) return r.value;
187
+ const salvaged = extractStructured(lastText, schema);
188
+ if (salvaged !== void 0) return salvaged;
189
+ if (errored !== void 0) {
190
+ if (TURN_LIMIT_RE.test(errored) && attempt < maxAttempts - 1) {
191
+ messages.push({ role: "assistant", content: lastText });
192
+ messages.push({ role: "user", content: "You have used your entire tool-call budget. Call `submit` NOW with the findings you already have. Do not read, grep or inspect anything else." });
193
+ continue;
194
+ }
195
+ lastError = errored;
196
+ break;
197
+ }
198
+ messages.push({ role: "assistant", content: lastText });
199
+ messages.push({
200
+ role: "user",
201
+ content: "You did not call the `submit` tool. Call `submit` now with your result as structured arguments \u2014 do not answer in prose."
202
+ });
203
+ }
204
+ if (opts.signal.aborted) throw new Error("cancelled");
205
+ if (outOfTime()) throw new Error("the model chain did not produce a result within its total budget");
206
+ opts.onStructuralFailure?.(model, "answered in prose instead of calling submit");
207
+ }
208
+ throw new Error(lastError ?? "structured role: submit was not called (whole model chain tried)");
209
+ }
210
+
211
+ export {
212
+ ToolRegistry,
213
+ runStructuredRole
214
+ };
@@ -1160,212 +1160,6 @@ async function runToCompletion(opts) {
1160
1160
  return last;
1161
1161
  }
1162
1162
 
1163
- // src/tools/registry.ts
1164
- import { z } from "zod";
1165
- var ToolRegistry = class {
1166
- tools = /* @__PURE__ */ new Map();
1167
- /**
1168
- * Registered and callable, but whose SCHEMA is withheld until something asks for it.
1169
- *
1170
- * A schema is paid for on every turn, whether or not the tool is ever used. Measured across twelve runs:
1171
- * 49 MCP tool schemas came to 86,620 characters (~21,655 tokens), 242 calls carried them, and that is
1172
- * ~5.2M of the 21.7M input tokens billed — 24% of everything — for FIVE tool calls, of two distinct tools.
1173
- * The catalogue that names them costs 900 characters (see MAX_TOOL_NOTE_CHARS); it is the schemas that are
1174
- * expensive, and a schema nobody is about to use buys nothing.
1175
- */
1176
- deferred = /* @__PURE__ */ new Set();
1177
- /** Bumped by anything that changes what `schemas()` would return, so the derivation can be cached. */
1178
- version = 0;
1179
- cached;
1180
- register(tool) {
1181
- this.tools.set(tool.name, tool);
1182
- this.deferred.delete(tool.name);
1183
- this.version++;
1184
- }
1185
- /** Callable by name from the moment it is registered; sent to the model only once {@link surface}d. */
1186
- registerDeferred(tool) {
1187
- this.tools.set(tool.name, tool);
1188
- this.deferred.add(tool.name);
1189
- this.version++;
1190
- }
1191
- /**
1192
- * Hands over the schemas for these names, from the next turn onward.
1193
- *
1194
- * Returns the ones that were actually withheld, so a caller can say what it just made available and stay
1195
- * quiet about what was already there.
1196
- */
1197
- surface(names) {
1198
- const opened = names.filter((n) => this.deferred.has(n));
1199
- for (const n of opened) this.deferred.delete(n);
1200
- if (opened.length) this.version++;
1201
- return opened;
1202
- }
1203
- /** Everything still withheld — what a search tool searches. */
1204
- deferredTools() {
1205
- return [...this.deferred].map((n) => this.tools.get(n)).filter((t) => t !== void 0);
1206
- }
1207
- /**
1208
- * A withheld tool is still CALLABLE.
1209
- *
1210
- * A model that reads the catalogue and calls the name straight off is right, and refusing it to enforce a
1211
- * search step would spend a turn teaching it a rule that exists for our benefit, not its.
1212
- */
1213
- get(name) {
1214
- return this.tools.get(name);
1215
- }
1216
- list() {
1217
- return [...this.tools.values()];
1218
- }
1219
- /**
1220
- * Tool schemas to send to the LLM: zod parameters → JSON Schema (zod 4 native). Withheld ones are omitted.
1221
- *
1222
- * …and so is a tool that has withdrawn itself. `Tool.broken` was documented as being read "where tools are
1223
- * OFFERED, so a broken one stops being handed to fresh agents" — and only `find_tool` ever read it, which
1224
- * covers the deferred tools and not the ones already on the list.
1225
- *
1226
- * Measured on one run: `mcp__angular-cli__list_projects` answered its first caller with a reply that failed
1227
- * its own declared output schema and withdrew itself. It was then offered to seventeen more agents, who
1228
- * called it twenty-eight more times. Every one of those was answered instantly, without touching the
1229
- * server — and still cost a whole model turn to learn what the run already knew.
1230
- */
1231
- schemas() {
1232
- const withdrawn = this.list().reduce((n, t) => n + (t.broken === void 0 ? 0 : 1), 0);
1233
- if (this.cached?.version === this.version && this.cached.withdrawn === withdrawn) return this.cached.schemas;
1234
- const schemas = this.list().filter((t) => !this.deferred.has(t.name) && t.broken === void 0).map((t) => ({
1235
- name: t.name,
1236
- description: t.description,
1237
- // MCP tools already carry a JSON Schema; everyone else derives it from their zod parameters.
1238
- parameters: t.rawSchema ?? z.toJSONSchema(t.parameters, { target: "draft-7" })
1239
- }));
1240
- this.cached = { version: this.version, withdrawn, schemas };
1241
- return schemas;
1242
- }
1243
- };
1244
-
1245
- // src/core/types.ts
1246
- var DEADLINE_MESSAGE = "the model did not answer within its deadline";
1247
- var CHAIN_BUDGET_MESSAGE = "the chain's total budget ran out before this model was given a fair turn";
1248
-
1249
- // src/agent/structured.ts
1250
- function valueAt(args, path) {
1251
- let cur = args;
1252
- for (const key of path) {
1253
- if (typeof cur !== "object" || cur === null) return void 0;
1254
- cur = cur[key];
1255
- }
1256
- return cur;
1257
- }
1258
- function whatWasWrong(issues, args) {
1259
- return issues.map((i) => {
1260
- const where = i.path.length ? i.path.join(".") : void 0;
1261
- const got = valueAt(args, i.path);
1262
- const shown = got === void 0 ? "nothing" : JSON.stringify(got);
1263
- const head = where ? `${where}: ${i.message}` : i.message;
1264
- if (got === void 0 && /received\s+(undefined|null|nothing)/i.test(i.message)) {
1265
- const parent = i.path.length > 1 ? valueAt(args, i.path.slice(0, -1)) : args;
1266
- const sent = parent && typeof parent === "object" ? Object.keys(parent) : [];
1267
- return sent.length ? `${head} \u2014 you sent only ${sent.map((k) => `\`${k}\``).join(", ")}` : head;
1268
- }
1269
- return `${head} \u2014 got ${shown.length > 120 ? `${shown.slice(0, 120)}\u2026` : shown}`;
1270
- }).join("; ");
1271
- }
1272
- function buildSubmitTool(schema) {
1273
- let box;
1274
- const tool = {
1275
- name: "submit",
1276
- description: "When you are done, submit your result in structured form with this tool.",
1277
- permissionLevel: "safe",
1278
- parameters: schema,
1279
- run: async (rawArgs) => {
1280
- const parsed = schema.safeParse(rawArgs);
1281
- if (!parsed.success) {
1282
- return { content: `submit: invalid output: ${whatWasWrong(parsed.error.issues, rawArgs)}`, isError: true };
1283
- }
1284
- box = { value: parsed.data };
1285
- return { content: "received", isError: false };
1286
- }
1287
- };
1288
- return { tool, result: () => box };
1289
- }
1290
- function extractStructured(text, schema) {
1291
- const trimmed = text.trim();
1292
- if (!trimmed) return void 0;
1293
- const candidates = [trimmed];
1294
- const block = trimmed.match(/\{[\s\S]*\}/);
1295
- if (block) candidates.push(block[0]);
1296
- for (const c of candidates) {
1297
- try {
1298
- const parsed = schema.safeParse(JSON.parse(c));
1299
- if (parsed.success) return parsed.data;
1300
- } catch {
1301
- }
1302
- }
1303
- return void 0;
1304
- }
1305
- var TURN_LIMIT_RE = /maximum turn count exceeded/i;
1306
- async function runStructuredRole(opts, schema, maxAttempts = 2) {
1307
- const handle = buildSubmitTool(schema);
1308
- const registry = new ToolRegistry();
1309
- for (const t of opts.tools.list()) registry.register(t);
1310
- registry.register(handle.tool);
1311
- const chain = [opts.model, ...opts.fallbacks ?? []];
1312
- const total = opts.totalMs ? AbortSignal.timeout(opts.totalMs) : void 0;
1313
- const outOfTime = () => total?.aborted === true;
1314
- const signalFor = () => {
1315
- const parts = [opts.signal];
1316
- if (total) parts.push(total);
1317
- if (opts.perAttemptMs) parts.push(AbortSignal.timeout(opts.perAttemptMs));
1318
- return parts.length === 1 ? opts.signal : AbortSignal.any(parts);
1319
- };
1320
- let lastError;
1321
- for (let ci = 0; ci < chain.length; ci++) {
1322
- const model = chain[ci];
1323
- if (ci > 0) opts.onFallback?.(chain[ci - 1], model, "structured: previous model returned no valid result");
1324
- const messages = [...opts.messages];
1325
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1326
- if (opts.signal.aborted) throw new Error("cancelled");
1327
- if (outOfTime()) break;
1328
- let lastText = "";
1329
- let errored;
1330
- for await (const ev of runRoleAgent({ ...opts, model, fallbacks: [], messages, tools: registry, signal: signalFor() })) {
1331
- if (ev.type === "error") {
1332
- errored = ev.message;
1333
- break;
1334
- }
1335
- if (ev.type === "abort") {
1336
- if (opts.signal.aborted) throw new Error("cancelled");
1337
- errored = total?.aborted ? CHAIN_BUDGET_MESSAGE : DEADLINE_MESSAGE;
1338
- break;
1339
- }
1340
- if (ev.type === "message.done") lastText = ev.message.content ?? lastText;
1341
- if (handle.result() !== void 0) break;
1342
- }
1343
- const r = handle.result();
1344
- if (r !== void 0) return r.value;
1345
- const salvaged = extractStructured(lastText, schema);
1346
- if (salvaged !== void 0) return salvaged;
1347
- if (errored !== void 0) {
1348
- if (TURN_LIMIT_RE.test(errored) && attempt < maxAttempts - 1) {
1349
- messages.push({ role: "assistant", content: lastText });
1350
- messages.push({ role: "user", content: "You have used your entire tool-call budget. Call `submit` NOW with the findings you already have. Do not read, grep or inspect anything else." });
1351
- continue;
1352
- }
1353
- lastError = errored;
1354
- break;
1355
- }
1356
- messages.push({ role: "assistant", content: lastText });
1357
- messages.push({
1358
- role: "user",
1359
- content: "You did not call the `submit` tool. Call `submit` now with your result as structured arguments \u2014 do not answer in prose."
1360
- });
1361
- }
1362
- if (opts.signal.aborted) throw new Error("cancelled");
1363
- if (outOfTime()) throw new Error("the model chain did not produce a result within its total budget");
1364
- opts.onStructuralFailure?.(model, "answered in prose instead of calling submit");
1365
- }
1366
- throw new Error(lastError ?? "structured role: submit was not called (whole model chain tried)");
1367
- }
1368
-
1369
1163
  export {
1370
1164
  fmtTokens,
1371
1165
  fmtDuration,
@@ -1381,7 +1175,6 @@ export {
1381
1175
  setTelemetry,
1382
1176
  telemetry,
1383
1177
  redactSecrets,
1384
- runToCompletion,
1385
- ToolRegistry,
1386
- runStructuredRole
1178
+ runRoleAgent,
1179
+ runToCompletion
1387
1180
  };
@@ -1,56 +1,60 @@
1
1
  import {
2
2
  respondIn
3
3
  } from "./chunk-M2RKCIGV.js";
4
+ import {
5
+ askInUserLanguage,
6
+ inUserLanguage
7
+ } from "./chunk-UTHLEW5V.js";
4
8
  import {
5
9
  Board,
6
10
  LONG_CALL_MS,
7
11
  WHAT_IT_COST,
8
- applySkills,
9
12
  buildAskUserTool,
10
- buildRememberTool,
11
- buildSkillTool,
12
13
  changedByMerge,
13
14
  commitFile,
14
15
  commitStep,
15
- constitutionNote,
16
- constitutionPath,
17
16
  createDefaultRegistry,
18
- describeInherited,
19
- describeTopUp,
20
17
  editFileTool,
21
18
  extractChoicesFrom,
22
19
  hasWorkAgainst,
23
- nextFeatureSlug,
24
20
  normalizeQuestion,
25
- placedSkills,
26
- readOnlyRegistry,
27
21
  refreshAfterChange,
28
- routeSkills,
29
22
  routeTask,
30
23
  runCycleWithRole,
31
24
  runImplementer,
32
25
  runReviewLoop,
33
- runReviewer,
34
- scaffoldFeature,
35
26
  squashTask,
36
27
  subjectOf,
37
28
  worktreeState,
38
29
  writeFileTool,
39
30
  writerRegistry
40
- } from "./chunk-UEWVVN5L.js";
41
- import {
42
- resolveMainBranch
43
- } from "./chunk-QF4MP6BS.js";
31
+ } from "./chunk-PIG54WFU.js";
44
32
  import {
45
- askInUserLanguage,
46
- inUserLanguage
47
- } from "./chunk-UGESK765.js";
33
+ buildRememberTool,
34
+ constitutionNote,
35
+ constitutionPath,
36
+ describeInherited,
37
+ describeTopUp,
38
+ nextFeatureSlug,
39
+ readOnlyRegistry,
40
+ routeSkills,
41
+ runReviewer,
42
+ scaffoldFeature
43
+ } from "./chunk-NBTH2VVI.js";
48
44
  import {
49
45
  clearCheckpoint,
50
46
  isContinuePrompt,
51
47
  readCheckpoint,
52
48
  writeCheckpoint
53
49
  } from "./chunk-ZSQ24YDJ.js";
50
+ import {
51
+ resolveMainBranch
52
+ } from "./chunk-QF4MP6BS.js";
53
+ import {
54
+ applySkills,
55
+ buildSkillTool,
56
+ placedSkills
57
+ } from "./chunk-MZM24M5M.js";
54
58
  import {
55
59
  defaultGitRunner
56
60
  } from "./chunk-LPQU436C.js";
@@ -72,25 +76,27 @@ import {
72
76
  relationStrength,
73
77
  supersedes,
74
78
  verifyAnchors
75
- } from "./chunk-LLL7QWXB.js";
79
+ } from "./chunk-BFIZMM4G.js";
76
80
  import {
77
81
  ToolRegistry,
82
+ runStructuredRole
83
+ } from "./chunk-CYLPQWIF.js";
84
+ import {
78
85
  handedOver,
79
- runStructuredRole,
80
86
  runToCompletion,
81
87
  stripThinking,
82
88
  telemetry
83
- } from "./chunk-AE36LLL2.js";
89
+ } from "./chunk-JLWQCA7B.js";
84
90
  import {
85
91
  TRACE_INDEX,
86
92
  mergeTraceIndexes,
87
93
  parseTraceIndex,
88
94
  serializeTraceIndex,
89
95
  traceRootRel
90
- } from "./chunk-KAGKX2YT.js";
96
+ } from "./chunk-ZPJP2VH5.js";
91
97
  import {
92
98
  loadGraphSync
93
- } from "./chunk-XEGQT5EN.js";
99
+ } from "./chunk-4M6LXNG2.js";
94
100
  import {
95
101
  inLinkedWorktree,
96
102
  sessionBase,
@@ -2733,7 +2739,7 @@ var RefinerSchema = z6.object({
2733
2739
  * needs no specification. The prompt's own words for `verify` describe that request exactly; they were just
2734
2740
  * nowhere near the field being filled in.
2735
2741
  */
2736
- intent: z6.enum(["chat", "feature", "bugfix", "govern", "undo", "verify"]).describe(
2742
+ intent: z6.enum(["chat", "feature", "bugfix", "govern", "undo", "verify", "research"]).describe(
2737
2743
  "What the request PRODUCES, not what it mentions. `verify`: a record of what EXISTING software DID \u2014 running scenarios, querying the database or logs for evidence, writing or extending a test report. Anything whose output is findings rather than changed behaviour is verify, including a follow-up that only adds more evidence to a report already being written. `feature`: new or changed behaviour in the product. `bugfix`: existing behaviour is wrong and must be corrected. `govern`: the output is a governing document (constitution, conventions), no source changes. `undo`: reverse what the previous turn did. `chat`: a question or conversation, nothing to build. A verify request never needs a specification or a plan: if the answer is 'run it and write down what happened', it is verify."
2738
2744
  ),
2739
2745
  // The natural language the user wrote in (English name, e.g. "Turkish") → the coach replies in it.
@@ -2761,6 +2767,7 @@ function routeIntent(intent) {
2761
2767
  if (intent === "chat") return "chat";
2762
2768
  if (intent === "undo") return "undo";
2763
2769
  if (intent === "verify") return "verify";
2770
+ if (intent === "research") return "research";
2764
2771
  return intent === "govern" ? "govern" : "pipeline";
2765
2772
  }
2766
2773
 
@@ -3217,7 +3224,7 @@ async function runUpstream(deps, ensureWorktree, prompt, askUser, maxRounds, his
3217
3224
  emitPhase("verify");
3218
3225
  const cwd = await documentWorkdir(process.cwd(), prompt, ensureWorktree, r.title);
3219
3226
  laneCheckpoint(cwd, "verify", resume, prompt, r);
3220
- const { runVerify, describeVerify, currentBranchOf } = await import("./verify-LC57A6H2.js");
3227
+ const { runVerify, describeVerify, currentBranchOf } = await import("./verify-2DTBG6RG.js");
3221
3228
  const branch = await currentBranchOf(cwd);
3222
3229
  const res = await runVerify({
3223
3230
  deps,
@@ -3238,6 +3245,31 @@ async function runUpstream(deps, ensureWorktree, prompt, askUser, maxRounds, his
3238
3245
  written: res.reportWritten
3239
3246
  };
3240
3247
  }
3248
+ if (laneFor(r, prompt, resume) === "research") {
3249
+ emitPhase("research");
3250
+ const cwd = await documentWorkdir(process.cwd(), prompt, ensureWorktree, r.title);
3251
+ laneCheckpoint(cwd, "research", resume, prompt, r);
3252
+ const { runResearch, describeResearch } = await import("./research-MHBNZ6SA.js");
3253
+ const { currentBranchOf } = await import("./verify-2DTBG6RG.js");
3254
+ const branch = await currentBranchOf(cwd);
3255
+ const res = await runResearch({
3256
+ deps,
3257
+ workdir: cwd,
3258
+ prompt: r.refinedPrompt,
3259
+ title: r.title,
3260
+ // The report is read by a person, so it is written in theirs — see src/engine/language.ts.
3261
+ ...r.language ? { language: r.language } : {},
3262
+ note: (text) => emit({ kind: "note", text })
3263
+ });
3264
+ return {
3265
+ intent: r.intent,
3266
+ refinedPrompt: r.refinedPrompt,
3267
+ kind: "researched",
3268
+ report: describeResearch(res, branch),
3269
+ reportPath: res.reportPath,
3270
+ written: res.written
3271
+ };
3272
+ }
3241
3273
  if (laneFor(r, prompt, resume) === "govern") {
3242
3274
  emitPhase("constitution");
3243
3275
  const templates2 = await deps.specKit();
@@ -3260,11 +3292,11 @@ async function runUpstream(deps, ensureWorktree, prompt, askUser, maxRounds, his
3260
3292
  if (!resume && !hasPreservedWork && routeIntent(r.intent) === "pipeline") {
3261
3293
  const cwd = workingIn?.() ?? process.cwd();
3262
3294
  emitPhase("sizing");
3263
- const { sizeRequest } = await import("./triage-FCYHD2AQ.js");
3295
+ const { sizeRequest } = await import("./triage-IFCVL5MA.js");
3264
3296
  const size = await sizeRequest(deps, cwd, r.refinedPrompt);
3265
3297
  let small = size.verdict === "small";
3266
3298
  if (size.verdict === "unsure") {
3267
- const { describeSizeDoubt } = await import("./triage-FCYHD2AQ.js");
3299
+ const { describeSizeDoubt } = await import("./triage-IFCVL5MA.js");
3268
3300
  const answer = await askInUserLanguage(
3269
3301
  deps,
3270
3302
  askUser,
@@ -3282,8 +3314,8 @@ Which is it?`,
3282
3314
  if (small) {
3283
3315
  emitPhase("small change");
3284
3316
  emit({ kind: "note", text: `\u26A1 Small change \u2014 ${size.reason}. No branch, no spec, no plan.` });
3285
- const { runSmallChange, describeSmallChange } = await import("./fix-ONLA45HD.js");
3286
- const { currentBranchOf } = await import("./verify-LC57A6H2.js");
3317
+ const { runSmallChange, describeSmallChange } = await import("./fix-SSDUVV4T.js");
3318
+ const { currentBranchOf } = await import("./verify-2DTBG6RG.js");
3287
3319
  const res = await runSmallChange(deps, cwd, r.title, r.refinedPrompt, size);
3288
3320
  return {
3289
3321
  intent: r.intent,
@@ -4044,6 +4076,9 @@ async function runJob(deps, opts) {
4044
4076
  if (up.kind === "tweaked") {
4045
4077
  return { kind: "tweaked", report: up.report, done: up.done, refinedPrompt: up.refinedPrompt };
4046
4078
  }
4079
+ if (up.kind === "researched") {
4080
+ return { kind: "researched", report: up.report, reportPath: up.reportPath, written: up.written, refinedPrompt: up.refinedPrompt };
4081
+ }
4047
4082
  if (up.kind === "verified") {
4048
4083
  return { kind: "verified", report: up.report, reportPath: up.reportPath, written: up.written, refinedPrompt: up.refinedPrompt };
4049
4084
  }