toolroll 0.9.34 → 0.9.37

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 (195) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +7 -0
  3. package/dist/assignment-adapters.d.ts +12 -48
  4. package/dist/assignment-adapters.js +33 -43
  5. package/dist/assignment.d.ts +1 -0
  6. package/dist/assignment.js +2 -2
  7. package/dist/browser-workspace.d.ts +3 -3
  8. package/dist/build-review.d.ts +1 -1
  9. package/dist/build-review.js +2 -1
  10. package/dist/builder.js +4 -7
  11. package/dist/chat-acceptance.d.ts +2 -2
  12. package/dist/chat-actions.d.ts +18 -12
  13. package/dist/chat-actions.js +15 -65
  14. package/dist/chat-ask.js +5 -5
  15. package/dist/chat-decide.d.ts +14 -1
  16. package/dist/chat-decide.js +12 -6
  17. package/dist/chat-delivery-state.d.ts +12 -70
  18. package/dist/chat-delivery-state.js +17 -135
  19. package/dist/chat-delivery.js +58 -30
  20. package/dist/chat-flow.js +8 -6
  21. package/dist/chat-inbox.js +1 -1
  22. package/dist/coding-context.d.ts +2 -39
  23. package/dist/coding-context.js +2 -1
  24. package/dist/coding-handoff.d.ts +6 -25
  25. package/dist/coding-handoff.js +11 -5
  26. package/dist/coding-types.d.ts +2 -26
  27. package/dist/coding-workspace.d.ts +2 -0
  28. package/dist/coding-workspace.js +50 -14
  29. package/dist/contracts/acceptance-terms.d.ts +14 -0
  30. package/dist/contracts/acceptance-terms.js +16 -0
  31. package/dist/contracts/chat-actions.d.ts +328 -0
  32. package/dist/contracts/chat-actions.js +133 -0
  33. package/dist/contracts/chat-callback-rows.d.ts +59 -0
  34. package/dist/contracts/chat-callback-rows.js +46 -0
  35. package/dist/contracts/chat-content.d.ts +276 -0
  36. package/dist/contracts/chat-content.js +127 -0
  37. package/dist/contracts/chat-tables.d.ts +6 -0
  38. package/dist/contracts/chat-tables.js +136 -0
  39. package/dist/contracts/coding-context.d.ts +97 -0
  40. package/dist/contracts/coding-context.js +60 -0
  41. package/dist/contracts/coding-handoff.d.ts +44 -0
  42. package/dist/contracts/coding-handoff.js +47 -0
  43. package/dist/contracts/coding-workspace.d.ts +165 -0
  44. package/dist/contracts/coding-workspace.js +71 -0
  45. package/dist/contracts/contract.d.ts +40 -7
  46. package/dist/contracts/contract.js +155 -19
  47. package/dist/contracts/decision.d.ts +45 -0
  48. package/dist/contracts/decision.js +45 -0
  49. package/dist/contracts/discord-callback.d.ts +45 -0
  50. package/dist/contracts/discord-callback.js +34 -0
  51. package/dist/contracts/flow-card.d.ts +50 -0
  52. package/dist/contracts/flow-card.js +46 -0
  53. package/dist/contracts/flow-propose.d.ts +325 -0
  54. package/dist/contracts/flow-propose.js +41 -0
  55. package/dist/contracts/flow-send.d.ts +119 -0
  56. package/dist/contracts/flow-send.js +112 -0
  57. package/dist/contracts/flow.d.ts +19405 -0
  58. package/dist/contracts/flow.js +364 -0
  59. package/dist/contracts/gateway-tools.d.ts +374 -0
  60. package/dist/contracts/gateway-tools.js +94 -0
  61. package/dist/contracts/handoff.d.ts +91 -0
  62. package/dist/contracts/handoff.js +82 -0
  63. package/dist/contracts/lead-context.d.ts +165 -0
  64. package/dist/contracts/lead-context.js +73 -0
  65. package/dist/contracts/lead-tools.d.ts +1905 -0
  66. package/dist/contracts/lead-tools.js +265 -0
  67. package/dist/contracts/memory-pass.d.ts +158 -0
  68. package/dist/contracts/memory-pass.js +102 -0
  69. package/dist/contracts/project-knowledge.d.ts +126 -0
  70. package/dist/contracts/project-knowledge.js +93 -0
  71. package/dist/contracts/project-memory.d.ts +117 -0
  72. package/dist/contracts/project-memory.js +51 -0
  73. package/dist/contracts/project-skills.d.ts +121 -0
  74. package/dist/contracts/project-skills.js +76 -0
  75. package/dist/contracts/proof.d.ts +136 -0
  76. package/dist/contracts/proof.js +110 -0
  77. package/dist/contracts/review-findings.d.ts +46 -0
  78. package/dist/contracts/review-findings.js +48 -0
  79. package/dist/contracts/route.d.ts +484 -0
  80. package/dist/contracts/route.js +128 -0
  81. package/dist/contracts/scope.d.ts +105 -0
  82. package/dist/contracts/scope.js +114 -0
  83. package/dist/contracts/scout-report.d.ts +234 -0
  84. package/dist/contracts/scout-report.js +141 -0
  85. package/dist/contracts/slack-callback.d.ts +43 -0
  86. package/dist/contracts/slack-callback.js +35 -0
  87. package/dist/contracts/sort-answer.d.ts +83 -0
  88. package/dist/contracts/sort-answer.js +77 -0
  89. package/dist/contracts/stage-output.d.ts +127 -0
  90. package/dist/contracts/stage-output.js +166 -0
  91. package/dist/contracts/task-sizing.d.ts +24 -0
  92. package/dist/contracts/task-sizing.js +28 -0
  93. package/dist/contracts/teammate-turn.d.ts +43 -0
  94. package/dist/contracts/teammate-turn.js +51 -0
  95. package/dist/contracts/teams-callback.d.ts +14 -0
  96. package/dist/contracts/teams-callback.js +14 -0
  97. package/dist/contracts/telegram-callback.d.ts +120 -0
  98. package/dist/contracts/telegram-callback.js +97 -0
  99. package/dist/contracts/verification-receipt.d.ts +148 -0
  100. package/dist/contracts/verification-receipt.js +87 -0
  101. package/dist/decision.d.ts +11 -20
  102. package/dist/decision.js +86 -167
  103. package/dist/demo.js +28 -11
  104. package/dist/discord-chat.js +31 -20
  105. package/dist/evidence.d.ts +3 -40
  106. package/dist/evidence.js +5 -1
  107. package/dist/flow-engine.js +14 -17
  108. package/dist/flow-gallery.d.ts +3 -2
  109. package/dist/flow-items.d.ts +4 -13
  110. package/dist/flow-items.js +1 -15
  111. package/dist/flow-send.d.ts +6 -37
  112. package/dist/flow-send.js +18 -14
  113. package/dist/flow-share.d.ts +10 -32
  114. package/dist/flow-share.js +72 -84
  115. package/dist/flow-sort.d.ts +7 -26
  116. package/dist/flow-sort.js +25 -20
  117. package/dist/flow-starters.d.ts +2 -1
  118. package/dist/flow-steps.js +3 -2
  119. package/dist/flow-triggers.d.ts +15 -72
  120. package/dist/flow-triggers.js +94 -83
  121. package/dist/flows.d.ts +37 -220
  122. package/dist/flows.js +356 -387
  123. package/dist/invoke.js +4 -4
  124. package/dist/lead-context.d.ts +4 -2
  125. package/dist/lead-context.js +24 -17
  126. package/dist/mate-tools.d.ts +4 -0
  127. package/dist/mate-tools.js +111 -308
  128. package/dist/mcp-connect.d.ts +27 -4
  129. package/dist/mcp-connect.js +92 -17
  130. package/dist/mcp.js +84 -216
  131. package/dist/memory-pass.d.ts +17 -42
  132. package/dist/memory-pass.js +54 -26
  133. package/dist/operate.js +31 -2
  134. package/dist/phase-routing.d.ts +19 -72
  135. package/dist/phase-routing.js +34 -164
  136. package/dist/plan.d.ts +5 -5
  137. package/dist/plan.js +5 -25
  138. package/dist/process-recovery-eligibility.d.ts +2 -2
  139. package/dist/process-recovery-eligibility.js +13 -8
  140. package/dist/project-knowledge.d.ts +16 -35
  141. package/dist/project-knowledge.js +42 -21
  142. package/dist/project-memory.d.ts +2 -28
  143. package/dist/project-memory.js +28 -13
  144. package/dist/project-skills.d.ts +16 -38
  145. package/dist/project-skills.js +41 -32
  146. package/dist/project-tools.d.ts +1 -0
  147. package/dist/project-tools.js +4 -4
  148. package/dist/proof.d.ts +15 -91
  149. package/dist/proof.js +90 -249
  150. package/dist/provider.d.ts +2 -0
  151. package/dist/provider.js +4 -16
  152. package/dist/result-evidence-readers.d.ts +42 -0
  153. package/dist/result-evidence-readers.js +91 -0
  154. package/dist/result-shots.d.ts +1 -1
  155. package/dist/result-shots.js +2 -1
  156. package/dist/review-switch.d.ts +3 -8
  157. package/dist/reviewer.d.ts +8 -12
  158. package/dist/reviewer.js +11 -35
  159. package/dist/scope.d.ts +21 -43
  160. package/dist/scope.js +42 -128
  161. package/dist/scout-report.d.ts +9 -194
  162. package/dist/scout-report.js +113 -264
  163. package/dist/scout.js +15 -11
  164. package/dist/serve.js +17 -101
  165. package/dist/slack-chat.js +26 -20
  166. package/dist/slack-state.js +2 -1
  167. package/dist/store.d.ts +9 -32
  168. package/dist/store.js +18 -15
  169. package/dist/structured-output.js +2 -1
  170. package/dist/subscription-chat.d.ts +4 -0
  171. package/dist/subscription-chat.js +4 -1
  172. package/dist/surface.js +8 -1
  173. package/dist/task-review-brief.d.ts +69 -0
  174. package/dist/task-review-brief.js +179 -0
  175. package/dist/task-sizing.d.ts +3 -27
  176. package/dist/task-sizing.js +11 -26
  177. package/dist/task-text.d.ts +0 -5
  178. package/dist/task-text.js +1 -3
  179. package/dist/teammate-question.js +7 -5
  180. package/dist/teammate-work.js +9 -5
  181. package/dist/teammates.d.ts +13 -64
  182. package/dist/teammates.js +32 -33
  183. package/dist/teams-chat.js +20 -11
  184. package/dist/telegram-flow.js +4 -3
  185. package/dist/telegram-mate.d.ts +4 -14
  186. package/dist/telegram-mate.js +8 -7
  187. package/dist/telegram-team.d.ts +2 -19
  188. package/dist/telegram.d.ts +1 -1
  189. package/dist/telegram.js +52 -42
  190. package/dist/text-limits.d.ts +212 -1
  191. package/dist/text-limits.js +214 -1
  192. package/dist/tools-ui.d.ts +2 -1
  193. package/dist/tools-ui.js +7 -4
  194. package/dist/verification-evidence.js +24 -12
  195. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,107 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.37 — 2026-10-06
