@coreplane/switchboard 1.249.1 → 1.251.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 +46 -4
- package/dist/assets/deploy/cloudflare-memory/worker.ts +260 -1
- package/dist/assets/deploy/cloudflare-resident/Dockerfile +12 -3
- package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +7 -0
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +3 -2
- package/dist/assets/project.json +4 -0
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +1 -1
- package/dist/assets/src/core/authz/policy.ts +10 -0
- package/dist/assets/src/core/authz/resource.ts +6 -2
- package/dist/assets/src/core/authz/types.ts +2 -0
- package/dist/assets/src/core/budgets.ts +13 -0
- package/dist/assets/src/core/coordinator/contract.ts +120 -0
- package/dist/assets/src/core/coordinator/driver.ts +17 -3
- package/dist/assets/src/core/modelCard.ts +19 -3
- package/dist/assets/src/core/prDescriptionTypes.ts +35 -0
- package/dist/assets/src/core/provider.ts +57 -3
- package/dist/assets/src/core/refusal.ts +14 -0
- package/dist/assets/src/core/runEvents.ts +49 -2
- package/dist/assets/src/core/runFriction.ts +2 -0
- package/dist/assets/src/core/runLedger/decisions.ts +9 -1
- package/dist/assets/src/core/runLedger/sessionLog.ts +10 -0
- package/dist/assets/src/core/runLedger/transcript.ts +13 -3
- package/dist/assets/src/core/runLedger/types.ts +72 -6
- package/dist/assets/src/core/runRecord.ts +34 -0
- package/dist/assets/src/core/ship/contract.ts +17 -4
- package/dist/assets/src/core/ship/coordinator.ts +70 -4
- package/dist/assets/src/core/trace/attrs.ts +13 -2
- package/dist/assets/src/core/trace/workerTrace.ts +3 -2
- package/dist/assets/src/core/types.ts +3 -0
- package/dist/assets/web/dist/.vite/manifest.json +58 -52
- package/dist/assets/web/dist/assets/DeliveryPage-DUXd-Sl-.js +1 -0
- package/dist/assets/web/dist/assets/HomePage-mSiqEEcN.js +2 -0
- package/dist/assets/web/dist/assets/{PendingTurnRow-BuRre8it.js → PendingTurnRow-DDhMhrI7.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-BIUXyz6K.js → ResidentDetailPage-BnEoOnGQ.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-BZymgSAb.js → ResidentsIndexPage-Dxpgf-l-.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-CSo4-vld.js +1 -0
- package/dist/assets/web/dist/assets/{RunRoutePage-bgkjkA0p.js → RunRoutePage-9klVWhSF.js} +6 -6
- package/dist/assets/web/dist/assets/{RunsIndexPage-8S944AzB.js → RunsIndexPage-BplMIgaw.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-8bBtG9y3.js → ScheduledPage-B_GgeJrb.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-DQeNvfaV.js → SettingsPage-BXX4R113.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-BPE5syBa.js → StatusDot-BOaw8le9.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DkoeZfTs.js → Tooltip-DYZZ4l4V.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-BUzw--Ii.js → UnitRoutePage-BaSW5Odq.js} +1 -1
- package/dist/assets/web/dist/assets/budgets-BvWYKPsY.js +1 -0
- package/dist/assets/web/dist/assets/{dist-D11y9ZJ4.js → dist-BCVXeBJ9.js} +1 -1
- package/dist/assets/web/dist/assets/indexRow-Bde9OZxG.js +1 -0
- package/dist/assets/web/dist/assets/{main-B6LcgNM6.js → main-Dkcbtu3u.js} +2 -2
- package/dist/assets/web/dist/assets/sseReplay-DPwdsaok.js +9 -0
- package/dist/cli.js +4594 -2743
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/DeliveryPage-DF4aQypG.js +0 -1
- package/dist/assets/web/dist/assets/HomePage-BpQRky8B.js +0 -2
- package/dist/assets/web/dist/assets/RunFoldRow-3m4CPRI4.js +0 -1
- package/dist/assets/web/dist/assets/indexRow-BmK74Vp1.js +0 -1
- package/dist/assets/web/dist/assets/sseReplay-DXC7kGbN.js +0 -9
|
@@ -59,6 +59,14 @@ export interface LiveRunMeta {
|
|
|
59
59
|
/** The app that relayed the request for the person (authorization.md item 14): a resume or restart keeps app ∩ person at the gates. */
|
|
60
60
|
postedBy?: string;
|
|
61
61
|
effort?: string;
|
|
62
|
+
/** A ship pipeline's parent (record 0060): claimed under the host key
|
|
63
|
+
* (`hostKey.ts`) while `threadKey` here names the thread itself, so every
|
|
64
|
+
* record, notice and rebuilt handle files by the metadata's thread and the
|
|
65
|
+
* occupancy readers that key on the ledger's key column never see it. */
|
|
66
|
+
hosted?: true;
|
|
67
|
+
/** The run's index label (the registry sets it at create): on the row so a
|
|
68
|
+
* hosted run listed from the ledger reads as its registry view does. */
|
|
69
|
+
label?: string;
|
|
62
70
|
ref?: string;
|
|
63
71
|
headSha?: string;
|
|
64
72
|
pr?: number;
|
|
@@ -96,10 +104,10 @@ export interface LiveRunMeta {
|
|
|
96
104
|
request?: Record<string, unknown>;
|
|
97
105
|
/** The router's decision when it chose the run's preset (routing-and-config
|
|
98
106
|
* item 21) — the same fields the record's `route` event carries. On the row
|
|
99
|
-
* so a resume repaints the card as it was (`
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
107
|
+
* so a resume repaints the card as it was (the `route reason:` note at
|
|
108
|
+
* debug, the parts) and a record built from the row knows it was routed
|
|
109
|
+
* without reading the events. Absent for a preset a person, a scope or the
|
|
110
|
+
* default chose. */
|
|
103
111
|
route?: {
|
|
104
112
|
preset: string;
|
|
105
113
|
reason: string;
|
|
@@ -250,7 +258,65 @@ export interface CompactionEntry {
|
|
|
250
258
|
|
|
251
259
|
/** The rows a step write carries, each at its log index: the previous step's
|
|
252
260
|
* results and this step's assistant turn as messages, and pi's compaction
|
|
253
|
-
* entry as a row of its own between them.
|
|
254
|
-
|
|
261
|
+
* entry as a row of its own between them. The optional `actor` on a message
|
|
262
|
+
* turn is the platform-namespaced id of the person who authored it (record
|
|
263
|
+
* 0057); absent for machine turns, compaction rows and pre-0057 rows. */
|
|
264
|
+
export type TranscriptTurn =
|
|
265
|
+
{ idx: number; message: ChatMessage; actor?: string } | { idx: number; compaction: CompactionEntry };
|
|
255
266
|
|
|
256
267
|
export type AppendableEvent = RunEvent & { seq: number };
|
|
268
|
+
|
|
269
|
+
/** One intake verdict as the ledger stores it (docs/reference/specs/run-history.md item
|
|
270
|
+
* 59; docs/decisions/0058): keyed by the message (`<channel>:<ts>`), written
|
|
271
|
+
* first-writer-wins so one unmentioned thread reply is decided once across
|
|
272
|
+
* processes and the reconnect catch-up reads the verdict instead of deciding
|
|
273
|
+
* again. The shape is the intake seam's (`src/core/intake.ts` re-exports it);
|
|
274
|
+
* spelled out here because this file is shared with the state Worker. */
|
|
275
|
+
export interface IntakeReceipt {
|
|
276
|
+
verdict: "addressed" | "silent";
|
|
277
|
+
reason: string;
|
|
278
|
+
source: "model" | "mode" | "error" | "timeout";
|
|
279
|
+
mode: "mention" | "classify";
|
|
280
|
+
/** The `<provider>/<model>` ref the verdict ran on. */
|
|
281
|
+
model: string;
|
|
282
|
+
/** The deciding process's generation counter, for telling a retried write's
|
|
283
|
+
* own landed row from another writer's. */
|
|
284
|
+
gen: number;
|
|
285
|
+
threadKey: string;
|
|
286
|
+
/** Epoch ms. */
|
|
287
|
+
decidedAt: number;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** What an intake write answers: whether THIS write landed, and the row that
|
|
291
|
+
* stands — the first writer's, whoever that was. */
|
|
292
|
+
export interface IntakeWriteResult {
|
|
293
|
+
inserted: boolean;
|
|
294
|
+
stored: IntakeReceipt;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** `listIntake`'s filters: a thread's rows, rows since an instant, or both. */
|
|
298
|
+
export interface IntakeQuery {
|
|
299
|
+
threadKey?: string;
|
|
300
|
+
since?: number;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** The receipt as the Worker route validates it: every field present and of
|
|
304
|
+
* its type, the enums closed — a malformed receipt is 400, never stored. */
|
|
305
|
+
export function isIntakeReceipt(v: unknown): v is IntakeReceipt {
|
|
306
|
+
if (typeof v !== "object" || v === null) return false;
|
|
307
|
+
const r = v as Record<string, unknown>;
|
|
308
|
+
return (
|
|
309
|
+
(r.verdict === "addressed" || r.verdict === "silent") &&
|
|
310
|
+
typeof r.reason === "string" &&
|
|
311
|
+
(r.source === "model" || r.source === "mode" || r.source === "error" || r.source === "timeout") &&
|
|
312
|
+
(r.mode === "mention" || r.mode === "classify") &&
|
|
313
|
+
typeof r.model === "string" &&
|
|
314
|
+
typeof r.gen === "number" &&
|
|
315
|
+
Number.isFinite(r.gen) &&
|
|
316
|
+
typeof r.threadKey === "string" &&
|
|
317
|
+
r.threadKey.length > 0 &&
|
|
318
|
+
r.threadKey.length <= 256 &&
|
|
319
|
+
typeof r.decidedAt === "number" &&
|
|
320
|
+
Number.isFinite(r.decidedAt)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
@@ -39,6 +39,12 @@ import {
|
|
|
39
39
|
// reached `finish`.
|
|
40
40
|
export type RunStatus = "completed" | "stopped_soft" | "stopped_hard" | "failed" | "interrupted";
|
|
41
41
|
|
|
42
|
+
/** How every store-only reader renders a record still in its provisional
|
|
43
|
+
* window (run-history.md item 27's third state): not `interrupted` (a real
|
|
44
|
+
* terminal state) and not `running` (the store cannot know). One string for
|
|
45
|
+
* the CLI's `runs list`/`runs get` and the web's index row and run page. */
|
|
46
|
+
export const PROVISIONAL_LABEL = "unfinished — no finish recorded";
|
|
47
|
+
|
|
42
48
|
const RUN_STATUSES: readonly RunStatus[] = ["completed", "stopped_soft", "stopped_hard", "failed", "interrupted"];
|
|
43
49
|
|
|
44
50
|
/** Every `runs.*` id: checked before any store call. */
|
|
@@ -115,6 +121,13 @@ export interface RunRecord {
|
|
|
115
121
|
stepCount?: number;
|
|
116
122
|
schema?: number;
|
|
117
123
|
status: RunStatus;
|
|
124
|
+
/** Present on a tombstone record still in its provisional window — the
|
|
125
|
+
* start-of-run `interrupted` or the drain-deadline upgrade — before the
|
|
126
|
+
* run's final write lands. Absent on every final (finished) record. A
|
|
127
|
+
* store-only reader must render a provisional record as "unfinished — no
|
|
128
|
+
* finish recorded" rather than as `interrupted`, because the run may still
|
|
129
|
+
* be live in a registry the reader cannot see (run-history.md item 27). */
|
|
130
|
+
provisional?: true;
|
|
118
131
|
/** The failure by name, when a `failed` run has one (item 57):
|
|
119
132
|
* `policy_refusal`, the provider refused the run's model call under its
|
|
120
133
|
* usage policy. Absent on a run that did not fail, on one that failed for
|
|
@@ -190,6 +203,11 @@ export interface RunRecord {
|
|
|
190
203
|
* item 48), stored at the claim so a retried spawn finds its run. Present
|
|
191
204
|
* exactly when `parentInstanceId` is — both or neither, never one alone. */
|
|
192
205
|
idempotencyKey?: string;
|
|
206
|
+
/** The plan runner instance this run's hand-off created (record 0051 R2;
|
|
207
|
+
* item 2): the last `ship_handoff` event, folded at the assembly like the
|
|
208
|
+
* coordinator tag. Present only on a ship run whose hand-off succeeded;
|
|
209
|
+
* a record written before the event has none. */
|
|
210
|
+
instanceId?: string;
|
|
193
211
|
/** Where the run's conversation started (item 52): `channel` — its own
|
|
194
212
|
* thread's history, as for every run a person, a schedule or a coordinator
|
|
195
213
|
* started — or `parent` — a spawned child seeded from its parent's text
|
|
@@ -266,6 +284,16 @@ export function leaseOfEvents(events: readonly RunEvent[]): RunLease | undefined
|
|
|
266
284
|
return undefined;
|
|
267
285
|
}
|
|
268
286
|
|
|
287
|
+
/** The plan runner instance a ship run's events say its hand-off created —
|
|
288
|
+
* the last `ship_handoff` wins — or nothing (record 0051 R2): projected onto
|
|
289
|
+
* `RunRecord.instanceId` the way `coordinator_tag` rides the record, so the
|
|
290
|
+
* thread's owner rule finds the instance from the page's ship run. */
|
|
291
|
+
export function instanceIdOfEvents(events: readonly RunEvent[]): string | undefined {
|
|
292
|
+
let id: string | undefined;
|
|
293
|
+
for (const e of events) if (e.type === "ship_handoff") id = e.instanceId;
|
|
294
|
+
return id;
|
|
295
|
+
}
|
|
296
|
+
|
|
269
297
|
/** The pull request a run's events say it opened or edited — the last
|
|
270
298
|
* `pr_opened` wins, as an edit after an open names the same PR — or nothing. */
|
|
271
299
|
export function prOfEvents(events: readonly RunEvent[]): RunPullRequest | undefined {
|
|
@@ -908,6 +936,9 @@ export function isRunRecord(v: unknown): v is RunRecord {
|
|
|
908
936
|
(typeof r.idempotencyKey !== "string" || !IDEMPOTENCY_KEY_PATTERN.test(r.idempotencyKey))
|
|
909
937
|
)
|
|
910
938
|
return false;
|
|
939
|
+
// The instance a ship run's hand-off created (record 0051 R2; item 2).
|
|
940
|
+
if (r.instanceId !== undefined && (typeof r.instanceId !== "string" || !INSTANCE_ID_PATTERN.test(r.instanceId)))
|
|
941
|
+
return false;
|
|
911
942
|
if (typeof r.channelId !== "string" || typeof r.userId !== "string" || typeof r.threadKey !== "string") return false;
|
|
912
943
|
if (r.relayedBy !== undefined && typeof r.relayedBy !== "string") return false;
|
|
913
944
|
if (r.authenticatedAs !== undefined && typeof r.authenticatedAs !== "string") return false;
|
|
@@ -926,6 +957,9 @@ export function isRunRecord(v: unknown): v is RunRecord {
|
|
|
926
957
|
return false;
|
|
927
958
|
}
|
|
928
959
|
if (!RUN_STATUSES.includes(r.status as RunStatus)) return false;
|
|
960
|
+
// A provisional tombstone carries `provisional: true`; any other value (false,
|
|
961
|
+
// a string, etc.) is a malformed record — only the presence of the flag matters.
|
|
962
|
+
if (r.provisional !== undefined && r.provisional !== true) return false;
|
|
929
963
|
if (!isFiniteNumber(r.eventCount) || !isFiniteNumber(r.storedEventCount)) return false;
|
|
930
964
|
if (typeof r.truncated !== "boolean") return false;
|
|
931
965
|
if (!Array.isArray(r.events)) return false;
|
|
@@ -439,17 +439,30 @@ export const TIMEOUT_ON_LONG_COMMANDS =
|
|
|
439
439
|
"State a timeout on any command you expect to run longer than a minute: a timeout that reaches past the loop's " +
|
|
440
440
|
"end is refused before the command runs, never cut midway.";
|
|
441
441
|
|
|
442
|
+
/** The fast gates a plan child runs before every push, named one by one
|
|
443
|
+
* (agent-ship item 13; agent-coding item 9). "Its cheapest proving checks"
|
|
444
|
+
* left the choice to the child, and children chose wrong: prettier was
|
|
445
|
+
* reported clean while `format:check` was red, and hygiene imprints reached
|
|
446
|
+
* CI that `hygiene:check` would have caught locally. Naming the commands
|
|
447
|
+
* makes each gate a receipt — the exit line goes into the handoff's verified
|
|
448
|
+
* list, and a gate the child could not run is unproven, never claimed clean. */
|
|
449
|
+
export const FAST_GATES_BEFORE_PUSH =
|
|
450
|
+
"The fast gates, before every push: `npx prettier --check` on the changed files, `npm run hygiene:check`, " +
|
|
451
|
+
"`npm run specs:check`, and `tsc --noEmit` on the touched project under `NODE_OPTIONS=--max-old-space-size=6144`. " +
|
|
452
|
+
"Paste each command's exit line into the handoff's verified list; a gate you could not run goes under unproven " +
|
|
453
|
+
"and is never claimed clean.";
|
|
454
|
+
|
|
442
455
|
function renderFirstInstruction(rebase: ChildContract["rebase"]): string {
|
|
443
456
|
const branch = rebase.branch ? `\`${rebase.branch}\`` : "the unit's branch";
|
|
444
457
|
const onto = rebase.onto ? `\`${rebase.onto}\`` : "the merged parent";
|
|
445
458
|
return (
|
|
446
459
|
`Rebase ${branch} onto ${onto} before any other work — the parent unit has merged and the base has moved; ` +
|
|
447
460
|
`the only writes are your own on that branch. A conflict ends the unit: report it as the handoff and stop. ` +
|
|
448
|
-
`Push the branch as soon as the change exists and
|
|
461
|
+
`Push the branch as soon as the change exists and the fast gates pass — before the project's ` +
|
|
449
462
|
`full verification, which runs after that push with any fix as a further commit; an unpushed tree does not ` +
|
|
450
|
-
`survive the run's end. Right before each push, fetch ${onto} again and rebase
|
|
451
|
-
`you worked, so the pull request is not born conflicting. At the wind-down note,
|
|
452
|
-
`say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`
|
|
463
|
+
`survive the run's end. ${FAST_GATES_BEFORE_PUSH} Right before each push, fetch ${onto} again and rebase ` +
|
|
464
|
+
`once more if it moved while you worked, so the pull request is not born conflicting. At the wind-down note, ` +
|
|
465
|
+
`commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`
|
|
453
466
|
);
|
|
454
467
|
}
|
|
455
468
|
|
|
@@ -453,6 +453,13 @@ export type CoordinatorAction =
|
|
|
453
453
|
* submitted description when the record holds one) instead of answering
|
|
454
454
|
* `none` over stranded work. */
|
|
455
455
|
recover?: { runId: string };
|
|
456
|
+
/** The pull request the machine has adopted (`state.pr`), when it holds
|
|
457
|
+
* one: the child may have worked that pull request's own head branch,
|
|
458
|
+
* not the unit's (issue 1799), so when nothing heads the unit's branch
|
|
459
|
+
* the bot follows this number and answers the pull request's LIVE state
|
|
460
|
+
* — open at a fresh head, merged, or closed (`prClosed`) — never `none`
|
|
461
|
+
* over a record fact written minutes earlier. */
|
|
462
|
+
pr?: number;
|
|
456
463
|
}
|
|
457
464
|
| { type: "merge"; step: string; prNumber: number; headSha: string }
|
|
458
465
|
/** Wait for the intake's checks-settled event at the approved head, bounded as the fallback. */
|
|
@@ -509,6 +516,10 @@ export type PrCheck =
|
|
|
509
516
|
* naming where the scope landed is the `already_landed` ending
|
|
510
517
|
* (agent-ship item 12); absent, the fact is unknown and never claimed. */
|
|
511
518
|
aheadOfBase?: number;
|
|
519
|
+
/** The action's followed pull request (`pr`) was verified CLOSED
|
|
520
|
+
* unmerged: the machine must not brief a review round on it. Absent when
|
|
521
|
+
* nothing was followed or the follow could not read the pull request. */
|
|
522
|
+
prClosed?: boolean;
|
|
512
523
|
}
|
|
513
524
|
| {
|
|
514
525
|
state: "open";
|
|
@@ -704,6 +715,10 @@ export interface UnitPipelineInput {
|
|
|
704
715
|
* pre-check finds the open pull request still at exactly this head, there is
|
|
705
716
|
* nothing to code and the attempt starts at the review round. */
|
|
706
717
|
lastPush?: string;
|
|
718
|
+
/** The runs page base (`<PUBLIC_BASE_URL>/runs`), the plan route's answer:
|
|
719
|
+
* the report's pointer at a child's write-up links its run page with it and
|
|
720
|
+
* names the run id without it — the machine never reads an environment. */
|
|
721
|
+
runPageBase?: string;
|
|
707
722
|
}
|
|
708
723
|
|
|
709
724
|
type Phase =
|
|
@@ -935,6 +950,9 @@ export function nextAction(s: UnitPipelineState): CoordinatorAction {
|
|
|
935
950
|
type: "pr-check",
|
|
936
951
|
step: `${roundStep(s, p.round)}/pr-check`,
|
|
937
952
|
...(p.dead !== undefined ? { recover: { runId: p.runId } } : {}),
|
|
953
|
+
// The adopted pull request rides the check so the bot can follow it
|
|
954
|
+
// when nothing heads the unit's branch (issue 1799).
|
|
955
|
+
...(s.pr !== undefined ? { pr: s.pr.number } : {}),
|
|
938
956
|
};
|
|
939
957
|
case "merge":
|
|
940
958
|
return { type: "merge", step: `${unit}/merge/${p.n}`, prNumber: p.pr.number, headSha: p.headSha };
|
|
@@ -1286,6 +1304,22 @@ function settlePrCheck(s: UnitPipelineState, phase: Extract<Phase, { at: "pr-che
|
|
|
1286
1304
|
[roundNote(round, "aborted")],
|
|
1287
1305
|
);
|
|
1288
1306
|
}
|
|
1307
|
+
// Nothing heads the unit's branch, but the machine holds the round's pull
|
|
1308
|
+
// request (`pr_opened` off the child's record, or an earlier round's
|
|
1309
|
+
// adoption): the child worked on that pull request's own head branch, not
|
|
1310
|
+
// the unit's — a re-issued task in the thread of an existing pull request
|
|
1311
|
+
// (issue 1799). The round HAS its pull request — at round 0 and after a
|
|
1312
|
+
// findings step alike, since the same child keeps repushing that branch
|
|
1313
|
+
// through every later round — so it carries on to the (re-)review at the
|
|
1314
|
+
// head the child pushed through the shared open settle (a findings step
|
|
1315
|
+
// that repushed nothing keeps its abort, item 7), instead of the unit
|
|
1316
|
+
// ending "no pull request" or "closed out from under" over work that
|
|
1317
|
+
// stands. Never over a followed answer that VERIFIED the pull request
|
|
1318
|
+
// closed (`prClosed`): a review briefed on a closed pull request reviews
|
|
1319
|
+
// nothing, so the endings below stand — and are then truthful. Dead
|
|
1320
|
+
// children never reach here: their endings are above.
|
|
1321
|
+
if (s.pr !== undefined && pr.prClosed !== true)
|
|
1322
|
+
return roundOnOpenPr(s, phase, { prNumber: s.pr.number, url: s.pr.url });
|
|
1289
1323
|
if (round.index === 0) {
|
|
1290
1324
|
// The scope already landed (agent-ship item 12): the child's handoff
|
|
1291
1325
|
// names where, and the branch carries no commits over the base — two
|
|
@@ -1370,6 +1404,18 @@ function settlePrCheck(s: UnitPipelineState, phase: Extract<Phase, { at: "pr-che
|
|
|
1370
1404
|
[roundNote(round, "aborted")],
|
|
1371
1405
|
);
|
|
1372
1406
|
}
|
|
1407
|
+
return roundOnOpenPr(s, phase, pr);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/** The round carried on its open pull request: the pr-check's `open` answer,
|
|
1411
|
+
* or — when nothing heads the unit's branch — the pull request the machine
|
|
1412
|
+
* already holds (issue 1799), at the head the child pushed. */
|
|
1413
|
+
function roundOnOpenPr(
|
|
1414
|
+
s: UnitPipelineState,
|
|
1415
|
+
phase: Extract<Phase, { at: "pr-check" }>,
|
|
1416
|
+
pr: { prNumber: number; url: string; headSha?: string },
|
|
1417
|
+
): Transition {
|
|
1418
|
+
const { round } = phase;
|
|
1373
1419
|
const head = pr.headSha ?? phase.childHead;
|
|
1374
1420
|
const next: UnitPipelineState = { ...s, pr: { number: pr.prNumber, url: pr.url } };
|
|
1375
1421
|
if (round.kind === "findings") {
|
|
@@ -1647,6 +1693,18 @@ function dispositionFor(s: UnitPipelineState, finding: Finding, round: number):
|
|
|
1647
1693
|
return undefined;
|
|
1648
1694
|
}
|
|
1649
1695
|
|
|
1696
|
+
/** Where a child's write-up lives, for the report's pointer (agent-ship item
|
|
1697
|
+
* 12): the child's own message in the thread is the single copy of the detail
|
|
1698
|
+
* — posted moments before the unit-end — so the report points at it instead
|
|
1699
|
+
* of repeating it: the run page when the plan named the base, the run id
|
|
1700
|
+
* otherwise. Undefined when no run is known to point at. */
|
|
1701
|
+
function writeUpPointer(s: UnitPipelineState, kind: RoundKind, runId: string | undefined): string | undefined {
|
|
1702
|
+
if (runId === undefined) return undefined;
|
|
1703
|
+
const base = s.input.runPageBase;
|
|
1704
|
+
const at = base !== undefined ? `${base.replace(/\/+$/, "")}/${encodeURIComponent(runId)}` : `run ${runId}`;
|
|
1705
|
+
return `The ${presetOf(kind)} child's write-up is its own message above in this thread — the single copy of the detail (${at}).`;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1650
1708
|
/** How the budget went, in the card's words: coding, review, waiting minutes. */
|
|
1651
1709
|
function budgetSplitLine(spent: ShipBudgetSpent, maxMinutes: number): string {
|
|
1652
1710
|
const min = (ms: number) => Math.round(ms / MIN);
|
|
@@ -1815,7 +1873,11 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1815
1873
|
case "stopped":
|
|
1816
1874
|
return join([
|
|
1817
1875
|
`${e.mode === "hard" ? "⛔" : "⏹"} Ship stopped by operator (${e.mode} stop) after ${rounds}.${prLine}`,
|
|
1818
|
-
|
|
1876
|
+
writeUpPointer(
|
|
1877
|
+
s,
|
|
1878
|
+
e.round.kind,
|
|
1879
|
+
e.round.kind === "review" ? s.reviewRunByRound[e.round.index] : s.lastCodingRunId,
|
|
1880
|
+
),
|
|
1819
1881
|
e.postedReview
|
|
1820
1882
|
? "ℹ️ A changes-requested review was posted this round before the stop — its findings stand on the PR."
|
|
1821
1883
|
: undefined,
|
|
@@ -1823,22 +1885,26 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1823
1885
|
]);
|
|
1824
1886
|
case "aborted":
|
|
1825
1887
|
return join([
|
|
1826
|
-
e.finalReply,
|
|
1827
1888
|
e.reason,
|
|
1889
|
+
writeUpPointer(
|
|
1890
|
+
s,
|
|
1891
|
+
e.round?.kind ?? "coding",
|
|
1892
|
+
e.round?.kind === "review" ? s.reviewRunByRound[e.round.index] : s.lastCodingRunId,
|
|
1893
|
+
),
|
|
1828
1894
|
e.renewal !== undefined ? `🔁 Not renewed: ${e.renewal.line}.` : undefined,
|
|
1829
1895
|
`⚠️ Ship aborted after ${rounds}.`,
|
|
1830
1896
|
reissue,
|
|
1831
1897
|
]);
|
|
1832
1898
|
case "continued":
|
|
1833
1899
|
return join([
|
|
1834
|
-
e.
|
|
1900
|
+
writeUpPointer(s, e.round.kind, e.runId),
|
|
1835
1901
|
`🔁 Segment ${e.segment - 1} ended at its lease with the unit unfinished — ${e.line}. Segment ${e.segment} opens in this thread${e.from !== undefined ? ` from \`${e.from.slice(0, 7)}\`` : ""} under a fresh ${s.input.caps.maxMinutes}-minute lease, with this segment's write-up as its request; ${e.renewalsLeft} renewal${e.renewalsLeft === 1 ? "" : "s"} remain${e.spendUsd !== null ? `, $${e.spendUsd.toFixed(2)} spent so far` : ""}.`,
|
|
1836
1902
|
budgetSplitLine(e.spent, s.input.caps.maxMinutes),
|
|
1837
1903
|
]);
|
|
1838
1904
|
case "no_verdict":
|
|
1839
1905
|
return join([
|
|
1840
1906
|
`⚠️ Review round ${e.round.index} ended without a submitted verdict (budget, refusal, or stop) — ship never converts that into a request for changes, so no findings step ran.`,
|
|
1841
|
-
e.
|
|
1907
|
+
writeUpPointer(s, e.round.kind, s.reviewRunByRound[e.round.index]),
|
|
1842
1908
|
`⚠️ Ship aborted after ${rounds}.`,
|
|
1843
1909
|
reissue,
|
|
1844
1910
|
]);
|
|
@@ -22,8 +22,16 @@ export interface AttrDomain {
|
|
|
22
22
|
caughtUp: boolean;
|
|
23
23
|
files: number;
|
|
24
24
|
dedupe: "fresh" | "duplicate";
|
|
25
|
-
/** How the requester was found (slack-channel.md item 13): the sender, the relay
|
|
26
|
-
requester: "message" | "relay-footer" | "
|
|
25
|
+
/** How the requester was found (slack-channel.md item 13): the sender, the configured relay app's footer, or the app itself. */
|
|
26
|
+
requester: "message" | "relay-footer" | "bot";
|
|
27
|
+
/** The intake verdict on an unmentioned thread reply (routing-and-config.md
|
|
28
|
+
* item 27, record 0058); the reason is free text and lives on the receipt
|
|
29
|
+
* row and the log line, never on a span. */
|
|
30
|
+
intake: "addressed" | "silent";
|
|
31
|
+
/** How the verdict was reached (`IntakeSource`). */
|
|
32
|
+
intakeSource: "model" | "mode" | "error" | "timeout";
|
|
33
|
+
/** What became of the receipt (`IntakeReceiptOutcome`). */
|
|
34
|
+
intakeReceipt: "inserted" | "existing" | "failed" | "absent";
|
|
27
35
|
// dispatch.* / run.* / post.*
|
|
28
36
|
outcome: string;
|
|
29
37
|
/** The refusal's code (src/core/refusal.ts) — on the `dispatch.refuse` span
|
|
@@ -169,6 +177,9 @@ const ATTR_TYPE: Record<SpanAttrKey, "string" | "number" | "boolean"> = {
|
|
|
169
177
|
files: "number",
|
|
170
178
|
dedupe: "string",
|
|
171
179
|
requester: "string",
|
|
180
|
+
intake: "string",
|
|
181
|
+
intakeSource: "string",
|
|
182
|
+
intakeReceipt: "string",
|
|
172
183
|
outcome: "string",
|
|
173
184
|
refusal: "string",
|
|
174
185
|
cause: "string",
|
|
@@ -54,9 +54,10 @@ export function shimRoute(pathname: string): string | undefined {
|
|
|
54
54
|
if (pathname.startsWith("/admin/")) return "admin";
|
|
55
55
|
// The artifact copy (deploy/cloudflare/artifactsCopy.ts): the shim's own route, a 1 GB stream.
|
|
56
56
|
if (pathname === "/artifacts/copy") return "artifacts";
|
|
57
|
-
// The model proxy's
|
|
57
|
+
// The model proxy's three routes (docs/reference/specs/model-proxy.md): a bounded
|
|
58
58
|
// request per model call, forwarded to the container like everything else.
|
|
59
|
-
if (pathname === "/v1/messages" || pathname === "/v1/chat/completions"
|
|
59
|
+
if (pathname === "/v1/messages" || pathname === "/v1/chat/completions" || pathname === "/v1/responses")
|
|
60
|
+
return "model-proxy";
|
|
60
61
|
// The pi harness's four routes (docs/reference/specs/harness-pi.md item 7): a
|
|
61
62
|
// run's extension asking for its tools, a verdict, a relayed tool's result,
|
|
62
63
|
// how a compaction is written.
|
|
@@ -146,6 +146,9 @@ export interface HistoryItem {
|
|
|
146
146
|
images?: ImageAttachment[];
|
|
147
147
|
/** Non-image files attached to this turn, if any (user turns only in practice). */
|
|
148
148
|
documents?: DocumentAttachment[];
|
|
149
|
+
/** The platform-namespaced id of the author (e.g. `slack:U…`); absent for
|
|
150
|
+
* bot turns and channels that do not stamp user ids (record 0057). */
|
|
151
|
+
user?: string;
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
/** What the run is doing right now, typed so each channel draws it in its
|