toolroll 0.9.90 → 0.9.92

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 (80) 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/build-review.js +2 -0
  5. package/dist/chat-actions.d.ts +11 -3
  6. package/dist/chat-actions.js +47 -2
  7. package/dist/chat-decide.js +2 -0
  8. package/dist/chat-review.d.ts +2 -1
  9. package/dist/chat-review.js +2 -2
  10. package/dist/contracts/chat-actions.d.ts +10 -0
  11. package/dist/contracts/chat-actions.js +4 -0
  12. package/dist/contracts/console-api.d.ts +1 -0
  13. package/dist/contracts/console-api.js +3 -0
  14. package/dist/contracts/route-results.d.ts +121 -0
  15. package/dist/contracts/route-results.js +71 -0
  16. package/dist/contracts/route.d.ts +343 -5
  17. package/dist/contracts/route.js +36 -7
  18. package/dist/contracts/store-json.d.ts +2 -0
  19. package/dist/dispose.js +6 -0
  20. package/dist/flow-engine.js +1 -1
  21. package/dist/flow-pull-request.js +1 -1
  22. package/dist/lead-doors.js +4 -2
  23. package/dist/lead-tools.js +10 -2
  24. package/dist/local-owner.d.ts +1 -1
  25. package/dist/local-owner.js +11 -0
  26. package/dist/memory-cli.d.ts +3 -0
  27. package/dist/memory-cli.js +3 -3
  28. package/dist/memory-pass.js +5 -1
  29. package/dist/model-choices.d.ts +7 -3
  30. package/dist/model-choices.js +26 -11
  31. package/dist/models-cli.d.ts +205 -0
  32. package/dist/models-cli.js +143 -4
  33. package/dist/models-ui.d.ts +57 -0
  34. package/dist/models-ui.js +44 -1
  35. package/dist/operate-remote-arguments.js +4 -4
  36. package/dist/operate-remote.js +1 -1
  37. package/dist/operate.js +53 -5
  38. package/dist/phase-routing.d.ts +82 -2
  39. package/dist/phase-routing.js +132 -18
  40. package/dist/project-delete.d.ts +4 -0
  41. package/dist/project-delete.js +12 -4
  42. package/dist/project-knowledge.d.ts +6 -6
  43. package/dist/project-memory.d.ts +8 -0
  44. package/dist/project-memory.js +20 -1
  45. package/dist/pull-request-flow.js +1 -1
  46. package/dist/release-adapters.d.ts +2 -0
  47. package/dist/release-adapters.js +30 -1
  48. package/dist/release-auto.js +12 -3
  49. package/dist/result-actions.d.ts +4 -0
  50. package/dist/result-actions.js +3 -0
  51. package/dist/route-hooks.d.ts +54 -0
  52. package/dist/route-hooks.js +179 -0
  53. package/dist/route-results.d.ts +70 -0
  54. package/dist/route-results.js +340 -0
  55. package/dist/route-rules.d.ts +324 -0
  56. package/dist/route-rules.js +781 -0
  57. package/dist/route-savings.d.ts +136 -0
  58. package/dist/route-savings.js +193 -0
  59. package/dist/route-store.d.ts +300 -0
  60. package/dist/route-store.js +598 -0
  61. package/dist/scope.d.ts +3 -1
  62. package/dist/scope.js +4 -1
  63. package/dist/server/render-chat.js +2 -2
  64. package/dist/server/render-tasks.d.ts +7 -0
  65. package/dist/server/render-tasks.js +13 -0
  66. package/dist/server/route-table.js +12 -0
  67. package/dist/server/settings.d.ts +2 -1
  68. package/dist/server/settings.js +87 -1
  69. package/dist/server/task-views.js +6 -0
  70. package/dist/server/tasks.js +3 -0
  71. package/dist/settings-proofs.js +2 -0
  72. package/dist/setup-guide.d.ts +2 -2
  73. package/dist/spend-ui.d.ts +11 -0
  74. package/dist/spend-ui.js +49 -2
  75. package/dist/store.d.ts +38 -2
  76. package/dist/store.js +320 -35
  77. package/dist/surface.js +16 -4
  78. package/dist/task-outcome-cli.js +5 -2
  79. package/dist/toolroll-update.js +3 -2
  80. package/package.json +1 -1