4
+
5
+ - **Zod revamp, waves 1 and 2 finished.** Every handoff between a model and
6
+ Toolroll, and the context passed between steps, is now one schema each in
7
+ `src/contracts/`: the lead's 51 tools and the 25 MCP gateway tools, the
8
+ scout report, task sizing, reviewer findings, decisions, teammate turns and
9
+ sort answers, the lead's context bundle, project knowledge, memory and
10
+ skills, chat actions and channel callbacks, and the desktop coding handoff,
11
+ context and session record. A model gets the schema as `--json-schema`
12
+ where its harness takes one; a refusal names the field
13
+ (`steps[0].routes[0].goesTo: required`). Everything that was accepted before
14
+ is still accepted: tool calls with extra or loose arguments, model answers
15
+ with nulls or overlong text (clipped as before), saved reports, rows,
16
+ sessions, chat buttons and proposals. Replayed against real saved data
17
+ (scout reports, findings, decisions, 410 scopes and 796 routes, 279 chat
18
+ buttons, every saved flow and card), all of it reads the same.
19
+ - **Research can read PostHog.** A research run gets a connected service's
20
+ query tool (PostHog's `exec`) only when the access the service actually
21
+ granted is read-only. Otherwise it skips it and says why on the run and the
22
+ integration, with a **Reconnect read-only** action. Connecting PostHog as
23
+ usual is unchanged, and builds keep every tool. Connections made before this
24
+ release didn't record what was granted, so research skips them until you
25
+ reconnect read-only.
26
+ - **A question's recommendation can carry its reason.** `connect: because…`
27
+ now counts as recommending `connect`, instead of failing the question.
28
+ - **A Telegram update Toolroll can't read** is logged and answered OK, so
29
+ Telegram doesn't keep resending it. A Slack, Discord or Teams message part
30
+ that can't be read is dropped with its reason instead of retried forever.
31
+ - **Rolling back:** flow cards written by 0.9.37 read as empty in 0.9.36, so a
32
+ rollback loses the step outputs of cards still in progress.
33
+ - **Scopes, acceptance criteria and sealed routes have one contract.** A task's
34
+ scope (goal, exclusions, touch paths, rubric) and an acceptance criterion are
35
+ described once from the plan contract's own fields (`src/contracts/scope.ts`);
36
+ a sealed agent route, in both versions it has been saved in, with its
37
+ overrides and task size, is one schema (`src/contracts/route.ts`). The shared
38
+ rubric input schema is ready for the lead-tool migration. Every saved scope,
39
+ standing order and sealed route reads and digests exactly as before, so no
40
+ approval changes.
41
+ Nothing new is refused: a scope still names up to 50 paths and a criterion
42
+ still ignores keys it doesn't use. Rubric and route refusals are now
43
+ field-named lines (`acceptance[0].statement over 1,000 bytes`), and a route
44
+ made by a newer Toolroll says so.
45
+ - **What a flow's steps hand on has one contract.** A finished zone's
46
+ `{{stage.…}}` words, a card's saved outputs, and what Send to me and Person
47
+ chooses send are each described once (`src/contracts/stage-output.ts`,
48
+ `flow-send.ts`, `flow-card.ts`). Saved cards and sends read exactly as
49
+ before. A build or research task filed after a step whose output was too
50
+ long to pass on now gets that output whole, not just a link to it.
51
+ - **Tightened, on purpose.** Saving a flow with a new `{{stage.…}}` that its
52
+ zone doesn't hand on is refused, naming where:
53
+ `stages[2].instructions: stage.research.unknown is not available (Research
54
+ hands on {{stage.research}}, {{stage.research.items}} and
55
+ {{stage.research.report}})`. This covers the canvas, `toolroll flows
56
+ create/edit/import`, the lead and templates. A reference a flow already had
57
+ is never refused, and nothing changes for flows that are only read or run.
58
+ Card outputs and kept sends are saved with a version; ones a newer Toolroll
59
+ wrote are refused rather than misread or overwritten.
60
+
61
+ ## 0.9.36 — 2026-10-06
62
+
63
+ - **Flows have one contract.** Every kind of zone, step, route, answer,
64
+ option, trigger and the flow file is described once
65
+ (`src/contracts/flow.ts`), and that description is what the lead's
66
+ `propose_flow` is told, what `toolroll flows create/edit/import` and the
67
+ console check, and what `docs/flow-file.schema.json` publishes (now
68
+ generated, so it includes Send to me, Person chooses and `ifReplied`, which
69
+ the hand-written one had missed). A refusal names the field and suggests the
70
+ one meant: `steps[0].routes[0]: unknown key 'to' (did you mean goesTo?)`,
71
+ `zones[2]: unknown key 'onFail' (did you mean ifFails?)`, instead of
72
+ "there's no step called ." Every saved flow, template, starter, kit and
73
+ gallery flow reads exactly as before: the same saved bytes and digests, and
74
+ the same exported files.
75
+ - **Tightened, on purpose.** A step, flow file zone, trigger setting or
76
+ `propose_flow` argument with a key its kind doesn't take is refused by name
77
+ rather than silently ignored: `planning` on a Holding step, `remindAfter` on
78
+ a Wait step, `ifFails` on a Sort step (use `ifNotSure`, which it meant) or
79
+ `next` on a Sort or Person chooses step. A sort's `sureAt` is 0.5–99. A flow
80
+ file's zones keep only the words their kind uses when exported, and a file
81
+ or drawing made by a newer Toolroll is refused as such. Messages for flows
82
+ and triggers changed from "Zone Build: …" sentences to path-named lines.
83
+
84
+ - **Builder handoffs and proofs have one contract.** The handoff a builder
85
+ leaves, its proof and the verification receipt are versioned schemas. Fifteen
86
+ real past runs replay with identical verdicts, receipt bytes and digests.
87
+ - **One compact review packet.** `toolroll task review <id> --brief` (or
88
+ `--json`) shows a build's conclusion, changed files, check result, review
89
+ findings by severity, screenshot paths and what to do next, in about 40
90
+ lines and without logs or diffs.
91
+ - **PostHog connects in one short step.** Its sign-in now uses PostHog's own
92
+ MCP permission preset instead of listing every permission, which showed a
93
+ 75-group permission screen. Sign-in returns are now logged.
94
+ - **Tests can't mistake a real process for a fake one.** Fake process IDs in
95
+ tests sit above every platform's limit, and a guard refuses new low ones (a
96
+ Brave tab and a GitHub runner process had made tests fail at random).
97
+
98
+ ## 0.9.35 — 2026-10-05
99
+
100
+ - **One-click sign-ins ask for every permission a service lists.** Toolroll
101
+ asked for only the first 20, so a service needing one later in its list
102
+ issued a token it then refused (PostHog needs `user:read`, its 140th).
103
+ Reconnect a service that failed its test after signing in.
104
+
3
105
  ## 0.9.34 — 2026-10-05
