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.
Files changed (124) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/approval-authority.d.ts +9 -1
  3. package/dist/approval-authority.js +15 -2
  4. package/dist/assignment-status.d.ts +1 -1
  5. package/dist/assignment-ui.js +2 -2
  6. package/dist/assignment.js +11 -3
  7. package/dist/browser/workspace.css +1 -1
  8. package/dist/browser/workspace.js +30 -30
  9. package/dist/browser-crew.js +2 -2
  10. package/dist/browser-workspace.d.ts +3 -5
  11. package/dist/build-review.js +2 -0
  12. package/dist/builder.js +21 -2
  13. package/dist/chat-actions.d.ts +11 -3
  14. package/dist/chat-actions.js +47 -2
  15. package/dist/chat-decide.js +2 -0
  16. package/dist/chat-review.d.ts +2 -1
  17. package/dist/chat-review.js +2 -2
  18. package/dist/check-facts.d.ts +124 -0
  19. package/dist/check-facts.js +272 -0
  20. package/dist/check-report.d.ts +5 -0
  21. package/dist/check-report.js +4 -3
  22. package/dist/cli.js +5 -0
  23. package/dist/contracts/chat-actions.d.ts +10 -0
  24. package/dist/contracts/chat-actions.js +4 -0
  25. package/dist/contracts/console-api.d.ts +3 -2
  26. package/dist/contracts/console-api.js +5 -2
  27. package/dist/contracts/release-check.d.ts +25 -1
  28. package/dist/contracts/release-check.js +19 -0
  29. package/dist/contracts/route-results.d.ts +121 -0
  30. package/dist/contracts/route-results.js +71 -0
  31. package/dist/contracts/route.d.ts +343 -5
  32. package/dist/contracts/route.js +36 -7
  33. package/dist/contracts/store-json.d.ts +2 -0
  34. package/dist/desktop-update.d.ts +2 -2
  35. package/dist/desktop-update.js +13 -9
  36. package/dist/dispose.js +6 -0
  37. package/dist/eval-hooks.d.ts +55 -0
  38. package/dist/eval-hooks.js +227 -4
  39. package/dist/flaky.d.ts +17 -0
  40. package/dist/flaky.js +9 -0
  41. package/dist/flow-engine.js +1 -1
  42. package/dist/flow-pull-request.js +1 -1
  43. package/dist/lead-doors.js +4 -2
  44. package/dist/lead-tools.js +10 -2
  45. package/dist/local-owner.d.ts +1 -1
  46. package/dist/local-owner.js +11 -0
  47. package/dist/memory-cli.d.ts +3 -0
  48. package/dist/memory-cli.js +3 -3
  49. package/dist/memory-pass.js +5 -1
  50. package/dist/model-choices.d.ts +7 -3
  51. package/dist/model-choices.js +26 -11
  52. package/dist/models-cli.d.ts +205 -0
  53. package/dist/models-cli.js +143 -4
  54. package/dist/models-ui.d.ts +57 -0
  55. package/dist/models-ui.js +44 -1
  56. package/dist/needs-you.d.ts +7 -7
  57. package/dist/needs-you.js +12 -10
  58. package/dist/operate-remote-arguments.js +4 -4
  59. package/dist/operate-remote.js +1 -1
  60. package/dist/operate.js +191 -23
  61. package/dist/phase-routing.d.ts +82 -2
  62. package/dist/phase-routing.js +132 -18
  63. package/dist/project-delete.d.ts +4 -0
  64. package/dist/project-delete.js +16 -4
  65. package/dist/project-memory.d.ts +8 -0
  66. package/dist/project-memory.js +20 -1
  67. package/dist/pull-request-flow.js +1 -1
  68. package/dist/push.d.ts +10 -3
  69. package/dist/push.js +22 -6
  70. package/dist/release-adapters.js +11 -1
  71. package/dist/release-approval.d.ts +191 -0
  72. package/dist/release-approval.js +382 -0
  73. package/dist/release-auto.js +22 -4
  74. package/dist/release-card.d.ts +73 -4
  75. package/dist/release-card.js +168 -23
  76. package/dist/release-cli.js +9 -1
  77. package/dist/release.d.ts +30 -0
  78. package/dist/release.js +42 -3
  79. package/dist/result-actions.d.ts +4 -0
  80. package/dist/result-actions.js +3 -0
  81. package/dist/result-acts.d.ts +3 -3
  82. package/dist/result-acts.js +1 -1
  83. package/dist/route-hooks.d.ts +54 -0
  84. package/dist/route-hooks.js +179 -0
  85. package/dist/route-results.d.ts +70 -0
  86. package/dist/route-results.js +340 -0
  87. package/dist/route-rules.d.ts +324 -0
  88. package/dist/route-rules.js +781 -0
  89. package/dist/route-savings.d.ts +136 -0
  90. package/dist/route-savings.js +193 -0
  91. package/dist/route-store.d.ts +300 -0
  92. package/dist/route-store.js +598 -0
  93. package/dist/scope.d.ts +3 -1
  94. package/dist/scope.js +4 -1
  95. package/dist/server/guards.js +1 -0
  96. package/dist/server/pages-assets.js +1 -1
  97. package/dist/server/render-chat.js +2 -2
  98. package/dist/server/render-release.d.ts +17 -0
  99. package/dist/server/render-release.js +84 -0
  100. package/dist/server/render-results.d.ts +0 -2
  101. package/dist/server/render-results.js +4 -6
  102. package/dist/server/render-tasks.d.ts +7 -0
  103. package/dist/server/render-tasks.js +16 -3
  104. package/dist/server/route-table.d.ts +1 -1
  105. package/dist/server/route-table.js +19 -1
  106. package/dist/server/settings.d.ts +2 -1
  107. package/dist/server/settings.js +87 -1
  108. package/dist/server/task-views.js +6 -0
  109. package/dist/server/tasks.d.ts +15 -0
  110. package/dist/server/tasks.js +105 -20
  111. package/dist/settings-proofs.js +2 -0
  112. package/dist/spend-ui.d.ts +11 -0
  113. package/dist/spend-ui.js +49 -2
  114. package/dist/stop-proof.d.ts +67 -0
  115. package/dist/stop-proof.js +293 -0
  116. package/dist/store.d.ts +106 -31
  117. package/dist/store.js +653 -128
  118. package/dist/surface.js +17 -5
  119. package/dist/task-outcome-cli.js +24 -2
  120. package/dist/toolroll-update-ui.js +2 -2
  121. package/dist/toolroll-update.js +4 -2
  122. package/dist/work-index.js +7 -9
  123. package/dist/work-summary.d.ts +2 -2
  124. 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; version 1 (before sizing) still reads. */