@@ -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;
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"
@@ -507,7 +507,7 @@ export function revisionWithNote(store, card, target, note, actor, repos, eviden
507
507
  return null;
508
508
  const revised = requestResultChanges(store, evidenceRoot, {
509
509
  run: runId, source: revisionSourceOf(store.getScope(runTask)?.digest ?? null), actor, repos,
510
- batch: "", note, path: "", line: "", request: `flow-${card.id}-${card.entry}`, allowMode: true,
510
+ batch: "", note, path: "", line: "", request: `flow-${card.id}-${card.entry}`, allowMode: true, origin: "flow",
511
511
  }, now);
512
512
  return revised.ok ? revised.id : null;
513
513
  }
@@ -140,7 +140,7 @@ export async function pullRequestStep(store, flow, definition, stage, card, now,
140
140
  const runTask = store.externalIdFor(store.getRun(runId)?.taskRef ?? -1);
141
141
  const revised = runTask === null ? null : requestResultChanges(store, io.evidenceRoot, {
142
142
  run: runId, source: revisionSourceOf(store.getScope(runTask)?.digest ?? null), actor: "toolroll", repos: [flow.repo], batch: "",
143
- note: found?.note ?? said, path: "", line: "", allowMode: true,
143
+ note: found?.note ?? said, path: "", line: "", allowMode: true, origin: "flow",
144
144
  request: createHash("sha256").update(`flow-ci ${card.id} ${card.entry} ${publication.id}`).digest("hex").slice(0, 32),
145
145
  }, now);
146
146
  if (revised?.ok === true)
@@ -25,7 +25,7 @@ import { teamChatAuthorization, subscriptionTeamChatProvider } from './team-chat
25
25
  import { readProjectAccess } from "./project-access.js";
26
26
  import { TeamLeads } from './team-leads.js';
27
27
  import { fileTaskProposal } from "./proposal.js";
28
- import { proposeGuarded } from "./scope.js";
28
+ import { machineActing, proposeGuarded } from "./scope.js";
29
29
  import { isTaskSize, PHASES, specWords } from "./phase-routing.js";
30
30
  import { isProviderId } from "./provider.js";
31
31
  import { applyChatReview } from "./chat-review.js";
@@ -377,7 +377,9 @@ function executeProposal(store, actor, kind, payload, now, options) {
377
377
  const request = payload;
378
378
  if (request.snapshot == null || request.snapshot.task !== taskId || (request.operation !== "note" && request.operation !== "revise"))
379
379
  return refuse("refused", "This feedback card is incomplete. Ask again.");
380
- const result = applyChatReview(store, actor, options.evidenceRoot, request, now, options.via !== "cli");
380
+ // A review card is the lead's (or a coordinator's) proposal; a person confirming it here decided the result needs
381
+ // these changes, so the revision is theirs. Under the lead's own chain or a remote run nobody confirmed it: lead.
382
+ const result = applyChatReview(store, actor, options.evidenceRoot, request, now, options.via !== "cli", machineActing() ? "lead" : "person");
381
383
  return result.ok ? { ok: true, kind, taskId: result.taskId, said: result.said } : refuse("stale", result.message);
382
384
  }
383
385
  // The place the proposer saw must be the place the task holds now
@@ -1,3 +1,4 @@
1
+ import { rulesOf, ruleWords as earnedRuleWords, suggestionsFor, suggestionWords, trialOffersFor, trialOfferWords, trialsOf, trialWords } from "./route-rules.js";
1
2
  import { sameFeedbackImage } from "./result-review.js";
2
3
  import { sweepLine, sweepTable } from "./lead-sweep.js";
3
4
  import { connectionsOf, oneClickOf } from "./mcp-connect.js";
@@ -1602,7 +1603,7 @@ const LEAD_TOOL_HANDLERS = {
1602
1603
  },
1603
1604
  },