4
106
 
5
107
  - **Plans have one contract.** The first step of making every handoff robust:
package/README.md CHANGED
@@ -117,10 +117,17 @@ toolroll project use /path/to/project
117
117
  toolroll project show
118
118
  toolroll brief
119
119
  toolroll assignment show <task>
120
+ toolroll task review <task> --brief
120
121
  toolroll task complete <task>
121
122
  toolroll task revise <task> --feedback "Keep the filter selected after reload."
122
123
  ```
123
124
 
125
+ `task review <task> --brief` combines the latest finished build or report's conclusion,
126
+ file counts, checks, review findings, screenshot paths, plan and next actions.
127
+ Use `--run <id>` for an earlier attempt, `--json` for the same fields, and `--all`
128
+ to include LOW findings. Lists stay bounded, with omitted entries counted; missing
129
+ fields say `not recorded`. The lead token works through `--token` or `TOOLROLL_LEAD_TOKEN`.
130
+
124
131
  Completion and revisions use the local sign-in already saved by `up`. Completion
125
132
  reports the exact run, commit and check outcome it handled. Agents use a scoped
126
133
  credential (`project use /path/to/project --token-file /path/to/credential`) and
@@ -1,87 +1,52 @@
1
1
  import { type AssignmentSnapshot } from "./assignment.js";
2
2
  import type { Store } from "./store.js";
3
- export declare const ASSIGNMENT_ACTIONS: readonly ["show", "updates", "claim", "check", "brief", "inbox", "ack"];
4
- export type AssignmentOperation = typeof ASSIGNMENT_ACTIONS[number];
3
+ import { type AssignmentInput, type AssignmentOperation } from "./contracts/gateway-tools.js";
4
+ export declare const ASSIGNMENT_ACTIONS: AssignmentOperation[];
5
+ export type { AssignmentOperation };
5
6
  type Args = Record<string, unknown>;
6
7
  type Failure = {
7
8
  ok: false;
8
9
  reason: string;
9
10
  message: string;
10
11
  };
12
+ /** The gateway's assignment tools: names and words. Each one's input is ASSIGNMENT_INPUTS[operation]
13
+ * (src/contracts/gateway-tools.ts), which `toolroll assignment` reads its flags with too. */
11
14
  export declare const ASSIGNMENT_TOOLS: readonly [{
12
15
  readonly name: "get_assignment";
13
16
  readonly operation: "show";
14
17
  readonly description: "Read one assignment's root, current execution, owner, exact result and handoff receipt. Approval, proof and deployment remain separate facts.";
15
- readonly inputSchema: {
16
- type: string;
17
- properties: Record<string, unknown>;
18
- required: string[];
19
- additionalProperties: boolean;
20
- };
21
18
  }, {
22
19
  readonly name: "list_assignment_updates";
23
20
  readonly operation: "updates";
24
21
  readonly description: "Read durable assignment updates in your projects after a cursor. Save nextCursor after processing; repeated reads do not acknowledge or deliver anything.";
25
- readonly inputSchema: {
26
- type: string;
27
- properties: Record<string, unknown>;
28
- required: string[];
29
- additionalProperties: boolean;
30
- };
31
22
  }, {
32
23
  readonly name: "claim_assignment";
33
24
  readonly operation: "claim";
34
25
  readonly description: "Record yourself as this assignment's lead. Repeating your claim is safe; another active owner cannot be replaced. This grants no approval or execution authority.";
35
- readonly inputSchema: {
36
- type: string;
37
- properties: Record<string, unknown>;
38
- required: string[];
39
- additionalProperties: boolean;
40
- };
41
26
  }, {
42
27
  readonly name: "acknowledge_assignment";
43
28
  readonly operation: "check";
44
29
  readonly description: "Acknowledge the exact ready-to-check receipt as its lead, using the current receipt digest. This does not accept failed proof, answer decisions, approve work, publish or deploy.";
45
- readonly inputSchema: {
46
- type: string;
47
- properties: Record<string, unknown>;
48
- required: string[];
49
- additionalProperties: boolean;
50
- };
51
30
  }, {
52
31
  readonly name: "get_assignment_brief";
53
32
  readonly operation: "brief";
54
33
  readonly description: "Catch up from the local database: current work, decisions, saved results and project knowledge. Bounded read only; no model call or task mutation.";
55
- readonly inputSchema: {
56
- type: string;
57
- properties: Record<string, unknown>;
58
- required: string[];
59
- additionalProperties: boolean;
60
- };
61
34
  }, {
62
35
  readonly name: "get_assignment_inbox";
63
36
  readonly operation: "inbox";
64
37
  readonly description: "Receive a durable batch of your assignments' status changes. Use a stable consumer name. Until acknowledged, the same batch survives reconnects and restarts. Receiving never completes or reruns work.";
65
- readonly inputSchema: {
66
- type: string;
67
- properties: Record<string, unknown>;
68
- required: string[];
69
- additionalProperties: boolean;
70
- };
71
38
  }, {
72
39
  readonly name: "acknowledge_assignment_delivery";
73
40
  readonly operation: "ack";
74
41
  readonly description: "Acknowledge receipt of an inbox batch after handling its updates. This advances only your delivery cursor; it never marks work complete, answers a decision or starts a revision.";
75
- readonly inputSchema: {
76
- type: string;
77
- properties: Record<string, unknown>;
78
- required: string[];
79
- additionalProperties: boolean;
80
- };
81
42
  }];
82
- /** The CLI and MCP use the same bounded input contract. MCP also validates its
83
- * descriptor before dispatch; this check protects direct adapter callers. */
84
- export declare function assignmentArgumentProblem(operation: AssignmentOperation, args: Args): string | null;
43
+ /** The CLI and MCP read an assignment call with the same schema; MCP reads it before dispatch too, and this protects
44
+ * direct adapter callers. A refusal is its path-named lines; a ref or project path with control characters, which
45
+ * JSON Schema can't say, is refused after parsing. */
46
+ export declare function readAssignmentArgs<O extends AssignmentOperation>(operation: O, args: Args): {
47
+ ok: true;
48
+ value: AssignmentInput<O>;
49
+ } | Failure;
85
50
  /** Every read and mutation authenticates inside the same transaction as its
86
51
  * projection. A cached name/allowlist cannot keep a revoked identity alive. */
87
52
  export declare function assignmentForCoordinator(store: Store, token: string, operation: AssignmentOperation, args: Args, now: Date, evidenceRoot?: string): Failure | {
@@ -179,4 +144,3 @@ export declare function coordinatorToken(flags: Map<string, string | true>, env:
179
144
  token: string | null;
180
145
  } | Failure;
181
146
  export declare function runAssignmentCommand(positional: readonly string[], flags: Map<string, string | true>, context: AssignmentCliContext): number;
182
- export {};
@@ -8,47 +8,36 @@ import { assignmentCatchUp } from "./assignment-brief.js";
8
8
  import { assignmentInbox, acknowledgeAssignmentDelivery } from "./assignment-delivery.js";
9
9
  import { syncAssignmentStatuses } from "./assignment-status.js";
10
10
  import { envelopeJson } from "./envelope.js";
11
- export const ASSIGNMENT_ACTIONS = ["show", "updates", "claim", "check", "brief", "inbox", "ack"];
11
+ import { parseContract } from "./contracts/contract.js";
12
+ import { ASSIGNMENT_INPUTS } from "./contracts/gateway-tools.js";
13
+ export const ASSIGNMENT_ACTIONS = Object.keys(ASSIGNMENT_INPUTS);
12
14
  const failure = (reason, message) => ({ ok: false, reason, message });
13
- const reference = { type: "string", minLength: 1, maxLength: 64 };
14
- const digest = { type: "string", minLength: 64, maxLength: 64, pattern: "^[a-f0-9]{64}$" };
15
- const batchId = { type: "string", minLength: 32, maxLength: 32, pattern: "^[a-f0-9]{32}$" };
16
- const consumer = { type: "string", minLength: 1, maxLength: 64, pattern: "^[a-z0-9][a-z0-9-]{0,63}$" };
17
- const object = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
15
+ /** The gateway's assignment tools: names and words. Each one's input is ASSIGNMENT_INPUTS[operation]
16
+ * (src/contracts/gateway-tools.ts), which `toolroll assignment` reads its flags with too. */
18
17
  export const ASSIGNMENT_TOOLS = [
19
- { name: "get_assignment", operation: "show", description: "Read one assignment's root, current execution, owner, exact result and handoff receipt. Approval, proof and deployment remain separate facts.", inputSchema: object({ ref: reference }, ["ref"]) },
20
- { name: "list_assignment_updates", operation: "updates", description: "Read durable assignment updates in your projects after a cursor. Save nextCursor after processing; repeated reads do not acknowledge or deliver anything.", inputSchema: object({ after: { type: "integer", minimum: 0, maximum: Number.MAX_SAFE_INTEGER }, limit: { type: "integer", minimum: 1, maximum: 100 } }) },
21
- { name: "claim_assignment", operation: "claim", description: "Record yourself as this assignment's lead. Repeating your claim is safe; another active owner cannot be replaced. This grants no approval or execution authority.", inputSchema: object({ ref: reference }, ["ref"]) },
22
- { name: "acknowledge_assignment", operation: "check", description: "Acknowledge the exact ready-to-check receipt as its lead, using the current receipt digest. This does not accept failed proof, answer decisions, approve work, publish or deploy.", inputSchema: object({ ref: reference, digest }, ["ref", "digest"]) },
23
- { name: "get_assignment_brief", operation: "brief", description: "Catch up from the local database: current work, decisions, saved results and project knowledge. Bounded read only; no model call or task mutation.", inputSchema: object({ repo: { type: "string", minLength: 1, maxLength: 4096 }, limit: { type: "integer", minimum: 1, maximum: 25 } }) },
24
- { name: "get_assignment_inbox", operation: "inbox", description: "Receive a durable batch of your assignments' status changes. Use a stable consumer name. Until acknowledged, the same batch survives reconnects and restarts. Receiving never completes or reruns work.", inputSchema: object({ consumer, limit: { type: "integer", minimum: 1, maximum: 100 } }, ["consumer"]) },
25
- { name: "acknowledge_assignment_delivery", operation: "ack", description: "Acknowledge receipt of an inbox batch after handling its updates. This advances only your delivery cursor; it never marks work complete, answers a decision or starts a revision.", inputSchema: object({ consumer, batchId }, ["consumer", "batchId"]) },
18
+ { name: "get_assignment", operation: "show", description: "Read one assignment's root, current execution, owner, exact result and handoff receipt. Approval, proof and deployment remain separate facts." },
19
+ { name: "list_assignment_updates", operation: "updates", description: "Read durable assignment updates in your projects after a cursor. Save nextCursor after processing; repeated reads do not acknowledge or deliver anything." },
20
+ { name: "claim_assignment", operation: "claim", description: "Record yourself as this assignment's lead. Repeating your claim is safe; another active owner cannot be replaced. This grants no approval or execution authority." },
21
+ { name: "acknowledge_assignment", operation: "check", description: "Acknowledge the exact ready-to-check receipt as its lead, using the current receipt digest. This does not accept failed proof, answer decisions, approve work, publish or deploy." },
22
+ { name: "get_assignment_brief", operation: "brief", description: "Catch up from the local database: current work, decisions, saved results and project knowledge. Bounded read only; no model call or task mutation." },
23
+ { name: "get_assignment_inbox", operation: "inbox", description: "Receive a durable batch of your assignments' status changes. Use a stable consumer name. Until acknowledged, the same batch survives reconnects and restarts. Receiving never completes or reruns work." },
24
+ { name: "acknowledge_assignment_delivery", operation: "ack", description: "Acknowledge receipt of an inbox batch after handling its updates. This advances only your delivery cursor; it never marks work complete, answers a decision or starts a revision." },
26
25
  ];
27
- /** The CLI and MCP use the same bounded input contract. MCP also validates its
28
- * descriptor before dispatch; this check protects direct adapter callers. */
29
- export function assignmentArgumentProblem(operation, args) {
26
+ /** The CLI and MCP read an assignment call with the same schema; MCP reads it before dispatch too, and this protects
27
+ * direct adapter callers. A refusal is its path-named lines; a ref or project path with control characters, which
28
+ * JSON Schema can't say, is refused after parsing. */
29
+ export function readAssignmentArgs(operation, args) {
30
30
  if (!ASSIGNMENT_ACTIONS.includes(operation))
31
- return "Choose show, updates, claim, check, brief, inbox, or ack.";
32
- const allowed = operation === "brief" ? ["repo", "limit"] : operation === "inbox" ? ["consumer", "limit"] : operation === "ack" ? ["consumer", "batchId"] : operation === "updates" ? ["after", "limit"] : operation === "check" ? ["ref", "digest"] : ["ref"];
33
- for (const key of Object.keys(args))
34
- if (!allowed.includes(key))
35
- return `Unknown argument: ${key}.`;
36
- if (["show", "claim", "check"].includes(operation) && (typeof args["ref"] !== "string" || args["ref"].length < 1 || args["ref"].length > 64 || /[\u0000-\u001f\u007f]/.test(args["ref"])))
37
- return "Choose a task id, 1–64 characters without control characters.";
38
- if (operation === "check" && (typeof args["digest"] !== "string" || !/^[a-f0-9]{64}$/.test(args["digest"])))
39
- return "Use the exact 64-character receipt digest from assignment show.";
40
- if ((operation === "inbox" || operation === "ack") && (typeof args["consumer"] !== "string" || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(args["consumer"])))
41
- return "Use a stable consumer name with 1–64 lowercase letters, digits or hyphens.";
42
- if (operation === "ack" && (typeof args["batchId"] !== "string" || !/^[a-f0-9]{32}$/.test(args["batchId"])))
43
- return "Use the exact batchId from assignment inbox.";
44
- if (args["repo"] !== undefined && (typeof args["repo"] !== "string" || args["repo"].length < 1 || args["repo"].length > 4096 || /[\u0000-\u001f\u007f]/.test(args["repo"])))
45
- return "Choose a project path without control characters.";
46
- for (const [name, min, max] of [["after", 0, Number.MAX_SAFE_INTEGER], ["limit", 1, operation === "brief" ? 25 : 100]]) {
47
- const value = args[name];
48
- if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max))
49
- return `${name} must be an integer from ${min} to ${max}.`;
31
+ return failure("usage", "Choose show, updates, claim, check, brief, inbox, or ack.");
32
+ const read = parseContract(ASSIGNMENT_INPUTS[operation], args);
33
+ if (!read.ok)
34
+ return failure("usage", read.issues.map(one => one.line).join("; "));
35
+ const value = read.value;
36
+ for (const field of ["ref", "repo"]) {
37
+ if (typeof value[field] === "string" && /[\u0000-\u001f\u007f]/.test(value[field]))
38
+ return failure("usage", `${field}: must not contain control characters`);
50
39
  }
51
- return null;
40
+ return { ok: true, value };
52
41
  }
53
42
  function readAssignment(store, operation, args, now, access, root) {
54
43
  if (operation === "brief")
@@ -61,9 +50,10 @@ function readAssignment(store, operation, args, now, access, root) {
61
50
  /** Every read and mutation authenticates inside the same transaction as its
62
51
  * projection. A cached name/allowlist cannot keep a revoked identity alive. */
63
52
  export function assignmentForCoordinator(store, token, operation, args, now, evidenceRoot) {
64
- const problem = assignmentArgumentProblem(operation, args);
65
- if (problem !== null)
66
- return failure("usage", problem);
53
+ const read = readAssignmentArgs(operation, args);
54
+ if (!read.ok)
55
+ return read;
56
+ args = read.value;
67
57
  // Authenticate before maintenance, then authenticate again inside delivery.
68
58
  // Reconciliation owns one short transaction per family and cannot sit under
69
59
  // the delivery transaction's writer reservation.
@@ -225,11 +215,11 @@ export function runAssignmentCommand(positional, flags, context) {
225
215
  for (const flag of operationFlags) {
226
216
  const value = flags.get(flag);
227
217
  if (value !== undefined)
228
- args[flag === "batch" ? "batchId" : flag] = ["after", "limit"].includes(flag) ? typeof value === "string" && /^\d+$/.test(value) ? Number(value) : NaN : value;
218
+ args[flag === "batch" ? "batchId" : flag] = ["after", "limit"].includes(flag) && typeof value === "string" && /^\d+$/.test(value) ? Number(value) : value;
229
219
  }
230
- const problem = assignmentArgumentProblem(operation, args);
231
- if (problem !== null)
232
- return emit(failure("usage", problem));
220
+ const read = readAssignmentArgs(operation, args);
221
+ if (!read.ok)
222
+ return emit(read);
233
223
  const credential = coordinatorToken(flags, context.env ?? process.env);
234
224
  if ("ok" in credential)
235
225
  return emit(credential);
@@ -170,6 +170,7 @@ export declare function assignmentBrief(assignment: AssignmentSnapshot | null):
170
170
  status: "not-recorded";
171
171
  };
172
172
  } | null;
173
+ export declare function assignmentChecksForRun(store: Store, root: string | undefined, runId: number, now: Date): AssignmentChecks;
173
174
  export declare function assignmentOf(store: Store, taskId: string, now: Date, access: AssignmentAccess, root?: string): AssignmentSnapshot | null;
174
175
  type MutationResult = {
175
176
  ok: true;
@@ -83,7 +83,7 @@ function ownerOf(store, rootId, repo) {
83
83
  }
84
84
  // Report the recorded check outcome, never turn a model verdict into a check.
85
85
  // Follow-up checks on the same commit (Run checks) upgrade a pass and show a failure.
86
- function nativeChecks(store, root, runId, now) {
86
+ export function assignmentChecksForRun(store, root, runId, now) {
87
87
  const own = buildChecks(store, root, runId);
88
88
  const run = store.getRun(runId);
89
89
  const followUps = followUpChecksOf(store, runId, now, root);
@@ -163,7 +163,7 @@ export function assignmentOf(store, taskId, now, access, root) {
163
163
  const reports = artifacts.filter(a => a.kind === "report");
164
164
  const unavailable = family.versions.flatMap(version => store.runsFor(version.refId).flatMap(run => store.artifactsFor(run.id).filter(artifact => root === undefined || !readVerifiedArtifact(root, artifact).ok)
165
165
  .map(artifact => `Saved ${artifact.kind} #${artifact.id} (run ${run.id}) is unavailable or changed.`)));
