@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.
- package/dist/{app-5FXHE7GX.js → app-KR7TAHUD.js} +82 -40
- package/dist/{chunk-LNW557IO.js → chunk-372X5HHU.js} +2 -2
- package/dist/{chunk-XEGQT5EN.js → chunk-4M6LXNG2.js} +1 -1
- package/dist/{chunk-6OSEQOYY.js → chunk-6S4WWQMN.js} +2 -2
- package/dist/chunk-ACTVFJRW.js +989 -0
- package/dist/{chunk-LLL7QWXB.js → chunk-BFIZMM4G.js} +6 -6
- package/dist/chunk-CYLPQWIF.js +214 -0
- package/dist/{chunk-AE36LLL2.js → chunk-JLWQCA7B.js} +2 -209
- package/dist/{chunk-XYZVZPAY.js → chunk-KXBYRU4W.js} +66 -31
- package/dist/chunk-MZM24M5M.js +251 -0
- package/dist/chunk-NBTH2VVI.js +1945 -0
- package/dist/chunk-PIG54WFU.js +2873 -0
- package/dist/{run-P6ZYL5JL.js → chunk-QJYVZPLG.js} +133 -389
- package/dist/{chunk-YPZP7LYL.js → chunk-UANNVVIU.js} +1 -1
- package/dist/{chunk-UGESK765.js → chunk-UTHLEW5V.js} +1 -1
- package/dist/{chunk-KAGKX2YT.js → chunk-ZPJP2VH5.js} +10 -1
- package/dist/cli.js +262 -69
- package/dist/{fix-ONLA45HD.js → fix-SSDUVV4T.js} +11 -8
- package/dist/{ongoing-WHYXPW24.js → ongoing-6NUSPSCV.js} +3 -2
- package/dist/{project-graph-5HNPRFQG.js → project-graph-OGIM2B33.js} +1 -1
- package/dist/research-MHBNZ6SA.js +111 -0
- package/dist/run-V5ZLZ3LS.js +274 -0
- package/dist/{save-skills-ZW5GY6KV.js → save-skills-NPKTYNAF.js} +2 -2
- package/dist/{trace-X6TU3AG6.js → trace-UVMZZRA5.js} +1 -1
- package/dist/{trace-adopt-URECQWJV.js → trace-adopt-7HWELJFE.js} +1 -1
- package/dist/{trace-run-7U4WJZ3V.js → trace-run-OBOD552Q.js} +9 -4
- package/dist/{triage-FCYHD2AQ.js → triage-IFCVL5MA.js} +7 -6
- package/dist/{verify-LC57A6H2.js → verify-2DTBG6RG.js} +26 -20
- package/package.json +1 -1
- package/dist/chunk-MRZVA5JB.js +0 -163
- package/dist/chunk-UEWVVN5L.js +0 -5691
- 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-
|
|
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-
|
|
15
|
+
} from "./chunk-ZPJP2VH5.js";
|
|
16
16
|
import {
|
|
17
17
|
areaOf,
|
|
18
18
|
loadGraphSync
|
|
19
|
-
} from "./chunk-
|
|
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
|
-
|
|
1385
|
-
|
|
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-
|
|
41
|
-
import {
|
|
42
|
-
resolveMainBranch
|
|
43
|
-
} from "./chunk-QF4MP6BS.js";
|
|
31
|
+
} from "./chunk-PIG54WFU.js";
|
|
44
32
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
96
|
+
} from "./chunk-ZPJP2VH5.js";
|
|
91
97
|
import {
|
|
92
98
|
loadGraphSync
|
|
93
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
3286
|
-
const { currentBranchOf } = await import("./verify-
|
|
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
|
}
|