@coreplane/switchboard 1.260.4 → 1.260.6
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/assets/deploy/cloudflare/package.json +2 -2
- package/dist/assets/deploy/cloudflare-docs/package.json +1 -1
- package/dist/assets/deploy/cloudflare-memory/package.json +2 -2
- package/dist/assets/deploy/cloudflare-memory/worker.ts +222 -11
- package/dist/assets/deploy/cloudflare-resident/package.json +2 -2
- package/dist/assets/deploy/cloudflare-resident/worker.ts +124 -40
- package/dist/assets/deploy/cloudflare-sandbox/package.json +2 -2
- package/dist/assets/package-lock.json +712 -97
- package/dist/assets/package.json +4 -4
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/core/budgets.ts +10 -0
- package/dist/assets/src/core/coordinator/contract.ts +5 -0
- package/dist/assets/src/core/coordinator/driver.ts +155 -29
- package/dist/assets/src/core/pipelineStanding.ts +2 -0
- package/dist/assets/src/core/plane/decide.ts +19 -2
- package/dist/assets/src/core/provider.ts +380 -0
- package/dist/assets/src/core/refusal.ts +2 -1
- package/dist/assets/src/core/runEvents.ts +8 -0
- package/dist/assets/src/core/runLedger/decisions.ts +10 -1
- package/dist/assets/src/core/runLedger/types.ts +13 -1
- package/dist/assets/src/core/runRecord.ts +31 -0
- package/dist/assets/src/core/ship/contract.ts +2 -0
- package/dist/assets/src/core/ship/coordinator.ts +515 -30
- package/dist/assets/src/core/types.ts +6 -0
- package/dist/assets/src/execution/residentRebind.ts +15 -0
- package/dist/assets/src/execution/residentReuse.ts +23 -2
- package/dist/assets/web/dist/.vite/manifest.json +67 -67
- package/dist/assets/web/dist/assets/{DeliveryPage-BaemWKW8.js → DeliveryPage-BUJckSuW.js} +1 -1
- package/dist/assets/web/dist/assets/{HomePage-fgKQHLaO.js → HomePage-CBQpcnaW.js} +1 -1
- package/dist/assets/web/dist/assets/{PendingTurnRow-vuWiDzvz.js → PendingTurnRow-DIC86uWA.js} +1 -1
- package/dist/assets/web/dist/assets/{PlanePage-DXMJf8FB.js → PlanePage-CfrbMh8t.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-DdC8yU_S.js → ResidentDetailPage-C-SgOUTl.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-CSM-KODv.js → ResidentsIndexPage-CiDhvXok.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-DZ8huwUU.js +1 -0
- package/dist/assets/web/dist/assets/{RunRoutePage-jo6NZxsP.js → RunRoutePage-Cp6wicYc.js} +3 -3
- package/dist/assets/web/dist/assets/{RunsIndexPage-Dx1Y4sSq.js → RunsIndexPage-BPXtfwwx.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-BuB_NZyR.js → ScheduledPage-DbvP48HI.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-Cn96jB6Q.js → SettingsPage-B87lZaiN.js} +1 -1
- package/dist/assets/web/dist/assets/{SilentTurn-D2cJfpEq.js → SilentTurn-CPED_XCV.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-DDHUCPVJ.js → StatusDot-CFLn-DZ2.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-B4VIHkAI.js → Tooltip-Dx9YBVEx.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-C-2DfjLI.js → UnitRoutePage-d4A2X_o7.js} +1 -1
- package/dist/assets/web/dist/assets/budgets-BQgcCyvb.js +1 -0
- package/dist/assets/web/dist/assets/{dist-Cf1CmCSK.js → dist-Cn51vWpw.js} +11 -11
- package/dist/assets/web/dist/assets/{indexRow-Dxg7C8s9.js → indexRow-BsWcHHVh.js} +1 -1
- package/dist/assets/web/dist/assets/{main-DxPqaFu9.js → main-DYwRHh0F.js} +2 -2
- package/dist/assets/web/dist/assets/{sseReplay-BzoeVxZB.js → sseReplay-DGRDRLua.js} +1 -1
- package/dist/cli.js +2550 -924
- package/package.json +4 -4
- package/dist/assets/web/dist/assets/RunFoldRow-Csik3wD6.js +0 -1
- package/dist/assets/web/dist/assets/budgets-KNNkT4PZ.js +0 -1
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.260.
|
|
3
|
+
"version": "1.260.6",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -89,11 +89,11 @@
|
|
|
89
89
|
"@earendil-works/pi-ai": "0.85.1",
|
|
90
90
|
"@slack/bolt": "^5.1.0",
|
|
91
91
|
"aws4fetch": "^1.0.20",
|
|
92
|
-
"e2b": "^2.
|
|
92
|
+
"e2b": "^2.49.1",
|
|
93
93
|
"mdast-util-from-markdown": "^2.0.3",
|
|
94
94
|
"undici": "^8.10.2",
|
|
95
|
-
"yaml": "^2.
|
|
96
|
-
"zod": "^4.5
|
|
95
|
+
"yaml": "^2.9.1",
|
|
96
|
+
"zod": "^4.6.5"
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
99
|
"@earendil-works/pi-coding-agent": "0.85.1",
|
package/dist/assets/source.json
CHANGED
|
@@ -150,6 +150,11 @@ export function intakeReceiptRetentionMs(catchUpWindowMs: number): number {
|
|
|
150
150
|
* prompt one bounds the ratio the gate is judged by. */
|
|
151
151
|
export const INTAKE_RECOVERY_WINDOW_MS = 10 * MINUTE_MS;
|
|
152
152
|
|
|
153
|
+
/** How long one process owns the right to post a stored intake-failure
|
|
154
|
+
* sentence. A rejected post releases immediately; this bound only recovers a
|
|
155
|
+
* process that died after the atomic claim and before it could finish it. */
|
|
156
|
+
export const INTAKE_DELIVERY_CLAIM_MS = 5 * MINUTE_MS;
|
|
157
|
+
|
|
153
158
|
/** The presets that run the tool loop, and the one pipeline preset. */
|
|
154
159
|
export const LOOP_PRESETS = [
|
|
155
160
|
"general",
|
|
@@ -249,6 +254,11 @@ export const HOSTED_DEADLINE_MARGIN_MINUTES = 60;
|
|
|
249
254
|
* and the pause before a busy spawn is asked again. */
|
|
250
255
|
export const SHIP_WAIT = { marginMinutes: 5, chunkMinutes: 5, mergeChunkMinutes: 5, busyRetryMinutes: 2 } as const;
|
|
251
256
|
|
|
257
|
+
/** A finish event and the run record's durable write are separate deliveries.
|
|
258
|
+
* After the runner receives its own event, it gives the record this short
|
|
259
|
+
* visibility bound before treating an opaque absence as permanent. */
|
|
260
|
+
export const SHIP_RECORD_VISIBILITY = { retries: 3, retryMs: secondsToMs(1) } as const;
|
|
261
|
+
|
|
252
262
|
/** How long a restarting close gives its replacement dispatch to claim the
|
|
253
263
|
* same run before a read with no successor treats the close as final
|
|
254
264
|
* (run-history item 47a). One whole coordinator wait chunk lets an ordinary
|
|
@@ -489,6 +489,10 @@ export interface CoordinatorUnit {
|
|
|
489
489
|
* — no pre-check, no branch, no round 0. A task string's row only; written by
|
|
490
490
|
* the hand-off, read by the driver into the machine's input. */
|
|
491
491
|
resume?: { pr: number; headSha?: string; url?: string };
|
|
492
|
+
/** The decision-record number reserved at admission for this unit. A unit
|
|
493
|
+
* that writes a record carries it through every attempt and briefs its child
|
|
494
|
+
* as `record: NNNN`; the child never scans the directory for a number. */
|
|
495
|
+
record?: string;
|
|
492
496
|
/** The head the unit's own coding child last pushed, recorded when the unit
|
|
493
497
|
* ended `review_pending` (the wall clock capped after the pull request was
|
|
494
498
|
* opened or updated): the next attempt's pre-check starts at the review
|
|
@@ -659,6 +663,7 @@ export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
|
|
|
659
663
|
if (r.issue !== undefined && !isFinite(r.issue)) return false;
|
|
660
664
|
if (r.pr !== undefined && !isPr(r.pr)) return false;
|
|
661
665
|
if (r.resume !== undefined && !isResume(r.resume)) return false;
|
|
666
|
+
if (r.record !== undefined && (typeof r.record !== "string" || !/^\d{4}$/.test(r.record))) return false;
|
|
662
667
|
if (r.lastPush !== undefined && !isText(r.lastPush)) return false;
|
|
663
668
|
if (r.segments !== undefined && (!Array.isArray(r.segments) || !r.segments.every(isSegment))) return false;
|
|
664
669
|
if (r.idle !== undefined && !isUnitIdle(r.idle)) return false;
|
|
@@ -39,7 +39,15 @@
|
|
|
39
39
|
// plan's re-issue. A task string's ship branch waits for a person. Node-free:
|
|
40
40
|
// the shim Worker imports this by relative path.
|
|
41
41
|
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
DAY_MS,
|
|
44
|
+
DEFAULT_GRANT,
|
|
45
|
+
GRANT_RENEWALS_MAX,
|
|
46
|
+
IDLE_DAYS_MAX,
|
|
47
|
+
SHIP_RECORD_VISIBILITY,
|
|
48
|
+
type Grant,
|
|
49
|
+
type GrantSource,
|
|
50
|
+
} from "../budgets.js";
|
|
43
51
|
import { isFindingShape, type Finding } from "../reviewVerdict.js";
|
|
44
52
|
import { DEFAULT_VERBOSITY, isVerbosity, type Verbosity } from "../verbosity.js";
|
|
45
53
|
import {
|
|
@@ -53,6 +61,7 @@ import {
|
|
|
53
61
|
type UnitStatus,
|
|
54
62
|
renderUnitReport,
|
|
55
63
|
reenterApprovedRebase,
|
|
64
|
+
reconcileTerminalPr,
|
|
56
65
|
settleUnit,
|
|
57
66
|
startUnit,
|
|
58
67
|
type ChildFacts,
|
|
@@ -110,6 +119,7 @@ export type CoordinatorStepRoute =
|
|
|
110
119
|
| "branch"
|
|
111
120
|
| "spawn"
|
|
112
121
|
| "read-record"
|
|
122
|
+
| "steer"
|
|
113
123
|
| "pr-check"
|
|
114
124
|
| "round"
|
|
115
125
|
| "unit-end"
|
|
@@ -334,11 +344,25 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
334
344
|
// The hard stop's mark, as the bot's answer carries it (record 0060; issue
|
|
335
345
|
// 1924): a finished child's unit ends stopped on it.
|
|
336
346
|
const stopped = a.body.stopped === true ? { stopped: true as const } : {};
|
|
347
|
+
const pullRequest = isRecord(a.body.pullRequest)
|
|
348
|
+
? prCheckReturn(step, {
|
|
349
|
+
status: a.status,
|
|
350
|
+
body: { ok: true, ...a.body.pullRequest, at: a.body.at },
|
|
351
|
+
}).pr
|
|
352
|
+
: undefined;
|
|
337
353
|
// The interrupted child restarted from its request (issue 1903): the bot
|
|
338
354
|
// answers the live successor's id, and the machine keeps the wait on it.
|
|
339
355
|
const restarted = typeof a.body.restartedAs === "string" ? { restartedAs: a.body.restartedAs } : {};
|
|
340
356
|
if (!run.finished)
|
|
341
|
-
return {
|
|
357
|
+
return {
|
|
358
|
+
type: "read-record",
|
|
359
|
+
step,
|
|
360
|
+
run: { finished: false },
|
|
361
|
+
...(pullRequest !== undefined ? { pullRequest } : {}),
|
|
362
|
+
...stopped,
|
|
363
|
+
...restarted,
|
|
364
|
+
at: a.body.at,
|
|
365
|
+
};
|
|
342
366
|
if (typeof run.status !== "string") throw new UnreadableAnswer("read-record", a, "status");
|
|
343
367
|
// The typed artifacts as the bot's record carries them — shape-checked where
|
|
344
368
|
// they were written (the run record's validator), read here as they are.
|
|
@@ -367,6 +391,7 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
367
391
|
type: "read-record",
|
|
368
392
|
step,
|
|
369
393
|
...stopped,
|
|
394
|
+
...(pullRequest !== undefined ? { pullRequest } : {}),
|
|
370
395
|
run: {
|
|
371
396
|
finished: true,
|
|
372
397
|
status: run.status as Extract<ChildFacts, { finished: true }>["status"],
|
|
@@ -435,8 +460,8 @@ function entryHumanGate(v: unknown):
|
|
|
435
460
|
};
|
|
436
461
|
}
|
|
437
462
|
|
|
438
|
-
function prCheckReturn(step: string, a: BotAnswer): StepReturn {
|
|
439
|
-
const { ok, state, prNumber, url, headSha, sha, mergedAt, at } = a.body;
|
|
463
|
+
function prCheckReturn(step: string, a: BotAnswer): Extract<StepReturn, { type: "pr-check" }> {
|
|
464
|
+
const { ok, state, prNumber, url, headSha, sha, mergedAt, mergedBy, closedBy, at } = a.body;
|
|
440
465
|
if (ok === true && state === "none") {
|
|
441
466
|
const { unrecovered, aheadOfBase, prClosed } = a.body;
|
|
442
467
|
return {
|
|
@@ -464,6 +489,7 @@ function prCheckReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
464
489
|
prNumber,
|
|
465
490
|
url,
|
|
466
491
|
...(typeof headSha === "string" ? { headSha } : {}),
|
|
492
|
+
...(typeof a.body.headBranchExists === "boolean" ? { headBranchExists: a.body.headBranchExists } : {}),
|
|
467
493
|
// The entry facts (issue 1689): the branch's own tip and whether the
|
|
468
494
|
// bot's approval stands at the head, read at the unit-start's pre-check
|
|
469
495
|
// so a re-issued plan resumes at review or at the merge decision.
|
|
@@ -494,10 +520,36 @@ function prCheckReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
494
520
|
typeof sha === "string" &&
|
|
495
521
|
typeof mergedAt === "string"
|
|
496
522
|
)
|
|
497
|
-
return {
|
|
523
|
+
return {
|
|
524
|
+
type: "pr-check",
|
|
525
|
+
step,
|
|
526
|
+
pr: {
|
|
527
|
+
state: "merged",
|
|
528
|
+
prNumber,
|
|
529
|
+
url,
|
|
530
|
+
sha,
|
|
531
|
+
mergedAt,
|
|
532
|
+
...(typeof mergedBy === "string" ? { mergedBy } : {}),
|
|
533
|
+
},
|
|
534
|
+
at,
|
|
535
|
+
};
|
|
536
|
+
if (
|
|
537
|
+
ok === true &&
|
|
538
|
+
state === "closed" &&
|
|
539
|
+
typeof prNumber === "number" &&
|
|
540
|
+
typeof url === "string" &&
|
|
541
|
+
typeof closedBy === "string"
|
|
542
|
+
)
|
|
543
|
+
return { type: "pr-check", step, pr: { state: "closed", prNumber, url, closedBy }, at };
|
|
498
544
|
throw new UnreadableAnswer("pr-check", a, "state");
|
|
499
545
|
}
|
|
500
546
|
|
|
547
|
+
function steerReturn(step: string, a: BotAnswer): Extract<StepReturn, { type: "steer" }> {
|
|
548
|
+
if (a.body.ok === true && (a.body.outcome === "steered" || a.body.outcome === "not_live"))
|
|
549
|
+
return { type: "steer", step, outcome: a.body.outcome, at: a.body.at };
|
|
550
|
+
throw new UnreadableAnswer("steer", a, "outcome");
|
|
551
|
+
}
|
|
552
|
+
|
|
501
553
|
/** The round's checks read as the bot answered it (record 0055): the runs at
|
|
502
554
|
* the reviewed head, or none when GitHub could not be read — the machine
|
|
503
555
|
* treats an absent read as pending and asks again at the chunk's end. A retry
|
|
@@ -510,6 +562,14 @@ function checksReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
510
562
|
type: "checks",
|
|
511
563
|
step,
|
|
512
564
|
...(isRoundChecks(checks) ? { checks } : {}),
|
|
565
|
+
...(isRecord(a.body.pullRequest)
|
|
566
|
+
? {
|
|
567
|
+
pullRequest: prCheckReturn(step, {
|
|
568
|
+
status: a.status,
|
|
569
|
+
body: { ok: true, ...a.body.pullRequest, at },
|
|
570
|
+
}).pr,
|
|
571
|
+
}
|
|
572
|
+
: {}),
|
|
513
573
|
// The pull request is a draft (issue 2063): the machine's table holds the
|
|
514
574
|
// unit for the ready event instead of merging or ending without a cause.
|
|
515
575
|
...(draft === true ? { draft: true } : {}),
|
|
@@ -531,11 +591,31 @@ const isRoundChecks = (v: unknown): v is RoundChecks =>
|
|
|
531
591
|
(v.expected === undefined || (Array.isArray(v.expected) && v.expected.every((n: unknown) => typeof n === "string")));
|
|
532
592
|
|
|
533
593
|
function mergeReturn(step: string, a: BotAnswer): StepReturn {
|
|
534
|
-
const { ok, outcome, by, sha, mergedAt, reason, at } = a.body;
|
|
594
|
+
const { ok, outcome, by, sha, mergedAt, mergedBy, reason, at } = a.body;
|
|
535
595
|
// The door found the pull request already merged after the approval: the
|
|
536
596
|
// merge commit and the time ride the answer, and the unit ends `by: other`.
|
|
537
597
|
if (ok === true && outcome === "merged" && by === "other" && typeof sha === "string" && typeof mergedAt === "string")
|
|
538
|
-
return {
|
|
598
|
+
return {
|
|
599
|
+
type: "merge",
|
|
600
|
+
step,
|
|
601
|
+
outcome: "merged",
|
|
602
|
+
by: "other",
|
|
603
|
+
sha,
|
|
604
|
+
mergedAt,
|
|
605
|
+
...(typeof mergedBy === "string" ? { mergedBy } : {}),
|
|
606
|
+
at,
|
|
607
|
+
};
|
|
608
|
+
if (ok === true && outcome === "recheck" && isRecord(a.body.pullRequest))
|
|
609
|
+
return {
|
|
610
|
+
type: "merge",
|
|
611
|
+
step,
|
|
612
|
+
outcome: "recheck",
|
|
613
|
+
pullRequest: prCheckReturn(step, {
|
|
614
|
+
status: a.status,
|
|
615
|
+
body: { ok: true, ...a.body.pullRequest, at },
|
|
616
|
+
}).pr,
|
|
617
|
+
at,
|
|
618
|
+
};
|
|
539
619
|
if (ok === true && outcome === "merged" && typeof sha === "string")
|
|
540
620
|
return { type: "merge", step, outcome: "merged", sha, at };
|
|
541
621
|
if (
|
|
@@ -568,15 +648,47 @@ async function call(
|
|
|
568
648
|
bot: CoordinatorBot,
|
|
569
649
|
route: CoordinatorStepRoute,
|
|
570
650
|
body: Record<string, unknown>,
|
|
651
|
+
acceptOpaqueNotFound = false,
|
|
571
652
|
): Promise<BotReply> {
|
|
572
653
|
const reply = await bot.step(route, body);
|
|
573
654
|
const read = readBotAnswer(reply.status, reply.text);
|
|
574
|
-
if (!read.ok)
|
|
655
|
+
if (!read.ok) {
|
|
656
|
+
if (acceptOpaqueNotFound && isOpaqueNotFound(reply)) return reply;
|
|
657
|
+
throw new Error(`the bot did not answer ${route}: ${read.reason}`);
|
|
658
|
+
}
|
|
575
659
|
const transient = transientRefusal(read.answer);
|
|
576
660
|
if (transient !== undefined) throw new Error(transient);
|
|
577
661
|
return reply;
|
|
578
662
|
}
|
|
579
663
|
|
|
664
|
+
/** The read door's authorization mask: absent and denied are deliberately the
|
|
665
|
+
* same reply. The runner may pace that reply only from its own addressed
|
|
666
|
+
* finish event; parsing it here never changes what another caller sees. */
|
|
667
|
+
function isOpaqueNotFound(reply: BotReply): boolean {
|
|
668
|
+
if (reply.status !== 404) return false;
|
|
669
|
+
try {
|
|
670
|
+
const body = JSON.parse(reply.text) as { ok?: unknown; error?: unknown };
|
|
671
|
+
return body?.ok === false && body.error === "not_found";
|
|
672
|
+
} catch {
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
async function readRecordStep(
|
|
678
|
+
step: StepRunner,
|
|
679
|
+
bot: CoordinatorBot,
|
|
680
|
+
action: Extract<CoordinatorAction, { type: "read-record" }>,
|
|
681
|
+
body: Record<string, unknown>,
|
|
682
|
+
): Promise<BotReply> {
|
|
683
|
+
for (let retry = 0; ; retry++) {
|
|
684
|
+
const name = retry === 0 ? action.step : `${action.step}/record-read/${retry}`;
|
|
685
|
+
const reply = await step.do(name, STEP_CONFIG, () => call(bot, "read-record", body, true));
|
|
686
|
+
if (!isOpaqueNotFound(reply) || action.finishedObserved !== true || retry >= SHIP_RECORD_VISIBILITY.retries)
|
|
687
|
+
return reply;
|
|
688
|
+
await step.sleep(`${action.step}/record-visible/${retry + 1}`, SHIP_RECORD_VISIBILITY.retryMs);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
580
692
|
/** The answer a stored reply carries — the reply passed `call` once, so it reads the same on replay. */
|
|
581
693
|
function answerOf(route: CoordinatorStepRoute, reply: BotReply): BotAnswer {
|
|
582
694
|
const read = readBotAnswer(reply.status, reply.text);
|
|
@@ -672,10 +784,17 @@ async function perform(
|
|
|
672
784
|
return { type: "wait", step: action.step, outcome: await waitForRun(step, action) };
|
|
673
785
|
case "read-record":
|
|
674
786
|
return readRecordReturn(
|
|
787
|
+
action.step,
|
|
788
|
+
answerOf("read-record", await readRecordStep(step, bot, action, { ...tag, runId: action.runId })),
|
|
789
|
+
);
|
|
790
|
+
case "steer":
|
|
791
|
+
return steerReturn(
|
|
675
792
|
action.step,
|
|
676
793
|
answerOf(
|
|
677
|
-
"
|
|
678
|
-
await step.do(action.step, STEP_CONFIG, () =>
|
|
794
|
+
"steer",
|
|
795
|
+
await step.do(action.step, STEP_CONFIG, () =>
|
|
796
|
+
call(bot, "steer", { ...tag, runId: action.runId, reason: action.reason }),
|
|
797
|
+
),
|
|
679
798
|
),
|
|
680
799
|
);
|
|
681
800
|
case "pr-check":
|
|
@@ -939,11 +1058,17 @@ async function runUnit(
|
|
|
939
1058
|
factsStep,
|
|
940
1059
|
answerOf(
|
|
941
1060
|
"pr-check",
|
|
942
|
-
await step.do(factsStep, STEP_CONFIG, () =>
|
|
1061
|
+
await step.do(factsStep, STEP_CONFIG, () =>
|
|
1062
|
+
call(bot, "pr-check", {
|
|
1063
|
+
...tag,
|
|
1064
|
+
checks: true,
|
|
1065
|
+
...(state.pr !== undefined ? { pr: state.pr.number } : {}),
|
|
1066
|
+
}),
|
|
1067
|
+
),
|
|
943
1068
|
),
|
|
944
1069
|
);
|
|
945
|
-
if (check.type === "pr-check" && check.pr.state === "merged")
|
|
946
|
-
|
|
1070
|
+
if (check.type === "pr-check" && (check.pr.state === "merged" || check.pr.state === "closed"))
|
|
1071
|
+
state = reconcileTerminalPr(state, check.pr);
|
|
947
1072
|
else if (check.type === "pr-check" && check.pr.state === "open")
|
|
948
1073
|
endFacts = {
|
|
949
1074
|
...(check.pr.autoMergeEnabled !== undefined ? { autoMergeEnabled: check.pr.autoMergeEnabled } : {}),
|
|
@@ -973,6 +1098,7 @@ async function runUnit(
|
|
|
973
1098
|
state = reenterApprovedRebase(state);
|
|
974
1099
|
continue pipeline;
|
|
975
1100
|
}
|
|
1101
|
+
const ending = state.ending ?? note.ending;
|
|
976
1102
|
// The last coding child's run is named so the bot can put its handoff
|
|
977
1103
|
// — the deviations it recorded — on the unit's board issue beside the
|
|
978
1104
|
// ending (agent-ship item 14).
|
|
@@ -981,20 +1107,20 @@ async function runUnit(
|
|
|
981
1107
|
// Two copies (routing-and-config item 28): the full report for the
|
|
982
1108
|
// row and the board, and the thread's at the request's verbosity.
|
|
983
1109
|
ending: {
|
|
984
|
-
kind:
|
|
1110
|
+
kind: ending.kind,
|
|
985
1111
|
report: renderUnitReport(state, endFacts),
|
|
986
1112
|
threadReport: renderUnitReport(state, endFacts, state.input.verbosity ?? DEFAULT_VERBOSITY),
|
|
987
1113
|
// An idle ending carries its continuation facts (record 0051): the
|
|
988
1114
|
// bot writes them on the row's `idle` in place of an ending, with
|
|
989
1115
|
// the coding run id it already receives below.
|
|
990
|
-
...(
|
|
1116
|
+
...(ending.kind === "idle"
|
|
991
1117
|
? {
|
|
992
|
-
why:
|
|
993
|
-
renewalsLeft:
|
|
994
|
-
...(
|
|
995
|
-
spendUsd:
|
|
996
|
-
...(
|
|
997
|
-
...(
|
|
1118
|
+
why: ending.why,
|
|
1119
|
+
renewalsLeft: ending.renewalsLeft,
|
|
1120
|
+
...(ending.from !== undefined ? { from: ending.from } : {}),
|
|
1121
|
+
spendUsd: ending.spendUsd,
|
|
1122
|
+
...(ending.handoff !== undefined ? { handoff: ending.handoff } : {}),
|
|
1123
|
+
...(ending.humanGate !== undefined ? { humanGate: ending.humanGate } : {}),
|
|
998
1124
|
}
|
|
999
1125
|
: {}),
|
|
1000
1126
|
},
|
|
@@ -1002,21 +1128,21 @@ async function runUnit(
|
|
|
1002
1128
|
// A review_pending ending names the child's own last push so the next
|
|
1003
1129
|
// attempt's pre-check can start at the review round (the row's lastPush)
|
|
1004
1130
|
// — an idled one the same, off the idle's `from` (record 0051).
|
|
1005
|
-
...(
|
|
1006
|
-
? { headSha:
|
|
1007
|
-
:
|
|
1008
|
-
? { headSha:
|
|
1131
|
+
...(ending.kind === "review_pending" && ending.headSha !== undefined
|
|
1132
|
+
? { headSha: ending.headSha }
|
|
1133
|
+
: ending.kind === "idle" && ending.why === "review_pending" && ending.from !== undefined
|
|
1134
|
+
? { headSha: ending.from }
|
|
1009
1135
|
: {}),
|
|
1010
1136
|
...(state.lastCodingRunId !== undefined ? { codingRunId: state.lastCodingRunId } : {}),
|
|
1011
1137
|
// A continued ending is a segment's end, not the unit's: the bot
|
|
1012
1138
|
// writes the renewal as a row keyed by the next segment's index
|
|
1013
1139
|
// (decision 0046), so a runner reclaimed here never renews twice.
|
|
1014
|
-
...(
|
|
1140
|
+
...(ending.kind === "continued"
|
|
1015
1141
|
? {
|
|
1016
1142
|
segment: {
|
|
1017
|
-
index:
|
|
1018
|
-
...(
|
|
1019
|
-
runId:
|
|
1143
|
+
index: ending.segment,
|
|
1144
|
+
...(ending.from !== undefined ? { from: ending.from } : {}),
|
|
1145
|
+
runId: ending.runId,
|
|
1020
1146
|
},
|
|
1021
1147
|
}
|
|
1022
1148
|
: {}),
|
|
@@ -116,6 +116,7 @@ export const ROUND_STAGE = {
|
|
|
116
116
|
* issue 2100) — closes the unit the same way. */
|
|
117
117
|
export const ENDING_STAGE = {
|
|
118
118
|
merged: "merged",
|
|
119
|
+
closed: "ended",
|
|
119
120
|
already_landed: "merged",
|
|
120
121
|
merge_ready: "merge-ready",
|
|
121
122
|
held: "ended",
|
|
@@ -144,6 +145,7 @@ export const ENDING_STAGE = {
|
|
|
144
145
|
* 2100), so a new ending kind fails the build here. */
|
|
145
146
|
export const ENDING_WORDS = {
|
|
146
147
|
merged: "merged",
|
|
148
|
+
closed: "closed",
|
|
147
149
|
already_landed: "merged",
|
|
148
150
|
merge_ready: "merge-ready",
|
|
149
151
|
held: "held",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// literal where `clock:check` expects it.)
|
|
11
11
|
|
|
12
12
|
import { PLANE, minutesToMs } from "../budgets.js";
|
|
13
|
+
import type { ProviderFailureCause } from "../provider.js";
|
|
13
14
|
import type { PlaneFinding } from "./findings.js";
|
|
14
15
|
|
|
15
16
|
// ---- endings and their causes (record 0064, "Endings and the watches") ------------------------
|
|
@@ -139,6 +140,9 @@ export interface PlaneLevelRow {
|
|
|
139
140
|
side: "below" | "above";
|
|
140
141
|
reportedAt: number;
|
|
141
142
|
generation: string;
|
|
143
|
+
/** A provider row's typed down cause; absent on resident rows, provider-up,
|
|
144
|
+
* and reports from an older bot generation. */
|
|
145
|
+
cause?: ProviderFailureCause;
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
/** The side a resident's level reads for an observer that knows the current
|
|
@@ -300,7 +304,13 @@ export type PlaneEvent =
|
|
|
300
304
|
/** A provider's level as the model proxy reported it: `up` on a relayed
|
|
301
305
|
* success, `down` on a failure past its one retry. `up` re-issues every
|
|
302
306
|
* turn held parked on the provider, once each. */
|
|
303
|
-
| {
|
|
307
|
+
| {
|
|
308
|
+
kind: "provider_level";
|
|
309
|
+
at: number;
|
|
310
|
+
provider: string;
|
|
311
|
+
level: "up" | "down";
|
|
312
|
+
cause?: ProviderFailureCause;
|
|
313
|
+
}
|
|
304
314
|
/** A run parked on its provider (record 0064): the harness holds the turn,
|
|
305
315
|
* the lease keeps counting, and the provider's next `up` steers it once. */
|
|
306
316
|
| { kind: "park"; at: number; runId: string; provider: string }
|
|
@@ -692,7 +702,13 @@ function onHeartbeat(
|
|
|
692
702
|
* then walks the queue for anything waiting on `provider_up`. */
|
|
693
703
|
function onProviderLevel(
|
|
694
704
|
state: PlaneState,
|
|
695
|
-
event: {
|
|
705
|
+
event: {
|
|
706
|
+
kind: "provider_level";
|
|
707
|
+
at: number;
|
|
708
|
+
provider: string;
|
|
709
|
+
level: "up" | "down";
|
|
710
|
+
cause?: ProviderFailureCause;
|
|
711
|
+
},
|
|
696
712
|
): PlaneDecision {
|
|
697
713
|
const row: PlaneLevelRow = {
|
|
698
714
|
resident: event.provider,
|
|
@@ -700,6 +716,7 @@ function onProviderLevel(
|
|
|
700
716
|
side: event.level === "up" ? "below" : "above",
|
|
701
717
|
reportedAt: event.at,
|
|
702
718
|
generation: "",
|
|
719
|
+
...(event.level === "down" && event.cause !== undefined ? { cause: event.cause } : {}),
|
|
703
720
|
};
|
|
704
721
|
const levels = [...state.levels.filter((l) => !(l.resident === event.provider && l.name === "provider")), row];
|
|
705
722
|
const writes: PlaneWrite[] = [{ table: "plane_levels", op: "put", row }];
|