@coreplane/switchboard 1.256.0 → 1.258.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 +14 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +340 -7
- package/dist/assets/deploy/cloudflare-resident/drain.ts +49 -4
- package/dist/assets/deploy/cloudflare-resident/worker.ts +52 -4
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +5 -3
- package/dist/assets/project.json +17 -9
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +65 -2
- package/dist/assets/src/core/authz/grants.ts +12 -0
- package/dist/assets/src/core/authz/policy.ts +15 -0
- package/dist/assets/src/core/budgets.ts +66 -18
- package/dist/assets/src/core/coordinator/contract.ts +45 -5
- package/dist/assets/src/core/coordinator/driver.ts +6 -3
- package/dist/assets/src/core/delivery.ts +8 -5
- package/dist/assets/src/core/pipelineStanding.ts +2 -0
- package/dist/assets/src/core/plane/decide.ts +477 -12
- package/dist/assets/src/core/plane/findings.ts +120 -0
- package/dist/assets/src/core/runEvents.ts +54 -14
- package/dist/assets/src/core/runFriction.ts +16 -0
- package/dist/assets/src/core/runLedger/types.ts +16 -3
- package/dist/assets/src/core/runRecord.ts +12 -0
- package/dist/assets/src/core/ship/contract.ts +14 -2
- package/dist/assets/src/core/ship/coordinator.ts +179 -51
- package/dist/assets/src/core/ship/renewal.ts +23 -20
- package/dist/assets/src/core/trace/attrs.ts +1 -1
- package/dist/assets/src/core/untrusted.ts +35 -0
- package/dist/assets/src/execution/residentCredentials.ts +7 -4
- package/dist/assets/web/dist/.vite/manifest.json +97 -72
- package/dist/assets/web/dist/assets/{DeliveryPage-BBAyLwPq.js → DeliveryPage-DadntjSp.js} +1 -1
- package/dist/assets/web/dist/assets/HomePage-BEEpTnVT.js +1 -0
- package/dist/assets/web/dist/assets/{PendingTurnRow-DGINv9XT.js → PendingTurnRow-DBPMLVsm.js} +1 -1
- package/dist/assets/web/dist/assets/PlanePage-DLusMGQ0.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-jYhEeeyu.js → ResidentDetailPage-CPnHnrxy.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-DvFmlV4U.js → ResidentsIndexPage-C9hASwHL.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-OPe7Z0Cz.js +1 -0
- package/dist/assets/web/dist/assets/{RunRoutePage-CvZ-TOT3.js → RunRoutePage-o1xzkTGZ.js} +4 -4
- package/dist/assets/web/dist/assets/RunsIndexPage-0YAClYQh.js +1 -0
- package/dist/assets/web/dist/assets/{ScheduledPage-Viim-bus.js → ScheduledPage-BIdfC0V3.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-CYUy8McC.js → SettingsPage-CnqWAjl3.js} +1 -1
- package/dist/assets/web/dist/assets/SilentTurn-DGNg0sIZ.js +2 -0
- package/dist/assets/web/dist/assets/SlackMark-CtiHrNjr.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-Dv6UMaPy.js → StatusDot-D_D_iJS8.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-Bd-5Rypv.js → Tooltip-DVFmM7on.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-7fAXFoB_.js +1 -0
- package/dist/assets/web/dist/assets/budgets-BRLm6oID.js +1 -0
- package/dist/assets/web/dist/assets/{dist-Nl3uaxrP.js → dist-C1VX-_mm.js} +1 -1
- package/dist/assets/web/dist/assets/durationTone-CVpX_yIk.js +1 -0
- package/dist/assets/web/dist/assets/{indexRow-DborJPFp.js → indexRow-BO_GIdF6.js} +1 -1
- package/dist/assets/web/dist/assets/main-Cl7xHG34.js +28 -0
- package/dist/assets/web/dist/assets/main-DGC6WFqS.css +1 -0
- package/dist/assets/web/dist/assets/{sseReplay-DE6wv1Ua.js → sseReplay-BjlKmQsY.js} +1 -1
- package/dist/cli.js +3096 -1136
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/HomePage-BEiStFwW.js +0 -3
- package/dist/assets/web/dist/assets/PlanePage-JEj-lqgz.js +0 -1
- package/dist/assets/web/dist/assets/RunFoldRow-DvWzR1JQ.js +0 -1
- package/dist/assets/web/dist/assets/RunsIndexPage-JqVSDOok.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-B81wEnKN.js +0 -1
- package/dist/assets/web/dist/assets/budgets-c1eumrqD.js +0 -1
- package/dist/assets/web/dist/assets/durationTone-U_rOqo6r.js +0 -1
- package/dist/assets/web/dist/assets/main-C4QfwybO.css +0 -1
- package/dist/assets/web/dist/assets/main-DRxWlffc.js +0 -28
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// The plane's findings (docs/decisions/0064, "Endings and the watches"): a
|
|
2
|
+
// finding is filed when no move applies
|
|
3
|
+
// — it carries the watch's name and the timeline of events that fired it, and
|
|
4
|
+
// two findings on one watch and subject are ONE, their timelines merged. The
|
|
5
|
+
// plane itself calls no model: the bot dispatches a research-tier run over the
|
|
6
|
+
// timeline inside the untrusted fence (`findingResearchRequest`) and files the
|
|
7
|
+
// answer through the path `friction propose` uses (`filePlaneFinding`, the
|
|
8
|
+
// same `IssueTracker` seam). The dedupe half is node-free, like `decide.ts`.
|
|
9
|
+
|
|
10
|
+
import { wrapUntrusted } from "../untrusted.js";
|
|
11
|
+
|
|
12
|
+
/** The tracker seam, structurally the `IssueTracker` of
|
|
13
|
+
* `src/execution/githubIssues.ts` (the friction proposer's) — declared here
|
|
14
|
+
* so this module stays node-free like `decide.ts` and imports nothing the
|
|
15
|
+
* Worker's build cannot carry. */
|
|
16
|
+
export interface PlaneIssueRef {
|
|
17
|
+
number: number;
|
|
18
|
+
url: string;
|
|
19
|
+
title: string;
|
|
20
|
+
body: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PlaneIssueTracker {
|
|
24
|
+
listOpen(repo: string, label: string): Promise<PlaneIssueRef[]>;
|
|
25
|
+
create(repo: string, issue: { title: string; body: string; labels: string[] }): Promise<PlaneIssueRef>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** One event on a finding's timeline: when, and what the watch saw — words
|
|
29
|
+
* the bot recorded, never a model's. */
|
|
30
|
+
export interface PlaneFindingEvent {
|
|
31
|
+
at: number;
|
|
32
|
+
what: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A finding: the watch, its subject (the run, pull request or instance the
|
|
36
|
+
* watch fired about) and the timeline. Deduplicated by watch and subject —
|
|
37
|
+
* `planeFindingKey` — so the same incident observed twice is one finding. */
|
|
38
|
+
export interface PlaneFinding {
|
|
39
|
+
watch: string;
|
|
40
|
+
subject: string;
|
|
41
|
+
timeline: PlaneFindingEvent[];
|
|
42
|
+
firstAt: number;
|
|
43
|
+
lastAt: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function planeFindingKey(f: Pick<PlaneFinding, "watch" | "subject">): string {
|
|
47
|
+
return `${f.watch}#${f.subject}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A timeline is bounded: a watch that fires forever grows one finding, not
|
|
51
|
+
* unbounded storage — the newest events are kept. */
|
|
52
|
+
export const PLANE_FINDING_TIMELINE_CAP = 50;
|
|
53
|
+
|
|
54
|
+
/** Fold one observation into the set: a finding already standing for the same
|
|
55
|
+
* watch and subject absorbs the timeline (two findings on one subject are
|
|
56
|
+
* one); a new pair appends. Pure — the object commits the returned set. */
|
|
57
|
+
export function mergePlaneFindings(existing: readonly PlaneFinding[], incoming: PlaneFinding): PlaneFinding[] {
|
|
58
|
+
const key = planeFindingKey(incoming);
|
|
59
|
+
const standing = existing.find((f) => planeFindingKey(f) === key);
|
|
60
|
+
if (!standing) return [...existing, { ...incoming, timeline: incoming.timeline.slice(-PLANE_FINDING_TIMELINE_CAP) }];
|
|
61
|
+
const timeline = [...standing.timeline, ...incoming.timeline]
|
|
62
|
+
.sort((a, b) => a.at - b.at)
|
|
63
|
+
.slice(-PLANE_FINDING_TIMELINE_CAP);
|
|
64
|
+
const merged: PlaneFinding = {
|
|
65
|
+
...standing,
|
|
66
|
+
timeline,
|
|
67
|
+
firstAt: Math.min(standing.firstAt, incoming.firstAt),
|
|
68
|
+
lastAt: Math.max(standing.lastAt, incoming.lastAt),
|
|
69
|
+
};
|
|
70
|
+
return existing.map((f) => (planeFindingKey(f) === key ? merged : f));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The request text for the research-tier run the bot dispatches over a
|
|
74
|
+
* finding (record 0064): the watch and subject are the bot's own words; the
|
|
75
|
+
* timeline — recorded run output, GitHub words, whatever the watch saw — is
|
|
76
|
+
* data inside the untrusted fence, never instructions. */
|
|
77
|
+
export function findingResearchRequest(finding: PlaneFinding): string {
|
|
78
|
+
const timeline = finding.timeline.map((e) => `${new Date(e.at).toISOString()} — ${e.what}`).join("\n");
|
|
79
|
+
return [
|
|
80
|
+
`The orchestration plane's \`${finding.watch}\` watch fired for \`${finding.subject}\` and no mechanical move applied.`,
|
|
81
|
+
`Investigate the timeline below and write up what happened, why the watch fired, and what change would prevent it — a report to file as an issue, nothing executed.`,
|
|
82
|
+
wrapUntrusted(timeline),
|
|
83
|
+
].join("\n\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The issue title a finding files under — also the dedupe key on the tracker:
|
|
87
|
+
* an open issue with this exact title is the same finding, not filed twice. */
|
|
88
|
+
export function planeFindingIssueTitle(finding: Pick<PlaneFinding, "watch" | "subject">): string {
|
|
89
|
+
return `plane finding: ${finding.watch} — ${finding.subject}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const PLANE_FINDING_LABEL = "switchboard-plane";
|
|
93
|
+
|
|
94
|
+
export type FiledPlaneFinding = { kind: "filed"; issue: PlaneIssueRef } | { kind: "duplicate"; issue: PlaneIssueRef };
|
|
95
|
+
|
|
96
|
+
/** File one finding's write-up through the path `friction propose` uses — the
|
|
97
|
+
* `IssueTracker` seam over the repository's open issues: an open issue with
|
|
98
|
+
* the finding's title is a duplicate (the tracker-side half of the dedupe),
|
|
99
|
+
* else the report is created under the plane's label. The body is the
|
|
100
|
+
* research run's answer with the timeline appended as data. */
|
|
101
|
+
export async function filePlaneFinding(
|
|
102
|
+
finding: PlaneFinding,
|
|
103
|
+
report: string,
|
|
104
|
+
opts: { tracker: PlaneIssueTracker; repo: string; label?: string },
|
|
105
|
+
): Promise<FiledPlaneFinding> {
|
|
106
|
+
const label = opts.label ?? PLANE_FINDING_LABEL;
|
|
107
|
+
const title = planeFindingIssueTitle(finding);
|
|
108
|
+
const open = await opts.tracker.listOpen(opts.repo, label);
|
|
109
|
+
const existing = open.find((i) => i.title === title);
|
|
110
|
+
if (existing) return { kind: "duplicate", issue: existing };
|
|
111
|
+
const timeline = finding.timeline.map((e) => `- ${new Date(e.at).toISOString()} — ${e.what}`).join("\n");
|
|
112
|
+
return {
|
|
113
|
+
kind: "filed",
|
|
114
|
+
issue: await opts.tracker.create(opts.repo, {
|
|
115
|
+
title,
|
|
116
|
+
body: `${report}\n\n## Timeline\n\n${timeline}`,
|
|
117
|
+
labels: [label],
|
|
118
|
+
}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -93,6 +93,11 @@ export type RunNoteKind =
|
|
|
93
93
|
* note says that loop's settlement instead: the executor waited for the
|
|
94
94
|
* wake and the run went on. */
|
|
95
95
|
| "sandbox_restarted"
|
|
96
|
+
/** The run was admitted onto a drained fleet (docs/reference/specs/resident-repos.md
|
|
97
|
+
* item 69) and waited at its attach for the deploy to finish: the summary
|
|
98
|
+
* names the wait. Published by the dispatcher after the attach, so `runs
|
|
99
|
+
* friction` reads the drain as the wait's category instead of "none". */
|
|
100
|
+
| "drain_wait"
|
|
96
101
|
| "stop_requested"
|
|
97
102
|
| "stopped"
|
|
98
103
|
/** A stop the loop or turn had to ask pi for again reached it: the series a
|
|
@@ -123,6 +128,13 @@ export type RunNoteKind =
|
|
|
123
128
|
* (docs/reference/specs/run-history.md item 37); the summary says how many calls were
|
|
124
129
|
* in flight at the kill and how each was settled. Published by the runner. */
|
|
125
130
|
| "resumed"
|
|
131
|
+
/** The restart the run's `restarting` close promised never claimed the run
|
|
132
|
+
* (issue 2081): its dispatch died between the close and the successor's
|
|
133
|
+
* claim, so this interrupted close is the run's end, not a restart. The
|
|
134
|
+
* summary names how the dispatch ended. Published by the dispatcher over
|
|
135
|
+
* the closed record — a kind of its own, so the interruption's cause is
|
|
136
|
+
* still read off the roll's earlier words, never off the dispatch error. */
|
|
137
|
+
| "restart_died"
|
|
126
138
|
/** A control was decided against the model card and is not native (record
|
|
127
139
|
* 0052): a fallback (`applied` differs from `asked`, `vouched` true) or
|
|
128
140
|
* an unvouched send (`vouched` false). One note per degraded control,
|
|
@@ -317,6 +329,7 @@ export const RUN_NOTE_KINDS = [
|
|
|
317
329
|
"sandbox_dead",
|
|
318
330
|
"fleet_busy",
|
|
319
331
|
"sandbox_restarted",
|
|
332
|
+
"drain_wait",
|
|
320
333
|
"stop_requested",
|
|
321
334
|
"stopped",
|
|
322
335
|
"stop_landed",
|
|
@@ -326,6 +339,7 @@ export const RUN_NOTE_KINDS = [
|
|
|
326
339
|
"mcp_unavailable",
|
|
327
340
|
"follow_up",
|
|
328
341
|
"resumed",
|
|
342
|
+
"restart_died",
|
|
329
343
|
"control_degraded",
|
|
330
344
|
"seed",
|
|
331
345
|
"redispatch",
|
|
@@ -430,6 +444,11 @@ export type ShipRoundOutcome =
|
|
|
430
444
|
| "dequeued"
|
|
431
445
|
| "aborted"
|
|
432
446
|
| "stopped"
|
|
447
|
+
/** The round-0 coding child concluded its round with an answer instead of a
|
|
448
|
+
* pull request (issue 2086): its handoff records a deviation — a stop
|
|
449
|
+
* condition, a blocked precondition — so the unit ends held for the
|
|
450
|
+
* person's word and no renewal is judged. */
|
|
451
|
+
| "held"
|
|
433
452
|
/** The coding round ended at its lease with the unit unfinished and the row
|
|
434
453
|
* showing progress, and the grant renewed: the next segment opens (decision 0046). */
|
|
435
454
|
| "continued"
|
|
@@ -537,15 +556,16 @@ export type RouteInputObject3 = { readonly [key: string]: RouteInputLeafOrList |
|
|
|
537
556
|
export type RouteInputValue = RouteInputLeafOrList | RouteInputObject3;
|
|
538
557
|
|
|
539
558
|
/** How a door decision about a state change ended (docs/decisions/0044-a-routed-write-is-confirmed-in-proportion-to-its-blast-radius.md):
|
|
540
|
-
* `hand_back` — the
|
|
541
|
-
*
|
|
559
|
+
* `hand_back` — the door held a state-changing command and ran nothing: a
|
|
560
|
+
* typed surface's refusal naming the typed form, or a chat surface's refusal
|
|
561
|
+
* naming why the click could not mint; `offered` — the same decision on a
|
|
542
562
|
* channel that can show a confirmation: the line, its risk and a button, a
|
|
543
563
|
* row stored in the config object, nothing invoked; `confirmed` — the stored
|
|
544
|
-
* input ran at the click, as the requester, through the typed line's path
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
export type RouteOutcome = "hand_back" | "offered" | "confirmed" | "
|
|
564
|
+
* input ran at the click, as the requester, through the typed line's path.
|
|
565
|
+
* A routed read carries no outcome: it is not a decision about a state
|
|
566
|
+
* change. (A record from before the hand-back retired from chat may carry
|
|
567
|
+
* `pasted`; nothing writes or reads it now.) */
|
|
568
|
+
export type RouteOutcome = "hand_back" | "offered" | "confirmed" | "refused";
|
|
549
569
|
|
|
550
570
|
export type RunEvent =
|
|
551
571
|
/** `callId` is the provider's tool_use id — the explicit pair key between a
|
|
@@ -895,7 +915,17 @@ export type RunEvent =
|
|
|
895
915
|
* (`by: "push"`), or the budget-end salvage pushed (`by: "salvage"`),
|
|
896
916
|
* whether or not a pull request follows — the fact renewal reads. Published
|
|
897
917
|
* straight to the registry like `pr_opened`. Additive: unknown → ignored. */
|
|
898
|
-
| {
|
|
918
|
+
| {
|
|
919
|
+
type: "pushed_head";
|
|
920
|
+
ref: string;
|
|
921
|
+
sha: string;
|
|
922
|
+
by: "push" | "salvage";
|
|
923
|
+
/** No uncommitted or unpushed work at the push (record 0064): the fact
|
|
924
|
+
* the plane's soft stop reads. Absent where the measure was missing. */
|
|
925
|
+
clean?: boolean;
|
|
926
|
+
seq?: number;
|
|
927
|
+
at?: number;
|
|
928
|
+
}
|
|
899
929
|
/** The coordinator tag as a fact of the run (docs/reference/specs/run-history.md
|
|
900
930
|
* item 48a): the instance the run is a child of, the unit its idempotency
|
|
901
931
|
* key named, and the base branch its pull request targets — published by
|
|
@@ -1014,11 +1044,10 @@ export type RunEvent =
|
|
|
1014
1044
|
* how the invoke ended is the run's own status and `answer`. A door
|
|
1015
1045
|
* decision about a state change (record 0044) is a command run too, with
|
|
1016
1046
|
* `outcome` saying which: a hand-back invoked nothing and its `answer` is
|
|
1017
|
-
* the
|
|
1018
|
-
*
|
|
1019
|
-
*
|
|
1020
|
-
* the
|
|
1021
|
-
* `handBackRunId` naming the hand-back's record. */
|
|
1047
|
+
* the refusal the surface was shown (the typed form on a typed surface);
|
|
1048
|
+
* an offer invoked nothing and its `answer` is the offer as the channel
|
|
1049
|
+
* shows it; a confirmation is the stored input run at the click, its
|
|
1050
|
+
* `answer` the command's own text as a routed read's is. */
|
|
1022
1051
|
| {
|
|
1023
1052
|
type: "route";
|
|
1024
1053
|
preset: string;
|
|
@@ -1038,7 +1067,6 @@ export type RunEvent =
|
|
|
1038
1067
|
* (record 0054): a refusal after a command was bound is a run
|
|
1039
1068
|
* record, and the door report counts it by cause and code. */
|
|
1040
1069
|
refusalCode?: string;
|
|
1041
|
-
handBackRunId?: string;
|
|
1042
1070
|
seq?: number;
|
|
1043
1071
|
at?: number;
|
|
1044
1072
|
}
|
|
@@ -1060,6 +1088,11 @@ export type RunEvent =
|
|
|
1060
1088
|
mode: "shadow" | "on";
|
|
1061
1089
|
outcome: "binds" | "question" | "refusal" | "non_decision";
|
|
1062
1090
|
reason: string;
|
|
1091
|
+
/** The decision was the loop's floor (record 0069, as amended): a turn
|
|
1092
|
+
* that ended with no tool call, or the bounded re-asks ran out — the
|
|
1093
|
+
* readers' route ran the person's own request, and the event never
|
|
1094
|
+
* re-enters the loop. Additive: unknown → ignored. */
|
|
1095
|
+
floored?: true;
|
|
1063
1096
|
/** A bind marked `confirmed` is a pending question's confirmed proposal
|
|
1064
1097
|
* (`bindFromAnswer`): the line itself carries the task — the person's
|
|
1065
1098
|
* message was the word "yes" — so a confirmed preset line routes its
|
|
@@ -1069,6 +1102,13 @@ export type RunEvent =
|
|
|
1069
1102
|
/** A question's proposed line, redacted and cut like the receipt — what
|
|
1070
1103
|
* the next turn's "yes" binds (`bindFromAnswer`). */
|
|
1071
1104
|
proposal?: string;
|
|
1105
|
+
/** A question's original ask, redacted and capped: the request the
|
|
1106
|
+
* question interrupted, kept so the person's next words in the thread
|
|
1107
|
+
* join back onto it (`joinedAnswerRequest` —
|
|
1108
|
+
* `<request> — <question>: <answer>`) and bind as the request would
|
|
1109
|
+
* have been, never routed as a bare fragment. Set on `question`
|
|
1110
|
+
* outcomes alone. Additive: unknown → ignored. */
|
|
1111
|
+
request?: string;
|
|
1072
1112
|
refusalCause?: string;
|
|
1073
1113
|
refusalText?: string;
|
|
1074
1114
|
attempts?: ReadonlyArray<{ outcome: "accepted" | "violation"; violation?: string }>;
|
|
@@ -39,6 +39,7 @@ export type FrictionCategory =
|
|
|
39
39
|
| "wrap_up"
|
|
40
40
|
| "budget_hit"
|
|
41
41
|
| "infra_failure"
|
|
42
|
+
| "drain_wait"
|
|
42
43
|
| "unkept_promise";
|
|
43
44
|
|
|
44
45
|
export const FRICTION_CATEGORIES: readonly FrictionCategory[] = [
|
|
@@ -50,6 +51,7 @@ export const FRICTION_CATEGORIES: readonly FrictionCategory[] = [
|
|
|
50
51
|
"wrap_up",
|
|
51
52
|
"budget_hit",
|
|
52
53
|
"infra_failure",
|
|
54
|
+
"drain_wait",
|
|
53
55
|
"unkept_promise",
|
|
54
56
|
];
|
|
55
57
|
|
|
@@ -63,6 +65,7 @@ export const CATEGORY_LABEL: Record<FrictionCategory, string> = {
|
|
|
63
65
|
wrap_up: "agent wind-down",
|
|
64
66
|
budget_hit: "budget hits",
|
|
65
67
|
infra_failure: "infra failures",
|
|
68
|
+
drain_wait: "the fleet drain",
|
|
66
69
|
unkept_promise: "unkept promises",
|
|
67
70
|
};
|
|
68
71
|
|
|
@@ -78,6 +81,7 @@ export const DENOMINATOR_OF: Record<FrictionCategory, "tool" | "run"> = {
|
|
|
78
81
|
wrap_up: "run",
|
|
79
82
|
budget_hit: "run",
|
|
80
83
|
infra_failure: "run",
|
|
84
|
+
drain_wait: "run",
|
|
81
85
|
unkept_promise: "run",
|
|
82
86
|
};
|
|
83
87
|
|
|
@@ -574,6 +578,18 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
574
578
|
eventIndex: index,
|
|
575
579
|
});
|
|
576
580
|
return;
|
|
581
|
+
case "drain_wait":
|
|
582
|
+
// The run was admitted onto a drained fleet (resident-repos item 69;
|
|
583
|
+
// issue 2044): the wait is the deploy's, its own category — a verdict
|
|
584
|
+
// of "none" over a run that sat half an hour at the drain was the
|
|
585
|
+
// incident's shape. No extent: the wait precedes the loop's spans.
|
|
586
|
+
findings.push({
|
|
587
|
+
category: "drain_wait",
|
|
588
|
+
severity: "medium",
|
|
589
|
+
summary: `fleet drained: ${ev.summary}`,
|
|
590
|
+
eventIndex: index,
|
|
591
|
+
});
|
|
592
|
+
return;
|
|
577
593
|
case "sandbox_restarted":
|
|
578
594
|
// The container rolled under the run: on pi the run ends here and its
|
|
579
595
|
// request starts over (harness-pi item 16); on the deleted native loop
|
|
@@ -79,6 +79,12 @@ export interface LiveRunMeta {
|
|
|
79
79
|
/** The run that spawned this one (item 46), so a reclaimed child's record
|
|
80
80
|
* still names its parent. Absent on every run a person or a schedule started. */
|
|
81
81
|
parentRunId?: string;
|
|
82
|
+
/** The run this claim restarts (record 0064; run-history item 54): set by
|
|
83
|
+
* the restart-from-request dispatch, so the plane knows the claim continues
|
|
84
|
+
* a run it saw end `restarting` — the object sends the waiting parent
|
|
85
|
+
* `child-resumed-<runId>` from it, and a `restartOf` ask passes the windows
|
|
86
|
+
* and the memory line. Absent on every fresh request. */
|
|
87
|
+
restartOf?: string;
|
|
82
88
|
/** The coordinator instance this run is a child of, and the key its spawn
|
|
83
89
|
* carried (item 48) — stored at the claim, so a reclaimed child's record
|
|
84
90
|
* still sends the parent its event and a retried spawn finds its run. */
|
|
@@ -150,10 +156,13 @@ export interface LiveRunRow {
|
|
|
150
156
|
state: RunState;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
|
-
/** One tool call the step dispatched; `tool` decides how a resume settles it.
|
|
159
|
+
/** One tool call the step dispatched; `tool` decides how a resume settles it.
|
|
160
|
+
* `boundMs` is the bound the call declared (a bash `timeout`), when it stated
|
|
161
|
+
* one — what the plane judges a `long_call` steer against (record 0064). */
|
|
154
162
|
export interface InFlightCall {
|
|
155
163
|
callId: string;
|
|
156
164
|
tool: string;
|
|
165
|
+
boundMs?: number;
|
|
157
166
|
}
|
|
158
167
|
|
|
159
168
|
/** Written BEFORE a step's tools run (after its transcript turns landed). */
|
|
@@ -286,7 +295,7 @@ export type AppendableEvent = RunEvent & { seq: number };
|
|
|
286
295
|
export interface IntakeReceipt {
|
|
287
296
|
verdict: "addressed" | "silent";
|
|
288
297
|
reason: string;
|
|
289
|
-
source: "model" | "mode" | "error" | "timeout";
|
|
298
|
+
source: "model" | "mode" | "question" | "error" | "timeout";
|
|
290
299
|
/** The structured seam's attempts (docs/decisions/0067): what each answer
|
|
291
300
|
* violated, or that it was accepted; absent when no model was asked. */
|
|
292
301
|
attempts?: ReadonlyArray<{ outcome: "accepted" | "violation"; violation?: string }>;
|
|
@@ -322,7 +331,11 @@ export function isIntakeReceipt(v: unknown): v is IntakeReceipt {
|
|
|
322
331
|
return (
|
|
323
332
|
(r.verdict === "addressed" || r.verdict === "silent") &&
|
|
324
333
|
typeof r.reason === "string" &&
|
|
325
|
-
(r.source === "model" ||
|
|
334
|
+
(r.source === "model" ||
|
|
335
|
+
r.source === "mode" ||
|
|
336
|
+
r.source === "question" ||
|
|
337
|
+
r.source === "error" ||
|
|
338
|
+
r.source === "timeout") &&
|
|
326
339
|
(r.mode === "mention" || r.mode === "classify") &&
|
|
327
340
|
typeof r.model === "string" &&
|
|
328
341
|
typeof r.gen === "number" &&
|
|
@@ -225,6 +225,12 @@ export interface RunRecord {
|
|
|
225
225
|
* carried onto the record at the seal, so a history reader draws the run as
|
|
226
226
|
* a pipeline. Absent on every other run and on older records. */
|
|
227
227
|
hosted?: true;
|
|
228
|
+
/** The reattach path restarted this run from its request when its workspace
|
|
229
|
+
* could not be re-attached (record 0064; item 47a): the `interrupted` close
|
|
230
|
+
* is not the run's end — the same id carries on — so a waiting parent keeps
|
|
231
|
+
* waiting for `child_resumed` instead of ending its unit on this record.
|
|
232
|
+
* Absent on every ending that is final. */
|
|
233
|
+
restarting?: true;
|
|
228
234
|
/** Where the run's conversation started (item 52): `channel` — its own
|
|
229
235
|
* thread's history, as for every run a person, a schedule or a coordinator
|
|
230
236
|
* started — or `parent` — a spawned child seeded from its parent's text
|
|
@@ -432,6 +438,9 @@ export interface RunOperatorDecision {
|
|
|
432
438
|
question?: string;
|
|
433
439
|
/** A question's proposed line — what the next turn's "yes" binds. */
|
|
434
440
|
proposal?: string;
|
|
441
|
+
/** A question's original ask — what the person's next words join back onto
|
|
442
|
+
* (`joinedAnswerRequest`). */
|
|
443
|
+
request?: string;
|
|
435
444
|
refusalCause?: string;
|
|
436
445
|
refusalText?: string;
|
|
437
446
|
/** The structured seam's attempts (record 0067): what each answer violated,
|
|
@@ -456,6 +465,7 @@ export function operatorOfEvents(events: readonly RunEvent[]): RunOperatorDecisi
|
|
|
456
465
|
...(e.binds ? { binds: e.binds.map((b) => ({ line: b.line, reason: b.reason })) } : {}),
|
|
457
466
|
...(e.question !== undefined ? { question: e.question } : {}),
|
|
458
467
|
...(e.proposal !== undefined ? { proposal: e.proposal } : {}),
|
|
468
|
+
...(e.request !== undefined ? { request: e.request } : {}),
|
|
459
469
|
...(e.refusalCause !== undefined ? { refusalCause: e.refusalCause } : {}),
|
|
460
470
|
...(e.refusalText !== undefined ? { refusalText: e.refusalText } : {}),
|
|
461
471
|
...(e.attempts
|
|
@@ -1017,6 +1027,8 @@ export function isRunRecord(v: unknown): v is RunRecord {
|
|
|
1017
1027
|
// marker, like `provisional`, is the literal `true` or absent.
|
|
1018
1028
|
if (r.pipeline !== undefined && !isPipelineSummaryShape(r.pipeline)) return false;
|
|
1019
1029
|
if (r.hosted !== undefined && r.hosted !== true) return false;
|
|
1030
|
+
// A restarting close carries the literal `true` or nothing (record 0064).
|
|
1031
|
+
if (r.restarting !== undefined && r.restarting !== true) return false;
|
|
1020
1032
|
if (typeof r.channelId !== "string" || typeof r.userId !== "string" || typeof r.threadKey !== "string") return false;
|
|
1021
1033
|
if (r.relayedBy !== undefined && typeof r.relayedBy !== "string") return false;
|
|
1022
1034
|
if (r.authenticatedAs !== undefined && typeof r.authenticatedAs !== "string") return false;
|
|
@@ -461,6 +461,19 @@ export const GATE_RECEIPTS =
|
|
|
461
461
|
"Paste each command's exit line into the PR description's validation table as the row's proof; a gate you " +
|
|
462
462
|
"could not run goes under the handoff's unproven list and is never claimed clean.";
|
|
463
463
|
|
|
464
|
+
/** The rebase before every push lives in the coding preset's own instructions
|
|
465
|
+
* (`REBASE_BEFORE_PUSH`, src/agents/registry.ts — agent-coding item 13;
|
|
466
|
+
* record 0071 mechanism one): the base fetched, the branch rebased onto it,
|
|
467
|
+
* the fast gates re-run on the rebased tree, then the push — always, never
|
|
468
|
+
* configurable. The contract used to carry the conditional form ("rebase once
|
|
469
|
+
* more if it moved while you worked"); now the first instruction points at
|
|
470
|
+
* the preset's paragraph, the same shape as the fast-gates pointer above, so
|
|
471
|
+
* the child reads the rule once and cannot read a softer one here. */
|
|
472
|
+
export const REBASE_POINTER =
|
|
473
|
+
"The rebase before every push is your preset instructions' rule (REBASE BEFORE EVERY PUSH): it holds before " +
|
|
474
|
+
"each push here — always, not configurable — so every head you push is current with its base and the pull " +
|
|
475
|
+
"request is never born conflicting.";
|
|
476
|
+
|
|
464
477
|
function renderFirstInstruction(rebase: ChildContract["rebase"]): string {
|
|
465
478
|
const branch = rebase.branch ? `\`${rebase.branch}\`` : "the unit's branch";
|
|
466
479
|
const onto = rebase.onto ? `\`${rebase.onto}\`` : "the merged parent";
|
|
@@ -470,8 +483,7 @@ function renderFirstInstruction(rebase: ChildContract["rebase"]): string {
|
|
|
470
483
|
`the only writes are your own on that branch. A conflict ends the unit: report it as the handoff and stop. ` +
|
|
471
484
|
`Push the branch as soon as the change exists and the fast gates pass — the project's full verification ` +
|
|
472
485
|
`is CI's gate, run there after the push with any fix as a further commit; an unpushed tree does not ` +
|
|
473
|
-
`survive the run's end. ${gates}
|
|
474
|
-
`once more if it moved while you worked, so the pull request is not born conflicting. At the wind-down note, ` +
|
|
486
|
+
`survive the run's end. ${gates} ${REBASE_POINTER} At the wind-down note, ` +
|
|
475
487
|
`commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`
|
|
476
488
|
);
|
|
477
489
|
}
|