@vibes.diy/api-svc 14.3.91 → 14.3.93
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/intern/design-repair.js +5 -6
- package/intern/design-repair.js.map +1 -1
- package/intern/instant-build.d.ts +66 -0
- package/intern/instant-build.js +458 -40
- package/intern/instant-build.js.map +1 -1
- package/intern/opens-ast.d.ts +19 -0
- package/intern/opens-ast.js +75 -0
- package/intern/opens-ast.js.map +1 -0
- package/intern/opens-browser-globals.d.ts +1 -0
- package/intern/opens-browser-globals.js +217 -0
- package/intern/opens-browser-globals.js.map +1 -0
- package/intern/opens-check.d.ts +19 -1
- package/intern/opens-check.js +257 -279
- package/intern/opens-check.js.map +1 -1
- package/intern/opens-hooks.d.ts +7 -0
- package/intern/opens-hooks.js +176 -0
- package/intern/opens-hooks.js.map +1 -0
- package/intern/opens-inert.d.ts +10 -0
- package/intern/opens-inert.js +182 -0
- package/intern/opens-inert.js.map +1 -0
- package/intern/opens-scope.d.ts +61 -0
- package/intern/opens-scope.js +380 -0
- package/intern/opens-scope.js.map +1 -0
- package/intern/repair-ladder.d.ts +53 -0
- package/intern/repair-ladder.js +318 -0
- package/intern/repair-ladder.js.map +1 -0
- package/intern/save-build-pass.js +2 -2
- package/intern/save-build-pass.js.map +1 -1
- package/package.json +13 -13
- package/public/handle-prompt-context.js +12 -1
- package/public/handle-prompt-context.js.map +1 -1
- package/public/prompt-section-actions.js +7 -1
- package/public/prompt-section-actions.js.map +1 -1
package/intern/instant-build.js
CHANGED
|
@@ -5,11 +5,23 @@ import { createBlockStream, isCodeEnd, } from "@vibes.diy/call-ai-v2";
|
|
|
5
5
|
import { lazyType } from "@vibes.diy/api-types/lazy-type";
|
|
6
6
|
import { createBlockAccumulator, createStreamingResolver, resolveCodeBlocks } from "./prompt-streaming.js";
|
|
7
7
|
import { synthesizeImports } from "./import-synth.js";
|
|
8
|
-
import { checkOpens, OPENS_ADVISORY_CLASSES } from "./opens-check.js";
|
|
8
|
+
import { checkOpens, collapseOpensFindings, newOpensFindings, opensBaseline, opensFindingKey, OPENS_ADVISORY_CLASSES, } from "./opens-check.js";
|
|
9
|
+
import { fillDefect, gapLadder, identFill, intactnessDefect, mergeGaps, parseModule, regionFill, regionMessages, splice, } from "./repair-ladder.js";
|
|
9
10
|
import { saveRepairFindings } from "./save-repair.js";
|
|
10
11
|
import { composeRepairAsk, renderRepairFiles } from "./save-repair-io.js";
|
|
11
12
|
import { createPromptAssetFetch, promptsPkgBaseUrl } from "./prompt-asset-fetch.js";
|
|
12
13
|
import { loadActiveSettings, lookOptionForTurn } from "./prompt-assembly.js";
|
|
14
|
+
import { MercuryCircuitBreaker } from "./mercury-resilience.js";
|
|
15
|
+
export const INSTANT_MERCURY_FANOUT = 4;
|
|
16
|
+
export const INSTANT_REPAIR_MODE = "router";
|
|
17
|
+
export const INSTANT_LADDER_ROUNDS = 4;
|
|
18
|
+
export const INSTANT_THROTTLED_ROUNDS_MAX = 6;
|
|
19
|
+
export const INSTANT_REPAIR_ROWS_MAX = 24;
|
|
20
|
+
export const INSTANT_REEMIT_CLASSES = new Set(["out-of-scope-identifier", "undefined-identifier"]);
|
|
21
|
+
export function instantMercuryMaxAttempts(mode) {
|
|
22
|
+
return mode === "reemit" ? 3 : 1 + INSTANT_LADDER_ROUNDS + 1;
|
|
23
|
+
}
|
|
24
|
+
export const INSTANT_MERCURY_MAX_ATTEMPTS = instantMercuryMaxAttempts(INSTANT_REPAIR_MODE);
|
|
13
25
|
export const INSTANT_MERCURY_MODEL = "inception/mercury-2.5";
|
|
14
26
|
export const INSTANT_HAIKU_MODEL = "anthropic/claude-haiku-5.5";
|
|
15
27
|
export const INSTANT_MAX_OUTPUT_TOKENS = 32_000;
|
|
@@ -17,6 +29,7 @@ export const INSTANT_MERCURY_CALL_MS = 40_000;
|
|
|
17
29
|
export const INSTANT_HAIKU_CALL_MS = 90_000;
|
|
18
30
|
export const INSTANT_STAGE_BUDGET_MS = 150_000;
|
|
19
31
|
export const INSTANT_MAX_ATTEMPTS = 5;
|
|
32
|
+
export const INSTANT_MERCURY_BREAKER = new MercuryCircuitBreaker();
|
|
20
33
|
export function instantModelChoice(name) {
|
|
21
34
|
switch (name) {
|
|
22
35
|
case "mercury":
|
|
@@ -26,9 +39,16 @@ export function instantModelChoice(name) {
|
|
|
26
39
|
reasoning: { effort: "none" },
|
|
27
40
|
reasoningEffort: "none",
|
|
28
41
|
callMs: INSTANT_MERCURY_CALL_MS,
|
|
42
|
+
maxAttempts: INSTANT_MERCURY_MAX_ATTEMPTS,
|
|
29
43
|
};
|
|
30
44
|
case "haiku":
|
|
31
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
name,
|
|
47
|
+
id: INSTANT_HAIKU_MODEL,
|
|
48
|
+
reasoning: { enabled: false },
|
|
49
|
+
callMs: INSTANT_HAIKU_CALL_MS,
|
|
50
|
+
maxAttempts: INSTANT_MAX_ATTEMPTS,
|
|
51
|
+
};
|
|
32
52
|
}
|
|
33
53
|
}
|
|
34
54
|
export const INSTANT_PARAGRAPH = "Build the interaction this sentence names, fully working, with realistic launch content. " +
|
|
@@ -149,11 +169,23 @@ export function autofixAndCheck(args) {
|
|
|
149
169
|
const fixed = synthesizeImports({ vfs: args.vfs, paths: args.changed.filter((path) => args.vfs.has(path)), look: args.look });
|
|
150
170
|
const files = [...fixed.vfs].map(([filename, content]) => ({ type: "code-block", filename, lang: "jsx", content }));
|
|
151
171
|
const parse = saveRepairFindings(files, new Set(args.changed)).map((finding) => `${finding.path.replace(/^\/+/, "")}: ${finding.message}`);
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
const verdict = checkOpens(fixed.vfs);
|
|
173
|
+
const blocking = newOpensFindings(verdict, args.baseline ?? new Set());
|
|
174
|
+
const line = (finding) => `${finding.file.replace(/^\/+/, "")}:${finding.line}: ${finding.message}`;
|
|
175
|
+
const opens = collapseOpensFindings(blocking).map(line);
|
|
176
|
+
const advisory = verdict.findings
|
|
177
|
+
.filter((finding) => OPENS_ADVISORY_CLASSES.has(finding.class) === false && blocking.includes(finding) === false)
|
|
178
|
+
.map(line);
|
|
155
179
|
const findings = [...parse, ...opens];
|
|
156
|
-
return {
|
|
180
|
+
return {
|
|
181
|
+
vfs: fixed.vfs,
|
|
182
|
+
opens: findings.length === 0,
|
|
183
|
+
findings,
|
|
184
|
+
blocking,
|
|
185
|
+
unlocated: parse.length,
|
|
186
|
+
advisory,
|
|
187
|
+
notes: fixed.notes,
|
|
188
|
+
};
|
|
157
189
|
}
|
|
158
190
|
const completionBody = lazyType(() => type({
|
|
159
191
|
"model?": "string",
|
|
@@ -212,6 +244,13 @@ export function instantModelCallFor(vctx) {
|
|
|
212
244
|
});
|
|
213
245
|
};
|
|
214
246
|
}
|
|
247
|
+
function isThrottle(error) {
|
|
248
|
+
return /\bstatus 429\b/.test(error.message);
|
|
249
|
+
}
|
|
250
|
+
function throttleWaitMs(n, random) {
|
|
251
|
+
const exp = Math.min(8_000, 1_000 * 2 ** Math.max(0, n - 1));
|
|
252
|
+
return Math.round(exp / 2 + random() * (exp / 2));
|
|
253
|
+
}
|
|
215
254
|
async function callWithin(call, req, cancel) {
|
|
216
255
|
const abort = new AbortController();
|
|
217
256
|
const timer = setTimeout(function expire() {
|
|
@@ -255,39 +294,267 @@ function pauseUnlessCancelled(ms, cancel) {
|
|
|
255
294
|
done();
|
|
256
295
|
});
|
|
257
296
|
}
|
|
297
|
+
function findingIds(findings) {
|
|
298
|
+
const seen = new Map();
|
|
299
|
+
return findings.map((finding) => {
|
|
300
|
+
const key = opensFindingKey(finding);
|
|
301
|
+
const n = seen.get(key) ?? 0;
|
|
302
|
+
seen.set(key, n + 1);
|
|
303
|
+
return `${key}#${n}`;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function countByKey(findings) {
|
|
307
|
+
const out = new Map();
|
|
308
|
+
for (const finding of findings)
|
|
309
|
+
out.set(opensFindingKey(finding), (out.get(opensFindingKey(finding)) ?? 0) + 1);
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
function ladderable(path) {
|
|
313
|
+
return /\.(jsx?|mjs)$/.test(path);
|
|
314
|
+
}
|
|
315
|
+
export function repairIntactness(baseline, after, exempt = new Set()) {
|
|
316
|
+
for (const [path, was] of baseline.files) {
|
|
317
|
+
const now = after.get(path);
|
|
318
|
+
if (now === undefined || now === was || isSourcePath(path) === false)
|
|
319
|
+
continue;
|
|
320
|
+
const defect = intactnessDefect(was, now, exempt, baseline.lines);
|
|
321
|
+
if (defect !== undefined) {
|
|
322
|
+
return { path: path.replace(/^\/+/, ""), defect, ratio: now.split("\n").length / Math.max(1, was.split("\n").length) };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
export function intactnessReask(defect) {
|
|
328
|
+
switch (defect) {
|
|
329
|
+
case "shrank":
|
|
330
|
+
return "the reply: it left part of the app out — write the whole file, every line that already worked";
|
|
331
|
+
case "grew":
|
|
332
|
+
return "the reply: it rewrote far more than the fix needed — change only what the findings require";
|
|
333
|
+
case "names":
|
|
334
|
+
return "the reply: it dropped functions, components or variables the app still uses — keep every one that already worked";
|
|
335
|
+
case "loop":
|
|
336
|
+
return "the reply: it repeats the same lines over and over — write each line once";
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function isSourcePath(path) {
|
|
340
|
+
return /\.(jsx?|tsx?|mjs)$/.test(path);
|
|
341
|
+
}
|
|
342
|
+
async function ladderRound(args) {
|
|
343
|
+
const t0 = args.now();
|
|
344
|
+
const ids = findingIds(args.findings);
|
|
345
|
+
const items = [];
|
|
346
|
+
for (let i = 0; i < args.findings.length; i++) {
|
|
347
|
+
const finding = args.findings[i];
|
|
348
|
+
const src = args.current.get(finding.file);
|
|
349
|
+
if (src === undefined || ladderable(finding.file) === false)
|
|
350
|
+
continue;
|
|
351
|
+
const program = parseModule(src);
|
|
352
|
+
if (program === undefined)
|
|
353
|
+
continue;
|
|
354
|
+
const ladder = gapLadder(src, program, finding);
|
|
355
|
+
const rung = args.rungs.get(ids[i]) ?? 0;
|
|
356
|
+
const gap = ladder[rung];
|
|
357
|
+
if (gap !== undefined)
|
|
358
|
+
items.push({ id: ids[i], finding, gap });
|
|
359
|
+
}
|
|
360
|
+
const groups = [...new Set(items.map((item) => item.finding.file))].flatMap((file) => {
|
|
361
|
+
const src = args.current.get(file) ?? "";
|
|
362
|
+
const inFile = items.filter((item) => item.finding.file === file);
|
|
363
|
+
return mergeGaps(src, inFile.map((item) => item.gap)).map((merged) => ({ file, src, gap: merged.gap, members: merged.members.map((i) => inFile[i]) }));
|
|
364
|
+
});
|
|
365
|
+
if (groups.length === 0) {
|
|
366
|
+
return { round: { kind: "ladder", ms: 0, gaps: [] }, usage: [], touched: [], noGaps: true, throttled: false, cancelled: false };
|
|
367
|
+
}
|
|
368
|
+
const usage = [];
|
|
369
|
+
const fills = await Promise.all(groups.map(async (group) => {
|
|
370
|
+
const messages = regionMessages({
|
|
371
|
+
system: args.system,
|
|
372
|
+
path: group.file,
|
|
373
|
+
src: group.src,
|
|
374
|
+
gap: group.gap,
|
|
375
|
+
findings: group.members.map((member) => member.finding),
|
|
376
|
+
userFirst: args.model.name === "haiku",
|
|
377
|
+
});
|
|
378
|
+
const answer = await callWithin(args.call, { model: args.model, messages, timeoutMs: args.timeoutMs }, args.cancel);
|
|
379
|
+
if (answer.kind === "cancelled")
|
|
380
|
+
return { result: "rejected:cancelled", climbs: false };
|
|
381
|
+
if (answer.kind === "deadline")
|
|
382
|
+
return { result: "rejected:deadline", climbs: false };
|
|
383
|
+
if (answer.reply.isErr()) {
|
|
384
|
+
return isThrottle(answer.reply.Err())
|
|
385
|
+
? { result: "rejected:throttled", climbs: false }
|
|
386
|
+
: { result: "rejected:error", climbs: false };
|
|
387
|
+
}
|
|
388
|
+
const reply = answer.reply.Ok();
|
|
389
|
+
if (reply.usage !== undefined)
|
|
390
|
+
usage.push(reply.usage);
|
|
391
|
+
const raw = regionFill(reply.text, group.src.slice(group.gap.start, group.gap.end));
|
|
392
|
+
if (raw === undefined)
|
|
393
|
+
return { result: "rejected:no-fill", climbs: true };
|
|
394
|
+
const fill = group.gap.node === "Identifier" ? identFill(group.src, group.gap, raw) : raw;
|
|
395
|
+
const defect = fillDefect(group.src, group.gap, fill);
|
|
396
|
+
if (defect !== undefined)
|
|
397
|
+
return { result: `rejected:${defect}`, climbs: true };
|
|
398
|
+
return { fill: group.gap.node === "Identifier" ? fill.trim() : fill, result: "accepted", climbs: false };
|
|
399
|
+
}));
|
|
400
|
+
const before = countByKey(args.findings);
|
|
401
|
+
const kept = [];
|
|
402
|
+
let gated;
|
|
403
|
+
let overridable;
|
|
404
|
+
const results = fills.map((f) => f.result);
|
|
405
|
+
for (let i = 0; i < groups.length; i++) {
|
|
406
|
+
const fill = fills[i].fill;
|
|
407
|
+
if (fill === undefined)
|
|
408
|
+
continue;
|
|
409
|
+
const trial = [...kept, { group: groups[i], fill }];
|
|
410
|
+
const vfs = new Map(args.current);
|
|
411
|
+
for (const file of new Set(trial.map((t) => t.group.file))) {
|
|
412
|
+
const inFile = trial.filter((t) => t.group.file === file);
|
|
413
|
+
vfs.set(file, splice(args.current.get(file) ?? "", inFile.map((t) => t.group.gap), inFile.map((t) => t.fill)));
|
|
414
|
+
}
|
|
415
|
+
const touched = [...new Set(trial.map((t) => t.group.file))];
|
|
416
|
+
const checked = autofixAndCheck({ vfs, changed: [...args.changed, ...touched], look: args.look, baseline: args.baseline });
|
|
417
|
+
const after = countByKey(checked.blocking);
|
|
418
|
+
const added = checked.unlocated > 0 || [...after].some(([key, n]) => n > (before.get(key) ?? 0));
|
|
419
|
+
const served = countByKey(groups[i].members.map((member) => member.finding));
|
|
420
|
+
const gone = [...served].every(([key, n]) => (after.get(key) ?? 0) <= (before.get(key) ?? 0) - n);
|
|
421
|
+
const gutted = repairIntactness(args.preRepair, checked.vfs, args.exempt);
|
|
422
|
+
if (added)
|
|
423
|
+
results[i] = "rejected:regress";
|
|
424
|
+
else if (gone === false)
|
|
425
|
+
results[i] = "rejected:still";
|
|
426
|
+
else if (gutted !== undefined) {
|
|
427
|
+
results[i] = `rejected:gutted:${gutted.defect}`;
|
|
428
|
+
if (checked.opens && (overridable === undefined || Math.abs(1 - gutted.ratio) < Math.abs(1 - overridable.ratio))) {
|
|
429
|
+
overridable = { gated: checked, ratio: gutted.ratio, touched };
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
kept.push({ group: groups[i], fill });
|
|
434
|
+
gated = checked;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
for (let i = 0; i < groups.length; i++) {
|
|
438
|
+
const refused = results[i] !== "accepted" && (fills[i].climbs || fills[i].fill !== undefined);
|
|
439
|
+
if (refused === false)
|
|
440
|
+
continue;
|
|
441
|
+
for (const member of groups[i].members)
|
|
442
|
+
args.rungs.set(member.id, (args.rungs.get(member.id) ?? 0) + 1);
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
round: {
|
|
446
|
+
kind: "ladder",
|
|
447
|
+
ms: args.now() - t0,
|
|
448
|
+
gaps: groups.slice(0, 8).map((group, i) => ({
|
|
449
|
+
rung: group.gap.level,
|
|
450
|
+
lines: group.gap.endLine - group.gap.startLine + 1,
|
|
451
|
+
result: results[i],
|
|
452
|
+
})),
|
|
453
|
+
},
|
|
454
|
+
usage,
|
|
455
|
+
...(gated === undefined ? {} : { gated }),
|
|
456
|
+
...(overridable === undefined ? {} : { overridable }),
|
|
457
|
+
touched: [...new Set(kept.map((k) => k.group.file))],
|
|
458
|
+
noGaps: false,
|
|
459
|
+
throttled: results.length > 0 && results.every((r) => r === "rejected:throttled") && usage.length === 0,
|
|
460
|
+
cancelled: results.some((r) => r === "rejected:cancelled"),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
258
463
|
export async function runInstantCandidate(args) {
|
|
464
|
+
const baseline = args.baseline ?? opensBaseline(args.base);
|
|
259
465
|
const now = args.now ?? Date.now;
|
|
260
|
-
const
|
|
466
|
+
const random = args.random ?? Math.random;
|
|
467
|
+
const repairMode = args.repairMode ?? INSTANT_REPAIR_MODE;
|
|
468
|
+
let route = repairMode === "router" ? undefined : repairMode;
|
|
469
|
+
const ladderBudget = () => (route === "ladder" ? (args.ladderRounds ?? INSTANT_LADDER_ROUNDS) : 0);
|
|
470
|
+
const modeLabel = () => (repairMode === "router" ? `router:${route ?? "none"}` : repairMode);
|
|
261
471
|
const { model } = args.spec;
|
|
472
|
+
const maxAttempts = args.maxAttempts ?? model.maxAttempts;
|
|
473
|
+
const system = args.spec.messages.filter((message) => message.role === "system");
|
|
262
474
|
const usage = [];
|
|
475
|
+
const repairs = [];
|
|
263
476
|
const messages = [...args.spec.messages];
|
|
477
|
+
const rungs = new Map();
|
|
264
478
|
let attempts = 0;
|
|
479
|
+
let throttled = 0;
|
|
480
|
+
let throttledRounds = 0;
|
|
481
|
+
let repairsDropped = 0;
|
|
265
482
|
let providerErrors = 0;
|
|
483
|
+
let ladderRun = 0;
|
|
266
484
|
let firstFindings = [];
|
|
485
|
+
let liveFindings = [];
|
|
267
486
|
let lastError = "no attempt";
|
|
268
487
|
let current = args.base;
|
|
269
488
|
let changed = new Set();
|
|
489
|
+
let repairing = false;
|
|
490
|
+
const editBase = sourceFiles(args.base);
|
|
491
|
+
let preRepair = editBase.size > 0 ? { files: editBase, lines: "shrink" } : undefined;
|
|
492
|
+
const repairNames = new Set();
|
|
493
|
+
let overridable;
|
|
494
|
+
let reemitStartedAt;
|
|
495
|
+
function record(row) {
|
|
496
|
+
if (repairs.length < INSTANT_REPAIR_ROWS_MAX)
|
|
497
|
+
repairs.push(row);
|
|
498
|
+
else
|
|
499
|
+
repairsDropped++;
|
|
500
|
+
}
|
|
270
501
|
function end(gate, extra) {
|
|
271
502
|
return {
|
|
272
503
|
name: model.name,
|
|
504
|
+
candidate: args.spec.label ?? model.name,
|
|
273
505
|
model: model.id,
|
|
274
506
|
gate,
|
|
275
507
|
attempts,
|
|
508
|
+
throttled,
|
|
276
509
|
ms: now() - args.startedAt,
|
|
277
510
|
usage,
|
|
278
511
|
abortedInFlight: false,
|
|
279
512
|
findings: firstFindings,
|
|
513
|
+
repairs,
|
|
514
|
+
...(repairsDropped > 0 ? { repairsDropped } : {}),
|
|
280
515
|
...extra,
|
|
281
516
|
};
|
|
282
517
|
}
|
|
518
|
+
function noteOverridable(candidate) {
|
|
519
|
+
if (overridable === undefined || Math.abs(1 - candidate.ratio) < Math.abs(1 - overridable.ratio))
|
|
520
|
+
overridable = candidate;
|
|
521
|
+
}
|
|
522
|
+
function exhausted(error) {
|
|
523
|
+
if (overridable === undefined)
|
|
524
|
+
return end("fail", { error });
|
|
525
|
+
record({ kind: overridable.kind, mode: modeLabel(), ms: 0, result: "accepted:gate-overridden" });
|
|
526
|
+
return end("pass", { vfs: overridable.gated.vfs, changed: [...new Set([...changed, ...overridable.touched])] });
|
|
527
|
+
}
|
|
528
|
+
function waitWithin(ms) {
|
|
529
|
+
return pauseUnlessCancelled(Math.max(0, Math.min(ms, args.deadlineAt - now())), args.cancel);
|
|
530
|
+
}
|
|
531
|
+
const paidLeft = () => maxAttempts - (attempts - throttled);
|
|
532
|
+
function queueReemit(findings, lastReply) {
|
|
533
|
+
const shown = lastReply ??
|
|
534
|
+
renderRepairFiles({
|
|
535
|
+
vfs: new Map([...changed].flatMap((path) => {
|
|
536
|
+
const content = current.get(path);
|
|
537
|
+
return content === undefined ? [] : [[path, content]];
|
|
538
|
+
})),
|
|
539
|
+
intro: "Here is the app.",
|
|
540
|
+
});
|
|
541
|
+
messages.push({ role: "assistant", content: [{ type: "text", text: shown }] });
|
|
542
|
+
messages.push({ role: "user", content: [{ type: "text", text: composeRepairAsk(findings) }] });
|
|
543
|
+
reemitStartedAt = now();
|
|
544
|
+
}
|
|
283
545
|
for (;;) {
|
|
284
546
|
if (args.cancel.aborted)
|
|
285
547
|
return end("cancelled", {});
|
|
286
548
|
const left = args.deadlineAt - now();
|
|
287
549
|
if (left <= 0)
|
|
288
|
-
return
|
|
289
|
-
if (
|
|
290
|
-
return
|
|
550
|
+
return exhausted(`stage budget spent after ${attempts} call(s): ${lastError}`);
|
|
551
|
+
if (paidLeft() <= 0)
|
|
552
|
+
return exhausted(`${attempts} calls without a passing answer: ${lastError}`);
|
|
553
|
+
const hold = args.throttle === undefined ? 0 : Math.min(args.throttle.holdUntil - now(), left);
|
|
554
|
+
if (hold > 0) {
|
|
555
|
+
await pauseUnlessCancelled(hold, args.cancel);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
291
558
|
attempts++;
|
|
292
559
|
const answer = await callWithin(args.call, { model, messages, timeoutMs: Math.min(model.callMs, left) }, args.cancel);
|
|
293
560
|
if (answer.kind === "cancelled")
|
|
@@ -299,34 +566,138 @@ export async function runInstantCandidate(args) {
|
|
|
299
566
|
if (answer.reply.isErr()) {
|
|
300
567
|
lastError = answer.reply.Err().message;
|
|
301
568
|
providerErrors++;
|
|
302
|
-
|
|
569
|
+
if (isThrottle(answer.reply.Err())) {
|
|
570
|
+
throttled++;
|
|
571
|
+
const wait = throttleWaitMs(providerErrors, random);
|
|
572
|
+
if (args.throttle === undefined)
|
|
573
|
+
await waitWithin(wait);
|
|
574
|
+
else
|
|
575
|
+
args.throttle.holdUntil = Math.max(args.throttle.holdUntil, now() + wait);
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
await waitWithin(Math.min(8_000, 1_000 * 2 ** (providerErrors - 1)));
|
|
303
579
|
continue;
|
|
304
580
|
}
|
|
305
581
|
providerErrors = 0;
|
|
306
582
|
const reply = answer.reply.Ok();
|
|
307
583
|
if (reply.usage !== undefined)
|
|
308
584
|
usage.push(reply.usage);
|
|
585
|
+
const reemitMs = reemitStartedAt === undefined ? undefined : now() - reemitStartedAt;
|
|
586
|
+
reemitStartedAt = undefined;
|
|
309
587
|
const parsed = await parseCandidateText(reply.text, current);
|
|
310
588
|
if (parsed.isErr()) {
|
|
311
589
|
lastError = parsed.Err().message;
|
|
590
|
+
repairing = true;
|
|
591
|
+
preRepair ??= { files: current, lines: "none" };
|
|
592
|
+
if (reemitMs !== undefined)
|
|
593
|
+
record({ kind: "reemit", mode: modeLabel(), ms: reemitMs, result: "rejected:no-file" });
|
|
312
594
|
messages.push({ role: "assistant", content: [{ type: "text", text: reply.text }] });
|
|
313
595
|
messages.push({ role: "user", content: [{ type: "text", text: composeRepairAsk([`the reply: ${lastError}`]) }] });
|
|
314
596
|
continue;
|
|
315
597
|
}
|
|
316
|
-
|
|
317
|
-
const checked = autofixAndCheck({ vfs: parsed.Ok().vfs, changed: [...
|
|
598
|
+
const nextChanged = new Set([...changed, ...parsed.Ok().changed]);
|
|
599
|
+
const checked = autofixAndCheck({ vfs: parsed.Ok().vfs, changed: [...nextChanged], look: args.look, baseline });
|
|
318
600
|
const gated = parsed.Ok().applyErrors.length === 0
|
|
319
601
|
? checked
|
|
320
602
|
: { ...checked, opens: false, findings: [...parsed.Ok().applyErrors, ...checked.findings] };
|
|
603
|
+
const applyErrors = parsed.Ok().applyErrors.length;
|
|
321
604
|
if (gated.opens) {
|
|
322
|
-
|
|
605
|
+
const gutted = repairing && preRepair !== undefined ? repairIntactness(preRepair, gated.vfs, repairNames) : undefined;
|
|
606
|
+
if (gutted === undefined) {
|
|
607
|
+
if (reemitMs !== undefined)
|
|
608
|
+
record({ kind: "reemit", mode: modeLabel(), ms: reemitMs, result: "accepted" });
|
|
609
|
+
return end("pass", { vfs: gated.vfs, changed: [...nextChanged] });
|
|
610
|
+
}
|
|
611
|
+
record({ kind: "reemit", mode: modeLabel(), ms: reemitMs ?? 0, result: `rejected:gutted:${gutted.defect}` });
|
|
612
|
+
noteOverridable({ gated, ratio: gutted.ratio, touched: [...nextChanged], kind: "reemit" });
|
|
613
|
+
lastError = `the repair failed the intactness gate (${gutted.path}: ${gutted.defect})`;
|
|
614
|
+
queueReemit([...liveFindings, intactnessReask(gutted.defect)], reply.text);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if (reemitMs !== undefined) {
|
|
618
|
+
record({ kind: "reemit", mode: modeLabel(), ms: reemitMs, result: "rejected:still" });
|
|
619
|
+
rungs.clear();
|
|
323
620
|
}
|
|
621
|
+
preRepair ??= { files: gated.vfs, lines: gated.unlocated === 0 && applyErrors === 0 ? "both" : "none" };
|
|
622
|
+
repairing = true;
|
|
623
|
+
changed = nextChanged;
|
|
324
624
|
if (firstFindings.length === 0)
|
|
325
625
|
firstFindings = gated.findings.slice(0, 6);
|
|
326
626
|
lastError = gated.findings.slice(0, 3).join("; ");
|
|
627
|
+
liveFindings = gated.findings;
|
|
628
|
+
for (const finding of gated.blocking)
|
|
629
|
+
if (finding.name !== undefined)
|
|
630
|
+
repairNames.add(finding.name);
|
|
327
631
|
current = gated.vfs;
|
|
328
|
-
|
|
329
|
-
|
|
632
|
+
route ??=
|
|
633
|
+
gated.blocking.some((finding) => INSTANT_REEMIT_CLASSES.has(finding.class)) ||
|
|
634
|
+
gated.unlocated > 0 ||
|
|
635
|
+
parsed.Ok().applyErrors.length > 0
|
|
636
|
+
? "reemit"
|
|
637
|
+
: "ladder";
|
|
638
|
+
let state = gated;
|
|
639
|
+
while (ladderRun < ladderBudget() &&
|
|
640
|
+
paidLeft() > 1 &&
|
|
641
|
+
throttledRounds < INSTANT_THROTTLED_ROUNDS_MAX &&
|
|
642
|
+
applyErrors === 0 &&
|
|
643
|
+
state.unlocated === 0) {
|
|
644
|
+
if (args.cancel.aborted)
|
|
645
|
+
return end("cancelled", {});
|
|
646
|
+
const roundLeft = args.deadlineAt - now();
|
|
647
|
+
if (roundLeft <= 0)
|
|
648
|
+
return exhausted(`stage budget spent after ${attempts} call(s): ${lastError}`);
|
|
649
|
+
const round = await ladderRound({
|
|
650
|
+
call: args.call,
|
|
651
|
+
model,
|
|
652
|
+
system,
|
|
653
|
+
cancel: args.cancel,
|
|
654
|
+
timeoutMs: Math.min(model.callMs, roundLeft),
|
|
655
|
+
current,
|
|
656
|
+
findings: state.blocking,
|
|
657
|
+
rungs,
|
|
658
|
+
changed,
|
|
659
|
+
look: args.look,
|
|
660
|
+
baseline,
|
|
661
|
+
preRepair: preRepair ?? { files: current, lines: "none" },
|
|
662
|
+
exempt: repairNames,
|
|
663
|
+
...(args.throttle === undefined ? {} : { throttle: args.throttle }),
|
|
664
|
+
now,
|
|
665
|
+
});
|
|
666
|
+
if (round.noGaps)
|
|
667
|
+
break;
|
|
668
|
+
attempts++;
|
|
669
|
+
ladderRun++;
|
|
670
|
+
usage.push(...round.usage);
|
|
671
|
+
record({ ...round.round, mode: modeLabel() });
|
|
672
|
+
if (round.cancelled && args.cancel.aborted)
|
|
673
|
+
return end("cancelled", { abortedInFlight: true });
|
|
674
|
+
if (round.overridable !== undefined)
|
|
675
|
+
noteOverridable({ ...round.overridable, kind: "ladder" });
|
|
676
|
+
if (round.throttled) {
|
|
677
|
+
throttled++;
|
|
678
|
+
ladderRun--;
|
|
679
|
+
throttledRounds++;
|
|
680
|
+
const wait = throttleWaitMs(++providerErrors, random);
|
|
681
|
+
if (args.throttle !== undefined)
|
|
682
|
+
args.throttle.holdUntil = Math.max(args.throttle.holdUntil, now() + wait);
|
|
683
|
+
await waitWithin((args.throttle?.holdUntil ?? now() + wait) - now());
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
providerErrors = 0;
|
|
687
|
+
if (round.gated === undefined)
|
|
688
|
+
continue;
|
|
689
|
+
changed = new Set([...changed, ...round.touched]);
|
|
690
|
+
current = round.gated.vfs;
|
|
691
|
+
state = round.gated;
|
|
692
|
+
if (state.opens)
|
|
693
|
+
return end("pass", { vfs: state.vfs, changed: [...changed] });
|
|
694
|
+
lastError = state.findings.slice(0, 3).join("; ");
|
|
695
|
+
liveFindings = state.findings;
|
|
696
|
+
for (const finding of state.blocking)
|
|
697
|
+
if (finding.name !== undefined)
|
|
698
|
+
repairNames.add(finding.name);
|
|
699
|
+
}
|
|
700
|
+
queueReemit(state.findings, state === gated ? reply.text : undefined);
|
|
330
701
|
}
|
|
331
702
|
}
|
|
332
703
|
export async function raceInstantCandidates(args) {
|
|
@@ -358,20 +729,24 @@ export async function raceInstantCandidates(args) {
|
|
|
358
729
|
export function instantReceipt(race) {
|
|
359
730
|
return {
|
|
360
731
|
candidates: race.outcomes.map((outcome) => ({
|
|
732
|
+
candidate: outcome.candidate,
|
|
361
733
|
model: outcome.model,
|
|
362
734
|
ms: outcome.ms,
|
|
363
735
|
gate: outcome.gate,
|
|
364
736
|
attempts: outcome.attempts,
|
|
737
|
+
...(outcome.throttled > 0 ? { throttled: outcome.throttled } : {}),
|
|
738
|
+
...(outcome.repairs.length > 0 ? { repairs: outcome.repairs } : {}),
|
|
739
|
+
...(outcome.repairsDropped === undefined ? {} : { repairsDropped: outcome.repairsDropped }),
|
|
365
740
|
...(outcome.abortedInFlight ? { abortedInFlight: true } : {}),
|
|
366
741
|
})),
|
|
367
|
-
...(race.winner === undefined ? {} : { winner: race.winner.model }),
|
|
742
|
+
...(race.winner === undefined ? {} : { winner: race.winner.model, winnerCandidate: race.winner.candidate }),
|
|
368
743
|
};
|
|
369
744
|
}
|
|
370
745
|
export function instantTurnText(race) {
|
|
371
746
|
const winner = race.winner;
|
|
372
747
|
if (winner === undefined || winner.vfs === undefined || winner.changed === undefined) {
|
|
373
748
|
const why = race.outcomes
|
|
374
|
-
.map((outcome) => `${outcome.
|
|
749
|
+
.map((outcome) => `${outcome.candidate}: ${outcome.error ?? outcome.findings.slice(0, 2).join("; ")}`)
|
|
375
750
|
.join("\n");
|
|
376
751
|
return `The instant build wrote no files that open, so nothing was saved.\n\n${why}\n`;
|
|
377
752
|
}
|
|
@@ -415,29 +790,72 @@ export function mercuryMessages(args) {
|
|
|
415
790
|
{ role: "user", content: [{ type: "text", text: ask }] },
|
|
416
791
|
];
|
|
417
792
|
}
|
|
418
|
-
|
|
419
|
-
const { assembly } = args;
|
|
420
|
-
const specs = [
|
|
421
|
-
...(assembly.mercurySystem === undefined
|
|
422
|
-
? []
|
|
423
|
-
: [
|
|
424
|
-
{
|
|
425
|
-
model: instantModelChoice("mercury"),
|
|
426
|
-
messages: mercuryMessages({ system: assembly.mercurySystem, instruction: assembly.instruction, base: assembly.base }),
|
|
427
|
-
},
|
|
428
|
-
]),
|
|
429
|
-
{ model: instantModelChoice("haiku"), messages: args.haikuMessages },
|
|
430
|
-
];
|
|
431
|
-
const race = await raceInstantCandidates({
|
|
432
|
-
specs,
|
|
433
|
-
budgetMs: args.budgetMs ?? INSTANT_STAGE_BUDGET_MS,
|
|
434
|
-
turn: args.turn,
|
|
435
|
-
run: ({ spec, cancel, startedAt, deadlineAt }) => runInstantCandidate({ call: args.call, spec, base: assembly.base, look: assembly.look, cancel, startedAt, deadlineAt }),
|
|
436
|
-
});
|
|
793
|
+
function crashedCandidate(spec, startedAt, e) {
|
|
437
794
|
return {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
model:
|
|
795
|
+
name: spec.model.name,
|
|
796
|
+
candidate: spec.label ?? spec.model.name,
|
|
797
|
+
model: spec.model.id,
|
|
798
|
+
gate: "fail",
|
|
799
|
+
attempts: 0,
|
|
800
|
+
throttled: 0,
|
|
801
|
+
ms: Date.now() - startedAt,
|
|
802
|
+
usage: [],
|
|
803
|
+
abortedInFlight: false,
|
|
804
|
+
findings: [],
|
|
805
|
+
repairs: [],
|
|
806
|
+
error: `candidate crashed: ${e instanceof Error ? e.message : String(e)}`,
|
|
441
807
|
};
|
|
442
808
|
}
|
|
809
|
+
export function instantSpecs(args) {
|
|
810
|
+
const { assembly } = args;
|
|
811
|
+
const system = assembly.mercurySystem;
|
|
812
|
+
const mercury = system === undefined
|
|
813
|
+
? []
|
|
814
|
+
: Array.from({ length: Math.max(0, args.fanout) }, (_, i) => ({
|
|
815
|
+
model: instantModelChoice("mercury"),
|
|
816
|
+
label: `mercury-${i + 1}`,
|
|
817
|
+
messages: mercuryMessages({ system, instruction: assembly.instruction, base: assembly.base }),
|
|
818
|
+
}));
|
|
819
|
+
return [...mercury, { model: instantModelChoice("haiku"), label: "haiku", messages: args.haikuMessages }];
|
|
820
|
+
}
|
|
821
|
+
export async function runInstantBuild(args) {
|
|
822
|
+
const { assembly } = args;
|
|
823
|
+
const breaker = args.breaker ?? INSTANT_MERCURY_BREAKER;
|
|
824
|
+
const admission = assembly.mercurySystem === undefined ? undefined : breaker.admit();
|
|
825
|
+
const fanout = admission === undefined || admission === "closed" ? (args.mercuryFanout ?? INSTANT_MERCURY_FANOUT) : 1;
|
|
826
|
+
const specs = instantSpecs({ assembly, haikuMessages: args.haikuMessages, fanout });
|
|
827
|
+
const mercuryThrottle = { holdUntil: 0 };
|
|
828
|
+
const baseline = opensBaseline(sourceFiles(assembly.base));
|
|
829
|
+
let race;
|
|
830
|
+
try {
|
|
831
|
+
race = await raceInstantCandidates({
|
|
832
|
+
specs,
|
|
833
|
+
budgetMs: args.budgetMs ?? INSTANT_STAGE_BUDGET_MS,
|
|
834
|
+
turn: args.turn,
|
|
835
|
+
run: ({ spec, cancel, startedAt, deadlineAt }) => runInstantCandidate({
|
|
836
|
+
call: args.call,
|
|
837
|
+
spec,
|
|
838
|
+
base: assembly.base,
|
|
839
|
+
look: assembly.look,
|
|
840
|
+
cancel,
|
|
841
|
+
startedAt,
|
|
842
|
+
deadlineAt,
|
|
843
|
+
baseline,
|
|
844
|
+
...(spec.model.name === "mercury" ? { throttle: mercuryThrottle } : {}),
|
|
845
|
+
}).catch((e) => crashedCandidate(spec, startedAt, e)),
|
|
846
|
+
});
|
|
847
|
+
return {
|
|
848
|
+
response: instantResponse({ race, promptId: args.promptId }),
|
|
849
|
+
receipt: instantReceipt(race),
|
|
850
|
+
model: race.winner?.model ?? INSTANT_HAIKU_MODEL,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
finally {
|
|
854
|
+
if (admission !== undefined && admission !== "open") {
|
|
855
|
+
const mercury = race?.outcomes.filter((outcome) => outcome.name === "mercury") ?? [];
|
|
856
|
+
const trouble = mercury.some((o) => o.throttled > 0) && mercury.every((o) => o.usage.length === 0);
|
|
857
|
+
breaker.record(admission, race === undefined ? { ok: false, cause: "error" } : trouble ? { ok: false, cause: "rate-limited" } : { ok: true });
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
443
861
|
//# sourceMappingURL=instant-build.js.map
|