@coreplane/switchboard 1.261.1 → 1.261.3
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 +44 -0
- 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 +10 -8
- 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 +3 -0
- package/dist/assets/src/core/types.ts +6 -2
- package/dist/cli.js +761 -217
- package/package.json +1 -1
|
@@ -2732,6 +2732,38 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
2732
2732
|
return { ok: true };
|
|
2733
2733
|
}
|
|
2734
2734
|
|
|
2735
|
+
/** One compare-and-replace transaction reserves a legacy continuation for
|
|
2736
|
+
* exactly one caller. The whole expected JSON is the fence: any intervening
|
|
2737
|
+
* unit write, including another recovery, makes this caller stale. */
|
|
2738
|
+
async claimLegacyContinuation(
|
|
2739
|
+
expected: CoordinatorUnit,
|
|
2740
|
+
recovered: CoordinatorUnit,
|
|
2741
|
+
now: number,
|
|
2742
|
+
): Promise<{ ok: true } | { ok: false; reason: "stale" }> {
|
|
2743
|
+
let out: { ok: true } | { ok: false; reason: "stale" } = { ok: true };
|
|
2744
|
+
this.ctx.storage.transactionSync(() => {
|
|
2745
|
+
const row = this.sql
|
|
2746
|
+
.exec<{ json: string }>(
|
|
2747
|
+
`SELECT json FROM coordinator_units WHERE instance_id = ? AND unit = ?`,
|
|
2748
|
+
expected.instanceId,
|
|
2749
|
+
expected.unit,
|
|
2750
|
+
)
|
|
2751
|
+
.toArray()[0];
|
|
2752
|
+
if (row?.json !== JSON.stringify(expected)) {
|
|
2753
|
+
out = { ok: false, reason: "stale" };
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
this.sql.exec(
|
|
2757
|
+
`UPDATE coordinator_units SET json = ?, updated_at = ? WHERE instance_id = ? AND unit = ?`,
|
|
2758
|
+
JSON.stringify(recovered),
|
|
2759
|
+
now,
|
|
2760
|
+
expected.instanceId,
|
|
2761
|
+
expected.unit,
|
|
2762
|
+
);
|
|
2763
|
+
});
|
|
2764
|
+
return out;
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2735
2767
|
async listUnits(instanceId: string): Promise<CoordinatorUnit[]> {
|
|
2736
2768
|
return this.sql
|
|
2737
2769
|
.exec<{ json: string }>(`SELECT json FROM coordinator_units WHERE instance_id = ? ORDER BY rowid`, instanceId)
|
|
@@ -5187,6 +5219,7 @@ const LEDGER_ROUTES = new Set([
|
|
|
5187
5219
|
"/runs/coordinator/get",
|
|
5188
5220
|
"/runs/coordinator/stop",
|
|
5189
5221
|
"/runs/coordinator/units/put",
|
|
5222
|
+
"/runs/coordinator/units/claim-legacy-continuation",
|
|
5190
5223
|
"/runs/coordinator/units/list",
|
|
5191
5224
|
"/runs/coordinator/events/append",
|
|
5192
5225
|
"/runs/coordinator/events/list",
|
|
@@ -5886,6 +5919,17 @@ async function handleLedger(pathname: string, body: unknown, env: Env): Promise<
|
|
|
5886
5919
|
console.log(`[runs/coordinator/units/put] ${key.value} ${units[0]!.instanceId} ${units.length} row(s)`);
|
|
5887
5920
|
return json(r);
|
|
5888
5921
|
}
|
|
5922
|
+
if (pathname === "/runs/coordinator/units/claim-legacy-continuation") {
|
|
5923
|
+
if (!isCoordinatorUnit(b.expected) || !isCoordinatorUnit(b.recovered))
|
|
5924
|
+
return json({ error: "expected and recovered must be coordinator unit rows" }, 400);
|
|
5925
|
+
if (b.expected.instanceId !== b.recovered.instanceId || b.expected.unit !== b.recovered.unit)
|
|
5926
|
+
return json({ error: "expected and recovered must name the same unit" }, 400);
|
|
5927
|
+
const r = await stub.claimLegacyContinuation(b.expected, b.recovered, now);
|
|
5928
|
+
console.log(
|
|
5929
|
+
`[runs/coordinator/units/claim-legacy-continuation] ${key.value} ${b.expected.instanceId}:${b.expected.unit} → ${r.ok ? "claimed" : r.reason}`,
|
|
5930
|
+
);
|
|
5931
|
+
return r.ok ? json(r) : json(r, 409);
|
|
5932
|
+
}
|
|
5889
5933
|
if (pathname === "/runs/coordinator/units/list") {
|
|
5890
5934
|
if (typeof b.instanceId !== "string" || !INSTANCE_ID_PATTERN.test(b.instanceId))
|
|
5891
5935
|
return json({ error: "instanceId must be a Workflow instance id" }, 400);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.261.
|
|
3
|
+
"version": "1.261.3",
|
|
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.3",
|
|
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.3",
|
|
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.3",
|
|
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;
|
|
@@ -1137,14 +1137,16 @@ async function runUnit(
|
|
|
1137
1137
|
: {}),
|
|
1138
1138
|
},
|
|
1139
1139
|
...(state.pr !== undefined ? { pr: state.pr } : {}),
|
|
1140
|
-
// A
|
|
1141
|
-
//
|
|
1142
|
-
//
|
|
1143
|
-
...(ending.kind === "
|
|
1144
|
-
? { headSha:
|
|
1145
|
-
: ending.kind === "
|
|
1146
|
-
? { headSha: ending.
|
|
1147
|
-
:
|
|
1140
|
+
// A merge_ready ending retains the exact head its final approval
|
|
1141
|
+
// reviewed. Review-pending endings retain the coding child's push;
|
|
1142
|
+
// both become the row's durable lastPush for a later attempt.
|
|
1143
|
+
...(ending.kind === "merge_ready" && state.lastReviewHead !== undefined
|
|
1144
|
+
? { headSha: state.lastReviewHead }
|
|
1145
|
+
: ending.kind === "review_pending" && ending.headSha !== undefined
|
|
1146
|
+
? { headSha: ending.headSha }
|
|
1147
|
+
: ending.kind === "idle" && ending.why === "review_pending" && ending.from !== undefined
|
|
1148
|
+
? { headSha: ending.from }
|
|
1149
|
+
: {}),
|
|
1148
1150
|
...(state.lastCodingRunId !== undefined ? { codingRunId: state.lastCodingRunId } : {}),
|
|
1149
1151
|
// A continued ending is a segment's end, not the unit's: the bot
|
|
1150
1152
|
// writes the renewal as a row keyed by the next segment's index
|
|
@@ -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;
|
|
@@ -502,6 +502,8 @@ export type Brief =
|
|
|
502
502
|
kind: "findings";
|
|
503
503
|
unit: string;
|
|
504
504
|
pr: number;
|
|
505
|
+
/** The exact reviewed head the fix round may replace. */
|
|
506
|
+
headSha?: string;
|
|
505
507
|
/** The review run normally supplies the findings. An adopted attempt
|
|
506
508
|
* can recover them from the last posted verdict marker instead. */
|
|
507
509
|
reviewRunId?: string;
|
|
@@ -1466,6 +1468,7 @@ function briefFor(s: UnitPipelineState, round: RoundRef): Brief {
|
|
|
1466
1468
|
kind: "findings",
|
|
1467
1469
|
unit,
|
|
1468
1470
|
pr,
|
|
1471
|
+
...(s.lastReviewHead !== undefined ? { headSha: s.lastReviewHead } : {}),
|
|
1469
1472
|
...(reviewRunId !== undefined ? { reviewRunId } : {}),
|
|
1470
1473
|
...(reviewRunId === undefined || (answers !== undefined && answers.length > 0) ? { findings: issued } : {}),
|
|
1471
1474
|
...(answers !== undefined && answers.length > 0 ? { answers } : {}),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Verbosity } from "./verbosity.js";
|
|
2
|
+
|
|
1
3
|
// Channel abstraction. A channel (Slack, CLI, Discord, HTTP, ...) is only a
|
|
2
4
|
// transport: it receives text from a user somewhere, hands it to the core
|
|
3
5
|
// dispatcher as an IncomingMessage, and provides a ChannelIO for the core to
|
|
@@ -306,8 +308,10 @@ export interface ChannelIO {
|
|
|
306
308
|
* the row lives in the config object until the click or the expiry.
|
|
307
309
|
*/
|
|
308
310
|
offer?(offer: ConfirmationOffer): Promise<void>;
|
|
309
|
-
/** Create a progress indicator. Adapters may return a no-op handle.
|
|
310
|
-
|
|
311
|
+
/** Create a progress indicator. Adapters may return a no-op handle. The
|
|
312
|
+
* resolved verbosity reaches the transport so native lifecycle chrome can
|
|
313
|
+
* follow the same display contract as replies and cards. */
|
|
314
|
+
status(initial: StatusUpdate, display?: { verbosity?: Verbosity }): Promise<StatusHandle>;
|
|
311
315
|
/**
|
|
312
316
|
* Prior turns of this conversation, oldest first, excluding the triggering
|
|
313
317
|
* message and any bot status noise. Adapters without history return [].
|