@coreplane/switchboard 1.261.0 → 1.261.2
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-memory/worker.ts +1 -1
- 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 +39 -0
- package/dist/assets/src/core/coordinator/driver.ts +17 -5
- package/dist/assets/src/core/reviewVerdict.ts +33 -6
- package/dist/assets/src/core/runEvents.ts +15 -1
- package/dist/assets/src/core/runLedger/types.ts +4 -0
- package/dist/assets/src/core/ship/coordinator.ts +333 -72
- package/dist/assets/src/core/ship/renewal.ts +1 -1
- package/dist/assets/src/core/types.ts +6 -2
- package/dist/cli.js +1264 -662
- package/package.json +1 -1
|
@@ -2963,7 +2963,7 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
2963
2963
|
req.card ? JSON.stringify(req.card) : null,
|
|
2964
2964
|
req.system,
|
|
2965
2965
|
JSON.stringify(req.tools),
|
|
2966
|
-
JSON.stringify(req.state ?? {}),
|
|
2966
|
+
JSON.stringify({ ...existing!.state, ...(req.state ?? {}) }),
|
|
2967
2967
|
req.runId,
|
|
2968
2968
|
);
|
|
2969
2969
|
this.registerSession(req);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.261.
|
|
3
|
+
"version": "1.261.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.261.
|
|
9
|
+
"version": "1.261.2",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -21060,7 +21060,7 @@
|
|
|
21060
21060
|
},
|
|
21061
21061
|
"packages/switchboard": {
|
|
21062
21062
|
"name": "@coreplane/switchboard",
|
|
21063
|
-
"version": "1.261.
|
|
21063
|
+
"version": "1.261.2",
|
|
21064
21064
|
"license": "Apache-2.0",
|
|
21065
21065
|
"dependencies": {
|
|
21066
21066
|
"@earendil-works/pi-ai": "0.85.1",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.261.
|
|
3
|
+
"version": "1.261.2",
|
|
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
|
@@ -293,6 +293,20 @@ export interface RunFinishedPayload {
|
|
|
293
293
|
parentInstanceId: string;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
/** Durable authority for publishing another commit to an existing pull
|
|
297
|
+
* request. Every identity field is repeated deliberately: publication is
|
|
298
|
+
* allowed only when the unit, checkout and a fresh remote read all agree with
|
|
299
|
+
* this exact record. */
|
|
300
|
+
export interface ExistingPrPublicationBinding {
|
|
301
|
+
repo: string;
|
|
302
|
+
pr: number;
|
|
303
|
+
headRef: string;
|
|
304
|
+
baseRef: string;
|
|
305
|
+
expectedHeadSha: string;
|
|
306
|
+
publicationRef: string;
|
|
307
|
+
owner: { instanceId: string; unit: string };
|
|
308
|
+
}
|
|
309
|
+
|
|
296
310
|
/** What a coordinator's spawn stamps on the child's every row: the instance
|
|
297
311
|
* the child belongs to and the key the spawn carried — and, for the child's
|
|
298
312
|
* own post-step, the base its pull request targets. */
|
|
@@ -310,6 +324,10 @@ export interface CoordinatorTag {
|
|
|
310
324
|
* back off its ledger events; `coordinatorFields` still leaves it off the
|
|
311
325
|
* rows, so rows and records keep the shape written before it existed. */
|
|
312
326
|
base?: string;
|
|
327
|
+
/** The existing-PR publication authority this child received from its
|
|
328
|
+
* durable unit row and current Workflow round. Published on the run's event
|
|
329
|
+
* stream so a rehost retains the same fence. */
|
|
330
|
+
publication?: ExistingPrPublicationBinding;
|
|
313
331
|
}
|
|
314
332
|
|
|
315
333
|
/** The tag as the two flat record fields, or nothing — so a row, a summary
|
|
@@ -489,6 +507,9 @@ export interface CoordinatorUnit {
|
|
|
489
507
|
* — no pre-check, no branch, no round 0. A task string's row only; written by
|
|
490
508
|
* the hand-off, read by the driver into the machine's input. */
|
|
491
509
|
resume?: { pr: number; headSha?: string; url?: string };
|
|
510
|
+
/** Exact existing-PR publication authority. Absent for a fresh unit branch;
|
|
511
|
+
* an adopted or resumed PR may publish only through this binding. */
|
|
512
|
+
publication?: ExistingPrPublicationBinding;
|
|
492
513
|
/** The decision-record number reserved at admission for this unit. A unit
|
|
493
514
|
* that writes a record carries it through every attempt and briefs its child
|
|
494
515
|
* as `record: NNNN`; the child never scans the directory for a number. */
|
|
@@ -549,6 +570,23 @@ const isResume = (v: unknown): boolean =>
|
|
|
549
570
|
isFinite(v.pr) &&
|
|
550
571
|
(v.headSha === undefined || isText(v.headSha)) &&
|
|
551
572
|
(v.url === undefined || isText(v.url, 2048));
|
|
573
|
+
const isFullSha = (v: unknown): v is string => typeof v === "string" && /^[0-9a-f]{40}$/i.test(v);
|
|
574
|
+
const isPublication = (v: unknown): v is ExistingPrPublicationBinding =>
|
|
575
|
+
isObject(v) &&
|
|
576
|
+
typeof v.repo === "string" &&
|
|
577
|
+
REPO_SLUG.test(v.repo) &&
|
|
578
|
+
typeof v.pr === "number" &&
|
|
579
|
+
Number.isInteger(v.pr) &&
|
|
580
|
+
v.pr > 0 &&
|
|
581
|
+
isText(v.headRef) &&
|
|
582
|
+
isText(v.baseRef) &&
|
|
583
|
+
isFullSha(v.expectedHeadSha) &&
|
|
584
|
+
isText(v.publicationRef) &&
|
|
585
|
+
isObject(v.owner) &&
|
|
586
|
+
typeof v.owner.instanceId === "string" &&
|
|
587
|
+
INSTANCE_ID_PATTERN.test(v.owner.instanceId) &&
|
|
588
|
+
typeof v.owner.unit === "string" &&
|
|
589
|
+
UNIT_PATTERN.test(v.owner.unit);
|
|
552
590
|
const isThread = (v: unknown): boolean => isObject(v) && isText(v.threadKey) && isOptionalText(v.sourceUrl);
|
|
553
591
|
|
|
554
592
|
/** Structural check on a record from outside the process (a Worker response, an HTTP body). */
|
|
@@ -663,6 +701,7 @@ export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
|
|
|
663
701
|
if (r.issue !== undefined && !isFinite(r.issue)) return false;
|
|
664
702
|
if (r.pr !== undefined && !isPr(r.pr)) return false;
|
|
665
703
|
if (r.resume !== undefined && !isResume(r.resume)) return false;
|
|
704
|
+
if (r.publication !== undefined && !isPublication(r.publication)) return false;
|
|
666
705
|
if (r.record !== undefined && (typeof r.record !== "string" || !/^\d{4}$/.test(r.record))) return false;
|
|
667
706
|
if (r.lastPush !== undefined && !isText(r.lastPush)) return false;
|
|
668
707
|
if (r.segments !== undefined && (!Array.isArray(r.segments) || !r.segments.every(isSegment))) return false;
|
|
@@ -510,8 +510,9 @@ function prCheckReturn(step: string, a: BotAnswer): Extract<StepReturn, { type:
|
|
|
510
510
|
},
|
|
511
511
|
at,
|
|
512
512
|
};
|
|
513
|
-
// A merged pull request is read whole or not at all:
|
|
514
|
-
//
|
|
513
|
+
// A merged pull request is read whole or not at all: its source head is
|
|
514
|
+
// retained for findings reconciliation, while the merge commit and time
|
|
515
|
+
// are what the unit's ending and report carry.
|
|
515
516
|
if (
|
|
516
517
|
ok === true &&
|
|
517
518
|
state === "merged" &&
|
|
@@ -527,6 +528,7 @@ function prCheckReturn(step: string, a: BotAnswer): Extract<StepReturn, { type:
|
|
|
527
528
|
state: "merged",
|
|
528
529
|
prNumber,
|
|
529
530
|
url,
|
|
531
|
+
...(typeof headSha === "string" ? { headSha } : {}),
|
|
530
532
|
sha,
|
|
531
533
|
mergedAt,
|
|
532
534
|
...(typeof mergedBy === "string" ? { mergedBy } : {}),
|
|
@@ -923,9 +925,19 @@ async function tellStepThrew(
|
|
|
923
925
|
: at.round.kind === "review" && /\/review\/(read|checks)\b/.test(at.step)
|
|
924
926
|
? `after round ${at.round.index}'s review verdict (\`${at.step}\`)`
|
|
925
927
|
: `in round ${at.round.index} (\`${at.step}\`)`;
|
|
926
|
-
const
|
|
927
|
-
|
|
928
|
-
|
|
928
|
+
const findingsReconcile = at.round?.kind === "findings" && /\/findings(?:\/a\d+)?\/pr-check$/.test(at.step);
|
|
929
|
+
const report = findingsReconcile
|
|
930
|
+
? [
|
|
931
|
+
`⚠️ Review did not restart because Switchboard could not read the pull request after the completed findings work: ${line}`,
|
|
932
|
+
pr !== undefined
|
|
933
|
+
? `Saved-work fact: the existing pull request is ${pr.url}, but its current branch and exact head were not verified.`
|
|
934
|
+
: "Saved-work fact: no open pull request or exact remote head was verified.",
|
|
935
|
+
"No review was started. Next action: retry ship when the pull request is readable so Switchboard can verify the exact head first.",
|
|
936
|
+
].join("\n\n")
|
|
937
|
+
: [
|
|
938
|
+
`⚠️ Switchboard failed ${where}: ${line}`,
|
|
939
|
+
"The unit remains resumable from its recorded branch and pull request facts. No continuation action was scheduled. Next action: start ship again after the failing operation is available.",
|
|
940
|
+
].join("\n\n");
|
|
929
941
|
const body = {
|
|
930
942
|
...tag,
|
|
931
943
|
ending: {
|
|
@@ -150,6 +150,10 @@ export interface ReviewVerdict {
|
|
|
150
150
|
export const LGTM_TOKEN = "LGTM:";
|
|
151
151
|
export const CHANGES_TOKEN = "Changes requested:";
|
|
152
152
|
export const NO_VERDICT_LINE = "No verdict submitted — not approving.";
|
|
153
|
+
/** Conservative ceiling below GitHub's 65,536-character review-body limit.
|
|
154
|
+
* Counted as Unicode code points so clipping never splits a surrogate pair. */
|
|
155
|
+
export const MAX_REVIEW_POST_CODE_POINTS = 65_000;
|
|
156
|
+
const REVIEW_TRUNCATION_NOTE = "_(review truncated to fit GitHub's review size limit)_";
|
|
153
157
|
|
|
154
158
|
/** Parse an arbitrary tool input into a verdict, or null when it is not one.
|
|
155
159
|
* `addressSeverity` is the level in force for the run (default `minor`): an
|
|
@@ -383,7 +387,11 @@ function verdictMarker(verdict: ReviewVerdict | undefined, target: ReviewBodyTar
|
|
|
383
387
|
* `approve`), a GitHub alert callout with the verdict word, the pinned head
|
|
384
388
|
* and the finding counts, the findings as a table (severity, id + title, the
|
|
385
389
|
* file linked at the head), the model's text folded under `Full review`, and
|
|
386
|
-
* the machine-readable marker last.
|
|
390
|
+
* the machine-readable marker last. Only the prose may be clipped to fit the
|
|
391
|
+
* platform: every fixed verdict section and the marker remain byte-for-byte.
|
|
392
|
+
* If those sections leave no room for a visible bounded review, the oversized
|
|
393
|
+
* body is returned so the publication boundary can refuse it instead of
|
|
394
|
+
* silently dropping the write-up. The prose decides nothing above the fold.
|
|
387
395
|
*/
|
|
388
396
|
export function buildReviewPostBody(
|
|
389
397
|
answer: string,
|
|
@@ -395,10 +403,10 @@ export function buildReviewPostBody(
|
|
|
395
403
|
...(target ? [`head \`${target.head.slice(0, 7)}\``] : []),
|
|
396
404
|
verdict ? findingCounts(verdict.findings) : "the run ended without a submit_verdict call",
|
|
397
405
|
];
|
|
398
|
-
const
|
|
406
|
+
const fixedParts: string[] = [verdictLine(verdict), `> [!${calloutKind(verdict)}]\n> ${facts.join(" · ")}`];
|
|
399
407
|
const findings = verdict?.findings ?? [];
|
|
400
408
|
if (findings.length > 0) {
|
|
401
|
-
|
|
409
|
+
fixedParts.push(
|
|
402
410
|
[
|
|
403
411
|
"| Severity | Finding | Where |",
|
|
404
412
|
"| --- | --- | --- |",
|
|
@@ -409,10 +417,29 @@ export function buildReviewPostBody(
|
|
|
409
417
|
].join("\n"),
|
|
410
418
|
);
|
|
411
419
|
}
|
|
420
|
+
|
|
421
|
+
const marker = verdictMarker(verdict, target);
|
|
422
|
+
const fixedBody = [...fixedParts, marker].join("\n\n");
|
|
412
423
|
const prose = answer.trim();
|
|
413
|
-
if (prose)
|
|
414
|
-
|
|
415
|
-
|
|
424
|
+
if (!prose) return fixedBody;
|
|
425
|
+
|
|
426
|
+
const details = (text: string, truncated: boolean): string =>
|
|
427
|
+
`<details>\n<summary>Full review</summary>\n\n${text}${truncated ? `${text ? "\n\n" : ""}${REVIEW_TRUNCATION_NOTE}` : ""}\n\n</details>`;
|
|
428
|
+
const render = (text: string, truncated: boolean): string =>
|
|
429
|
+
[...fixedParts, details(text, truncated), marker].join("\n\n");
|
|
430
|
+
const full = render(prose, false);
|
|
431
|
+
if ([...full].length <= MAX_REVIEW_POST_CODE_POINTS) return full;
|
|
432
|
+
|
|
433
|
+
const clippedShell = render("", true);
|
|
434
|
+
const proseSeparatorCodePoints = 2; // the blank line before the note when clipped prose is present
|
|
435
|
+
const proseBudget = MAX_REVIEW_POST_CODE_POINTS - [...clippedShell].length - proseSeparatorCodePoints;
|
|
436
|
+
if (proseBudget > 0) return render([...prose].slice(0, proseBudget).join(""), true);
|
|
437
|
+
if ([...clippedShell].length <= MAX_REVIEW_POST_CODE_POINTS) return clippedShell;
|
|
438
|
+
|
|
439
|
+
// A successful post must retain a visible write-up. Return the full body
|
|
440
|
+
// oversized when even the bounded shell cannot fit, so postReviewComment
|
|
441
|
+
// rejects before fetch and the review remains on the Slack-only path.
|
|
442
|
+
return full;
|
|
416
443
|
}
|
|
417
444
|
|
|
418
445
|
/**
|
|
@@ -208,6 +208,11 @@ export type RunNoteKind =
|
|
|
208
208
|
* after the model's last turn — the release itself runs after the record
|
|
209
209
|
* is sealed — and set on the card's label too, so the loss is never silent. */
|
|
210
210
|
| "work_left_behind"
|
|
211
|
+
/** An existing pull request's exact publication fence denied the push while
|
|
212
|
+
* the workspace held a local checkpoint. The workspace stays attached
|
|
213
|
+
* instead of being discarded or redirected; the summary names the head,
|
|
214
|
+
* counts, denial and that retention beyond the run remains unverified. */
|
|
215
|
+
| "publication_blocked"
|
|
211
216
|
/** The run's ending may have left a command running in its workspace — a
|
|
212
217
|
* call cut by the ending's abort or interrupt, or open when the run failed —
|
|
213
218
|
* so the release tears the workspace down rather than pair it for the
|
|
@@ -356,6 +361,7 @@ export const RUN_NOTE_KINDS = [
|
|
|
356
361
|
"ledger_untracked",
|
|
357
362
|
"rebind_refused",
|
|
358
363
|
"work_left_behind",
|
|
364
|
+
"publication_blocked",
|
|
359
365
|
"workspace_torn_down",
|
|
360
366
|
"pr_not_opened",
|
|
361
367
|
"pr_head_unverified",
|
|
@@ -953,7 +959,15 @@ export type RunEvent =
|
|
|
953
959
|
* instead of letting the binding ref — the unit branch itself — stand in.
|
|
954
960
|
* `base` is absent when the spawn knew none; the post-step then falls to
|
|
955
961
|
* the coordinator store's `instance.base`. Additive: unknown → ignored. */
|
|
956
|
-
| {
|
|
962
|
+
| {
|
|
963
|
+
type: "coordinator_tag";
|
|
964
|
+
parentInstanceId: string;
|
|
965
|
+
unit?: string;
|
|
966
|
+
base?: string;
|
|
967
|
+
publication?: import("./coordinator/contract.js").ExistingPrPublicationBinding;
|
|
968
|
+
seq?: number;
|
|
969
|
+
at?: number;
|
|
970
|
+
}
|
|
957
971
|
/** The plan runner instance a ship run's hand-off created (record 0051 R2;
|
|
958
972
|
* docs/reference/specs/run-history.md item 2): published by the ship branch
|
|
959
973
|
* after `handOffToCoordinator` succeeds, straight to the registry like
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { ChatMessage } from "../chatMessage.js";
|
|
7
7
|
import { PROVIDER_FAILURE_CAUSES, type ProviderFailureCause, type ToolDef } from "../provider.js";
|
|
8
|
+
import type { Verbosity } from "../verbosity.js";
|
|
8
9
|
import type { ChannelVisibility } from "../authz/types.js";
|
|
9
10
|
import type { RunProfile } from "../../config/profile.js";
|
|
10
11
|
import type { RunEvent } from "../runEvents.js";
|
|
@@ -60,6 +61,9 @@ export interface LiveRunMeta {
|
|
|
60
61
|
/** The app that relayed the request for the person (authorization.md item 14): a resume or restart keeps app ∩ person at the gates. */
|
|
61
62
|
postedBy?: string;
|
|
62
63
|
effort?: string;
|
|
64
|
+
/** The request's resolved display level. Hosted parents have no model loop
|
|
65
|
+
* to rebuild it from, and their input event deliberately omits directives. */
|
|
66
|
+
verbosity?: Verbosity;
|
|
63
67
|
/** Decision-record reservation assigned before the attach, so a restart of
|
|
64
68
|
* the admitted task keeps the same brief and process environment. */
|
|
65
69
|
record?: string;
|