@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
|
@@ -76,6 +76,7 @@ import {
|
|
|
76
76
|
phaseTransition,
|
|
77
77
|
reclaimPhase,
|
|
78
78
|
selectReclaim,
|
|
79
|
+
unreadInbox,
|
|
79
80
|
} from "../../src/core/runLedger/decisions.ts";
|
|
80
81
|
import { INTAKE_DELIVERY_CLAIM_MS, intakeReceiptRetentionMs, minutesToMs, PLANE } from "../../src/core/budgets.ts";
|
|
81
82
|
import { PROVIDER_FAILURE_CAUSES, type ProviderFailureCause } from "../../src/core/provider.ts";
|
|
@@ -133,6 +134,8 @@ import {
|
|
|
133
134
|
type IntakeWriteResult,
|
|
134
135
|
type LivePhase,
|
|
135
136
|
type LiveRunRow,
|
|
137
|
+
type LiveStateAssignRequest,
|
|
138
|
+
type LiveStateAssignResult,
|
|
136
139
|
type ReclaimedRun,
|
|
137
140
|
type RunState,
|
|
138
141
|
type SessionHit,
|
|
@@ -153,6 +156,7 @@ import { isRunMetricsPoint, pointTurnsFinal, type RunMetricsPoint } from "../../
|
|
|
153
156
|
import { AnalyticsEngineSink, NullSink, type RunMetricsSink } from "./runMetricsSink.ts";
|
|
154
157
|
import { injectedBuildStamp } from "../../src/deploy/buildStamp.ts";
|
|
155
158
|
import { systemClock } from "../../src/core/trace/clock.ts";
|
|
159
|
+
import { assignRunLiveState } from "../../src/core/runLiveState.ts";
|
|
156
160
|
import { createTracer } from "../../src/core/trace/tracer.ts";
|
|
157
161
|
import { startAdoptedRoot, workerLogSink } from "../../src/core/trace/workerTrace.ts";
|
|
158
162
|
|
|
@@ -1538,6 +1542,9 @@ type LiveRow = {
|
|
|
1538
1542
|
};
|
|
1539
1543
|
|
|
1540
1544
|
function rowToLive(r: LiveRow): LiveRunRow {
|
|
1545
|
+
const state = JSON.parse(r.state_json) as RunState;
|
|
1546
|
+
const liveState = state.liveState as LiveRunRow["liveState"];
|
|
1547
|
+
const liveStateSeq = state.liveStateSeq;
|
|
1541
1548
|
return {
|
|
1542
1549
|
runId: r.run_id,
|
|
1543
1550
|
threadKey: r.thread_key,
|
|
@@ -1550,7 +1557,9 @@ function rowToLive(r: LiveRow): LiveRunRow {
|
|
|
1550
1557
|
card: r.card_json ? (JSON.parse(r.card_json) as LiveRunRow["card"]) : null,
|
|
1551
1558
|
system: r.system_text,
|
|
1552
1559
|
tools: JSON.parse(r.tools_json) as LiveRunRow["tools"],
|
|
1553
|
-
state
|
|
1560
|
+
state,
|
|
1561
|
+
...(liveState !== undefined ? { liveState } : {}),
|
|
1562
|
+
...(typeof liveStateSeq === "number" ? { liveStateSeq } : {}),
|
|
1554
1563
|
};
|
|
1555
1564
|
}
|
|
1556
1565
|
|
|
@@ -3077,6 +3086,78 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
3077
3086
|
return out;
|
|
3078
3087
|
}
|
|
3079
3088
|
|
|
3089
|
+
async assignLiveState(
|
|
3090
|
+
runId: string,
|
|
3091
|
+
gen: string,
|
|
3092
|
+
assignment: LiveStateAssignRequest,
|
|
3093
|
+
): Promise<LiveStateAssignResult> {
|
|
3094
|
+
let out: LiveStateAssignResult = { ok: false, reason: "unknown-run" };
|
|
3095
|
+
this.ctx.storage.transactionSync(() => {
|
|
3096
|
+
const row = this.liveRow(runId);
|
|
3097
|
+
const fence = checkFence(row, gen);
|
|
3098
|
+
if (!fence.ok) {
|
|
3099
|
+
out = fence;
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
if (!row) {
|
|
3103
|
+
out = { ok: false, reason: "unknown-run" };
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
const result = assignRunLiveState(
|
|
3107
|
+
assignment.restart ? undefined : row.liveState,
|
|
3108
|
+
row.liveStateSeq ?? 0,
|
|
3109
|
+
assignment,
|
|
3110
|
+
);
|
|
3111
|
+
if (!result.ok) {
|
|
3112
|
+
out = result;
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
3115
|
+
let liveStateSeq = row.liveStateSeq ?? 0;
|
|
3116
|
+
const last = this.sql
|
|
3117
|
+
.exec<{ m: number | null }>(`SELECT MAX(seq) AS m FROM run_events WHERE run_id = ?`, runId)
|
|
3118
|
+
.one().m;
|
|
3119
|
+
let lastEventSeq = last ?? 0;
|
|
3120
|
+
for (const source of assignment.sourceEvents ?? []) {
|
|
3121
|
+
if (source.seq <= lastEventSeq) {
|
|
3122
|
+
out = { ok: false, reason: "stale-sequence" };
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
lastEventSeq = source.seq;
|
|
3126
|
+
}
|
|
3127
|
+
const boundarySeq = result.event ? (assignment.eventSeq ?? lastEventSeq + 1) : undefined;
|
|
3128
|
+
if (boundarySeq !== undefined && boundarySeq <= lastEventSeq) {
|
|
3129
|
+
out = { ok: false, reason: "stale-sequence" };
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
for (const source of assignment.sourceEvents ?? []) {
|
|
3133
|
+
this.sql.exec(
|
|
3134
|
+
`INSERT INTO run_events (run_id, seq, json) VALUES (?, ?, ?)`,
|
|
3135
|
+
runId,
|
|
3136
|
+
source.seq,
|
|
3137
|
+
JSON.stringify(source),
|
|
3138
|
+
);
|
|
3139
|
+
liveStateSeq = source.seq;
|
|
3140
|
+
}
|
|
3141
|
+
if (result.event && boundarySeq !== undefined) {
|
|
3142
|
+
liveStateSeq = boundarySeq;
|
|
3143
|
+
this.sql.exec(
|
|
3144
|
+
`INSERT INTO run_events (run_id, seq, json) VALUES (?, ?, ?)`,
|
|
3145
|
+
runId,
|
|
3146
|
+
liveStateSeq,
|
|
3147
|
+
JSON.stringify({ ...result.event, seq: liveStateSeq }),
|
|
3148
|
+
);
|
|
3149
|
+
}
|
|
3150
|
+
const state = { ...row.state, ...assignment.statePatch, liveState: result.liveState, liveStateSeq };
|
|
3151
|
+
this.sql.exec(`UPDATE live_runs SET state_json = ? WHERE run_id = ?`, JSON.stringify(state), runId);
|
|
3152
|
+
out = {
|
|
3153
|
+
...result,
|
|
3154
|
+
...(result.event ? { event: { ...result.event, seq: liveStateSeq } } : {}),
|
|
3155
|
+
liveStateSeq,
|
|
3156
|
+
};
|
|
3157
|
+
});
|
|
3158
|
+
return out;
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3080
3161
|
async setState(runId: string, gen: string, state: RunState): Promise<FenceResult> {
|
|
3081
3162
|
let out: FenceResult = { ok: true };
|
|
3082
3163
|
this.ctx.storage.transactionSync(() => {
|
|
@@ -3256,15 +3337,22 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
3256
3337
|
.exec<{ json: string }>(`SELECT json FROM run_steps WHERE run_id = ? ORDER BY step DESC LIMIT 1`, row.runId)
|
|
3257
3338
|
.toArray()[0];
|
|
3258
3339
|
const lastStep = stepRow ? (JSON.parse(stepRow.json) as StepRecord) : null;
|
|
3259
|
-
|
|
3340
|
+
// Past the cursor, plus the deferred rows at or below it: the lowest
|
|
3341
|
+
// deferred seq bounds the read, and the record's predicate filters.
|
|
3342
|
+
const unread = unreadInbox(lastStep);
|
|
3343
|
+
const from = Math.min(
|
|
3344
|
+
lastStep?.inboxConsumedSeq ?? 0,
|
|
3345
|
+
...(lastStep?.inboxDeferredSeqs ?? []).map((s) => s - 1),
|
|
3346
|
+
);
|
|
3260
3347
|
const inbox = this.sql
|
|
3261
3348
|
.exec<{ seq: number; json: string }>(
|
|
3262
3349
|
`SELECT seq, json FROM run_inbox WHERE run_id = ? AND seq > ? ORDER BY seq ASC`,
|
|
3263
3350
|
row.runId,
|
|
3264
|
-
|
|
3351
|
+
from,
|
|
3265
3352
|
)
|
|
3266
3353
|
.toArray()
|
|
3267
|
-
.map((r) => ({ seq: r.seq, message: JSON.parse(r.json) as Record<string, unknown> }))
|
|
3354
|
+
.map((r) => ({ seq: r.seq, message: JSON.parse(r.json) as Record<string, unknown> }))
|
|
3355
|
+
.filter(unread);
|
|
3268
3356
|
const jobs = this.sql
|
|
3269
3357
|
.exec<{ kind: string; json: string }>(`SELECT kind, json FROM run_jobs WHERE run_id = ?`, row.runId)
|
|
3270
3358
|
.toArray()
|
|
@@ -5108,6 +5196,7 @@ const LEDGER_ROUTES = new Set([
|
|
|
5108
5196
|
"/runs/claim",
|
|
5109
5197
|
"/runs/heartbeat",
|
|
5110
5198
|
"/runs/append",
|
|
5199
|
+
"/runs/live-state",
|
|
5111
5200
|
"/runs/step",
|
|
5112
5201
|
"/runs/state",
|
|
5113
5202
|
"/runs/inbox",
|
|
@@ -5484,6 +5573,12 @@ function parseStep(v: unknown): Validated<StepRecord> {
|
|
|
5484
5573
|
return invalid(`record.${k} must be a non-negative integer`);
|
|
5485
5574
|
}
|
|
5486
5575
|
}
|
|
5576
|
+
if (
|
|
5577
|
+
s.inboxDeferredSeqs !== undefined &&
|
|
5578
|
+
(!Array.isArray(s.inboxDeferredSeqs) ||
|
|
5579
|
+
!s.inboxDeferredSeqs.every((seq) => typeof seq === "number" && Number.isInteger(seq) && seq > 0))
|
|
5580
|
+
)
|
|
5581
|
+
return invalid("record.inboxDeferredSeqs must be an array of positive integers");
|
|
5487
5582
|
if (!Array.isArray(s.inFlight)) return invalid("record.inFlight must be an array");
|
|
5488
5583
|
for (const c of s.inFlight) {
|
|
5489
5584
|
const call = c as Record<string, unknown>;
|
|
@@ -5940,6 +6035,13 @@ async function handleLedger(pathname: string, body: unknown, env: Env): Promise<
|
|
|
5940
6035
|
console.log(`[runs/append] ${key.value} ${runId.value} <- ${events.length} event(s), ok=${r.ok}`);
|
|
5941
6036
|
return fenced(r);
|
|
5942
6037
|
}
|
|
6038
|
+
if (pathname === "/runs/live-state") {
|
|
6039
|
+
if (typeof b.assignment !== "object" || b.assignment === null)
|
|
6040
|
+
return json({ error: "assignment must be an object" }, 400);
|
|
6041
|
+
const assignment = b.assignment as unknown as LiveStateAssignRequest;
|
|
6042
|
+
const r = await stub.assignLiveState(runId.value, g.value, assignment);
|
|
6043
|
+
return r.ok ? json(r) : json(r, r.reason === "fenced" || r.reason === "unknown-run" ? 409 : 400);
|
|
6044
|
+
}
|
|
5943
6045
|
if (pathname === "/runs/step") {
|
|
5944
6046
|
const record = parseStep(b.record);
|
|
5945
6047
|
if (!record.ok) return json({ error: record.error }, 400);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.261.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.261.0",
|
|
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.
|
|
21063
|
+
"version": "1.261.0",
|
|
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.
|
|
3
|
+
"version": "1.261.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
|
@@ -254,6 +254,11 @@ export const HOSTED_DEADLINE_MARGIN_MINUTES = 60;
|
|
|
254
254
|
* and the pause before a busy spawn is asked again. */
|
|
255
255
|
export const SHIP_WAIT = { marginMinutes: 5, chunkMinutes: 5, mergeChunkMinutes: 5, busyRetryMinutes: 2 } as const;
|
|
256
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
|
+
|
|
257
262
|
/** How long a restarting close gives its replacement dispatch to claim the
|
|
258
263
|
* same run before a read with no successor treats the close as final
|
|
259
264
|
* (run-history item 47a). One whole coordinator wait chunk lets an ordinary
|
|
@@ -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 {
|
|
@@ -640,15 +648,47 @@ async function call(
|
|
|
640
648
|
bot: CoordinatorBot,
|
|
641
649
|
route: CoordinatorStepRoute,
|
|
642
650
|
body: Record<string, unknown>,
|
|
651
|
+
acceptOpaqueNotFound = false,
|
|
643
652
|
): Promise<BotReply> {
|
|
644
653
|
const reply = await bot.step(route, body);
|
|
645
654
|
const read = readBotAnswer(reply.status, reply.text);
|
|
646
|
-
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
|
+
}
|
|
647
659
|
const transient = transientRefusal(read.answer);
|
|
648
660
|
if (transient !== undefined) throw new Error(transient);
|
|
649
661
|
return reply;
|
|
650
662
|
}
|
|
651
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
|
+
|
|
652
692
|
/** The answer a stored reply carries — the reply passed `call` once, so it reads the same on replay. */
|
|
653
693
|
function answerOf(route: CoordinatorStepRoute, reply: BotReply): BotAnswer {
|
|
654
694
|
const read = readBotAnswer(reply.status, reply.text);
|
|
@@ -745,10 +785,7 @@ async function perform(
|
|
|
745
785
|
case "read-record":
|
|
746
786
|
return readRecordReturn(
|
|
747
787
|
action.step,
|
|
748
|
-
answerOf(
|
|
749
|
-
"read-record",
|
|
750
|
-
await step.do(action.step, STEP_CONFIG, () => call(bot, "read-record", { ...tag, runId: action.runId })),
|
|
751
|
-
),
|
|
788
|
+
answerOf("read-record", await readRecordStep(step, bot, action, { ...tag, runId: action.runId })),
|
|
752
789
|
);
|
|
753
790
|
case "steer":
|
|
754
791
|
return steerReturn(
|
|
@@ -12,6 +12,30 @@ import type { UnitEnding } from "./ship/coordinator.js";
|
|
|
12
12
|
/** The stage vocabulary (record 0065): what every surface says a unit is in. */
|
|
13
13
|
export type Stage = "coding" | "review" | "fix" | "approved" | "merge-ready" | "merged" | "idle" | "ended";
|
|
14
14
|
|
|
15
|
+
export const HOSTED_PIPELINE_STARTING_DETAIL = "pipeline starting";
|
|
16
|
+
|
|
17
|
+
/** Safe detail for a hosted parent's shared `working` state, total over Stage. */
|
|
18
|
+
export function hostedStageDetail(stage: Stage): string {
|
|
19
|
+
switch (stage) {
|
|
20
|
+
case "coding":
|
|
21
|
+
return "coding a unit";
|
|
22
|
+
case "review":
|
|
23
|
+
return "reviewing a unit";
|
|
24
|
+
case "fix":
|
|
25
|
+
return "fixing review findings";
|
|
26
|
+
case "approved":
|
|
27
|
+
return "waiting for checks and merge";
|
|
28
|
+
case "merge-ready":
|
|
29
|
+
return "ready for a person to merge";
|
|
30
|
+
case "merged":
|
|
31
|
+
return "a unit merged";
|
|
32
|
+
case "idle":
|
|
33
|
+
return "waiting for the next pipeline step";
|
|
34
|
+
case "ended":
|
|
35
|
+
return "finishing the pipeline";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
15
39
|
export const STAGES = [
|
|
16
40
|
"coding",
|
|
17
41
|
"review",
|
|
@@ -12,6 +12,31 @@
|
|
|
12
12
|
import { PLANE, minutesToMs } from "../budgets.js";
|
|
13
13
|
import type { ProviderFailureCause } from "../provider.js";
|
|
14
14
|
import type { PlaneFinding } from "./findings.js";
|
|
15
|
+
import type { RunLiveStateName } from "../runLiveState.js";
|
|
16
|
+
|
|
17
|
+
/** The one user wording table for the server-owned live condition. */
|
|
18
|
+
export function liveStateWords(state: RunLiveStateName): string {
|
|
19
|
+
switch (state) {
|
|
20
|
+
case "admitted":
|
|
21
|
+
return "admitted";
|
|
22
|
+
case "waiting_deploy":
|
|
23
|
+
return "waiting for the current deploy";
|
|
24
|
+
case "waiting_repository":
|
|
25
|
+
return "waiting for the repository container";
|
|
26
|
+
case "falling_back":
|
|
27
|
+
return "switching to a fallback workspace";
|
|
28
|
+
case "preparing":
|
|
29
|
+
return "preparing the workspace";
|
|
30
|
+
case "working":
|
|
31
|
+
return "working";
|
|
32
|
+
case "waiting_provider":
|
|
33
|
+
return "waiting for the model provider";
|
|
34
|
+
case "wrapping_up":
|
|
35
|
+
return "wrapping up";
|
|
36
|
+
case "ended":
|
|
37
|
+
return "ended";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
15
40
|
|
|
16
41
|
// ---- endings and their causes (record 0064, "Endings and the watches") ------------------------
|
|
17
42
|
|
|
@@ -47,7 +47,7 @@ const CAUSE_OF = {
|
|
|
47
47
|
repo_not_onboarded: "request",
|
|
48
48
|
repo_access: "policy",
|
|
49
49
|
pr_head_unknown: "system",
|
|
50
|
-
|
|
50
|
+
workspace_head_mismatch: "system",
|
|
51
51
|
coordinator_thread_live: "system",
|
|
52
52
|
live_agent_allowlist: "policy",
|
|
53
53
|
follow_up_refused: "request",
|
|
@@ -5,6 +5,7 @@ import type { DescriptionIssue, PrDescription, RecordedJson, RenderedPointer } f
|
|
|
5
5
|
import type { HarnessScope } from "./harness/scope.js";
|
|
6
6
|
import type { ModelCard } from "./modelCard.js";
|
|
7
7
|
import type { ProviderFailureCause } from "./provider.js";
|
|
8
|
+
import type { RunStateEvent } from "./runLiveState.js";
|
|
8
9
|
|
|
9
10
|
/** The `pr_description` review artifact minus the event envelope
|
|
10
11
|
* (docs/reference/specs/reading-diff.md item 7). */
|
|
@@ -492,6 +493,7 @@ export function isHeadMaterial(event: RunEvent): boolean {
|
|
|
492
493
|
case "context":
|
|
493
494
|
case "reference":
|
|
494
495
|
case "run_meta":
|
|
496
|
+
case "run_state":
|
|
495
497
|
case "route":
|
|
496
498
|
return true;
|
|
497
499
|
case "run_note":
|
|
@@ -923,6 +925,9 @@ export type RunEvent =
|
|
|
923
925
|
* start of the loop; a resumed run carries the original. Head material,
|
|
924
926
|
* like `run_meta`. Additive: unknown → ignored. */
|
|
925
927
|
| { type: "lease"; startedAt: number; endsAt: number; loopEndsAt: number; seq?: number; at?: number }
|
|
928
|
+
/** A server-owned live-condition boundary. Same-state refreshes update the
|
|
929
|
+
* durable projection beside their source event and do not emit this event. */
|
|
930
|
+
| RunStateEvent
|
|
926
931
|
/** A head the run pushed (docs/reference/specs/run-history.md item 2; decision
|
|
927
932
|
* 0046): the branch and the sha the coding post-step observed on the remote
|
|
928
933
|
* (`by: "push"`), or the budget-end salvage pushed (`by: "salvage"`),
|
|
@@ -438,6 +438,7 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
438
438
|
ev.type === "operator" ||
|
|
439
439
|
ev.type === "refusal" ||
|
|
440
440
|
ev.type === "reference" ||
|
|
441
|
+
ev.type === "run_state" ||
|
|
441
442
|
ev.type === "lease" ||
|
|
442
443
|
ev.type === "pushed_head"
|
|
443
444
|
) {
|
|
@@ -537,6 +538,7 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
537
538
|
return;
|
|
538
539
|
}
|
|
539
540
|
|
|
541
|
+
if (ev.type !== "run_note") return;
|
|
540
542
|
switch (ev.kind) {
|
|
541
543
|
case "wrap_up":
|
|
542
544
|
// Its extent is the time from the warning to the end of the window (how
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Durable Object applies them inside one transaction; the in-memory ledger
|
|
3
3
|
// applies them in tests; both agree because this is the only copy.
|
|
4
4
|
|
|
5
|
-
import type { ClaimResult, FenceResult, IntakeReceipt, IntakeWriteResult, LivePhase } from "./types.js";
|
|
5
|
+
import type { ClaimResult, FenceResult, IntakeReceipt, IntakeWriteResult, LivePhase, StepRecord } from "./types.js";
|
|
6
6
|
|
|
7
7
|
/** One live run per thread. The existing row, if any, is what `live_runs` holds
|
|
8
8
|
* for the thread; the same run re-claimed by its owner is idempotent (a retry
|
|
@@ -80,6 +80,15 @@ export function selectReclaim<T extends { leaseUntil: number; phase: LivePhase;
|
|
|
80
80
|
return rows.filter((r) => r.ownerGen !== gen && (r.phase === "handoff" || r.leaseUntil <= now));
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/** Which inbox rows a reclaim hands the next generation (run-history item 40):
|
|
84
|
+
* every row past the last record's cursor, and the rows at or below it the
|
|
85
|
+
* record names deferred — handed to the run, never read by its model. */
|
|
86
|
+
export function unreadInbox(lastStep: Pick<StepRecord, "inboxConsumedSeq" | "inboxDeferredSeqs"> | null) {
|
|
87
|
+
const consumed = lastStep?.inboxConsumedSeq ?? 0;
|
|
88
|
+
const deferred = new Set(lastStep?.inboxDeferredSeqs ?? []);
|
|
89
|
+
return (item: { seq: number }): boolean => item.seq > consumed || deferred.has(item.seq);
|
|
90
|
+
}
|
|
91
|
+
|
|
83
92
|
/** The compare-and-swap table for a run's phase. `attaching → live` (the
|
|
84
93
|
* prompt landed) or `→ finishing` (the dispatch failed before it — never
|
|
85
94
|
* handoff: an attaching run has nothing to resume from, so the drain waits
|
|
@@ -9,6 +9,7 @@ import type { ChannelVisibility } from "../authz/types.js";
|
|
|
9
9
|
import type { RunProfile } from "../../config/profile.js";
|
|
10
10
|
import type { RunEvent } from "../runEvents.js";
|
|
11
11
|
import type { RunSeed, RunSession } from "../runRecord.js";
|
|
12
|
+
import type { AssignRunLiveStateInput, AssignRunLiveStateResult, RunLiveState } from "../runLiveState.js";
|
|
12
13
|
|
|
13
14
|
/** How long a generation's claim on a run lasts without a heartbeat. */
|
|
14
15
|
export const LEASE_MS = 30_000;
|
|
@@ -158,8 +159,28 @@ export interface LiveRunRow {
|
|
|
158
159
|
/** The tool definitions the run was started with, verbatim. */
|
|
159
160
|
tools: ToolDef[];
|
|
160
161
|
state: RunState;
|
|
162
|
+
/** The restart-safe live-condition projection. Optional on legacy rows. */
|
|
163
|
+
liveState?: RunLiveState;
|
|
164
|
+
/** Stream sequence that most recently produced or refreshed `liveState`. */
|
|
165
|
+
liveStateSeq?: number;
|
|
161
166
|
}
|
|
162
167
|
|
|
168
|
+
/** The atomic live-condition assignment request and answer shared by both ledger implementations. */
|
|
169
|
+
export type LiveStateAssignRequest = AssignRunLiveStateInput & {
|
|
170
|
+
/** Registry sequence reserved for a changed boundary before fan-out. */
|
|
171
|
+
eventSeq?: number;
|
|
172
|
+
/** Other durable row facts that must commit with the projection (for example hosting). */
|
|
173
|
+
statePatch?: RunState;
|
|
174
|
+
/** Existing activity facts committed with a same-state projection refresh. */
|
|
175
|
+
sourceEvents?: AppendableEvent[];
|
|
176
|
+
/** A new execution segment under the same run id starts after an ended predecessor. */
|
|
177
|
+
restart?: boolean;
|
|
178
|
+
};
|
|
179
|
+
export type LiveStateAssignResult =
|
|
180
|
+
| (Extract<AssignRunLiveStateResult, { ok: true }> & { liveStateSeq: number })
|
|
181
|
+
| Extract<AssignRunLiveStateResult, { ok: false }>
|
|
182
|
+
| { ok: false; reason: "fenced" | "unknown-run" };
|
|
183
|
+
|
|
163
184
|
/** One tool call the step dispatched; `tool` decides how a resume settles it.
|
|
164
185
|
* `boundMs` is the bound the call declared (a bash `timeout`), when it stated
|
|
165
186
|
* one — what the plane judges a `long_call` steer against (record 0064). */
|
|
@@ -179,6 +200,9 @@ export interface StepRecord {
|
|
|
179
200
|
inFlight: InFlightCall[];
|
|
180
201
|
/** The inbox `seq` the run has consumed up to (0 = none). */
|
|
181
202
|
inboxConsumedSeq: number;
|
|
203
|
+
/** Seqs at or below `inboxConsumedSeq` the run has not consumed yet: a
|
|
204
|
+
* reclaim offers them again with every row past the cursor. */
|
|
205
|
+
inboxDeferredSeqs?: number[];
|
|
182
206
|
remainingMs: number;
|
|
183
207
|
turn: number;
|
|
184
208
|
iteration: number;
|