26
- export declare const ROUTE_VERSION = 2;
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
- /** A sealed route as saved, in either version. */
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
- export type RouteLeg = z.infer<typeof routeLegSchema>;
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";
@@ -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; version 1 (before sizing) still reads. */
27
- export const ROUTE_VERSION = 2;
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(ROUTE_VERSION, {
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
- /** A sealed route as saved, in either version. */
76
- export const sealedRouteSchema = z.discriminatedUnion("version", [sealedRouteV1Schema, sealedRouteV2Schema]);
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 > ROUTE_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 ${ROUTE_VERSION})` }] };
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;
@@ -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, and `toolroll run settle` only where it
78
- * can clear the record (it refuses while anything of the run is alive). */
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;
@@ -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}. If nothing of it is running, run: ${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 ? `If nothing of it is running, run ${l.action}, then update again.` : "Update again once it has stopped.";
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 ? ` If nothing of it is running, run ${l.action}${stopped ? ", then update again" : ""}.` : stopped ? " Update again once it has stopped." : "";
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, and `toolroll run settle` only where it
288
- * can clear the record (it refuses while anything of the run is alive). */
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} --why "it is not running"`;
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, but Toolroll has no process ID for one of its processes, so it can't confirm that process ended`, action: settle };
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: settle };
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"