166
- const checks = result === null ? null : nativeChecks(store, root, result.id, now);
166
+ const checks = result === null ? null : assignmentChecksForRun(store, root, result.id, now);
167
167
  const finishedBuild = result?.role === "builder" && (result.outcome === "built" || result.outcome === "no-change") &&
168
168
  result.finishedAt !== null && /^[a-f0-9]{40}$/.test(result.headRevision ?? "");
169
169
  // Finished work returns to its lead or user. Strict terms and previous model
@@ -802,9 +802,9 @@ export type BrowserFlowStage = {
802
802
  wait?: {
803
803
  for: "reply" | "time" | "hours";
804
804
  minutes: number;
805
- from?: string;
806
- to?: string;
807
- timeZone?: string;
805
+ from?: string | undefined;
806
+ to?: string | undefined;
807
+ timeZone?: string | undefined;
808
808
  };
809
809
  /** A Pull request zone merges once checks pass (after a person approved), this way. */
810
810
  merge?: "squash" | "merge" | "rebase" | undefined;
@@ -1,6 +1,6 @@
1
1
  import type { Store } from "./store.js";
2
2
  import type { Runner } from "./builder.js";
3
- import type { BuildFinding } from "./review-switch.js";
3
+ import { type BuildFinding } from "./contracts/review-findings.js";
4
4
  export { buildReviewOf, buildReviewHeadline, buildReviewLines, findingWords, type BuildReviewView } from "./review-switch.js";
5
5
  export declare const AUTOMATIC_REVIEWER = "automatic review";
6
6
  /**
@@ -29,6 +29,7 @@ import { runCheckLevel } from "./check-levels.js";
29
29
  import { requestResultChanges } from "./result-actions.js";
30
30
  import { revisionSourceOf } from "./result-review.js";
31
31
  import { parseBuildFindings, REVIEW_PATCH_NAME } from "./reviewer.js";
32
+ import { FINDINGS_VERSION } from "./contracts/review-findings.js";
32
33
  import { heartbeat as runnerHeartbeat } from "./runner.js";
33
34
  import { TELEGRAM_TOKEN_ENVS } from "./names.js";
34
35
  import { CLAUDE_LIMITS } from "./scope.js";
@@ -90,7 +91,7 @@ function queueLocked(store, repo, runId, root, now) {
90
91
  }
91
92
  function settle(store, runId, state, fields, now) {
92
93
  store.handle.prepare("UPDATE build_review SET state = ?, findings_json = ?, reason = ?, sent_back_as = ?, finished_at = ? WHERE run = ? AND state = 'pending'")
93
- .run(state, fields.findings === undefined ? null : JSON.stringify({ version: 1, findings: fields.findings }), fields.reason ?? null, fields.sentBackAs ?? null, now.toISOString(), runId);
94
+ .run(state, fields.findings === undefined ? null : JSON.stringify({ version: FINDINGS_VERSION, findings: fields.findings }), fields.reason ?? null, fields.sentBackAs ?? null, now.toISOString(), runId);
94
95
  store.bumpWake();
95
96
  }
96
97
  /** Only the first automatic send-back in a task family: its revision's HIGH goes to the person. */
package/dist/builder.js CHANGED
@@ -63,6 +63,7 @@ import { openLiveLog } from "./live.js";
63
63
  import { CheckProgressTracker } from "./check-progress.js";
64
64
  import { captureParkEvidence, captureTerminalDiff, captureBaseTree, evidenceRoot, handoffName, isImagePath, WORKTREE_EVIDENCE_DIR, storeHandoffArtifact, looksLikeProtocolFile, mailboxName, progressFileName, proofFileName, rubricFileName, proposalFileName, quarantineMailboxes, readMailbox, readVerifiedArtifact, scanForSecrets, redactSecretLines, storeEvidence, validateScreenshotBytes, imageDimensions, SCREENSHOT_BYTE_CAP, boundStreamHeadTail } from "./evidence.js";
65
65
  import { PROOF_LIMITS, parseProof, serializeProof, adjudicate, artifactManifestOnly, sameDiffStatFacts } from "./proof.js";
66
+ import { parseHandoffArtifact } from "./contracts/handoff.js";
66
67
  import { captureReviewContext } from "./review-context.js";
67
68
  import { authoritySnapshotDigest, classifyRevisionAuthority, isMilestoneRegression, milestonesOf, parseExecutionPlanDocument, parsePlanRevisionProposal, parseProgressSnapshot, renderExecutionPlanDocument, PROGRESS_LIMITS, REVISION_LIMITS, } from "./plan.js";
68
69
  import { maybeSettleRepairChain } from "./dispose.js";
@@ -1115,11 +1116,9 @@ export async function build(store, request) {
1115
1116
  try {
1116
1117
  const verified = readVerifiedArtifact(root, handoffArtifact);
1117
1118
  if (verified.ok) {
1118
- const parsed = JSON.parse(verified.content.toString("utf8"));
1119
- if (parsed.branch === branch &&
1120
- typeof parsed.conclusion === "string" &&
1121
- parsed.freshness?.currentAsOf === baseRevision) {
1122
- previousHandoff = `A previous attempt (${String(parsed.outcome ?? "finished")}) left the branch exactly where it now stands and concluded: ${parsed.conclusion}`;
1119
+ const parsed = parseHandoffArtifact(verified.content.toString("utf8"));
1120
+ if (parsed.ok && parsed.value.branch === branch && parsed.value.freshness.currentAsOf === baseRevision) {
1121
+ previousHandoff = `A previous attempt (${parsed.value.outcome}) left the branch exactly where it now stands and concluded: ${parsed.value.conclusion}`;
1123
1122
  }
1124
1123
  }
1125
1124
  }
@@ -1740,7 +1739,6 @@ export async function settleProviderOutcome(captured, result) {
1740
1739
  store.setRunPhase(request.runId, "capturing-evidence");
1741
1740
  const diffEvidence = await captureTerminalDiff(store, git, worktree, pinnedBase, baseRevision, root, request.runId, clock());
1742
1741
  storeHandoffArtifact(store, root, {
1743
- schema: 1,
1744
1742
  taskId,
1745
1743
  runId: request.runId,
1746
1744
  provider,
@@ -1835,7 +1833,6 @@ export async function settleProviderOutcome(captured, result) {
1835
1833
  store.setRunPhase(request.runId, "capturing-evidence");
1836
1834
  const diffEvidence = await captureTerminalDiff(store, git, worktree, pinnedBase, head, root, request.runId, clock());
1837
1835
  storeHandoffArtifact(store, root, {
1838
- schema: 1,
1839
1836
  taskId,
1840
1837
  runId: request.runId,
1841
1838
  provider,
@@ -39,13 +39,13 @@ export declare function readAcceptanceEvidence(store: Store, who: VerifiedApprov
39
39
  requirement: string | null;
40
40
  state: string;
41
41
  details: (string | null)[];
42
- requiredEvidence: readonly import("./scope.js").EvidenceKind[];
42
+ requiredEvidence: readonly ("changed-path" | "check" | "manual-review" | "screenshot")[];
43
43
  reviewer: {
44
44
  judgement: import("./proof.js").CriterionJudgementWord;
45
45
  note: string | null;
46
46
  } | null;
47
47
  evidence: {
48
- kind: import("./scope.js").EvidenceKind;
48
+ kind: "changed-path" | "check" | "manual-review" | "screenshot";
49
49
  reference: string | null;
50
50
  }[];
51
51
  coverageGaps: (string | null)[];
@@ -1,5 +1,7 @@
1
1
  import { type Store } from "./store.js";
2
2
  import { type VerifiedApprover } from "./principal.js";
3
+ import { CHAT_ACTION_FIELDS, type SharedAction } from "./contracts/chat-actions.js";
4
+ export { CHAT_ACTION_FIELDS, type SharedAction };
3
5
  export declare const CHAT_ACTIONS: {
4
6
  readonly skill_import: {
5
7
  readonly label: "Add skill";
@@ -235,20 +237,11 @@ export declare const CHALLENGE_ACTIONS: ReadonlySet<ChatAction>;
235
237
  /** Actions about the person themselves, in no project: their card carries no project (repo ""). */
236
238
  export declare const OWNER_ACTIONS: ReadonlySet<ChatAction>;
237
239
  export declare function isChatAction(value: unknown): value is ChatAction;
238
- export type SharedAction = {
239
- operation: ChatAction;
240
- request: Record<string, unknown>;
241
- repo: string;
242
- title: string;
243
- terms: string[];
244
- stamp: string;
245
- state: Record<string, unknown>;
246
- };
247
- export declare const CHAT_ACTION_FIELDS: Record<ChatAction, readonly string[]>;
248
240
  /** No writes, including at preview. Store-derived snapshots are never accepted
249
241
  * from a model. Rebuilding the same request before execution catches changes. */
250
242
  export declare function prepareSharedAction(store: Store, who: VerifiedApprover, operation: ChatAction, input: Record<string, unknown>, root?: string, now?: Date): SharedAction;
251
- export declare function sharedActionPayload(value: Record<string, unknown>): SharedAction | null;
243
+ /** A saved proposal's action, read by its schema (contracts/chat-actions.ts); null when it can't be read. */
244
+ export declare function sharedActionPayload(value: unknown): SharedAction | null;
252
245
  export declare function sharedActionNeedsReview(action: SharedAction): boolean;
253
246
  /** Whether a paired phone may confirm this protected action behind its own
254
247
  * explicit challenge: the action is on the challenge list, needs no
@@ -256,7 +249,20 @@ export declare function sharedActionNeedsReview(action: SharedAction): boolean;
256
249
  export declare function sharedActionAllowsChallenge(action: SharedAction): boolean;
257
250
  export declare function sharedActionReviewPath(id: number): string;
258
251
  export declare function mintSharedActionReview(store: Store, who: VerifiedApprover, id: number, root: string, now: Date): {
259
- payload: SharedAction;
252
+ payload: {
253
+ version: 1;
254
+ operation: "decision_record" | "decision_retire" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "task_cancel" | "task_resume" | "teammate_answer" | "teammate_create" | "teammate_memory" | "teammate_note" | "teammate_routine" | "teammate_soul" | "teammate_state" | "teammate_tools" | "teammate_undo" | "tool_add" | "tool_remove";
255
+ request: {
256
+ [x: string]: unknown;
257
+ };
258
+ repo: string;
259
+ title: string;
260
+ terms: string[];
261
+ stamp: string;
262
+ state: {
263
+ [x: string]: unknown;
264
+ };
265
+ };
260
266
  nonce: string;
261
267
  };
262
268
  export type SharedActionOptions = {