1604
1605
  get_models: {
1605
- description: "Default agent per role with the exact model each name runs now, installed CLI versions and updates, and models released in the last two weeks. Read-only; changes happen in Settings → Models.",
1606
+ description: "Default agent per role with the exact model each name runs now, installed CLI versions and updates, models released in the last two weeks, and cheaper models your projects' own results suggest (with their earned rules), cheaper models to try and the trials in use. Read-only; accepting and starting a trial happen in Settings → Models; Not now is propose_action suggestion_not_now; stopping a trial is propose_action trial_stop.",
1606
1607
  handle: (ctx) => {
1607
1608
  const roles = ["plan", "build", "review", "repair"].map(phase => {
1608
1609
  const row = ctx.store.phaseConfig(INSTALLATION_SCOPE, phase);
@@ -1610,7 +1611,14 @@ const LEAD_TOOL_HANDLERS = {
1610
1611
  });
1611
1612
  const tools = runtimeStates(ctx.store).map(one => ({ name: one.name, installed: one.installed, latest: one.latest, updateAvailable: one.behind }));
1612
1613
  const newModels = ["claude", "codex", "gemini"].flatMap(source => seenModels(ctx.store, source)).filter(model => isNewModel(model, ctx.now)).map(model => ({ name: model.name, id: model.id, price: priceWords(model), released: model.releasedAt }));
1613
- return { ok: true, body: { roles, tools, newModels, change: "/settings/models" } };
1614
+ // Results routing: the same suggestions and rules Settings → Models and `toolroll models suggestions` show.
1615
+ const suggestions = ctx.who.repos.flatMap(repo => suggestionsFor(ctx.store, repo, ctx.now)).map(one => ({ id: one.id, repo: one.repo, words: suggestionWords(one),
1616
+ review: `/settings/models?scope=${encodeURIComponent(one.repo)}&suggestion=${encodeURIComponent(one.id)}#models-suggested` }));
1617
+ const rules = ctx.who.repos.flatMap(repo => rulesOf(ctx.store, repo, ctx.now)).map(one => ({ id: one.id, repo: one.repo, state: one.state, words: earnedRuleWords(ctx.store, one) }));
1618
+ const trialOffers = ctx.who.repos.flatMap(repo => trialOffersFor(ctx.store, repo, ctx.now)).map(one => ({ id: one.id, repo: one.repo, words: trialOfferWords(one),
1619
+ start: `/settings/models?scope=${encodeURIComponent(one.repo)}#models-suggested` }));
1620
+ const trials = ctx.who.repos.flatMap(repo => trialsOf(ctx.store, repo, ctx.now)).map(one => ({ id: one.id, repo: one.repo, state: one.state, words: trialWords(ctx.store, one) }));
1621
+ return { ok: true, body: { roles, tools, newModels, suggestions, rules, trialOffers, trials, change: "/settings/models" } };
1614
1622
  },
1615
1623
  },
1616
1624
  list_tasks: {
@@ -10,7 +10,7 @@ export declare function authorityOf(value: unknown): Authority | null;
10
10
  * The acts a local owner may do (plan C1). Ids only: each surface (a route row, a chat action, a CLI command) names
11
11
  * the act it is, and an act not in this list is refused to a local owner.
12
12
  */
13
- export declare const LOCAL_OWNER_ACTS: readonly ["file-task", "lead-send", "approve-plan", "answer-question", "stop-run", "pause-run", "resume-run", "complete-task", "request-revision", "archive-task", "add-project", "choose-default-agent", "mark-seen", "watch-agent", "read"];
13
+ export declare const LOCAL_OWNER_ACTS: readonly ["file-task", "lead-send", "approve-plan", "answer-question", "stop-run", "pause-run", "resume-run", "complete-task", "request-revision", "archive-task", "add-project", "choose-default-agent", "mark-seen", "watch-agent", "read", "routing-turn-off", "dismiss-suggestion", "trial-stop"];
14
14
  export type LocalOwnerAct = (typeof LOCAL_OWNER_ACTS)[number];
15
15
  /** Whether a runtime string names an act a local owner may do. */
16
16
  export declare function localOwnerAct(value: unknown): value is LocalOwnerAct;
@@ -32,6 +32,13 @@ export const LOCAL_OWNER_ACTS = Object.freeze([
32
32
  "mark-seen",
33
33
  "watch-agent",
34
34
  "read",
35
+ // Results routing (docs/plans/results-routing.md, Authority): lowering only — turning an earned rule off (or
36
+ // dropping a paused one), and saying Not now to a suggestion (or switching suggestions off). The writer
37
+ // (route-store.ts proveRuleDoor) checks the in-flight act is exactly the one it needs.
38
+ "routing-turn-off",
39
+ "dismiss-suggestion",
40
+ // Slice 3: stopping a trial of a cheaper model only lowers (proveRuleDoor checks this exact act).
41
+ "trial-stop",
35
42
  ]);
36
43
  const ACT_SET = new Set(LOCAL_OWNER_ACTS);
37
44
  /** Whether a runtime string names an act a local owner may do. */
@@ -150,6 +157,10 @@ export const CHAT_ACTION_LOCAL_ACTS = Object.freeze({
150
157
  subagent_create: null, subagent_soul: null, subagent_state: null, subagent_note: null, subagent_ask: null,
151
158
  subagent_answer: null, subagent_tools: null, subagent_memory: null, subagent_routine: null, subagent_undo: null,
152
159
  kit_setup: null, decision_record: null, decision_retire: null, lead_about_you: null,
160
+ // Results routing: Not now only lowers (the rule door checks this exact act).
161
+ suggestion_not_now: "dismiss-suggestion",
162
+ // Stopping a trial only lowers (the rule door checks this exact act).
163
+ trial_stop: "trial-stop",
153
164
  scope_approve: "approve-plan",
154
165
  result_accept: "complete-task",
155
166
  task_cancel: "stop-run",
@@ -200,6 +200,7 @@ export declare const MEMORY_DESCRIPTORS: readonly [{
200
200
  readonly takesValue: false;
201
201
  readonly meaning: 'read only the plane\'s own transcripts, not local Claude Code or Codex session files';
202
202
  }];
203
+ readonly audience: 'operator';
203
204
  }, {
204
205
  readonly action: 'retire';
205
206
  readonly synopsis: 'retire a decision that no longer holds: memory retire <id> --reason "<why>"';
@@ -250,6 +251,7 @@ export declare const MEMORY_DESCRIPTORS: readonly [{
250
251
  readonly takesValue: false;
251
252
  readonly meaning: 'read only the plane\'s own transcripts, not local Claude Code or Codex session files';
252
253
  }];
254
+ readonly audience: 'operator';
253
255
  }, {
254
256
  readonly action: 'propose';
255
257
  readonly synopsis: 'the backward pass: read recent sessions, keep a ledger of evidenced gaps, propose a few edits (writes nothing to memory)';
@@ -400,6 +402,7 @@ export declare const MEMORY_DESCRIPTORS: readonly [{
400
402
  readonly takesValue: false;
401
403
  readonly meaning: 'read only the plane\'s own transcripts, not local Claude Code or Codex session files';
402
404
  }];
405
+ readonly audience: 'operator';
403
406
  }, {
404
407
  readonly action: 'status';
405
408
  readonly synopsis: 'surface version, instruction budget, sessions analysed, open gaps and pending proposals';