toolroll 0.9.91 → 0.9.93
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/CHANGELOG.md +41 -0
- package/dist/approval-authority.d.ts +9 -1
- package/dist/approval-authority.js +15 -2
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +2 -2
- package/dist/assignment.js +11 -3
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +30 -30
- package/dist/browser-crew.js +2 -2
- package/dist/browser-workspace.d.ts +3 -5
- package/dist/build-review.js +2 -0
- package/dist/builder.js +21 -2
- package/dist/chat-actions.d.ts +11 -3
- package/dist/chat-actions.js +47 -2
- package/dist/chat-decide.js +2 -0
- package/dist/chat-review.d.ts +2 -1
- package/dist/chat-review.js +2 -2
- package/dist/check-facts.d.ts +124 -0
- package/dist/check-facts.js +272 -0
- package/dist/check-report.d.ts +5 -0
- package/dist/check-report.js +4 -3
- package/dist/cli.js +5 -0
- package/dist/contracts/chat-actions.d.ts +10 -0
- package/dist/contracts/chat-actions.js +4 -0
- package/dist/contracts/console-api.d.ts +3 -2
- package/dist/contracts/console-api.js +5 -2
- package/dist/contracts/release-check.d.ts +25 -1
- package/dist/contracts/release-check.js +19 -0
- package/dist/contracts/route-results.d.ts +121 -0
- package/dist/contracts/route-results.js +71 -0
- package/dist/contracts/route.d.ts +343 -5
- package/dist/contracts/route.js +36 -7
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/desktop-update.d.ts +2 -2
- package/dist/desktop-update.js +13 -9
- package/dist/dispose.js +6 -0
- package/dist/eval-hooks.d.ts +55 -0
- package/dist/eval-hooks.js +227 -4
- package/dist/flaky.d.ts +17 -0
- package/dist/flaky.js +9 -0
- package/dist/flow-engine.js +1 -1
- package/dist/flow-pull-request.js +1 -1
- package/dist/lead-doors.js +4 -2
- package/dist/lead-tools.js +10 -2
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +11 -0
- package/dist/memory-cli.d.ts +3 -0
- package/dist/memory-cli.js +3 -3
- package/dist/memory-pass.js +5 -1
- package/dist/model-choices.d.ts +7 -3
- package/dist/model-choices.js +26 -11
- package/dist/models-cli.d.ts +205 -0
- package/dist/models-cli.js +143 -4
- package/dist/models-ui.d.ts +57 -0
- package/dist/models-ui.js +44 -1
- package/dist/needs-you.d.ts +7 -7
- package/dist/needs-you.js +12 -10
- package/dist/operate-remote-arguments.js +4 -4
- package/dist/operate-remote.js +1 -1
- package/dist/operate.js +191 -23
- package/dist/phase-routing.d.ts +82 -2
- package/dist/phase-routing.js +132 -18
- package/dist/project-delete.d.ts +4 -0
- package/dist/project-delete.js +16 -4
- package/dist/project-memory.d.ts +8 -0
- package/dist/project-memory.js +20 -1
- package/dist/pull-request-flow.js +1 -1
- package/dist/push.d.ts +10 -3
- package/dist/push.js +22 -6
- package/dist/release-adapters.js +11 -1
- package/dist/release-approval.d.ts +191 -0
- package/dist/release-approval.js +382 -0
- package/dist/release-auto.js +22 -4
- package/dist/release-card.d.ts +73 -4
- package/dist/release-card.js +168 -23
- package/dist/release-cli.js +9 -1
- package/dist/release.d.ts +30 -0
- package/dist/release.js +42 -3
- package/dist/result-actions.d.ts +4 -0
- package/dist/result-actions.js +3 -0
- package/dist/result-acts.d.ts +3 -3
- package/dist/result-acts.js +1 -1
- package/dist/route-hooks.d.ts +54 -0
- package/dist/route-hooks.js +179 -0
- package/dist/route-results.d.ts +70 -0
- package/dist/route-results.js +340 -0
- package/dist/route-rules.d.ts +324 -0
- package/dist/route-rules.js +781 -0
- package/dist/route-savings.d.ts +136 -0
- package/dist/route-savings.js +193 -0
- package/dist/route-store.d.ts +300 -0
- package/dist/route-store.js +598 -0
- package/dist/scope.d.ts +3 -1
- package/dist/scope.js +4 -1
- package/dist/server/guards.js +1 -0
- package/dist/server/pages-assets.js +1 -1
- package/dist/server/render-chat.js +2 -2
- package/dist/server/render-release.d.ts +17 -0
- package/dist/server/render-release.js +84 -0
- package/dist/server/render-results.d.ts +0 -2
- package/dist/server/render-results.js +4 -6
- package/dist/server/render-tasks.d.ts +7 -0
- package/dist/server/render-tasks.js +16 -3
- package/dist/server/route-table.d.ts +1 -1
- package/dist/server/route-table.js +19 -1
- package/dist/server/settings.d.ts +2 -1
- package/dist/server/settings.js +87 -1
- package/dist/server/task-views.js +6 -0
- package/dist/server/tasks.d.ts +15 -0
- package/dist/server/tasks.js +105 -20
- package/dist/settings-proofs.js +2 -0
- package/dist/spend-ui.d.ts +11 -0
- package/dist/spend-ui.js +49 -2
- package/dist/stop-proof.d.ts +67 -0
- package/dist/stop-proof.js +293 -0
- package/dist/store.d.ts +106 -31
- package/dist/store.js +653 -128
- package/dist/surface.js +17 -5
- package/dist/task-outcome-cli.js +24 -2
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +4 -2
- package/dist/work-index.js +7 -9
- package/dist/work-summary.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result records (docs/plans/results-routing.md, slice 1): the words and shapes a task family's result record is kept
|
|
3
|
+
* and shown in. A policy path (release-auto.ts EVAL_POLICY): the states, reasons and the execution-class table decide
|
|
4
|
+
* what a cheaper model's evidence is, so a change to them needs a person.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
/** A record's lifecycle. Only mature and rejected records are decided; pending and provisional never count anywhere. */
|
|
8
|
+
export const RECORD_STATES = ["pending", "provisional", "mature", "rejected", "not-counted"];
|
|
9
|
+
/** Why a record was rejected, in the order they are checked. */
|
|
10
|
+
export const REJECT_REASONS = ["model-failures", "task-failed", "cancelled-after-result", "high-finding", "person-revision", "proof-refuted", "check-failed"];
|
|
11
|
+
/** Why a record is not counted. Never counted as good, never as 0. */
|
|
12
|
+
export const NOT_COUNTED_REASONS = [
|
|
13
|
+
"before-records", "not-eligible", "mixed-cohort", "mixed-models", "model-not-reported", "infrastructure-only", "cancelled-before-result",
|
|
14
|
+
"no-check", "check-unavailable", "touched-protected", "changed-files-unknown", "unanswered",
|
|
15
|
+
];
|
|
16
|
+
/** The plain words for each reason. */
|
|
17
|
+
export const REASON_WORDS = Object.freeze({
|
|
18
|
+
"model-failures": "two or more attempts failed on the model's own account",
|
|
19
|
+
"task-failed": "the task failed",
|
|
20
|
+
"cancelled-after-result": "it was cancelled after a result",
|
|
21
|
+
"high-finding": "the automatic review found a serious problem",
|
|
22
|
+
"person-revision": "a person asked for changes",
|
|
23
|
+
"proof-refuted": "its evidence didn't match the changes",
|
|
24
|
+
"check-failed": "its checks failed",
|
|
25
|
+
"before-records": "started before results were recorded",
|
|
26
|
+
"not-eligible": "not a small or routine task with default checks",
|
|
27
|
+
"mixed-cohort": "its attempts ran under different task sizes or settings",
|
|
28
|
+
"mixed-models": "its attempts ran on different models",
|
|
29
|
+
"model-not-reported": "the provider didn't report the model",
|
|
30
|
+
"infrastructure-only": "it failed only for reasons outside the model",
|
|
31
|
+
"cancelled-before-result": "it was cancelled before a result",
|
|
32
|
+
"no-check": "no checks configured",
|
|
33
|
+
"check-unavailable": "its check result can't be read",
|
|
34
|
+
"touched-protected": "touched protected files",
|
|
35
|
+
"changed-files-unknown": "changed files unknown",
|
|
36
|
+
"unanswered": "no answer in 30 days",
|
|
37
|
+
});
|
|
38
|
+
/** Why a run was refused a cohort before dispatch: any of these makes its record not counted ("not eligible"). */
|
|
39
|
+
export const INELIGIBLE_REASONS = ["no-sealed-route", "not-built-in", "large", "risky", "unsized", "strict", "evidence-demands", "automerge", "release", "protected"];
|
|
40
|
+
/** Who a revision came from (task_ref.revision_origin). Only `person` rejects a record. */
|
|
41
|
+
export const REVISION_ORIGINS = ["person", "automatic-review", "flow", "lead", "machine"];
|
|
42
|
+
/** A failed run's execution class, kept before the tick normalizes its reason (run.exec_class). */
|
|
43
|
+
export const EXEC_CLASSES = ["model", "infrastructure"];
|
|
44
|
+
/** The build leg's sealed selection, as a run's cohort keeps it (route v3 `selection`; earlier routes by `chosen`). */
|
|
45
|
+
export const selectionSchema = z.discriminatedUnion("kind", [
|
|
46
|
+
z.strictObject({ kind: z.literal("rule"), rule: z.number().int().positive(), ruleGeneration: z.number().int().positive() }),
|
|
47
|
+
z.strictObject({ kind: z.literal("trial"), rule: z.number().int().positive(), reservation: z.number().int().positive() }),
|
|
48
|
+
z.strictObject({ kind: z.enum(["configured", "override", "pin"]) }),
|
|
49
|
+
]);
|
|
50
|
+
/** One record as `toolroll models results --json` returns it. */
|
|
51
|
+
export const resultRecordSchema = z.strictObject({
|
|
52
|
+
task: z.string(),
|
|
53
|
+
repo: z.string().nullable(),
|
|
54
|
+
size: z.enum(["small", "medium"]).nullable(),
|
|
55
|
+
provider: z.string().nullable(),
|
|
56
|
+
model: z.string().nullable(),
|
|
57
|
+
state: z.enum(RECORD_STATES),
|
|
58
|
+
reason: z.enum([...REJECT_REASONS, ...NOT_COUNTED_REASONS]).nullable(),
|
|
59
|
+
words: z.string(),
|
|
60
|
+
resultRun: z.number().int().nullable(),
|
|
61
|
+
acceptedAt: z.string().nullable(),
|
|
62
|
+
decidedAt: z.string().nullable(),
|
|
63
|
+
failures: z.strictObject({ model: z.number().int(), infrastructure: z.number().int() }),
|
|
64
|
+
spend: z.strictObject({ apiMicrousd: z.number().int(), subscriptionRuns: z.number().int(), subscriptionTokens: z.number().int(), unknownRuns: z.number().int(), remediationRuns: z.number().int() }),
|
|
65
|
+
/** Counts that keep accumulating after the verdict is final, until the record closes. */
|
|
66
|
+
personRevisions: z.number().int(),
|
|
67
|
+
firstCheckPassed: z.boolean().nullable(),
|
|
68
|
+
turns: z.strictObject({ known: z.number().int(), unknownRuns: z.number().int() }),
|
|
69
|
+
closed: z.boolean(),
|
|
70
|
+
selections: z.array(selectionSchema),
|
|
71
|
+
});
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { type ContractResult } from "./contract.js";
|
|
15
|
+
import { type Selection } from "./route-results.js";
|
|
15
16
|
/** The phases a route has a leg for, in the order its legs run. */
|
|
16
17
|
export declare const ROUTE_PHASES: readonly ["plan", "build", "repair", "review"];
|
|
17
18
|
/** provider.ts's provider ids, restated (provider.ts sits above phase-routing.ts in the module graph) and pinned to
|
|
@@ -22,8 +23,15 @@ export declare const ROUTE_PROVIDERS: readonly ["claude", "codex", "openrouter",
|
|
|
22
23
|
export declare const MODEL_ID_SHAPE: RegExp;
|
|
23
24
|
export declare const RISKS: readonly ["routine", "elevated", "high"];
|
|
24
25
|
export declare const TASK_SIZE_WORDS: readonly ["small", "medium", "large"];
|
|
25
|
-
/** The route version this code seals
|
|
26
|
-
|
|
26
|
+
/** The newest route version this code seals (docs/plans/results-routing.md, slice 2): version 3 adds the build leg's
|
|
27
|
+
* sealed `selection`, and is sealed only for a build leg that carries one (an earned rule's). Every other route still
|
|
28
|
+
* seals as version 2 (PLAIN_ROUTE_VERSION), so its bytes and digest are what they always were; version 1 (before
|
|
29
|
+
* sizing) still reads. */
|
|
30
|
+
export declare const ROUTE_VERSION = 3;
|
|
31
|
+
/** The version a route with no selection seals as. */
|
|
32
|
+
export declare const PLAIN_ROUTE_VERSION = 2;
|
|
33
|
+
/** The newest route version this code reads. */
|
|
34
|
+
export declare const READ_ROUTE_VERSION = 3;
|
|
27
35
|
/** How big a change the task is, as it was sized (by the classifier, the description, or a person). */
|
|
28
36
|
export declare const taskSizingSchema: z.ZodObject<{
|
|
29
37
|
size: z.ZodEnum<{
|
|
@@ -114,6 +122,73 @@ export declare const routeLegSchema: z.ZodObject<{
|
|
|
114
122
|
reasons: z.ZodArray<z.ZodString>;
|
|
115
123
|
problem: z.ZodNullable<z.ZodString>;
|
|
116
124
|
}, z.core.$strict>;
|
|
125
|
+
/** One leg of a version 3 route: a version 2 leg, and the build leg may say how it was selected. */
|
|
126
|
+
export declare const routeLegV3Schema: z.ZodObject<{
|
|
127
|
+
phase: z.ZodEnum<{
|
|
128
|
+
build: "build";
|
|
129
|
+
plan: "plan";
|
|
130
|
+
repair: "repair";
|
|
131
|
+
review: "review";
|
|
132
|
+
}>;
|
|
133
|
+
provider: z.ZodEnum<{
|
|
134
|
+
claude: "claude";
|
|
135
|
+
cline: "cline";
|
|
136
|
+
codex: "codex";
|
|
137
|
+
cursor: "cursor";
|
|
138
|
+
gemini: "gemini";
|
|
139
|
+
goose: "goose";
|
|
140
|
+
opencode: "opencode";
|
|
141
|
+
openrouter: "openrouter";
|
|
142
|
+
qwen: "qwen";
|
|
143
|
+
}>;
|
|
144
|
+
model: z.ZodString;
|
|
145
|
+
tier: z.ZodEnum<{
|
|
146
|
+
light: "light";
|
|
147
|
+
routine: "routine";
|
|
148
|
+
strong: "strong";
|
|
149
|
+
}>;
|
|
150
|
+
chosen: z.ZodEnum<{
|
|
151
|
+
override: "override";
|
|
152
|
+
pinned: "pinned";
|
|
153
|
+
recommended: "recommended";
|
|
154
|
+
}>;
|
|
155
|
+
recommended: z.ZodObject<{
|
|
156
|
+
provider: z.ZodEnum<{
|
|
157
|
+
claude: "claude";
|
|
158
|
+
cline: "cline";
|
|
159
|
+
codex: "codex";
|
|
160
|
+
cursor: "cursor";
|
|
161
|
+
gemini: "gemini";
|
|
162
|
+
goose: "goose";
|
|
163
|
+
opencode: "opencode";
|
|
164
|
+
openrouter: "openrouter";
|
|
165
|
+
qwen: "qwen";
|
|
166
|
+
}>;
|
|
167
|
+
model: z.ZodString;
|
|
168
|
+
tier: z.ZodEnum<{
|
|
169
|
+
light: "light";
|
|
170
|
+
routine: "routine";
|
|
171
|
+
strong: "strong";
|
|
172
|
+
}>;
|
|
173
|
+
}, z.core.$strict>;
|
|
174
|
+
reasons: z.ZodArray<z.ZodString>;
|
|
175
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
176
|
+
selection: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
177
|
+
kind: z.ZodLiteral<"rule">;
|
|
178
|
+
rule: z.ZodNumber;
|
|
179
|
+
ruleGeneration: z.ZodNumber;
|
|
180
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
181
|
+
kind: z.ZodLiteral<"trial">;
|
|
182
|
+
rule: z.ZodNumber;
|
|
183
|
+
reservation: z.ZodNumber;
|
|
184
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
185
|
+
kind: z.ZodEnum<{
|
|
186
|
+
configured: "configured";
|
|
187
|
+
override: "override";
|
|
188
|
+
pin: "pin";
|
|
189
|
+
}>;
|
|
190
|
+
}, z.core.$strict>], "kind">>;
|
|
191
|
+
}, z.core.$strict>;
|
|
117
192
|
export declare const sealedRouteV1Schema: z.ZodObject<{
|
|
118
193
|
version: z.ZodLiteral<1>;
|
|
119
194
|
risk: z.ZodEnum<{
|
|
@@ -328,7 +403,137 @@ export declare const sealedRouteV2Schema: z.ZodObject<{
|
|
|
328
403
|
reason: z.ZodString;
|
|
329
404
|
}, z.core.$strict>>;
|
|
330
405
|
}, z.core.$strict>;
|
|
331
|
-
|
|
406
|
+
export declare const sealedRouteV3Schema: z.ZodObject<{
|
|
407
|
+
version: z.ZodLiteral<3>;
|
|
408
|
+
risk: z.ZodEnum<{
|
|
409
|
+
elevated: "elevated";
|
|
410
|
+
high: "high";
|
|
411
|
+
routine: "routine";
|
|
412
|
+
}>;
|
|
413
|
+
qualityMode: z.ZodEnum<{
|
|
414
|
+
default: "default";
|
|
415
|
+
strict: "strict";
|
|
416
|
+
}>;
|
|
417
|
+
publication: z.ZodEnum<{
|
|
418
|
+
automerge: "automerge";
|
|
419
|
+
none: "none";
|
|
420
|
+
notify: "notify";
|
|
421
|
+
}>;
|
|
422
|
+
evidence: z.ZodArray<z.ZodEnum<{
|
|
423
|
+
"changed-path": "changed-path";
|
|
424
|
+
check: "check";
|
|
425
|
+
"manual-review": "manual-review";
|
|
426
|
+
screenshot: "screenshot";
|
|
427
|
+
}>>;
|
|
428
|
+
posture: z.ZodEnum<{
|
|
429
|
+
economy: "economy";
|
|
430
|
+
strong: "strong";
|
|
431
|
+
}>;
|
|
432
|
+
demands: z.ZodArray<z.ZodString>;
|
|
433
|
+
legs: z.ZodArray<z.ZodObject<{
|
|
434
|
+
phase: z.ZodEnum<{
|
|
435
|
+
build: "build";
|
|
436
|
+
plan: "plan";
|
|
437
|
+
repair: "repair";
|
|
438
|
+
review: "review";
|
|
439
|
+
}>;
|
|
440
|
+
provider: z.ZodEnum<{
|
|
441
|
+
claude: "claude";
|
|
442
|
+
cline: "cline";
|
|
443
|
+
codex: "codex";
|
|
444
|
+
cursor: "cursor";
|
|
445
|
+
gemini: "gemini";
|
|
446
|
+
goose: "goose";
|
|
447
|
+
opencode: "opencode";
|
|
448
|
+
openrouter: "openrouter";
|
|
449
|
+
qwen: "qwen";
|
|
450
|
+
}>;
|
|
451
|
+
model: z.ZodString;
|
|
452
|
+
tier: z.ZodEnum<{
|
|
453
|
+
light: "light";
|
|
454
|
+
routine: "routine";
|
|
455
|
+
strong: "strong";
|
|
456
|
+
}>;
|
|
457
|
+
chosen: z.ZodEnum<{
|
|
458
|
+
override: "override";
|
|
459
|
+
pinned: "pinned";
|
|
460
|
+
recommended: "recommended";
|
|
461
|
+
}>;
|
|
462
|
+
recommended: z.ZodObject<{
|
|
463
|
+
provider: z.ZodEnum<{
|
|
464
|
+
claude: "claude";
|
|
465
|
+
cline: "cline";
|
|
466
|
+
codex: "codex";
|
|
467
|
+
cursor: "cursor";
|
|
468
|
+
gemini: "gemini";
|
|
469
|
+
goose: "goose";
|
|
470
|
+
opencode: "opencode";
|
|
471
|
+
openrouter: "openrouter";
|
|
472
|
+
qwen: "qwen";
|
|
473
|
+
}>;
|
|
474
|
+
model: z.ZodString;
|
|
475
|
+
tier: z.ZodEnum<{
|
|
476
|
+
light: "light";
|
|
477
|
+
routine: "routine";
|
|
478
|
+
strong: "strong";
|
|
479
|
+
}>;
|
|
480
|
+
}, z.core.$strict>;
|
|
481
|
+
reasons: z.ZodArray<z.ZodString>;
|
|
482
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
483
|
+
selection: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
484
|
+
kind: z.ZodLiteral<"rule">;
|
|
485
|
+
rule: z.ZodNumber;
|
|
486
|
+
ruleGeneration: z.ZodNumber;
|
|
487
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
488
|
+
kind: z.ZodLiteral<"trial">;
|
|
489
|
+
rule: z.ZodNumber;
|
|
490
|
+
reservation: z.ZodNumber;
|
|
491
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
492
|
+
kind: z.ZodEnum<{
|
|
493
|
+
configured: "configured";
|
|
494
|
+
override: "override";
|
|
495
|
+
pin: "pin";
|
|
496
|
+
}>;
|
|
497
|
+
}, z.core.$strict>], "kind">>;
|
|
498
|
+
}, z.core.$strict>>;
|
|
499
|
+
overrides: z.ZodArray<z.ZodObject<{
|
|
500
|
+
phase: z.ZodEnum<{
|
|
501
|
+
build: "build";
|
|
502
|
+
plan: "plan";
|
|
503
|
+
repair: "repair";
|
|
504
|
+
review: "review";
|
|
505
|
+
}>;
|
|
506
|
+
provider: z.ZodEnum<{
|
|
507
|
+
claude: "claude";
|
|
508
|
+
cline: "cline";
|
|
509
|
+
codex: "codex";
|
|
510
|
+
cursor: "cursor";
|
|
511
|
+
gemini: "gemini";
|
|
512
|
+
goose: "goose";
|
|
513
|
+
opencode: "opencode";
|
|
514
|
+
openrouter: "openrouter";
|
|
515
|
+
qwen: "qwen";
|
|
516
|
+
}>;
|
|
517
|
+
model: z.ZodString;
|
|
518
|
+
by: z.ZodString;
|
|
519
|
+
at: z.ZodString;
|
|
520
|
+
}, z.core.$strict>>;
|
|
521
|
+
size: z.ZodNullable<z.ZodObject<{
|
|
522
|
+
size: z.ZodEnum<{
|
|
523
|
+
large: "large";
|
|
524
|
+
medium: "medium";
|
|
525
|
+
small: "small";
|
|
526
|
+
}>;
|
|
527
|
+
risky: z.ZodBoolean;
|
|
528
|
+
source: z.ZodEnum<{
|
|
529
|
+
classifier: "classifier";
|
|
530
|
+
heuristic: "heuristic";
|
|
531
|
+
person: "person";
|
|
532
|
+
}>;
|
|
533
|
+
reason: z.ZodString;
|
|
534
|
+
}, z.core.$strict>>;
|
|
535
|
+
}, z.core.$strict>;
|
|
536
|
+
/** A sealed route as saved, in any version. */
|
|
332
537
|
export declare const sealedRouteSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
333
538
|
version: z.ZodLiteral<1>;
|
|
334
539
|
risk: z.ZodEnum<{
|
|
@@ -541,15 +746,148 @@ export declare const sealedRouteSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
541
746
|
}>;
|
|
542
747
|
reason: z.ZodString;
|
|
543
748
|
}, z.core.$strict>>;
|
|
749
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
750
|
+
version: z.ZodLiteral<3>;
|
|
751
|
+
risk: z.ZodEnum<{
|
|
752
|
+
elevated: "elevated";
|
|
753
|
+
high: "high";
|
|
754
|
+
routine: "routine";
|
|
755
|
+
}>;
|
|
756
|
+
qualityMode: z.ZodEnum<{
|
|
757
|
+
default: "default";
|
|
758
|
+
strict: "strict";
|
|
759
|
+
}>;
|
|
760
|
+
publication: z.ZodEnum<{
|
|
761
|
+
automerge: "automerge";
|
|
762
|
+
none: "none";
|
|
763
|
+
notify: "notify";
|
|
764
|
+
}>;
|
|
765
|
+
evidence: z.ZodArray<z.ZodEnum<{
|
|
766
|
+
"changed-path": "changed-path";
|
|
767
|
+
check: "check";
|
|
768
|
+
"manual-review": "manual-review";
|
|
769
|
+
screenshot: "screenshot";
|
|
770
|
+
}>>;
|
|
771
|
+
posture: z.ZodEnum<{
|
|
772
|
+
economy: "economy";
|
|
773
|
+
strong: "strong";
|
|
774
|
+
}>;
|
|
775
|
+
demands: z.ZodArray<z.ZodString>;
|
|
776
|
+
legs: z.ZodArray<z.ZodObject<{
|
|
777
|
+
phase: z.ZodEnum<{
|
|
778
|
+
build: "build";
|
|
779
|
+
plan: "plan";
|
|
780
|
+
repair: "repair";
|
|
781
|
+
review: "review";
|
|
782
|
+
}>;
|
|
783
|
+
provider: z.ZodEnum<{
|
|
784
|
+
claude: "claude";
|
|
785
|
+
cline: "cline";
|
|
786
|
+
codex: "codex";
|
|
787
|
+
cursor: "cursor";
|
|
788
|
+
gemini: "gemini";
|
|
789
|
+
goose: "goose";
|
|
790
|
+
opencode: "opencode";
|
|
791
|
+
openrouter: "openrouter";
|
|
792
|
+
qwen: "qwen";
|
|
793
|
+
}>;
|
|
794
|
+
model: z.ZodString;
|
|
795
|
+
tier: z.ZodEnum<{
|
|
796
|
+
light: "light";
|
|
797
|
+
routine: "routine";
|
|
798
|
+
strong: "strong";
|
|
799
|
+
}>;
|
|
800
|
+
chosen: z.ZodEnum<{
|
|
801
|
+
override: "override";
|
|
802
|
+
pinned: "pinned";
|
|
803
|
+
recommended: "recommended";
|
|
804
|
+
}>;
|
|
805
|
+
recommended: z.ZodObject<{
|
|
806
|
+
provider: z.ZodEnum<{
|
|
807
|
+
claude: "claude";
|
|
808
|
+
cline: "cline";
|
|
809
|
+
codex: "codex";
|
|
810
|
+
cursor: "cursor";
|
|
811
|
+
gemini: "gemini";
|
|
812
|
+
goose: "goose";
|
|
813
|
+
opencode: "opencode";
|
|
814
|
+
openrouter: "openrouter";
|
|
815
|
+
qwen: "qwen";
|
|
816
|
+
}>;
|
|
817
|
+
model: z.ZodString;
|
|
818
|
+
tier: z.ZodEnum<{
|
|
819
|
+
light: "light";
|
|
820
|
+
routine: "routine";
|
|
821
|
+
strong: "strong";
|
|
822
|
+
}>;
|
|
823
|
+
}, z.core.$strict>;
|
|
824
|
+
reasons: z.ZodArray<z.ZodString>;
|
|
825
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
826
|
+
selection: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
827
|
+
kind: z.ZodLiteral<"rule">;
|
|
828
|
+
rule: z.ZodNumber;
|
|
829
|
+
ruleGeneration: z.ZodNumber;
|
|
830
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
831
|
+
kind: z.ZodLiteral<"trial">;
|
|
832
|
+
rule: z.ZodNumber;
|
|
833
|
+
reservation: z.ZodNumber;
|
|
834
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
835
|
+
kind: z.ZodEnum<{
|
|
836
|
+
configured: "configured";
|
|
837
|
+
override: "override";
|
|
838
|
+
pin: "pin";
|
|
839
|
+
}>;
|
|
840
|
+
}, z.core.$strict>], "kind">>;
|
|
841
|
+
}, z.core.$strict>>;
|
|
842
|
+
overrides: z.ZodArray<z.ZodObject<{
|
|
843
|
+
phase: z.ZodEnum<{
|
|
844
|
+
build: "build";
|
|
845
|
+
plan: "plan";
|
|
846
|
+
repair: "repair";
|
|
847
|
+
review: "review";
|
|
848
|
+
}>;
|
|
849
|
+
provider: z.ZodEnum<{
|
|
850
|
+
claude: "claude";
|
|
851
|
+
cline: "cline";
|
|
852
|
+
codex: "codex";
|
|
853
|
+
cursor: "cursor";
|
|
854
|
+
gemini: "gemini";
|
|
855
|
+
goose: "goose";
|
|
856
|
+
opencode: "opencode";
|
|
857
|
+
openrouter: "openrouter";
|
|
858
|
+
qwen: "qwen";
|
|
859
|
+
}>;
|
|
860
|
+
model: z.ZodString;
|
|
861
|
+
by: z.ZodString;
|
|
862
|
+
at: z.ZodString;
|
|
863
|
+
}, z.core.$strict>>;
|
|
864
|
+
size: z.ZodNullable<z.ZodObject<{
|
|
865
|
+
size: z.ZodEnum<{
|
|
866
|
+
large: "large";
|
|
867
|
+
medium: "medium";
|
|
868
|
+
small: "small";
|
|
869
|
+
}>;
|
|
870
|
+
risky: z.ZodBoolean;
|
|
871
|
+
source: z.ZodEnum<{
|
|
872
|
+
classifier: "classifier";
|
|
873
|
+
heuristic: "heuristic";
|
|
874
|
+
person: "person";
|
|
875
|
+
}>;
|
|
876
|
+
reason: z.ZodString;
|
|
877
|
+
}, z.core.$strict>>;
|
|
544
878
|
}, z.core.$strict>], "version">;
|
|
545
879
|
export type SealedRoute = z.infer<typeof sealedRouteSchema>;
|
|
546
880
|
export type RouteVersion = SealedRoute["version"];
|
|
547
|
-
|
|
881
|
+
/** A leg as every reader returns it: only a version 3 build leg can carry a selection. */
|
|
882
|
+
export type RouteLeg = z.infer<typeof routeLegSchema> & {
|
|
883
|
+
selection?: Selection | undefined;
|
|
884
|
+
};
|
|
548
885
|
export type RouteOverride = z.infer<typeof routeOverrideSchema>;
|
|
549
886
|
export type TaskSizing = z.infer<typeof taskSizingSchema>;
|
|
550
887
|
/** A route as every reader returns it: a version 1 route reads with no size. */
|
|
551
|
-
export type PhaseRoute = Omit<z.infer<typeof sealedRouteV2Schema>, "version"> & {
|
|
888
|
+
export type PhaseRoute = Omit<z.infer<typeof sealedRouteV2Schema>, "version" | "legs"> & {
|
|
552
889
|
version: RouteVersion;
|
|
890
|
+
legs: RouteLeg[];
|
|
553
891
|
};
|
|
554
892
|
/** The posture legs actually run under: strong only when a leg runs a configured strong agent as recommended. */
|
|
555
893
|
export declare function postureOf(legs: readonly Pick<RouteLeg, "chosen" | "tier">[]): "economy" | "strong";
|
package/dist/contracts/route.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { EVIDENCE_KINDS } from "./acceptance-terms.js";
|
|
15
15
|
import { limited, parseContract, versioned } from "./contract.js";
|
|
16
|
+
import { selectionSchema } from "./route-results.js";
|
|
16
17
|
/** The phases a route has a leg for, in the order its legs run. */
|
|
17
18
|
export const ROUTE_PHASES = ["plan", "build", "repair", "review"];
|
|
18
19
|
/** provider.ts's provider ids, restated (provider.ts sits above phase-routing.ts in the module graph) and pinned to
|
|
@@ -23,8 +24,15 @@ export const ROUTE_PROVIDERS = ["claude", "codex", "openrouter", "gemini", "open
|
|
|
23
24
|
export const MODEL_ID_SHAPE = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
|
|
24
25
|
export const RISKS = ["routine", "elevated", "high"];
|
|
25
26
|
export const TASK_SIZE_WORDS = ["small", "medium", "large"];
|
|
26
|
-
/** The route version this code seals
|
|
27
|
-
|
|
27
|
+
/** The newest route version this code seals (docs/plans/results-routing.md, slice 2): version 3 adds the build leg's
|
|
28
|
+
* sealed `selection`, and is sealed only for a build leg that carries one (an earned rule's). Every other route still
|
|
29
|
+
* seals as version 2 (PLAIN_ROUTE_VERSION), so its bytes and digest are what they always were; version 1 (before
|
|
30
|
+
* sizing) still reads. */
|
|
31
|
+
export const ROUTE_VERSION = 3;
|
|
32
|
+
/** The version a route with no selection seals as. */
|
|
33
|
+
export const PLAIN_ROUTE_VERSION = 2;
|
|
34
|
+
/** The newest route version this code reads. */
|
|
35
|
+
export const READ_ROUTE_VERSION = ROUTE_VERSION;
|
|
28
36
|
const phase = z.enum(ROUTE_PHASES);
|
|
29
37
|
const provider = z.enum(ROUTE_PROVIDERS);
|
|
30
38
|
const model = z.string().regex(MODEL_ID_SHAPE, { error: "must be an exact model id: letters, digits and . _ : / -, not starting with a dash" });
|
|
@@ -53,6 +61,8 @@ function legSchema(tiers) {
|
|
|
53
61
|
}
|
|
54
62
|
/** One leg of a version 2 route: the light tier exists. */
|
|
55
63
|
export const routeLegSchema = legSchema(["light", "routine", "strong"]);
|
|
64
|
+
/** One leg of a version 3 route: a version 2 leg, and the build leg may say how it was selected. */
|
|
65
|
+
export const routeLegV3Schema = routeLegSchema.extend({ selection: selectionSchema.optional() });
|
|
56
66
|
const terms = {
|
|
57
67
|
risk: z.enum(RISKS),
|
|
58
68
|
qualityMode: z.enum(["default", "strict"]),
|
|
@@ -66,14 +76,20 @@ export const sealedRouteV1Schema = versioned(1, {
|
|
|
66
76
|
legs: z.array(legSchema(["routine", "strong"])).length(ROUTE_PHASES.length),
|
|
67
77
|
overrides: z.array(routeOverrideSchema),
|
|
68
78
|
});
|
|
69
|
-
export const sealedRouteV2Schema = versioned(
|
|
79
|
+
export const sealedRouteV2Schema = versioned(PLAIN_ROUTE_VERSION, {
|
|
70
80
|
...terms,
|
|
71
81
|
legs: z.array(routeLegSchema).length(ROUTE_PHASES.length),
|
|
72
82
|
overrides: z.array(routeOverrideSchema),
|
|
73
83
|
size: taskSizingSchema.nullable(),
|
|
74
84
|
});
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
export const sealedRouteV3Schema = versioned(READ_ROUTE_VERSION, {
|
|
86
|
+
...terms,
|
|
87
|
+
legs: z.array(routeLegV3Schema).length(ROUTE_PHASES.length),
|
|
88
|
+
overrides: z.array(routeOverrideSchema),
|
|
89
|
+
size: taskSizingSchema.nullable(),
|
|
90
|
+
});
|
|
91
|
+
/** A sealed route as saved, in any version. */
|
|
92
|
+
export const sealedRouteSchema = z.discriminatedUnion("version", [sealedRouteV1Schema, sealedRouteV2Schema, sealedRouteV3Schema]);
|
|
77
93
|
/** The posture legs actually run under: strong only when a leg runs a configured strong agent as recommended. */
|
|
78
94
|
export function postureOf(legs) {
|
|
79
95
|
return legs.some(leg => leg.chosen === "recommended" && leg.tier === "strong") ? "strong" : "economy";
|
|
@@ -94,14 +110,27 @@ export function readRouteOverrides(input) {
|
|
|
94
110
|
/** Read a sealed route: either version as itself, every leg, override and the size proved, nothing unknown. */
|
|
95
111
|
export function readSealedRoute(input) {
|
|
96
112
|
const version = typeof input === "object" && input !== null ? input["version"] : undefined;
|
|
97
|
-
if (typeof version === "number" && Number.isInteger(version) && version >
|
|
98
|
-
return { ok: false, issues: [{ path: "version", kind: "newer-version", line: `version: made by a newer Toolroll (version ${version}; this one reads up to ${
|
|
113
|
+
if (typeof version === "number" && Number.isInteger(version) && version > READ_ROUTE_VERSION) {
|
|
114
|
+
return { ok: false, issues: [{ path: "version", kind: "newer-version", line: `version: made by a newer Toolroll (version ${version}; this one reads up to ${READ_ROUTE_VERSION})` }] };
|
|
99
115
|
}
|
|
100
116
|
const parsed = parseContract(sealedRouteSchema, input);
|
|
101
117
|
if (!parsed.ok)
|
|
102
118
|
return parsed;
|
|
103
119
|
const route = parsed.value;
|
|
104
120
|
const issues = route.legs.flatMap((leg, index) => (leg.phase === ROUTE_PHASES[index] ? [] : [issue(`legs[${index}].phase`, `must be ${JSON.stringify(ROUTE_PHASES[index])} (legs run plan, build, repair, review)`)]));
|
|
121
|
+
// Version 3: only the build leg says how it was selected, and what it says agrees with how it was chosen.
|
|
122
|
+
route.legs.forEach((leg, index) => {
|
|
123
|
+
const selection = leg.selection;
|
|
124
|
+
if (selection === undefined)
|
|
125
|
+
return;
|
|
126
|
+
if (leg.phase !== "build") {
|
|
127
|
+
issues.push(issue(`legs[${index}].selection`, "only the build leg carries a selection"));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const chosen = selection.kind === "override" ? "override" : selection.kind === "pin" ? "pinned" : "recommended";
|
|
131
|
+
if (leg.chosen !== chosen)
|
|
132
|
+
issues.push(issue(`legs[${index}].selection`, `a ${selection.kind} selection is chosen ${JSON.stringify(chosen)}, not ${JSON.stringify(leg.chosen)}`));
|
|
133
|
+
});
|
|
105
134
|
const posture = postureOf(route.legs);
|
|
106
135
|
if (posture !== route.posture)
|
|
107
136
|
issues.push(issue("posture", `must be ${JSON.stringify(posture)}: that is what its legs run`));
|
|
@@ -210,12 +210,14 @@ declare const STORE_COLUMN_VALUES: {
|
|
|
210
210
|
subagent_state: "subagent_state";
|
|
211
211
|
subagent_tools: "subagent_tools";
|
|
212
212
|
subagent_undo: "subagent_undo";
|
|
213
|
+
suggestion_not_now: "suggestion_not_now";
|
|
213
214
|
sweep_cancel_rest: "sweep_cancel_rest";
|
|
214
215
|
sweep_file: "sweep_file";
|
|
215
216
|
task_cancel: "task_cancel";
|
|
216
217
|
task_resume: "task_resume";
|
|
217
218
|
tool_add: "tool_add";
|
|
218
219
|
tool_remove: "tool_remove";
|
|
220
|
+
trial_stop: "trial_stop";
|
|
219
221
|
}>;
|
|
220
222
|
request: z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>;
|
|
221
223
|
repo: z.ZodString;
|
package/dist/desktop-update.d.ts
CHANGED
|
@@ -74,8 +74,8 @@ export declare function desktopUpdateWaitingOf(databaseFile: string, inTheWay: (
|
|
|
74
74
|
/** The same settling reconcile the background worker runs: a finished run's leftover record of a process that never
|
|
75
75
|
* started settles once the run's process groups are proven gone. A record nothing can prove either way stays. */
|
|
76
76
|
export declare function settleLeftoverRecords(db: DatabaseSync, now: Date): number;
|
|
77
|
-
/** Store.stopQuiescenceProblem's reasons, said for a person: no internal terms
|
|
78
|
-
*
|
|
77
|
+
/** Store.stopQuiescenceProblem's reasons, said for a person: no internal terms. Nobody vouches that a run stopped:
|
|
78
|
+
* Toolroll proves it itself (stop-proof.ts), and `toolroll run settle` only has it look again now. */
|
|
79
79
|
export declare function plainLingering(fallbackRun: number, problem: string): LingeringRun;
|
|
80
80
|
export declare function previewDesktopUpdate(stateDir: string, installed: string, candidate: string, label: string, hooks?: UpdateHooks): Promise<{
|
|
81
81
|
old: DesktopBundle;
|
package/dist/desktop-update.js
CHANGED
|
@@ -193,11 +193,11 @@ export function lingeringWork(db) {
|
|
|
193
193
|
const lingering = lingeringRun(db);
|
|
194
194
|
return lingering && lingeringWords(lingering);
|
|
195
195
|
}
|
|
196
|
-
export const lingeringWords = (l) => l.action ? `${l.on}.
|
|
196
|
+
export const lingeringWords = (l) => l.action ? `${l.on}. To have Toolroll check again, run: ${l.action}` : `${l.on}.`;
|
|
197
197
|
/** What an update says when it stops waiting on `l`: what is in the way, the command, and that new work resumed. */
|
|
198
198
|
export const stoppedWaitingWords = (l) => `Stopped waiting after 2 minutes. ${l.on}. ${clearWords(l)} New work resumed; nothing was changed.`;
|
|
199
199
|
/** The next step once an update has stopped on `l`. */
|
|
200
|
-
export const clearWords = (l) => l.action ? `
|
|
200
|
+
export const clearWords = (l) => l.action ? `To have Toolroll check again, run ${l.action}, then update again.` : "Update again once it has stopped.";
|
|
201
201
|
export function updateWaitingWords(o) {
|
|
202
202
|
const { app, version, stopped, lingering: l } = o;
|
|
203
203
|
const update = `${app ? "App update" : "Update"} to ${version}`;
|
|
@@ -206,7 +206,7 @@ export function updateWaitingWords(o) {
|
|
|
206
206
|
const words = `${update} is waiting for ${on}. New work is paused.`;
|
|
207
207
|
return { app, version, stopped: false, run: null, on, action: null, words, short: words };
|
|
208
208
|
}
|
|
209
|
-
const next = l.action ? `
|
|
209
|
+
const next = l.action ? ` To have Toolroll check again, run ${l.action}${stopped ? ", then update again" : ""}.` : stopped ? " Update again once it has stopped." : "";
|
|
210
210
|
return { app, version, stopped, run: l.run, on: l.on, action: l.action,
|
|
211
211
|
words: `${update} ${stopped ? "stopped waiting" : "is waiting"}: ${l.on}.${next}`,
|
|
212
212
|
short: `${update} ${stopped ? `stopped: run #${l.run} is in the way` : `is waiting on run #${l.run}`}.${next}` };
|
|
@@ -284,18 +284,22 @@ export function desktopUpdateWaitingOf(databaseFile, inTheWay, stateDirs = [dirn
|
|
|
284
284
|
export function settleLeftoverRecords(db, now) {
|
|
285
285
|
return new Store(db).settleUnspawnedWitnesses(now);
|
|
286
286
|
}
|
|
287
|
-
/** Store.stopQuiescenceProblem's reasons, said for a person: no internal terms
|
|
288
|
-
*
|
|
287
|
+
/** Store.stopQuiescenceProblem's reasons, said for a person: no internal terms. Nobody vouches that a run stopped:
|
|
288
|
+
* Toolroll proves it itself (stop-proof.ts), and `toolroll run settle` only has it look again now. */
|
|
289
289
|
export function plainLingering(fallbackRun, problem) {
|
|
290
290
|
const run = Number(/run #(\d+)/.exec(problem)?.[1] ?? fallbackRun);
|
|
291
|
-
const settle = `toolroll run settle ${run}
|
|
291
|
+
const settle = `toolroll run settle ${run}`;
|
|
292
292
|
const pid = /process (\d+)/.exec(problem)?.[1];
|
|
293
293
|
if (/incomplete spawn witness|before its process witness was recorded/.test(problem))
|
|
294
|
-
return { run, on: `Run #${run} finished,
|
|
295
|
-
if (/belongs to another host|another host/.test(problem))
|
|
294
|
+
return { run, on: `Run #${run} finished, and Toolroll is making sure none of its processes is still running`, action: settle };
|
|
295
|
+
if (/belongs to another host|another host|another computer/.test(problem))
|
|
296
296
|
return { run, on: `Run #${run} finished on another computer, and Toolroll can only confirm its processes ended there`, action: `${settle} (on that computer)` };
|
|
297
|
+
if (/can't be proven stopped: .* is using its folder/.test(problem))
|
|
298
|
+
return { run, on: `Run #${run} finished, but something Toolroll didn't start is using its folder`, action: settle };
|
|
299
|
+
if (/can't be checked on this computer/.test(problem))
|
|
300
|
+
return { run, on: `Run #${run} finished, but Toolroll can't check this computer's processes`, action: settle };
|
|
297
301
|
if (/incomplete native containment custody|cannot be proven empty/.test(problem))
|
|
298
|
-
return { run, on: `Run #${run} finished, but Toolroll can't confirm its sandbox is empty`, action:
|
|
302
|
+
return { run, on: `Run #${run} finished, but Toolroll can't confirm its sandbox is empty`, action: null };
|
|
299
303
|
if (/still has members/.test(problem))
|
|
300
304
|
return { run, on: `Run #${run} finished, but processes are still running in its sandbox`, action: null };
|
|
301
305
|
if (/workspace is still held/.test(problem))
|
package/dist/dispose.js
CHANGED
|
@@ -25,6 +25,7 @@ import { verificationEvidence, failedVerificationEvidence } from "./verification
|
|
|
25
25
|
import { classifyGateFailure, describeGateFailure, gateFailureSummary } from "./gate-failure.js";
|
|
26
26
|
import { propose, approve, liveReleaseOf, releaseCheckScope } from "./scope.js";
|
|
27
27
|
import { headWithin } from "./names.js";
|
|
28
|
+
import { recordExecClass } from "./route-store.js";
|
|
28
29
|
/** The reasons tick classifies as the attempt itself breaking. */
|
|
29
30
|
const TICK_FAILURE_REASONS = new Set([
|
|
30
31
|
"agent",
|
|
@@ -247,6 +248,8 @@ function disposeBuildOutcomeLocked(context, result) {
|
|
|
247
248
|
return { kind: "malformed", sealed: sealed.ok };
|
|
248
249
|
}
|
|
249
250
|
const outcome = STANDALONE_BROKE_REASONS.has(result.reason) ? "failed" : "refused";
|
|
251
|
+
if (outcome === "failed")
|
|
252
|
+
recordExecClass(store, runId, result.reason);
|
|
250
253
|
store.finishRun(runId, { outcome, reason: result.reason, now: clock() });
|
|
251
254
|
return { kind: "recorded", outcome };
|
|
252
255
|
}
|
|
@@ -281,6 +284,9 @@ function disposeBuildOutcomeLocked(context, result) {
|
|
|
281
284
|
return { kind: "handed-back", requeued: handed.requeued };
|
|
282
285
|
}
|
|
283
286
|
if (TICK_FAILURE_REASONS.has(result.reason)) {
|
|
287
|
+
// Results routing (v148): the raw reason's execution class, kept on the run before the mapping below folds
|
|
288
|
+
// timeouts and setup failures together (route-store.ts recordExecClass; first write wins).
|
|
289
|
+
recordExecClass(store, runId, result.reason);
|
|
284
290
|
// One fenced transaction decides what the failure means. The
|
|
285
291
|
// classification trusts only what the machine itself observed.
|
|
286
292
|
const failureClass = result.reason === "agent-reported"
|