@coreplane/switchboard 1.260.5 → 1.261.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/deploy/cloudflare-memory/worker.ts +106 -4
- 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/budgets.ts +5 -0
- package/dist/assets/src/core/coordinator/driver.ts +43 -6
- package/dist/assets/src/core/pipelineStanding.ts +24 -0
- package/dist/assets/src/core/plane/decide.ts +25 -0
- package/dist/assets/src/core/refusal.ts +1 -1
- package/dist/assets/src/core/runEvents.ts +5 -0
- package/dist/assets/src/core/runFriction.ts +2 -0
- package/dist/assets/src/core/runLedger/decisions.ts +10 -1
- package/dist/assets/src/core/runLedger/types.ts +24 -0
- package/dist/assets/src/core/runLiveState.ts +205 -0
- package/dist/assets/src/core/runRecord.ts +4 -0
- package/dist/assets/src/core/ship/coordinator.ts +29 -6
- package/dist/assets/web/dist/.vite/manifest.json +67 -67
- package/dist/assets/web/dist/assets/{DeliveryPage-Cf1joUyA.js → DeliveryPage-BUJckSuW.js} +1 -1
- package/dist/assets/web/dist/assets/{HomePage-DZEAnCCv.js → HomePage-CBQpcnaW.js} +1 -1
- package/dist/assets/web/dist/assets/{PendingTurnRow-4B8JwETU.js → PendingTurnRow-DIC86uWA.js} +1 -1
- package/dist/assets/web/dist/assets/{PlanePage-CHU2szQs.js → PlanePage-CfrbMh8t.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-L5SRcN_O.js → ResidentDetailPage-C-SgOUTl.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-BIFf8qyE.js → ResidentsIndexPage-CiDhvXok.js} +1 -1
- package/dist/assets/web/dist/assets/{RunFoldRow-DzPIshJx.js → RunFoldRow-DZ8huwUU.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-Cru5IVux.js → RunRoutePage-Cp6wicYc.js} +3 -3
- package/dist/assets/web/dist/assets/{RunsIndexPage-CBkdPAdj.js → RunsIndexPage-BPXtfwwx.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-D_R55BDk.js → ScheduledPage-DbvP48HI.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-DlmZvAvf.js → SettingsPage-B87lZaiN.js} +1 -1
- package/dist/assets/web/dist/assets/{SilentTurn-PWMDMbMG.js → SilentTurn-CPED_XCV.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-Bq0mYMFo.js → StatusDot-CFLn-DZ2.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-Bs6zd7yE.js → Tooltip-Dx9YBVEx.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-BMGEZCvn.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-EkKVbIM-.js → dist-Cn51vWpw.js} +1 -1
- package/dist/assets/web/dist/assets/{indexRow-5s3hNzZ9.js → indexRow-BsWcHHVh.js} +1 -1
- package/dist/assets/web/dist/assets/{main-Disot0n_.js → main-DYwRHh0F.js} +2 -2
- package/dist/assets/web/dist/assets/{sseReplay-9gcMpylx.js → sseReplay-DGRDRLua.js} +1 -1
- package/dist/cli.js +1673 -722
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/budgets-0szUBIMv.js +0 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { PlaneEndingCause } from "./plane/decide.js";
|
|
2
|
+
import { oneLine, redactAndCap } from "./redact.js";
|
|
3
|
+
|
|
4
|
+
/** The server-owned condition of an admitted run. A plane queue row is not admitted and has none. */
|
|
5
|
+
export type RunLiveStateName =
|
|
6
|
+
| "admitted"
|
|
7
|
+
| "waiting_deploy"
|
|
8
|
+
| "waiting_repository"
|
|
9
|
+
| "falling_back"
|
|
10
|
+
| "preparing"
|
|
11
|
+
| "working"
|
|
12
|
+
| "waiting_provider"
|
|
13
|
+
| "wrapping_up"
|
|
14
|
+
| "ended";
|
|
15
|
+
|
|
16
|
+
export const RUN_LIVE_STATE_NAMES = [
|
|
17
|
+
"admitted",
|
|
18
|
+
"waiting_deploy",
|
|
19
|
+
"waiting_repository",
|
|
20
|
+
"falling_back",
|
|
21
|
+
"preparing",
|
|
22
|
+
"working",
|
|
23
|
+
"waiting_provider",
|
|
24
|
+
"wrapping_up",
|
|
25
|
+
"ended",
|
|
26
|
+
] as const satisfies readonly RunLiveStateName[];
|
|
27
|
+
|
|
28
|
+
export const LIVE_STATE_DETAIL_MAX = 160;
|
|
29
|
+
|
|
30
|
+
export type ResidentLiveStateObservation =
|
|
31
|
+
| { state: "waiting_deploy"; bound: number; reason: "deploy"; attempt: number }
|
|
32
|
+
| { state: "waiting_repository"; bound: number; reason: "repository_container"; attempt: number };
|
|
33
|
+
|
|
34
|
+
export type ResidentLiveStateObserver = (observation: ResidentLiveStateObservation) => Promise<void>;
|
|
35
|
+
|
|
36
|
+
export interface RunLiveState {
|
|
37
|
+
state: RunLiveStateName;
|
|
38
|
+
/** Epoch ms at the most recent state boundary. Same-state refreshes preserve it. */
|
|
39
|
+
since: number;
|
|
40
|
+
/** Absolute epoch-ms deadline of the durable fact that created this live condition. */
|
|
41
|
+
bound?: number;
|
|
42
|
+
/** Capped, redacted user words. Never a provider body or resident refusal code. */
|
|
43
|
+
detail?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RunStateEvent extends RunLiveState {
|
|
47
|
+
type: "run_state";
|
|
48
|
+
/** Required only for the terminal boundary; it names the plane's existing closed cause. */
|
|
49
|
+
cause?: PlaneEndingCause;
|
|
50
|
+
seq?: number;
|
|
51
|
+
at?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RunLiveStateFoldEvent {
|
|
55
|
+
type: string;
|
|
56
|
+
seq?: number;
|
|
57
|
+
at?: number;
|
|
58
|
+
state?: unknown;
|
|
59
|
+
since?: number;
|
|
60
|
+
bound?: number;
|
|
61
|
+
detail?: string;
|
|
62
|
+
endsAt?: number;
|
|
63
|
+
boundMs?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RunLiveStateMaterialized {
|
|
67
|
+
liveState?: RunLiveState;
|
|
68
|
+
/** Authoritative event sequence that produced or refreshed `liveState`. */
|
|
69
|
+
liveStateSeq?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const TRANSITIONS = {
|
|
73
|
+
admitted: ["waiting_deploy", "waiting_repository", "falling_back", "preparing", "working", "wrapping_up", "ended"],
|
|
74
|
+
waiting_deploy: ["waiting_repository", "falling_back", "preparing", "working", "wrapping_up", "ended"],
|
|
75
|
+
waiting_repository: ["waiting_deploy", "falling_back", "preparing", "working", "wrapping_up", "ended"],
|
|
76
|
+
falling_back: ["preparing", "working", "wrapping_up", "ended"],
|
|
77
|
+
preparing: ["waiting_deploy", "waiting_repository", "falling_back", "working", "wrapping_up", "ended"],
|
|
78
|
+
working: [
|
|
79
|
+
"waiting_deploy",
|
|
80
|
+
"waiting_repository",
|
|
81
|
+
"falling_back",
|
|
82
|
+
"preparing",
|
|
83
|
+
"waiting_provider",
|
|
84
|
+
"wrapping_up",
|
|
85
|
+
"ended",
|
|
86
|
+
],
|
|
87
|
+
waiting_provider: ["working", "wrapping_up", "ended"],
|
|
88
|
+
wrapping_up: ["ended"],
|
|
89
|
+
ended: [],
|
|
90
|
+
} as const satisfies Record<RunLiveStateName, readonly RunLiveStateName[]>;
|
|
91
|
+
|
|
92
|
+
const UNSAFE_DETAIL =
|
|
93
|
+
/(?:\bimage-stale\b|\battach-failed\b|\bparked-provider\b|<\/?(?:html|body|head)\b|\bbad gateway\b)/i;
|
|
94
|
+
|
|
95
|
+
/** Admit only display-ready prose. Typed mappers create normal details; this is the final leak guard. */
|
|
96
|
+
export function safeLiveStateDetail(detail: string | undefined): string | undefined {
|
|
97
|
+
if (detail === undefined) return undefined;
|
|
98
|
+
const line = oneLine(detail);
|
|
99
|
+
if (line === "" || UNSAFE_DETAIL.test(line)) return undefined;
|
|
100
|
+
return redactAndCap(line, LIVE_STATE_DETAIL_MAX - 1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type AssignRunLiveStateInput = {
|
|
104
|
+
expectedSeq: number;
|
|
105
|
+
at: number;
|
|
106
|
+
state: RunLiveStateName;
|
|
107
|
+
bound?: number;
|
|
108
|
+
detail?: string;
|
|
109
|
+
cause?: PlaneEndingCause;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type AssignRunLiveStateResult =
|
|
113
|
+
| { ok: true; liveState: RunLiveState; event?: RunStateEvent }
|
|
114
|
+
| {
|
|
115
|
+
ok: false;
|
|
116
|
+
reason:
|
|
117
|
+
"stale-sequence" | "terminal" | "invalid-transition" | "bound-required" | "invalid-bound" | "cause-required";
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** Pure assignment gate. Persistence chooses the next stream sequence and commits the result atomically. */
|
|
121
|
+
export function assignRunLiveState(
|
|
122
|
+
current: RunLiveState | undefined,
|
|
123
|
+
currentSeq: number,
|
|
124
|
+
input: AssignRunLiveStateInput,
|
|
125
|
+
): AssignRunLiveStateResult {
|
|
126
|
+
if (input.expectedSeq !== currentSeq) return { ok: false, reason: "stale-sequence" };
|
|
127
|
+
if (current === undefined && input.state !== "admitted") return { ok: false, reason: "invalid-transition" };
|
|
128
|
+
if (current?.state === "ended") return { ok: false, reason: "terminal" };
|
|
129
|
+
if (input.state !== "ended") {
|
|
130
|
+
if (input.bound === undefined) return { ok: false, reason: "bound-required" };
|
|
131
|
+
if (!Number.isFinite(input.bound) || input.bound < input.at) return { ok: false, reason: "invalid-bound" };
|
|
132
|
+
} else if (input.cause === undefined) return { ok: false, reason: "cause-required" };
|
|
133
|
+
if (
|
|
134
|
+
current !== undefined &&
|
|
135
|
+
current.state !== input.state &&
|
|
136
|
+
!TRANSITIONS[current.state].includes(input.state as never)
|
|
137
|
+
)
|
|
138
|
+
return { ok: false, reason: "invalid-transition" };
|
|
139
|
+
|
|
140
|
+
const detail = safeLiveStateDetail(input.detail);
|
|
141
|
+
const liveState: RunLiveState = {
|
|
142
|
+
state: input.state,
|
|
143
|
+
since: current?.state === input.state ? current.since : input.at,
|
|
144
|
+
...(input.state !== "ended" ? { bound: input.bound } : {}),
|
|
145
|
+
...(detail !== undefined ? { detail } : {}),
|
|
146
|
+
};
|
|
147
|
+
if (current?.state === input.state) return { ok: true, liveState };
|
|
148
|
+
return {
|
|
149
|
+
ok: true,
|
|
150
|
+
liveState,
|
|
151
|
+
event: {
|
|
152
|
+
type: "run_state",
|
|
153
|
+
...liveState,
|
|
154
|
+
...(input.state === "ended" ? { cause: input.cause } : {}),
|
|
155
|
+
at: input.at,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Rebuild the projection from boundaries. A later event sequence always defeats stale materialized state. */
|
|
161
|
+
export function foldRunLiveState(
|
|
162
|
+
events: readonly RunLiveStateFoldEvent[],
|
|
163
|
+
materialized: RunLiveStateMaterialized = {},
|
|
164
|
+
): RunLiveStateMaterialized {
|
|
165
|
+
let liveState = materialized.liveState;
|
|
166
|
+
let liveStateSeq = materialized.liveStateSeq ?? 0;
|
|
167
|
+
let leaseEndsAt: number | undefined;
|
|
168
|
+
for (const event of [...events].sort((left, right) => (left.seq ?? 0) - (right.seq ?? 0))) {
|
|
169
|
+
if (event.type === "lease" && event.endsAt !== undefined) leaseEndsAt = event.endsAt;
|
|
170
|
+
const seq = event.seq ?? 0;
|
|
171
|
+
if (seq <= liveStateSeq) continue;
|
|
172
|
+
if (
|
|
173
|
+
event.type === "run_state" &&
|
|
174
|
+
RUN_LIVE_STATE_NAMES.includes(event.state as RunLiveStateName) &&
|
|
175
|
+
event.since !== undefined
|
|
176
|
+
) {
|
|
177
|
+
liveState = {
|
|
178
|
+
state: event.state as RunLiveStateName,
|
|
179
|
+
since: event.since,
|
|
180
|
+
...(event.bound !== undefined ? { bound: event.bound } : {}),
|
|
181
|
+
...(event.detail !== undefined ? { detail: event.detail } : {}),
|
|
182
|
+
};
|
|
183
|
+
liveStateSeq = seq;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (
|
|
187
|
+
event.type === "tool_call" &&
|
|
188
|
+
liveState?.state === "working" &&
|
|
189
|
+
event.at !== undefined &&
|
|
190
|
+
event.boundMs !== undefined
|
|
191
|
+
) {
|
|
192
|
+
liveState = { ...liveState, bound: event.at + event.boundMs, detail: "running a tool" };
|
|
193
|
+
liveStateSeq = seq;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (event.type === "tool_result" && liveState?.state === "working" && leaseEndsAt !== undefined) {
|
|
197
|
+
liveState = { ...liveState, bound: leaseEndsAt, detail: "model turn" };
|
|
198
|
+
liveStateSeq = seq;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
...(liveState !== undefined ? { liveState } : {}),
|
|
203
|
+
...(liveStateSeq > 0 ? { liveStateSeq } : {}),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -5,6 +5,7 @@ import type { ProviderFailureCause } from "./provider.js";
|
|
|
5
5
|
import { isHeadMaterial, isSpanRecord } from "./runEvents.js";
|
|
6
6
|
import { isRunUsage, type RunUsage } from "./runUsage.js";
|
|
7
7
|
import type { PushedBranch } from "../execution/residentRebind.js";
|
|
8
|
+
import type { RunLiveState } from "./runLiveState.js";
|
|
8
9
|
import { isHandoffShape, type Handoff } from "./ship/handoff.js";
|
|
9
10
|
import {
|
|
10
11
|
type FindingDisposition,
|
|
@@ -124,6 +125,9 @@ export interface RunRecord {
|
|
|
124
125
|
stepCount?: number;
|
|
125
126
|
schema?: number;
|
|
126
127
|
status: RunStatus;
|
|
128
|
+
/** Final server-owned live condition. Optional on records from older writers. */
|
|
129
|
+
liveState?: RunLiveState;
|
|
130
|
+
liveStateSeq?: number;
|
|
127
131
|
/** Present on a tombstone record still in its provisional window — the
|
|
128
132
|
* start-of-run `interrupted` or the drain-deadline upgrade — before the
|
|
129
133
|
* run's final write lands. Absent on every final (finished) record. A
|
|
@@ -550,7 +550,7 @@ export type CoordinatorAction =
|
|
|
550
550
|
| { type: "branch"; step: string; branch: string; from: string }
|
|
551
551
|
| { type: "spawn"; step: string; preset: ChildPreset; round: RoundRef; budgetMinutes: number; brief: Brief }
|
|
552
552
|
| { type: "wait"; step: string; runId: string; timeoutMs: number }
|
|
553
|
-
| { type: "read-record"; step: string; runId: string }
|
|
553
|
+
| { type: "read-record"; step: string; runId: string; finishedObserved?: true }
|
|
554
554
|
/** Tell a child whose pull request became terminal or moved to end at its
|
|
555
555
|
* next turn boundary without publishing anything. This is a steer through
|
|
556
556
|
* the child's inbox, never a stop that tears down its workspace. */
|
|
@@ -1170,7 +1170,7 @@ type Phase =
|
|
|
1170
1170
|
| { at: "busy-wait"; round: RoundRef; runId?: string; n: number }
|
|
1171
1171
|
/** `until`: when the child's budget plus the margin runs out, counted from the spawn's answer — the wait's last slice ends there. */
|
|
1172
1172
|
| { at: "wait"; round: RoundRef; runId: string; n: number; until: number }
|
|
1173
|
-
| { at: "read"; round: RoundRef; runId: string; n: number; until: number }
|
|
1173
|
+
| { at: "read"; round: RoundRef; runId: string; n: number; until: number; finishedObserved: boolean }
|
|
1174
1174
|
/** A live child lost authority because its pull request became terminal,
|
|
1175
1175
|
* its reviewed head moved, or its branch disappeared. Steer once, then
|
|
1176
1176
|
* drain the child to its recorded end while ignoring every late artifact. */
|
|
@@ -1197,6 +1197,7 @@ type Phase =
|
|
|
1197
1197
|
n: number;
|
|
1198
1198
|
until: number;
|
|
1199
1199
|
supersession: ChildSupersession;
|
|
1200
|
+
finishedObserved: boolean;
|
|
1200
1201
|
}
|
|
1201
1202
|
/** A moved-head child has drained. Re-read the adopted pull request before
|
|
1202
1203
|
* restarting review because it may have merged, closed or moved again. */
|
|
@@ -1533,7 +1534,12 @@ export function nextAction(s: UnitPipelineState): CoordinatorAction {
|
|
|
1533
1534
|
timeoutMs: Math.min(waitSliceMs(s.clock, p.until), s.pr !== undefined ? PR_TRANSITION_GUARD_MS : WAIT_CHUNK_MS),
|
|
1534
1535
|
};
|
|
1535
1536
|
case "read":
|
|
1536
|
-
return {
|
|
1537
|
+
return {
|
|
1538
|
+
type: "read-record",
|
|
1539
|
+
step: `${roundStep(s, p.round)}/read/${p.n}`,
|
|
1540
|
+
runId: p.runId,
|
|
1541
|
+
...(p.finishedObserved ? { finishedObserved: true as const } : {}),
|
|
1542
|
+
};
|
|
1537
1543
|
case "steer":
|
|
1538
1544
|
return {
|
|
1539
1545
|
type: "steer",
|
|
@@ -1553,6 +1559,7 @@ export function nextAction(s: UnitPipelineState): CoordinatorAction {
|
|
|
1553
1559
|
type: "read-record",
|
|
1554
1560
|
step: `${roundStep(s, p.round)}/superseded/read/${p.n}`,
|
|
1555
1561
|
runId: p.runId,
|
|
1562
|
+
...(p.finishedObserved ? { finishedObserved: true as const } : {}),
|
|
1556
1563
|
};
|
|
1557
1564
|
case "superseded-pr-check":
|
|
1558
1565
|
return {
|
|
@@ -2960,11 +2967,23 @@ export function applyReturn(s: UnitPipelineState, ret: StepReturn): Transition {
|
|
|
2960
2967
|
notes: [],
|
|
2961
2968
|
};
|
|
2962
2969
|
}
|
|
2963
|
-
case "wait":
|
|
2970
|
+
case "wait": {
|
|
2971
|
+
const r = ret as Extract<StepReturn, { type: "wait" }>;
|
|
2964
2972
|
return {
|
|
2965
|
-
state: {
|
|
2973
|
+
state: {
|
|
2974
|
+
...s,
|
|
2975
|
+
phase: {
|
|
2976
|
+
at: "read",
|
|
2977
|
+
round: p.round,
|
|
2978
|
+
runId: p.runId,
|
|
2979
|
+
n: p.n,
|
|
2980
|
+
until: p.until,
|
|
2981
|
+
finishedObserved: r.outcome === "event",
|
|
2982
|
+
},
|
|
2983
|
+
},
|
|
2966
2984
|
notes: [],
|
|
2967
2985
|
};
|
|
2986
|
+
}
|
|
2968
2987
|
case "read": {
|
|
2969
2988
|
const r = ret as Extract<StepReturn, { type: "read-record" }>;
|
|
2970
2989
|
const supersession = childSupersession(clocked, p.round, r.pullRequest);
|
|
@@ -2979,6 +2998,7 @@ export function applyReturn(s: UnitPipelineState, ret: StepReturn): Transition {
|
|
|
2979
2998
|
n: p.n,
|
|
2980
2999
|
until: p.until,
|
|
2981
3000
|
supersession,
|
|
3001
|
+
finishedObserved: p.finishedObserved,
|
|
2982
3002
|
};
|
|
2983
3003
|
return finishSupersededChild(clocked, terminalPhase);
|
|
2984
3004
|
}
|
|
@@ -3098,7 +3118,8 @@ export function applyReturn(s: UnitPipelineState, ret: StepReturn): Transition {
|
|
|
3098
3118
|
notes: [],
|
|
3099
3119
|
};
|
|
3100
3120
|
}
|
|
3101
|
-
case "superseded-wait":
|
|
3121
|
+
case "superseded-wait": {
|
|
3122
|
+
const r = ret as Extract<StepReturn, { type: "wait" }>;
|
|
3102
3123
|
return {
|
|
3103
3124
|
state: {
|
|
3104
3125
|
...s,
|
|
@@ -3109,10 +3130,12 @@ export function applyReturn(s: UnitPipelineState, ret: StepReturn): Transition {
|
|
|
3109
3130
|
n: p.n,
|
|
3110
3131
|
until: p.until,
|
|
3111
3132
|
supersession: p.supersession,
|
|
3133
|
+
finishedObserved: r.outcome === "event",
|
|
3112
3134
|
},
|
|
3113
3135
|
},
|
|
3114
3136
|
notes: [],
|
|
3115
3137
|
};
|
|
3138
|
+
}
|
|
3116
3139
|
case "superseded-read": {
|
|
3117
3140
|
const r = ret as Extract<StepReturn, { type: "read-record" }>;
|
|
3118
3141
|
if (r.run.finished) return finishSupersededChild(clocked, p);
|