@coreplane/switchboard 1.207.1 → 1.208.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/assets/config/config.example.yaml +11 -11
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/core/coordinator/contract.ts +11 -0
- package/dist/assets/src/core/coordinator/driver.ts +22 -1
- package/dist/assets/src/core/reviewVerdict.ts +40 -0
- package/dist/assets/src/core/runEvents.ts +29 -1
- package/dist/assets/src/core/runFriction.ts +5 -4
- package/dist/assets/src/core/runRecord.ts +10 -1
- package/dist/assets/src/core/ship/contract.ts +9 -0
- package/dist/assets/src/core/ship/coordinator.ts +17 -12
- package/dist/assets/src/core/trace/streamSpans.ts +3 -0
- package/dist/assets/src/core/trace/workerTrace.ts +3 -0
- package/dist/assets/web/dist/.vite/manifest.json +18 -18
- package/dist/assets/web/dist/assets/{ResidentDetailPage-D3P21yeI.js → ResidentDetailPage-CV3wfAIP.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-DOYqnZ1q.js → ResidentsIndexPage-CLkWc50b.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-OmvrvPXY.js → RunRoutePage-CQYRfQ_B.js} +4 -4
- package/dist/assets/web/dist/assets/{RunsIndexPage-DWbSQtL4.js → RunsIndexPage-BLRPp_gk.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-CPKfJ4mR.js → ScheduledPage-C-VO4Ddl.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-COr8jTyM.js → StatusDot-CIAoBB5Y.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-fOqTZkNT.js → Tooltip-DEL1ic4g.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-BVjAWgkb.js → dist-CawBR4t8.js} +1 -1
- package/dist/assets/web/dist/assets/{main-DZbJaqUb.js → main-CctUbVOl.js} +2 -2
- package/dist/cli.js +6538 -6579
- package/package.json +1 -1
|
@@ -278,20 +278,20 @@ workspaceDir: ./workspaces
|
|
|
278
278
|
# tokenEnv: MEMORY_TOKEN # env var holding the Worker's bearer (default)
|
|
279
279
|
|
|
280
280
|
# agent:ship pipeline caps (docs/reference/specs/agent-ship.md). `agent:ship in owner/repo:
|
|
281
|
-
# <task>`
|
|
281
|
+
# <task>` hands the coding → review → fix loop to LGTM to the plan runner — the
|
|
282
|
+
# ShipCoordinator Workflow in the bot Worker, whose rounds are child runs — which
|
|
283
|
+
# needs the `coordinator` ingress entry, its grants, run history on the state
|
|
284
|
+
# Worker and PUBLIC_BASE_URL (docs/how-to/turn-features-on-and-off.md); without
|
|
285
|
+
# them the request is refused naming what is missing. Per unit: at most
|
|
282
286
|
# `maxRounds` review rounds and `maxMinutes` minutes of wall clock — whichever
|
|
283
|
-
# hits first ends the
|
|
284
|
-
# remaining
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
287
|
+
# hits first ends the unit, and each child round's own budget is clipped to the
|
|
288
|
+
# remaining time. `maxMinutes` is the ship preset's declared budget: a channel or
|
|
289
|
+
# user `boundary.maxMinutes` and a per-message `budget:` directive clip it like
|
|
290
|
+
# any preset's, and the card says so. Defaults shown; both must be integers >= 1;
|
|
291
|
+
# any other key under `ship` fails the load by name.
|
|
288
292
|
# ship:
|
|
289
|
-
# maxRounds: 3 # review rounds per
|
|
293
|
+
# maxRounds: 3 # review rounds per unit
|
|
290
294
|
# maxMinutes: 120 # the ship preset's wall-clock budget in minutes (the registry's default)
|
|
291
|
-
# coordinator: false # true hands every agent:ship request to the plan runner — the ShipCoordinator
|
|
292
|
-
# # Workflow in the bot Worker — instead of the in-process round loop; needs the
|
|
293
|
-
# # `coordinator` ingress entry, its grant, run history on the state Worker and
|
|
294
|
-
# # PUBLIC_BASE_URL (see docs/how-to/turn-features-on-and-off.md)
|
|
295
295
|
|
|
296
296
|
# The fan-out cap a spawning run meets (docs/reference/specs/agent-conductor.md).
|
|
297
297
|
# `agent:conductor` starts child runs as the person who asked — each an
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.208.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.208.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -18999,7 +18999,7 @@
|
|
|
18999
18999
|
},
|
|
19000
19000
|
"packages/switchboard": {
|
|
19001
19001
|
"name": "@coreplane/switchboard",
|
|
19002
|
-
"version": "1.
|
|
19002
|
+
"version": "1.208.0",
|
|
19003
19003
|
"license": "Apache-2.0",
|
|
19004
19004
|
"dependencies": {
|
|
19005
19005
|
"@anthropic-ai/sdk": "^0.124.0",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.208.0",
|
|
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",
|
package/dist/assets/source.json
CHANGED
|
@@ -145,6 +145,11 @@ export interface CoordinatorUnit {
|
|
|
145
145
|
/** The unit's board issue in the repository, when one titled by the unit id exists — the handoff's destination. */
|
|
146
146
|
issue?: number;
|
|
147
147
|
pr?: { number: number; url: string };
|
|
148
|
+
/** Resume at review (agent-ship item 10): the open pull request of ship's own
|
|
149
|
+
* the requester named, so the unit's pipeline opens at its first review round
|
|
150
|
+
* — no pre-check, no branch, no round 0. A task string's row only; written by
|
|
151
|
+
* the hand-off, read by the driver into the machine's input. */
|
|
152
|
+
resume?: { pr: number; headSha?: string; url?: string };
|
|
148
153
|
/** The round boundaries the coordinator reported, oldest first (the `ship_round` vocabulary). */
|
|
149
154
|
rounds: Array<{ index: number; agent: string; outcome: string; at: number }>;
|
|
150
155
|
/** How the unit ended: the ending's kind and the thread's report, when it has. */
|
|
@@ -163,6 +168,11 @@ const isOptionalText = (v: unknown): boolean => v === undefined || isText(v);
|
|
|
163
168
|
const isFinite = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
|
|
164
169
|
const isObject = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null;
|
|
165
170
|
const isPr = (v: unknown): boolean => isObject(v) && isFinite(v.number) && isText(v.url, 2048);
|
|
171
|
+
const isResume = (v: unknown): boolean =>
|
|
172
|
+
isObject(v) &&
|
|
173
|
+
isFinite(v.pr) &&
|
|
174
|
+
(v.headSha === undefined || isText(v.headSha)) &&
|
|
175
|
+
(v.url === undefined || isText(v.url, 2048));
|
|
166
176
|
|
|
167
177
|
/** Structural check on a record from outside the process (a Worker response, an HTTP body). */
|
|
168
178
|
export function isCoordinatorInstance(v: unknown): v is CoordinatorInstance {
|
|
@@ -194,6 +204,7 @@ export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
|
|
|
194
204
|
if (!isOptionalText(r.threadKey) || !isOptionalText(r.sourceUrl)) return false;
|
|
195
205
|
if (r.issue !== undefined && !isFinite(r.issue)) return false;
|
|
196
206
|
if (r.pr !== undefined && !isPr(r.pr)) return false;
|
|
207
|
+
if (r.resume !== undefined && !isResume(r.resume)) return false;
|
|
197
208
|
if (
|
|
198
209
|
!Array.isArray(r.rounds) ||
|
|
199
210
|
r.rounds.length > MAX_ROUNDS ||
|
|
@@ -233,7 +233,18 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
233
233
|
// The typed artifacts as the bot's record carries them — shape-checked where
|
|
234
234
|
// they were written (the run record's validator), read here as they are.
|
|
235
235
|
const facts = run as unknown as Omit<Extract<ChildFacts, { finished: true }>, "finished" | "status">;
|
|
236
|
-
const {
|
|
236
|
+
const {
|
|
237
|
+
finalReply,
|
|
238
|
+
pr,
|
|
239
|
+
headSha,
|
|
240
|
+
description,
|
|
241
|
+
verdict,
|
|
242
|
+
reviewPosted,
|
|
243
|
+
reviewPostReason,
|
|
244
|
+
reviewHead,
|
|
245
|
+
dispositions,
|
|
246
|
+
handoff,
|
|
247
|
+
} = facts;
|
|
237
248
|
return {
|
|
238
249
|
type: "read-record",
|
|
239
250
|
step,
|
|
@@ -246,6 +257,7 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
246
257
|
...(description !== undefined ? { description } : {}),
|
|
247
258
|
...(verdict !== undefined ? { verdict } : {}),
|
|
248
259
|
...(reviewPosted !== undefined ? { reviewPosted } : {}),
|
|
260
|
+
...(reviewPostReason !== undefined ? { reviewPostReason } : {}),
|
|
249
261
|
...(reviewHead !== undefined ? { reviewHead } : {}),
|
|
250
262
|
...(dispositions !== undefined ? { dispositions } : {}),
|
|
251
263
|
...(handoff !== undefined ? { handoff } : {}),
|
|
@@ -399,6 +411,10 @@ async function runUnit(
|
|
|
399
411
|
const start = readUnitStart(
|
|
400
412
|
answerOf("unit-start", await step.do(`${unit}/start`, STEP_CONFIG, () => call(bot, "unit-start", tag))),
|
|
401
413
|
);
|
|
414
|
+
// A resume at review (agent-ship item 10) rides the unit's row: the pull
|
|
415
|
+
// request of ship's own the requester named opens the pipeline at its first
|
|
416
|
+
// review round, with no pre-check, no branch and no round 0.
|
|
417
|
+
const resume = plan.units.find((u) => u.unit === unit)?.resume;
|
|
402
418
|
let state: UnitPipelineState = openUnitPipeline(
|
|
403
419
|
{
|
|
404
420
|
unit: { id: unit, branch: node.branch },
|
|
@@ -411,6 +427,7 @@ async function runUnit(
|
|
|
411
427
|
// approved at its head and the checks are green; any other branch — a
|
|
412
428
|
// task string's ship branch — waits for a person.
|
|
413
429
|
merge: parsePlanBranch(node.branch) !== undefined ? "runner" : "person",
|
|
430
|
+
...(resume !== undefined ? { resume } : {}),
|
|
414
431
|
},
|
|
415
432
|
start.at,
|
|
416
433
|
);
|
|
@@ -425,10 +442,14 @@ async function runUnit(
|
|
|
425
442
|
const body = { ...tag, index: note.index, agent: note.agent, outcome: note.outcome };
|
|
426
443
|
await step.do(`${unit}/note/${++notes}`, STEP_CONFIG, () => call(bot, "round", body));
|
|
427
444
|
} else {
|
|
445
|
+
// The last coding child's run is named so the bot can put its handoff
|
|
446
|
+
// — the deviations it recorded — on the unit's board issue beside the
|
|
447
|
+
// ending (agent-ship item 14).
|
|
428
448
|
const body = {
|
|
429
449
|
...tag,
|
|
430
450
|
ending: { kind: note.ending.kind, report: renderUnitReport(state) },
|
|
431
451
|
...(state.pr !== undefined ? { pr: state.pr } : {}),
|
|
452
|
+
...(state.lastCodingRunId !== undefined ? { codingRunId: state.lastCodingRunId } : {}),
|
|
432
453
|
};
|
|
433
454
|
await step.do(`${unit}/end`, STEP_CONFIG, () => call(bot, "unit-end", body));
|
|
434
455
|
}
|
|
@@ -259,6 +259,46 @@ export function isReviewVerdictShape(v: unknown): v is ReviewVerdict {
|
|
|
259
259
|
return true;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/** How a review run's post-step ended, as the run's record carries it
|
|
263
|
+
* (docs/reference/specs/agent-review.md item 18; run-history item 2): the verdict
|
|
264
|
+
* landed on a named pull request pinned to `head` (the verdict kind rides when
|
|
265
|
+
* one was submitted — a review that posted without a verdict posts the
|
|
266
|
+
* no-verdict line), or nothing landed and `reason` says why — a guard's
|
|
267
|
+
* refusal, an opt-out, no pull request, GitHub's own error. A coordinator's
|
|
268
|
+
* `read-record` answers `reviewPosted` from this before it asks GitHub, whose
|
|
269
|
+
* review list can lag a post it accepted a second ago. */
|
|
270
|
+
export type ReviewPost =
|
|
271
|
+
| { posted: true; target: { repo: string; number: number }; head: string; verdict?: ReviewVerdictKind }
|
|
272
|
+
| { posted: false; reason: string };
|
|
273
|
+
|
|
274
|
+
const REVIEW_POST_HEAD = /^[0-9a-f]{7,40}$/;
|
|
275
|
+
|
|
276
|
+
/** Structural check on a review post read back from a stored record: a posted
|
|
277
|
+
* outcome names its pull request and a 7-to-40-hex head, its verdict (when
|
|
278
|
+
* present) a known kind; a skipped one carries a string reason. */
|
|
279
|
+
export function isReviewPostShape(v: unknown): v is ReviewPost {
|
|
280
|
+
if (!isRecordLike(v)) return false;
|
|
281
|
+
if (v.posted === false) return typeof v.reason === "string";
|
|
282
|
+
if (v.posted !== true) return false;
|
|
283
|
+
const target = v.target;
|
|
284
|
+
if (
|
|
285
|
+
!isRecordLike(target) ||
|
|
286
|
+
typeof target.repo !== "string" ||
|
|
287
|
+
typeof target.number !== "number" ||
|
|
288
|
+
!Number.isInteger(target.number) ||
|
|
289
|
+
target.number <= 0
|
|
290
|
+
)
|
|
291
|
+
return false;
|
|
292
|
+
if (typeof v.head !== "string" || !REVIEW_POST_HEAD.test(v.head)) return false;
|
|
293
|
+
return v.verdict === undefined || VERDICT_KINDS.includes(v.verdict as string);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** The skip's reason through the redaction seam (it may carry GitHub's own
|
|
297
|
+
* words); a posted outcome has no free text and is returned as it is. */
|
|
298
|
+
export function redactReviewPost(post: ReviewPost, redact: (s: string) => string = redactSecrets): ReviewPost {
|
|
299
|
+
return post.posted ? post : { posted: false, reason: redact(post.reason) };
|
|
300
|
+
}
|
|
301
|
+
|
|
262
302
|
/** Structural check on a disposition set read back from a stored record. */
|
|
263
303
|
export function isFindingDispositionsShape(v: unknown): v is FindingDisposition[] {
|
|
264
304
|
return (
|
|
@@ -110,7 +110,15 @@ export type RunNoteKind =
|
|
|
110
110
|
* request would target (docs/reference/specs/pr-description.md item 5) —
|
|
111
111
|
* the summary names the branch. Published by the post-step, so a unit
|
|
112
112
|
* that ends without a pull request says why on the record and the card. */
|
|
113
|
-
| "pr_not_opened"
|
|
113
|
+
| "pr_not_opened"
|
|
114
|
+
/** A review run's post-step posted nothing to the pull request — a guard's
|
|
115
|
+
* refusal, an opt-out, no pull request resolved, GitHub's own error — and
|
|
116
|
+
* the summary names the pull request (when one was resolved) and the
|
|
117
|
+
* reason (docs/reference/specs/agent-review.md item 18). Published by the
|
|
118
|
+
* post-step beside the thread's Slack-only note, so the record says the
|
|
119
|
+
* verdict is Slack-only and a coordinator reading it never asks GitHub
|
|
120
|
+
* for a review that was never sent. */
|
|
121
|
+
| "review_not_posted";
|
|
114
122
|
|
|
115
123
|
/** Every `RunNoteKind`, as a value (a reader that filters notes by kind uses
|
|
116
124
|
* this; adding a kind to the union without adding it here is a type error). */
|
|
@@ -130,6 +138,7 @@ export const RUN_NOTE_KINDS = [
|
|
|
130
138
|
"description_turn",
|
|
131
139
|
"cold_sandbox",
|
|
132
140
|
"pr_not_opened",
|
|
141
|
+
"review_not_posted",
|
|
133
142
|
] as const satisfies readonly RunNoteKind[];
|
|
134
143
|
type _EveryKindListed = [RunNoteKind] extends [(typeof RUN_NOTE_KINDS)[number]] ? true : never;
|
|
135
144
|
const _everyKindListed: _EveryKindListed = true;
|
|
@@ -438,6 +447,25 @@ export type RunEvent =
|
|
|
438
447
|
* run record carries the PR URL as a fact of the run rather than only the
|
|
439
448
|
* channel reply's projection of it. Additive: unknown → ignored. */
|
|
440
449
|
| { type: "pr_opened"; url: string; number: number; created: boolean; seq?: number; at?: number }
|
|
450
|
+
/** The review post-step's outcome when the verdict landed
|
|
451
|
+
* (docs/reference/specs/agent-review.md item 18): the pull request it was
|
|
452
|
+
* posted to, the head it was pinned to (the carried head after a rebase,
|
|
453
|
+
* item 12) and the verdict kind when one was submitted. Published by the
|
|
454
|
+
* post-step straight to the registry BEFORE the stream finishes — the
|
|
455
|
+
* post-step runs inside the run loop, like the coding one — so the record
|
|
456
|
+
* carries the post as a fact of the run and a coordinator woken by the
|
|
457
|
+
* finish reads it there instead of asking GitHub, whose review list can
|
|
458
|
+
* lag the post it just accepted. A post that did not land is a
|
|
459
|
+
* `review_not_posted` note. Additive: unknown → ignored. */
|
|
460
|
+
| {
|
|
461
|
+
type: "review_posted";
|
|
462
|
+
repo: string;
|
|
463
|
+
number: number;
|
|
464
|
+
head: string;
|
|
465
|
+
verdict?: "approve" | "request_changes";
|
|
466
|
+
seq?: number;
|
|
467
|
+
at?: number;
|
|
468
|
+
}
|
|
441
469
|
/** One `agent:ship` round boundary (docs/reference/specs/agent-ship.md item 12): the
|
|
442
470
|
* pipeline publishes a `started` event when a round's child is dispatched
|
|
443
471
|
* and one settle event when its outcome is known (`ShipRoundOutcome`), so
|
|
@@ -370,7 +370,7 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
370
370
|
// final answer (`answer`) — are the run's story, not its steps: none counts
|
|
371
371
|
// toward `eventCount`.
|
|
372
372
|
let narrativeEvents = 0;
|
|
373
|
-
let sideFactEvents = 0; // skill_use / review_artifact / pr_description / pr_opened / ship_round: facts about the run, not steps
|
|
373
|
+
let sideFactEvents = 0; // skill_use / review_artifact / pr_description / pr_opened / review_posted / ship_round: facts about the run, not steps
|
|
374
374
|
let spanEvents = 0; // span_start / span_end (docs/reference/specs/tracing.md): timing records, not steps
|
|
375
375
|
let wrapUp: { index: number; at?: number } | undefined;
|
|
376
376
|
events.forEach((ev, index) => {
|
|
@@ -385,14 +385,15 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
385
385
|
}
|
|
386
386
|
// Side facts about the run, not steps: skill_use rides beside a use_skill
|
|
387
387
|
// call that already produced its own tool pair; review_artifact,
|
|
388
|
-
// pr_description, pr_opened and the ship_round boundaries
|
|
389
|
-
// by the dispatcher/pipeline outside the model loop
|
|
390
|
-
// any of them would distort the story.
|
|
388
|
+
// pr_description, pr_opened, review_posted and the ship_round boundaries
|
|
389
|
+
// are published by the dispatcher/pipeline outside the model loop
|
|
390
|
+
// entirely. Counting any of them would distort the story.
|
|
391
391
|
if (
|
|
392
392
|
ev.type === "skill_use" ||
|
|
393
393
|
ev.type === "review_artifact" ||
|
|
394
394
|
ev.type === "pr_description" ||
|
|
395
395
|
ev.type === "pr_opened" ||
|
|
396
|
+
ev.type === "review_posted" ||
|
|
396
397
|
ev.type === "ship_round"
|
|
397
398
|
) {
|
|
398
399
|
sideFactEvents++;
|
|
@@ -4,9 +4,11 @@ import type { RunEvent } from "./runEvents.js";
|
|
|
4
4
|
import { isHeadMaterial, isSpanRecord } from "./runEvents.js";
|
|
5
5
|
import { isHandoffShape, type Handoff } from "./ship/handoff.js";
|
|
6
6
|
import {
|
|
7
|
+
type FindingDisposition,
|
|
7
8
|
isFindingDispositionsShape,
|
|
9
|
+
isReviewPostShape,
|
|
8
10
|
isReviewVerdictShape,
|
|
9
|
-
type
|
|
11
|
+
type ReviewPost,
|
|
10
12
|
type ReviewVerdict,
|
|
11
13
|
} from "./reviewVerdict.js";
|
|
12
14
|
import { IDEMPOTENCY_KEY_PATTERN, INSTANCE_ID_PATTERN } from "./coordinator/contract.js";
|
|
@@ -114,6 +116,12 @@ export interface RunRecord {
|
|
|
114
116
|
/** The head a review run reviewed and posted against (7 to 40 lowercase hex),
|
|
115
117
|
* after the settle; present only on a review run that pinned one. */
|
|
116
118
|
reviewHead?: string;
|
|
119
|
+
/** How the review run's post-step ended (docs/reference/specs/agent-review.md
|
|
120
|
+
* item 18): the verdict posted to a named pull request at a pinned head, or
|
|
121
|
+
* not posted with the reason — what a coordinator's `read-record` answers
|
|
122
|
+
* `reviewPosted` from before it asks GitHub. Present only on a review run
|
|
123
|
+
* that reached its post-step; absent on records written before it existed. */
|
|
124
|
+
reviewPost?: ReviewPost;
|
|
117
125
|
/** The dispositions a fix round submitted through `submit_dispositions`
|
|
118
126
|
* (docs/reference/specs/agent-ship.md item 6), the last call's set, redacted;
|
|
119
127
|
* present only on a coding run dispatched as a fix round that submitted one. */
|
|
@@ -502,6 +510,7 @@ export function isRunRecord(v: unknown): v is RunRecord {
|
|
|
502
510
|
if (r.reviewHead !== undefined && (typeof r.reviewHead !== "string" || !REVIEW_HEAD_PATTERN.test(r.reviewHead)))
|
|
503
511
|
return false;
|
|
504
512
|
if (r.dispositions !== undefined && !isFindingDispositionsShape(r.dispositions)) return false;
|
|
513
|
+
if (r.reviewPost !== undefined && !isReviewPostShape(r.reviewPost)) return false;
|
|
505
514
|
if (r.profile !== undefined && !isRunProfileRecord(r.profile)) return false;
|
|
506
515
|
// A parent is named by a run id (item 46): the same shape as the record's own.
|
|
507
516
|
if (r.parentRunId !== undefined && (typeof r.parentRunId !== "string" || !RUN_ID_PATTERN.test(r.parentRunId)))
|
|
@@ -79,6 +79,10 @@ export interface ChildContract {
|
|
|
79
79
|
issue: { repo: string; number: number } | undefined;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** The PR-title gate's name, exported so another module can name the same
|
|
83
|
+
* gate without a copied string (`npm run check:pr-title`; CI's `title` check). */
|
|
84
|
+
export const PR_TITLE_GUARD = "check:pr-title";
|
|
85
|
+
|
|
82
86
|
/** The guards a child may not weaken (AGENTS.md's Commands table), one line each on what they refuse. */
|
|
83
87
|
export const GUARDS: readonly Guard[] = [
|
|
84
88
|
{
|
|
@@ -96,6 +100,11 @@ export const GUARDS: readonly Guard[] = [
|
|
|
96
100
|
refuses:
|
|
97
101
|
"a new imprint in the public tree (a company, a person, a tracker reference, a plan id, a platform id, a date); the recorded list only shrinks",
|
|
98
102
|
},
|
|
103
|
+
{
|
|
104
|
+
name: PR_TITLE_GUARD,
|
|
105
|
+
refuses:
|
|
106
|
+
"a title whose type, scope or grammar is not the changelog line, the scope being one of the code map's Areas",
|
|
107
|
+
},
|
|
99
108
|
{
|
|
100
109
|
name: "decisions:check",
|
|
101
110
|
refuses:
|
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
// Workflow instance in the bot's shim Worker with no model turn and no
|
|
5
5
|
// credential — decides between the steps it asks the bot for. The Workflow
|
|
6
6
|
// asks `nextAction`, performs it (a bot route, a `waitForEvent`, a sleep) and
|
|
7
|
-
// feeds the answer to `applyReturn`; everything the
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// with the loop's process gone.
|
|
7
|
+
// feeds the answer to `applyReturn`; everything the pipeline decides — which
|
|
8
|
+
// round is next, what a child's end means, when a cap ends the pipeline, when
|
|
9
|
+
// the pull request is merge-ready — is decided here over the step returns, so
|
|
10
|
+
// the endings hold with no process of the pipeline's own to die.
|
|
12
11
|
//
|
|
13
12
|
// Two machines, both pure. The plan cursor walks a plan record's unit graph:
|
|
14
13
|
// which units are ready (their dependencies merged), which one a failure
|
|
@@ -345,8 +344,12 @@ export type ChildFacts =
|
|
|
345
344
|
description?: boolean;
|
|
346
345
|
/** A review child's verdict. */
|
|
347
346
|
verdict?: { verdict: ReviewVerdictKind; summary?: string; findings: Finding[] };
|
|
348
|
-
/** Whether the review child's verdict landed on the pull request
|
|
347
|
+
/** Whether the review child's verdict landed on the pull request — the
|
|
348
|
+
* child's own record of its post, or GitHub's review list when the
|
|
349
|
+
* record is silent (http-ingress.md item 9). */
|
|
349
350
|
reviewPosted?: boolean;
|
|
351
|
+
/** Why the child recorded no post, when it recorded one it chose or failed. */
|
|
352
|
+
reviewPostReason?: string;
|
|
350
353
|
reviewHead?: string;
|
|
351
354
|
/** A fix child's dispositions. */
|
|
352
355
|
dispositions?: FindingDisposition[];
|
|
@@ -375,7 +378,7 @@ export type StepReturn =
|
|
|
375
378
|
| { type: "merge"; step: string; outcome: "pending" | "refused"; reason: string; at: number }
|
|
376
379
|
| { type: "sleep"; step: string };
|
|
377
380
|
|
|
378
|
-
/** How one unit's pipeline ended — the truthful vocabulary the
|
|
381
|
+
/** How one unit's pipeline ended — the truthful vocabulary the ship pipeline
|
|
379
382
|
* has, plus the merge's own: `merged` by the runner, or found merged (`by:
|
|
380
383
|
* other` — a person's merge, or an earlier attempt's that died after it, so
|
|
381
384
|
* the runner merged nothing), `merge_ready` for a person, `merge_refused` by
|
|
@@ -512,7 +515,7 @@ function waitSliceMs(clock: number, until: number): number {
|
|
|
512
515
|
}
|
|
513
516
|
|
|
514
517
|
/** The child's budget: its preset's own, clipped to the pipeline's remaining
|
|
515
|
-
* wall clock (
|
|
518
|
+
* wall clock (agent-ship item 8's clip), never under the two minutes a
|
|
516
519
|
* spawn accepts. */
|
|
517
520
|
function budgetMinutesFor(s: UnitPipelineState, preset: ChildPreset): number {
|
|
518
521
|
return Math.max(2, Math.min(s.input.childMinutes[preset], Math.floor(remainingMs(s) / MIN)));
|
|
@@ -606,7 +609,7 @@ const roundNote = (round: RoundRef, outcome: ShipRoundOutcome): CoordinatorNote
|
|
|
606
609
|
outcome,
|
|
607
610
|
});
|
|
608
611
|
|
|
609
|
-
/** Start a round if the reservation holds (
|
|
612
|
+
/** Start a round if the reservation holds (agent-ship item 8's check: a
|
|
610
613
|
* child clipped under the reserve cannot do useful work). */
|
|
611
614
|
function enterRound(s: UnitPipelineState, round: RoundRef, notes: CoordinatorNote[] = []): Transition {
|
|
612
615
|
const remaining = remainingMs(s);
|
|
@@ -720,13 +723,15 @@ function settleReview(
|
|
|
720
723
|
const notes = [roundNote(round, verdict.verdict)];
|
|
721
724
|
if (verdict.verdict === "approve") {
|
|
722
725
|
// Merge-ready stands on the POSTED approval: an approve whose post did
|
|
723
|
-
// not land left no approving review on the pull request.
|
|
726
|
+
// not land left no approving review on the pull request. The reason is
|
|
727
|
+
// the child's own when it recorded one; how to continue is the report's
|
|
728
|
+
// re-issue line, in the runner's words (`renderUnitReport`).
|
|
724
729
|
if (facts.reviewPosted === false)
|
|
725
730
|
return end(
|
|
726
731
|
next,
|
|
727
732
|
{
|
|
728
733
|
kind: "aborted",
|
|
729
|
-
reason: `⚠️ The review approved, but the approval could not be posted
|
|
734
|
+
reason: `⚠️ The review approved, but the approval could not be posted${facts.reviewPostReason !== undefined ? ` (${facts.reviewPostReason})` : ""} — the pull request carries no approving review.`,
|
|
730
735
|
round,
|
|
731
736
|
reviewRounds: next.reviewRounds,
|
|
732
737
|
},
|
|
@@ -1030,7 +1035,7 @@ function splitReport(s: UnitPipelineState): string {
|
|
|
1030
1035
|
return lines.join("\n");
|
|
1031
1036
|
}
|
|
1032
1037
|
|
|
1033
|
-
/** The thread's report for a unit's ending — the
|
|
1038
|
+
/** The thread's report for a unit's ending — the ship pipeline's own words
|
|
1034
1039
|
* for the endings it has, and the merge's for the ones it gains. */
|
|
1035
1040
|
export function renderUnitReport(s: UnitPipelineState): string {
|
|
1036
1041
|
const e = s.ending;
|
|
@@ -45,6 +45,7 @@ export const STREAMED_SPANS = [
|
|
|
45
45
|
"run.description_turn",
|
|
46
46
|
"run.observe_workspace",
|
|
47
47
|
"run.pr_post_step",
|
|
48
|
+
"run.review_post_step",
|
|
48
49
|
"run.reading_diff_join",
|
|
49
50
|
"run.pr_description_join",
|
|
50
51
|
"model.turn",
|
|
@@ -92,6 +93,7 @@ const GETTING_READY: ReadonlySet<string> = new Set([
|
|
|
92
93
|
const FINISHING_UP: ReadonlySet<string> = new Set([
|
|
93
94
|
"run.observe_workspace",
|
|
94
95
|
"run.pr_post_step",
|
|
96
|
+
"run.review_post_step",
|
|
95
97
|
"run.reading_diff_join",
|
|
96
98
|
"run.pr_description_join",
|
|
97
99
|
]);
|
|
@@ -150,6 +152,7 @@ export const PARENTS: Readonly<Record<string, readonly string[]>> = {
|
|
|
150
152
|
"run.description_turn": ["request", "ship.round"],
|
|
151
153
|
"run.observe_workspace": ["request", "ship.round"],
|
|
152
154
|
"run.pr_post_step": ["request", "ship.round"],
|
|
155
|
+
"run.review_post_step": ["request", "ship.round"],
|
|
153
156
|
"run.reading_diff_join": ["request", "ship.round"],
|
|
154
157
|
"run.pr_description_join": ["request", "ship.round"],
|
|
155
158
|
"model.turn": ["run.agent"],
|
|
@@ -52,6 +52,9 @@ export function shimRoute(pathname: string): string | undefined {
|
|
|
52
52
|
if (pathname === "/costs" || pathname.startsWith("/costs/")) return "costs";
|
|
53
53
|
if (pathname.startsWith("/api/")) return "api";
|
|
54
54
|
if (pathname.startsWith("/admin/")) return "admin";
|
|
55
|
+
// The model proxy's two routes (docs/reference/specs/model-proxy.md): a bounded
|
|
56
|
+
// request per model call, forwarded to the container like everything else.
|
|
57
|
+
if (pathname === "/v1/messages" || pathname === "/v1/chat/completions") return "model-proxy";
|
|
55
58
|
if (pathname === "/docs" || pathname.startsWith("/docs/")) return "docs";
|
|
56
59
|
if (pathname === "/" || pathname === "/index.html") return "page";
|
|
57
60
|
return "other";
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"src": "../node_modules/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-vietnamese-wght-normal.woff2"
|
|
29
29
|
},
|
|
30
30
|
"../node_modules/@pierre/diffs/dist/index.js": {
|
|
31
|
-
"file": "assets/dist-
|
|
31
|
+
"file": "assets/dist-CawBR4t8.js",
|
|
32
32
|
"name": "dist",
|
|
33
33
|
"src": "../node_modules/@pierre/diffs/dist/index.js",
|
|
34
34
|
"isDynamicEntry": true,
|
|
@@ -2562,16 +2562,16 @@
|
|
|
2562
2562
|
"_seed-DeKSHrt2.js"
|
|
2563
2563
|
]
|
|
2564
2564
|
},
|
|
2565
|
-
"_StatusDot-
|
|
2566
|
-
"file": "assets/StatusDot-
|
|
2565
|
+
"_StatusDot-CIAoBB5Y.js": {
|
|
2566
|
+
"file": "assets/StatusDot-CIAoBB5Y.js",
|
|
2567
2567
|
"name": "StatusDot",
|
|
2568
2568
|
"imports": [
|
|
2569
2569
|
"_seed-DeKSHrt2.js",
|
|
2570
|
-
"_Tooltip-
|
|
2570
|
+
"_Tooltip-DEL1ic4g.js"
|
|
2571
2571
|
]
|
|
2572
2572
|
},
|
|
2573
|
-
"_Tooltip-
|
|
2574
|
-
"file": "assets/Tooltip-
|
|
2573
|
+
"_Tooltip-DEL1ic4g.js": {
|
|
2574
|
+
"file": "assets/Tooltip-DEL1ic4g.js",
|
|
2575
2575
|
"name": "Tooltip",
|
|
2576
2576
|
"imports": [
|
|
2577
2577
|
"_AppShell-lYVcj-k7.js",
|
|
@@ -2789,7 +2789,7 @@
|
|
|
2789
2789
|
]
|
|
2790
2790
|
},
|
|
2791
2791
|
"src/main.ts": {
|
|
2792
|
-
"file": "assets/main-
|
|
2792
|
+
"file": "assets/main-CctUbVOl.js",
|
|
2793
2793
|
"name": "main",
|
|
2794
2794
|
"src": "src/main.ts",
|
|
2795
2795
|
"isEntry": true,
|
|
@@ -2841,19 +2841,19 @@
|
|
|
2841
2841
|
]
|
|
2842
2842
|
},
|
|
2843
2843
|
"src/pages/ResidentDetailPage.vue": {
|
|
2844
|
-
"file": "assets/ResidentDetailPage-
|
|
2844
|
+
"file": "assets/ResidentDetailPage-CV3wfAIP.js",
|
|
2845
2845
|
"name": "ResidentDetailPage",
|
|
2846
2846
|
"src": "src/pages/ResidentDetailPage.vue",
|
|
2847
2847
|
"isDynamicEntry": true,
|
|
2848
2848
|
"imports": [
|
|
2849
2849
|
"_AppShell-lYVcj-k7.js",
|
|
2850
2850
|
"_seed-DeKSHrt2.js",
|
|
2851
|
-
"_StatusDot-
|
|
2851
|
+
"_StatusDot-CIAoBB5Y.js",
|
|
2852
2852
|
"_residentDiskBudget-BMBKlYRH.js"
|
|
2853
2853
|
]
|
|
2854
2854
|
},
|
|
2855
2855
|
"src/pages/ResidentsIndexPage.vue": {
|
|
2856
|
-
"file": "assets/ResidentsIndexPage-
|
|
2856
|
+
"file": "assets/ResidentsIndexPage-CLkWc50b.js",
|
|
2857
2857
|
"name": "ResidentsIndexPage",
|
|
2858
2858
|
"src": "src/pages/ResidentsIndexPage.vue",
|
|
2859
2859
|
"isDynamicEntry": true,
|
|
@@ -2861,20 +2861,20 @@
|
|
|
2861
2861
|
"_AppShell-lYVcj-k7.js",
|
|
2862
2862
|
"_seed-DeKSHrt2.js",
|
|
2863
2863
|
"_wallClock-61NpVkY6.js",
|
|
2864
|
-
"_StatusDot-
|
|
2864
|
+
"_StatusDot-CIAoBB5Y.js",
|
|
2865
2865
|
"_residentDiskBudget-BMBKlYRH.js",
|
|
2866
2866
|
"_indexFormat-B-pd4r7Y.js"
|
|
2867
2867
|
]
|
|
2868
2868
|
},
|
|
2869
2869
|
"src/pages/RunRoutePage.vue": {
|
|
2870
|
-
"file": "assets/RunRoutePage-
|
|
2870
|
+
"file": "assets/RunRoutePage-CQYRfQ_B.js",
|
|
2871
2871
|
"name": "RunRoutePage",
|
|
2872
2872
|
"src": "src/pages/RunRoutePage.vue",
|
|
2873
2873
|
"isDynamicEntry": true,
|
|
2874
2874
|
"imports": [
|
|
2875
2875
|
"_AppShell-lYVcj-k7.js",
|
|
2876
2876
|
"_seed-DeKSHrt2.js",
|
|
2877
|
-
"_Tooltip-
|
|
2877
|
+
"_Tooltip-DEL1ic4g.js",
|
|
2878
2878
|
"src/main.ts",
|
|
2879
2879
|
"_wallClock-61NpVkY6.js",
|
|
2880
2880
|
"_NotFoundPage-BzWS4Ca1.js",
|
|
@@ -2890,31 +2890,31 @@
|
|
|
2890
2890
|
]
|
|
2891
2891
|
},
|
|
2892
2892
|
"src/pages/RunsIndexPage.vue": {
|
|
2893
|
-
"file": "assets/RunsIndexPage-
|
|
2893
|
+
"file": "assets/RunsIndexPage-BLRPp_gk.js",
|
|
2894
2894
|
"name": "RunsIndexPage",
|
|
2895
2895
|
"src": "src/pages/RunsIndexPage.vue",
|
|
2896
2896
|
"isDynamicEntry": true,
|
|
2897
2897
|
"imports": [
|
|
2898
2898
|
"_AppShell-lYVcj-k7.js",
|
|
2899
2899
|
"_seed-DeKSHrt2.js",
|
|
2900
|
-
"_Tooltip-
|
|
2900
|
+
"_Tooltip-DEL1ic4g.js",
|
|
2901
2901
|
"_wallClock-61NpVkY6.js",
|
|
2902
2902
|
"_RunsTabs-DEICQ4FY.js",
|
|
2903
|
-
"_StatusDot-
|
|
2903
|
+
"_StatusDot-CIAoBB5Y.js",
|
|
2904
2904
|
"_indexFormat-B-pd4r7Y.js",
|
|
2905
2905
|
"_favicon-CWPcvWvp.js",
|
|
2906
2906
|
"_localIso-L06jV29p.js"
|
|
2907
2907
|
]
|
|
2908
2908
|
},
|
|
2909
2909
|
"src/pages/ScheduledPage.vue": {
|
|
2910
|
-
"file": "assets/ScheduledPage-
|
|
2910
|
+
"file": "assets/ScheduledPage-C-VO4Ddl.js",
|
|
2911
2911
|
"name": "ScheduledPage",
|
|
2912
2912
|
"src": "src/pages/ScheduledPage.vue",
|
|
2913
2913
|
"isDynamicEntry": true,
|
|
2914
2914
|
"imports": [
|
|
2915
2915
|
"_AppShell-lYVcj-k7.js",
|
|
2916
2916
|
"_seed-DeKSHrt2.js",
|
|
2917
|
-
"_Tooltip-
|
|
2917
|
+
"_Tooltip-DEL1ic4g.js",
|
|
2918
2918
|
"_wallClock-61NpVkY6.js",
|
|
2919
2919
|
"_RunsTabs-DEICQ4FY.js",
|
|
2920
2920
|
"_indexFormat-B-pd4r7Y.js",
|