toolroll 0.9.84 → 0.9.86

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 (132) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +94 -1193
  3. package/dist/agent-fence.d.ts +9 -5
  4. package/dist/agent-fence.js +6 -5
  5. package/dist/agent-onboard.d.ts +2 -0
  6. package/dist/agent-onboard.js +3 -0
  7. package/dist/assignment.js +4 -3
  8. package/dist/browser/workspace.css +1 -1
  9. package/dist/browser/workspace.js +33 -31
  10. package/dist/browser-workspace.d.ts +45 -0
  11. package/dist/chat-actions.d.ts +11 -1
  12. package/dist/chat-actions.js +53 -1
  13. package/dist/chat-channel.d.ts +10 -1
  14. package/dist/chat-channel.js +36 -5
  15. package/dist/chat-decide.d.ts +16 -4
  16. package/dist/chat-decide.js +239 -31
  17. package/dist/chat-messages.d.ts +5 -0
  18. package/dist/chat-messages.js +1 -1
  19. package/dist/chat-quiet.js +6 -4
  20. package/dist/chat-review.d.ts +4 -0
  21. package/dist/chat-review.js +13 -4
  22. package/dist/chat-terms.d.ts +9 -0
  23. package/dist/chat-terms.js +46 -0
  24. package/dist/claim.d.ts +3 -0
  25. package/dist/claim.js +4 -1
  26. package/dist/contracts/chat-actions.d.ts +9 -0
  27. package/dist/contracts/chat-actions.js +3 -0
  28. package/dist/contracts/console-api.d.ts +6 -2
  29. package/dist/contracts/console-api.js +6 -2
  30. package/dist/contracts/flow-propose.d.ts +4 -1
  31. package/dist/contracts/flow-propose.js +1 -1
  32. package/dist/contracts/flow.d.ts +41 -0
  33. package/dist/contracts/flow.js +4 -2
  34. package/dist/contracts/lead-tools.d.ts +51 -0
  35. package/dist/contracts/lead-tools.js +5 -0
  36. package/dist/contracts/store-json.d.ts +2 -0
  37. package/dist/contracts/trigger-payloads.d.ts +20 -0
  38. package/dist/contracts/trigger-payloads.js +4 -0
  39. package/dist/contracts/youiyoux-request.d.ts +248 -0
  40. package/dist/contracts/youiyoux-request.js +152 -0
  41. package/dist/coordinator.js +1 -0
  42. package/dist/deploy-gate.js +4 -0
  43. package/dist/dispatch.d.ts +3 -0
  44. package/dist/dispatch.js +4 -1
  45. package/dist/flow-browser.d.ts +103 -0
  46. package/dist/flow-browser.js +204 -0
  47. package/dist/flow-cadence.d.ts +8 -2
  48. package/dist/flow-cadence.js +16 -3
  49. package/dist/flow-card-task.d.ts +9 -0
  50. package/dist/flow-card-task.js +15 -6
  51. package/dist/flow-code.d.ts +5 -1
  52. package/dist/flow-code.js +4 -1
  53. package/dist/flow-engine.js +16 -0
  54. package/dist/flow-live.js +9 -1
  55. package/dist/flow-pull-request.js +15 -7
  56. package/dist/flow-share.js +3 -1
  57. package/dist/flow-steps.d.ts +9 -1
  58. package/dist/flow-steps.js +55 -5
  59. package/dist/flow-triggers.js +55 -0
  60. package/dist/flows-cli.d.ts +19 -0
  61. package/dist/flows-cli.js +47 -2
  62. package/dist/flows-ui.js +11 -2
  63. package/dist/flows.js +5 -2
  64. package/dist/integrations-ui.js +9 -4
  65. package/dist/integrations.d.ts +9 -1
  66. package/dist/integrations.js +43 -2
  67. package/dist/landed-cleanup.d.ts +4 -0
  68. package/dist/landed-cleanup.js +3 -2
  69. package/dist/lead-capabilities.js +2 -1
  70. package/dist/lead-cli.js +3 -1
  71. package/dist/lead-tools.d.ts +2 -0
  72. package/dist/lead-tools.js +65 -10
  73. package/dist/lead-voice.js +16 -5
  74. package/dist/lead.d.ts +2 -0
  75. package/dist/lead.js +3 -3
  76. package/dist/operate.d.ts +2 -3
  77. package/dist/operate.js +226 -21
  78. package/dist/project-activity.d.ts +20 -0
  79. package/dist/project-activity.js +23 -0
  80. package/dist/project-cli.d.ts +1 -0
  81. package/dist/project-cli.js +36 -19
  82. package/dist/project-delete.js +7 -3
  83. package/dist/project-knowledge.d.ts +3 -3
  84. package/dist/project-outcomes.d.ts +119 -0
  85. package/dist/project-outcomes.js +79 -0
  86. package/dist/pull-request-flow.js +35 -4
  87. package/dist/result-actions.d.ts +4 -0
  88. package/dist/result-actions.js +41 -9
  89. package/dist/result-follow-ups.js +4 -0
  90. package/dist/result-review.d.ts +9 -0
  91. package/dist/result-review.js +45 -5
  92. package/dist/scope.d.ts +5 -0
  93. package/dist/scope.js +7 -0
  94. package/dist/server/chat.js +4 -2
  95. package/dist/server/console.css +41 -0
  96. package/dist/server/flows.js +15 -0
  97. package/dist/server/pages.js +11 -1
  98. package/dist/server/render-chat.js +3 -2
  99. package/dist/server/render-pages.d.ts +15 -2
  100. package/dist/server/render-pages.js +52 -4
  101. package/dist/server/render-results.d.ts +2 -0
  102. package/dist/server/render-results.js +20 -7
  103. package/dist/server/render-tasks.d.ts +9 -0
  104. package/dist/server/render-tasks.js +32 -11
  105. package/dist/server/route-table.js +12 -0
  106. package/dist/server/settings.js +18 -1
  107. package/dist/server/task-views.js +11 -0
  108. package/dist/server/tasks.d.ts +10 -0
  109. package/dist/server/tasks.js +95 -7
  110. package/dist/settings-proofs.js +1 -0
  111. package/dist/store.d.ts +116 -5
  112. package/dist/store.js +385 -24
  113. package/dist/surface.js +18 -2
  114. package/dist/task-archive.d.ts +10 -0
  115. package/dist/task-archive.js +16 -0
  116. package/dist/task-control.d.ts +1 -1
  117. package/dist/task-source.d.ts +13 -1
  118. package/dist/task-source.js +17 -1
  119. package/dist/telegram-lead.js +7 -5
  120. package/dist/telegram.js +1 -1
  121. package/dist/tidy.d.ts +130 -0
  122. package/dist/tidy.js +503 -0
  123. package/dist/toolroll-update.js +2 -2
  124. package/dist/webhooks.d.ts +6 -0
  125. package/dist/webhooks.js +25 -0
  126. package/dist/work-index.d.ts +28 -0
  127. package/dist/work-index.js +82 -28
  128. package/dist/workspace-ui.d.ts +1 -1
  129. package/dist/workspace-ui.js +3 -2
  130. package/dist/youiyoux-intake.d.ts +234 -0
  131. package/dist/youiyoux-intake.js +908 -0
  132. package/package.json +11 -8
@@ -168,6 +168,8 @@ declare const STORE_COLUMN_VALUES: {
168
168
  operation: z.ZodEnum<{
169
169
  decision_record: "decision_record";
170
170
  decision_retire: "decision_retire";
171
+ flow_browser: "flow_browser";
172
+ flow_browser_off: "flow_browser_off";
171
173
  flow_card_add: "flow_card_add";
172
174
  flow_card_approve: "flow_card_approve";
173
175
  flow_card_assign: "flow_card_assign";
@@ -45,6 +45,11 @@ export declare const githubRunSchema: z.ZodCatch<z.ZodObject<{
45
45
  display_title: z.ZodCatch<z.ZodOptional<z.ZodString>>;
46
46
  created_at: z.ZodCatch<z.ZodOptional<z.ZodString>>;
47
47
  html_url: z.ZodCatch<z.ZodOptional<z.ZodString>>;
48
+ id: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
49
+ event: z.ZodCatch<z.ZodOptional<z.ZodString>>;
50
+ head_repository: z.ZodCatch<z.ZodOptional<z.ZodObject<{
51
+ full_name: z.ZodCatch<z.ZodOptional<z.ZodString>>;
52
+ }, z.core.$strip>>>;
48
53
  }, z.core.$strip>>;
49
54
  /** `repos/:repo/issues` and `repos/:repo/pulls`: a list (anything else reads as an empty one). */
50
55
  export declare const githubIssueListSchema: z.ZodCatch<z.ZodArray<z.ZodCatch<z.ZodObject<{
@@ -111,6 +116,11 @@ export declare const githubRunListSchema: z.ZodCatch<z.ZodObject<{
111
116
  display_title: z.ZodCatch<z.ZodOptional<z.ZodString>>;
112
117
  created_at: z.ZodCatch<z.ZodOptional<z.ZodString>>;
113
118
  html_url: z.ZodCatch<z.ZodOptional<z.ZodString>>;
119
+ id: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
120
+ event: z.ZodCatch<z.ZodOptional<z.ZodString>>;
121
+ head_repository: z.ZodCatch<z.ZodOptional<z.ZodObject<{
122
+ full_name: z.ZodCatch<z.ZodOptional<z.ZodString>>;
123
+ }, z.core.$strip>>>;
114
124
  }, z.core.$strip>>>>>;
115
125
  }, z.core.$strip>>;
116
126
  /** A signed GitHub webhook event (`issues`, `pull_request`, `workflow_run`; a `ping` reads as an empty one). */
@@ -176,6 +186,11 @@ export declare const githubEventSchema: z.ZodCatch<z.ZodObject<{
176
186
  display_title: z.ZodCatch<z.ZodOptional<z.ZodString>>;
177
187
  created_at: z.ZodCatch<z.ZodOptional<z.ZodString>>;
178
188
  html_url: z.ZodCatch<z.ZodOptional<z.ZodString>>;
189
+ id: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
190
+ event: z.ZodCatch<z.ZodOptional<z.ZodString>>;
191
+ head_repository: z.ZodCatch<z.ZodOptional<z.ZodObject<{
192
+ full_name: z.ZodCatch<z.ZodOptional<z.ZodString>>;
193
+ }, z.core.$strip>>>;
179
194
  }, z.core.$strip>>>;
180
195
  }, z.core.$strip>>;
181
196
  /** A Linear issue, from the GraphQL query (`labels { nodes }`) or a webhook's `data` (`labels` as a list). */
@@ -353,6 +368,11 @@ export declare const readGithubRunList: (raw: unknown) => {
353
368
  display_title?: string | undefined;
354
369
  created_at?: string | undefined;
355
370
  html_url?: string | undefined;
371
+ id?: number | undefined;
372
+ event?: string | undefined;
373
+ head_repository?: {
374
+ full_name?: string | undefined;
375
+ } | undefined;
356
376
  }[] | undefined;
357
377
  };
358
378
  export declare const readGithubEvent: (raw: unknown) => GithubEvent;
@@ -42,6 +42,10 @@ export const githubRunSchema = z.object({
42
42
  display_title: text,
43
43
  created_at: text,
44
44
  html_url: text,
45
+ /** Read by tidy's CI observations (task-tidy.md): which run, why it ran, and whose repository its commit is in. */
46
+ id: loose(z.number()),
47
+ event: text,
48
+ head_repository: loose(z.object({ full_name: text })),
45
49
  }).catch({});
46
50
  /** `repos/:repo/issues` and `repos/:repo/pulls`: a list (anything else reads as an empty one). */
47
51
  export const githubIssueListSchema = z.array(githubIssueSchema).catch([]);
@@ -0,0 +1,248 @@
1
+ /**
2
+ * A youiyoux request as Toolroll reads it (youiyoux-intake.ts): the published `youi.request/v1` schema (youiyoux's
3
+ * schema/youi.request.schema.json), held to its own limits, and the `youi.proposal/v1` options file an options request's
4
+ * agent writes (schema/youi.proposal.schema.json). Unknown keys are allowed, as the published schemas allow them;
5
+ * everything Toolroll reads is checked here, and every string is outside text all the same.
6
+ */
7
+ import { z } from "zod";
8
+ import { type ContractResult } from "./contract.js";
9
+ export declare const REQUEST_ID: RegExp;
10
+ /** An options file's id: `p-` and the same 10 hex as the options request it answers. */
11
+ export declare const PROPOSAL_ID: RegExp;
12
+ /** The provenance every task filed from a request carries (task_ref.filed_via): immutable, and the seal reads it. */
13
+ export declare const YOUIYOUX_FILED_VIA = "youiyoux";
14
+ /** The one chat card a pass's filings make for a project (youiyoux-intake.ts), drawn by chat-decide.ts: the new tasks,
15
+ * Approve all and each one's own Approve and Open. */
16
+ export declare const YOUIYOUX_CARD_KIND = "youiyoux-requests";
17
+ /** Where a person sees and approves everything waiting on them in the console (flow-triggers.ts NEEDS_YOU_HREF). */
18
+ export declare const YOUIYOUX_HOME_PATH = "/work?view=needs-you";
19
+ export declare const YOUIYOUX_EVIDENCE: readonly ["screenshot", "check", "changed-path", "manual-review"];
20
+ /** What an options request offers its agent: the one file to write (`.youi/proposals/<proposal>.json`), the props an
21
+ * option may set (enum values from `values`), and whether it may give the component new text. */
22
+ export declare const youiyouxOfferSchema: z.ZodObject<{
23
+ proposal: z.ZodString;
24
+ file: z.ZodString;
25
+ choices: z.ZodRecord<z.ZodString, z.ZodObject<{
26
+ type: z.ZodEnum<{
27
+ boolean: "boolean";
28
+ enum: "enum";
29
+ number: "number";
30
+ string: "string";
31
+ }>;
32
+ values: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
+ default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
34
+ }, z.core.$strip>>;
35
+ text: z.ZodBoolean;
36
+ }, z.core.$strip>;
37
+ export type YouiyouxOffer = z.infer<typeof youiyouxOfferSchema>;
38
+ export declare const youiyouxTaskSchema: z.ZodObject<{
39
+ title: z.ZodString;
40
+ goal: z.ZodString;
41
+ touches: z.ZodArray<z.ZodString>;
42
+ touchesKnown: z.ZodBoolean;
43
+ why: z.ZodOptional<z.ZodString>;
44
+ not: z.ZodArray<z.ZodString>;
45
+ acceptance: z.ZodArray<z.ZodObject<{
46
+ statement: z.ZodString;
47
+ evidence: z.ZodArray<z.ZodEnum<{
48
+ "changed-path": "changed-path";
49
+ check: "check";
50
+ "manual-review": "manual-review";
51
+ screenshot: "screenshot";
52
+ }>>;
53
+ }, z.core.$strip>>;
54
+ offer: z.ZodOptional<z.ZodObject<{
55
+ proposal: z.ZodString;
56
+ file: z.ZodString;
57
+ choices: z.ZodRecord<z.ZodString, z.ZodObject<{
58
+ type: z.ZodEnum<{
59
+ boolean: "boolean";
60
+ enum: "enum";
61
+ number: "number";
62
+ string: "string";
63
+ }>;
64
+ values: z.ZodOptional<z.ZodArray<z.ZodString>>;
65
+ default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
66
+ }, z.core.$strip>>;
67
+ text: z.ZodBoolean;
68
+ }, z.core.$strip>>;
69
+ }, z.core.$strip>;
70
+ export declare const youiyouxRequestSchema: z.ZodObject<{
71
+ schema: z.ZodLiteral<"youi.request/v1">;
72
+ id: z.ZodString;
73
+ project: z.ZodString;
74
+ kind: z.ZodEnum<{
75
+ change: "change";
76
+ comment: "comment";
77
+ options: "options";
78
+ }>;
79
+ status: z.ZodEnum<{
80
+ addressed: "addressed";
81
+ closed: "closed";
82
+ open: "open";
83
+ }>;
84
+ body: z.ZodString;
85
+ author: z.ZodObject<{
86
+ kind: z.ZodEnum<{
87
+ agent: "agent";
88
+ person: "person";
89
+ }>;
90
+ name: z.ZodString;
91
+ }, z.core.$strip>;
92
+ createdAt: z.ZodString;
93
+ updatedAt: z.ZodString;
94
+ target: z.ZodObject<{
95
+ route: z.ZodString;
96
+ scope: z.ZodEnum<{
97
+ element: "element";
98
+ here: "here";
99
+ }>;
100
+ name: z.ZodString;
101
+ file: z.ZodOptional<z.ZodString>;
102
+ line: z.ZodOptional<z.ZodInt>;
103
+ }, z.core.$strip>;
104
+ task: z.ZodOptional<z.ZodObject<{
105
+ title: z.ZodString;
106
+ goal: z.ZodString;
107
+ touches: z.ZodArray<z.ZodString>;
108
+ touchesKnown: z.ZodBoolean;
109
+ why: z.ZodOptional<z.ZodString>;
110
+ not: z.ZodArray<z.ZodString>;
111
+ acceptance: z.ZodArray<z.ZodObject<{
112
+ statement: z.ZodString;
113
+ evidence: z.ZodArray<z.ZodEnum<{
114
+ "changed-path": "changed-path";
115
+ check: "check";
116
+ "manual-review": "manual-review";
117
+ screenshot: "screenshot";
118
+ }>>;
119
+ }, z.core.$strip>>;
120
+ offer: z.ZodOptional<z.ZodObject<{
121
+ proposal: z.ZodString;
122
+ file: z.ZodString;
123
+ choices: z.ZodRecord<z.ZodString, z.ZodObject<{
124
+ type: z.ZodEnum<{
125
+ boolean: "boolean";
126
+ enum: "enum";
127
+ number: "number";
128
+ string: "string";
129
+ }>;
130
+ values: z.ZodOptional<z.ZodArray<z.ZodString>>;
131
+ default: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
132
+ }, z.core.$strip>>;
133
+ text: z.ZodBoolean;
134
+ }, z.core.$strip>>;
135
+ }, z.core.$strip>>;
136
+ thread: z.ZodArray<z.ZodUnknown>;
137
+ }, z.core.$strip>;
138
+ export type YouiyouxRequest = z.infer<typeof youiyouxRequestSchema>;
139
+ export type YouiyouxTask = z.infer<typeof youiyouxTaskSchema>;
140
+ export declare function readYouiyouxRequest(raw: unknown): ContractResult<YouiyouxRequest>;
141
+ /** The one file an options request's agent may write: `.youi/proposals/p-<the request's hex>.json`. */
142
+ export declare const proposalIdOf: (request: string) => string;
143
+ export declare const proposalFileOf: (proposal: string) => string;
144
+ /** Why an options request can't be filed as youiyoux's contract has it, or null: it carries a task with an offer whose
145
+ * proposal is `p-<its own hex>`, whose file is that proposal's, and the task may touch that one file and nothing else. */
146
+ export declare function optionsProblem(request: YouiyouxRequest): string | null;
147
+ export declare const PROPOSAL_STATUSES: readonly ["open", "chosen", "dismissed"];
148
+ export declare const youiyouxProposalSchema: z.ZodObject<{
149
+ $schema: z.ZodOptional<z.ZodString>;
150
+ schema: z.ZodLiteral<"youi.proposal/v1">;
151
+ id: z.ZodString;
152
+ project: z.ZodString;
153
+ route: z.ZodString;
154
+ target: z.ZodObject<{
155
+ name: z.ZodString;
156
+ element: z.ZodOptional<z.ZodString>;
157
+ file: z.ZodOptional<z.ZodString>;
158
+ line: z.ZodOptional<z.ZodInt>;
159
+ column: z.ZodOptional<z.ZodInt>;
160
+ box: z.ZodObject<{
161
+ x: z.ZodNumber;
162
+ y: z.ZodNumber;
163
+ width: z.ZodNumber;
164
+ height: z.ZodNumber;
165
+ }, z.core.$strip>;
166
+ page: z.ZodOptional<z.ZodObject<{
167
+ width: z.ZodNumber;
168
+ height: z.ZodNumber;
169
+ }, z.core.$strip>>;
170
+ }, z.core.$strip>;
171
+ ask: z.ZodString;
172
+ author: z.ZodObject<{
173
+ kind: z.ZodEnum<{
174
+ agent: "agent";
175
+ person: "person";
176
+ }>;
177
+ name: z.ZodString;
178
+ }, z.core.$strip>;
179
+ createdAt: z.ZodString;
180
+ updatedAt: z.ZodString;
181
+ options: z.ZodArray<z.ZodObject<{
182
+ id: z.ZodEnum<{
183
+ a: "a";
184
+ b: "b";
185
+ c: "c";
186
+ d: "d";
187
+ }>;
188
+ label: z.ZodString;
189
+ why: z.ZodString;
190
+ set: z.ZodArray<z.ZodObject<{
191
+ prop: z.ZodString;
192
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
193
+ }, z.core.$strip>>;
194
+ text: z.ZodNullable<z.ZodString>;
195
+ }, z.core.$strip>>;
196
+ status: z.ZodEnum<{
197
+ chosen: "chosen";
198
+ dismissed: "dismissed";
199
+ open: "open";
200
+ }>;
201
+ choice: z.ZodOptional<z.ZodEnum<{
202
+ a: "a";
203
+ b: "b";
204
+ c: "c";
205
+ d: "d";
206
+ }>>;
207
+ decidedBy: z.ZodOptional<z.ZodObject<{
208
+ kind: z.ZodEnum<{
209
+ agent: "agent";
210
+ person: "person";
211
+ }>;
212
+ name: z.ZodString;
213
+ }, z.core.$strip>>;
214
+ decidedAt: z.ZodOptional<z.ZodString>;
215
+ request: z.ZodOptional<z.ZodString>;
216
+ for: z.ZodString;
217
+ }, z.core.$strip>;
218
+ export type YouiyouxProposal = z.infer<typeof youiyouxProposalSchema>;
219
+ export declare function readYouiyouxProposal(raw: unknown): ContractResult<YouiyouxProposal>;
220
+ /** What Toolroll writes beside a request (`youi.request-task/v1`): the task it became, how that task stands, and, when
221
+ * the console has an address, the task's link and `home`, where a person sees and approves all waiting work. */
222
+ export declare const YOUIYOUX_TASK_STATUSES: readonly ["queued", "needs-you", "building", "ready", "done", "failed", "stopped"];
223
+ export type YouiyouxTaskStatus = (typeof YOUIYOUX_TASK_STATUSES)[number];
224
+ /** A link youiyoux opens: http or https, at most 2,000 characters, with no user name or password in it. */
225
+ export declare const youiyouxLinkSchema: z.ZodString;
226
+ export declare const youiyouxRequestTaskSchema: z.ZodObject<{
227
+ schema: z.ZodLiteral<"youi.request-task/v1">;
228
+ request: z.ZodString;
229
+ runner: z.ZodLiteral<"Toolroll">;
230
+ task: z.ZodObject<{
231
+ id: z.ZodString;
232
+ url: z.ZodNullable<z.ZodString>;
233
+ }, z.core.$strict>;
234
+ home: z.ZodOptional<z.ZodString>;
235
+ status: z.ZodEnum<{
236
+ building: "building";
237
+ done: "done";
238
+ failed: "failed";
239
+ "needs-you": "needs-you";
240
+ queued: "queued";
241
+ ready: "ready";
242
+ stopped: "stopped";
243
+ }>;
244
+ detail: z.ZodString;
245
+ updatedAt: z.ZodString;
246
+ }, z.core.$strict>;
247
+ export type YouiyouxRequestTask = z.infer<typeof youiyouxRequestTaskSchema>;
248
+ export declare function readYouiyouxRequestTask(raw: unknown): ContractResult<YouiyouxRequestTask>;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * A youiyoux request as Toolroll reads it (youiyoux-intake.ts): the published `youi.request/v1` schema (youiyoux's
3
+ * schema/youi.request.schema.json), held to its own limits, and the `youi.proposal/v1` options file an options request's
4
+ * agent writes (schema/youi.proposal.schema.json). Unknown keys are allowed, as the published schemas allow them;
5
+ * everything Toolroll reads is checked here, and every string is outside text all the same.
6
+ */
7
+ import { z } from "zod";
8
+ import { parseContract } from "./contract.js";
9
+ export const REQUEST_ID = /^r-[0-9a-f]{10}$/;
10
+ /** An options file's id: `p-` and the same 10 hex as the options request it answers. */
11
+ export const PROPOSAL_ID = /^p-[0-9a-f]{10}$/;
12
+ /** The provenance every task filed from a request carries (task_ref.filed_via): immutable, and the seal reads it. */
13
+ export const YOUIYOUX_FILED_VIA = "youiyoux";
14
+ /** The one chat card a pass's filings make for a project (youiyoux-intake.ts), drawn by chat-decide.ts: the new tasks,
15
+ * Approve all and each one's own Approve and Open. */
16
+ export const YOUIYOUX_CARD_KIND = "youiyoux-requests";
17
+ /** Where a person sees and approves everything waiting on them in the console (flow-triggers.ts NEEDS_YOU_HREF). */
18
+ export const YOUIYOUX_HOME_PATH = "/work?view=needs-you";
19
+ const text = (max) => z.string().min(1).max(max);
20
+ const who = z.object({ kind: z.enum(["person", "agent"]), name: text(100) });
21
+ // The published schema's date-time: an ISO timestamp with a zone.
22
+ const when = z.string().max(40).regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/);
23
+ export const YOUIYOUX_EVIDENCE = ["screenshot", "check", "changed-path", "manual-review"];
24
+ /** A component prop as youiyoux names one: what an option may set. */
25
+ const PROP = /^[A-Za-z_$][\w$]{0,63}$/;
26
+ const value = z.union([z.string().max(2000), z.number().finite(), z.boolean()]);
27
+ /** What an options request offers its agent: the one file to write (`.youi/proposals/<proposal>.json`), the props an
28
+ * option may set (enum values from `values`), and whether it may give the component new text. */
29
+ export const youiyouxOfferSchema = z.object({
30
+ proposal: z.string().regex(PROPOSAL_ID),
31
+ file: text(500),
32
+ choices: z.record(z.string().regex(PROP), z.object({
33
+ type: z.enum(["enum", "string", "boolean", "number"]),
34
+ values: z.array(text(200)).max(100).optional(),
35
+ default: value.optional(),
36
+ })).refine(choices => Object.keys(choices).length <= 50, "at most 50 props"),
37
+ text: z.boolean(),
38
+ });
39
+ export const youiyouxTaskSchema = z.object({
40
+ title: text(120),
41
+ goal: text(4000),
42
+ touches: z.array(text(500)).max(50),
43
+ touchesKnown: z.boolean(),
44
+ why: text(500).optional(),
45
+ not: z.array(text(300)).max(10),
46
+ acceptance: z.array(z.object({ statement: text(300), evidence: z.array(z.enum(YOUIYOUX_EVIDENCE)).min(1) })).min(1).max(10),
47
+ /** An options request's: what its agent writes (optionsProblem holds it to the request). */
48
+ offer: youiyouxOfferSchema.optional(),
49
+ });
50
+ export const youiyouxRequestSchema = z.object({
51
+ schema: z.literal("youi.request/v1"),
52
+ id: z.string().regex(REQUEST_ID),
53
+ project: text(200),
54
+ kind: z.enum(["comment", "change", "options"]),
55
+ status: z.enum(["open", "addressed", "closed"]),
56
+ body: text(4000),
57
+ author: who,
58
+ createdAt: when,
59
+ updatedAt: when,
60
+ // Where the person pointed: the page, the component, and (when youiyoux knew it) the file and line that draw it.
61
+ target: z.object({ route: text(300), scope: z.enum(["here", "element"]), name: text(200), file: text(500).optional(), line: z.int().positive().max(Number.MAX_SAFE_INTEGER).optional() }),
62
+ task: youiyouxTaskSchema.optional(),
63
+ thread: z.array(z.unknown()).max(500),
64
+ });
65
+ export function readYouiyouxRequest(raw) {
66
+ return parseContract(youiyouxRequestSchema, raw);
67
+ }
68
+ /** The one file an options request's agent may write: `.youi/proposals/p-<the request's hex>.json`. */
69
+ export const proposalIdOf = (request) => `p-${request.slice(2)}`;
70
+ export const proposalFileOf = (proposal) => `.youi/proposals/${proposal}.json`;
71
+ /** Why an options request can't be filed as youiyoux's contract has it, or null: it carries a task with an offer whose
72
+ * proposal is `p-<its own hex>`, whose file is that proposal's, and the task may touch that one file and nothing else. */
73
+ export function optionsProblem(request) {
74
+ if (request.kind !== "options")
75
+ return null;
76
+ const offer = request.task?.offer;
77
+ if (request.task === undefined || offer === undefined)
78
+ return "it asks for options with no offer";
79
+ const proposal = proposalIdOf(request.id);
80
+ if (offer.proposal !== proposal)
81
+ return `its offer is for ${offer.proposal}, not ${proposal}`;
82
+ if (offer.file !== proposalFileOf(proposal))
83
+ return `its offer's file isn't ${proposalFileOf(proposal)}`;
84
+ if (!request.task.touchesKnown || request.task.touches.length !== 1 || request.task.touches[0] !== offer.file)
85
+ return "its task may touch more than its options file";
86
+ return null;
87
+ }
88
+ /** `youi.proposal/v1`: the options an agent puts on youiyoux's Screens canvas (youiyoux's packages/core/src/proposals.ts,
89
+ * mirrored). Toolroll checks only the shape and what it reads; youiyoux checks each option against the component's
90
+ * contract when it reads the file. */
91
+ const OPTION_IDS = ["a", "b", "c", "d"];
92
+ export const PROPOSAL_STATUSES = ["open", "chosen", "dismissed"];
93
+ export const youiyouxProposalSchema = z.object({
94
+ $schema: z.string().max(500).optional(),
95
+ schema: z.literal("youi.proposal/v1"),
96
+ id: z.string().regex(PROPOSAL_ID),
97
+ project: text(200),
98
+ route: text(300),
99
+ target: z.object({
100
+ name: text(200), element: text(200).optional(), file: text(500).optional(),
101
+ line: z.int().positive().max(Number.MAX_SAFE_INTEGER).optional(), column: z.int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
102
+ box: z.object({ x: z.number().finite(), y: z.number().finite(), width: z.number().finite().nonnegative(), height: z.number().finite().nonnegative() }),
103
+ page: z.object({ width: z.number().finite().positive(), height: z.number().finite().positive() }).optional(),
104
+ }),
105
+ ask: text(1000),
106
+ author: who,
107
+ createdAt: when,
108
+ updatedAt: when,
109
+ options: z.array(z.object({
110
+ id: z.enum(OPTION_IDS),
111
+ label: text(60),
112
+ why: text(500),
113
+ set: z.array(z.object({ prop: z.string().regex(PROP), value })).max(20),
114
+ text: text(2000).nullable(),
115
+ }).refine(option => option.set.length > 0 || option.text !== null, "an option changes a prop or the text")).min(2).max(4),
116
+ status: z.enum(PROPOSAL_STATUSES),
117
+ choice: z.enum(OPTION_IDS).optional(),
118
+ decidedBy: who.optional(),
119
+ decidedAt: when.optional(),
120
+ request: z.string().regex(REQUEST_ID).optional(),
121
+ for: z.string().regex(REQUEST_ID),
122
+ }).refine(doc => new Set(doc.options.map(option => option.id)).size === doc.options.length, "each option has its own letter")
123
+ .refine(doc => doc.choice === undefined || doc.options.some(option => option.id === doc.choice), "the choice is one of its options");
124
+ export function readYouiyouxProposal(raw) {
125
+ return parseContract(youiyouxProposalSchema, raw);
126
+ }
127
+ /** What Toolroll writes beside a request (`youi.request-task/v1`): the task it became, how that task stands, and, when
128
+ * the console has an address, the task's link and `home`, where a person sees and approves all waiting work. */
129
+ export const YOUIYOUX_TASK_STATUSES = ["queued", "needs-you", "building", "ready", "done", "failed", "stopped"];
130
+ /** A link youiyoux opens: http or https, at most 2,000 characters, with no user name or password in it. */
131
+ export const youiyouxLinkSchema = z.string().max(2000).refine(value => {
132
+ try {
133
+ const url = new URL(value);
134
+ return (url.protocol === "http:" || url.protocol === "https:") && url.username === "" && url.password === "";
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ }, "an http or https address with no credentials");
140
+ export const youiyouxRequestTaskSchema = z.strictObject({
141
+ schema: z.literal("youi.request-task/v1"),
142
+ request: z.string().regex(REQUEST_ID),
143
+ runner: z.literal("Toolroll"),
144
+ task: z.strictObject({ id: z.string().min(1).max(120), url: youiyouxLinkSchema.nullable() }),
145
+ home: youiyouxLinkSchema.optional(),
146
+ status: z.enum(YOUIYOUX_TASK_STATUSES),
147
+ detail: z.string().max(200),
148
+ updatedAt: when,
149
+ });
150
+ export function readYouiyouxRequestTask(raw) {
151
+ return parseContract(youiyouxRequestTaskSchema, raw);
152
+ }
@@ -326,6 +326,7 @@ export function listTasksFor(store, who, filter, now) {
326
326
  FROM task_ref JOIN task ON task.id = task_ref.external_id AND task_ref.backend = 'built-in'
327
327
  WHERE task_ref.repo IN (${sql})
328
328
  AND (? IS NULL OR task.state = ?)
329
+ AND NOT EXISTS (SELECT 1 FROM task_archive WHERE task_archive.task_ref = task_ref.id)
329
330
  AND task_ref.id > ?
330
331
  ORDER BY task_ref.id LIMIT ?`)
331
332
  .all(now.toISOString(), ...args, filter.state ?? null, filter.state ?? null, filter.cursor ?? 0, limit + 1);
@@ -8,6 +8,7 @@
8
8
  import { assignmentOf } from "./assignment.js";
9
9
  import { liveReleaseOf, personApprovalOf, releaseApprovalOf } from "./scope.js";
10
10
  import { verificationEvidence } from "./verification-evidence.js";
11
+ import { refArchived } from "./task-archive.js";
11
12
  import { releaseCiRunningOf } from "./release-ci-hold.js";
12
13
  import { nightlyDebtHold } from "./nightly-github.js";
13
14
  /** Whether run `runId` is a deployable candidate now, read only from the plane's records and this runtime's code. */
@@ -29,6 +30,9 @@ export function deploymentGate(store, input) {
29
30
  return refuse(`Run ${input.runId} is not a built candidate at ${head.slice(0, 7)}.`);
30
31
  if (scope === null || scope.approvedDigest !== scope.digest || scope.digest !== run.scopeDigest)
31
32
  return refuse("The run's signed scope is no longer the approved one.");
33
+ // A put-away task (v145, task-tidy.md) deploys nothing: restore it first.
34
+ if (refArchived(store, run.taskRef))
35
+ return refuse("This task is put away. Restore it before deploying.");
32
36
  // A person may approve during CI, including a release without carry terms. Every deployment road must wait
33
37
  // for the plane's exact-head observation, not only the release coordinator's polling loop.
34
38
  if (releaseCiRunningOf(store, ref.externalId) !== null)
@@ -26,6 +26,9 @@ export type TaskReadinessBlocker = {
26
26
  blockerId: string;
27
27
  blockerState: TaskState;
28
28
  message: string;
29
+ } | {
30
+ code: "archived";
31
+ message: string;
29
32
  };
30
33
  export type DispatchAction = "open-result" | "retry-task" | "place-task" | "write-scope" | "select-agent" | "approve-scope" | "answer-decision" | "unhold" | "inspect-hold" | "repair-dependency" | "repair-capability" | "start-worker"
31
34
  /** v50: ask for the bounded explicit review retry (`task review <run>`
package/dist/dispatch.js CHANGED
@@ -16,7 +16,7 @@ import { REVIEW_TOKENS, resultStatusOf, reviewFactsOf } from "./workspace-ui.js"
16
16
  import { replacedWords } from "./task-status.js";
17
17
  import { failedAttemptSentence, latestFinishedAttempt } from "./needs-you.js";
18
18
  import { buildReviewOf } from "./review-switch.js";
19
- import { BUILT_IN, parseCapabilityKey } from "./store.js";
19
+ import { BUILT_IN, refArchived, parseCapabilityKey } from "./store.js";
20
20
  export const DEFAULT_MAX_OPEN_DECISIONS = 5;
21
21
  /**
22
22
  * Task state, active holds, and unfinished dependencies in the exact order
@@ -33,6 +33,9 @@ export function taskReadinessBlocker(store, taskRef, now) {
33
33
  .get(BUILT_IN, taskRef);
34
34
  if (task === undefined)
35
35
  return { code: "unknown-task", message: "no such task" };
36
+ // A put-away task (v145) never dispatches, whatever its state: restore it first.
37
+ if (refArchived(store, taskRef))
38
+ return { code: "archived", message: "put away" };
36
39
  const state = String(task["state"]);
37
40
  if (state !== "queued")
38
41
  return { code: "state", state, message: `state is ${state}, not queued` };
@@ -0,0 +1,103 @@
1
+ import { type FlowDefinition } from "./flows.js";
2
+ import type { FlowRow, FlowScriptRow, Store } from "./store.js";
3
+ /** v144: each approval of browser use, live or ended. `digest` is browserApprovalDigest of what was approved. Additive only. */
4
+ export declare const FLOW_BROWSER_SCHEMA = "\nCREATE TABLE IF NOT EXISTS flow_browser_grant (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n flow INTEGER NOT NULL,\n repo TEXT NOT NULL,\n flow_revision INTEGER NOT NULL,\n flow_digest TEXT NOT NULL,\n digest TEXT NOT NULL,\n zones_json TEXT NOT NULL,\n signed_by TEXT NOT NULL,\n signed_at TEXT NOT NULL,\n proof TEXT NOT NULL,\n revoked_at TEXT,\n revoked_by TEXT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS flow_browser_live ON flow_browser_grant (flow) WHERE revoked_at IS NULL;\n";
5
+ /** One zone an approval covers: the zone, and the script it runs (name, version and content digest). */
6
+ export type BrowserZone = {
7
+ zone: string;
8
+ script: string;
9
+ version: number;
10
+ digest: string;
11
+ };
12
+ /** Everything an approval is about: the flow, its project, the drawing (revision and digest) and every browser zone. */
13
+ export type BrowserTerms = {
14
+ flow: number;
15
+ repo: string;
16
+ revision: number;
17
+ flowDigest: string;
18
+ zones: BrowserZone[];
19
+ };
20
+ export type FlowBrowserGrant = BrowserTerms & {
21
+ id: number;
22
+ digest: string;
23
+ signedBy: string;
24
+ signedAt: string;
25
+ proof: string;
26
+ };
27
+ /** The approval's digest: domain-separated over the whole of its terms, so a changed flow, drawing, revision or any
28
+ * covered script (a restored one is a new version) is a different approval. */
29
+ export declare function browserApprovalDigest(terms: BrowserTerms): string;
30
+ /** The flow's script zones that say they start a browser, each with the script it runs now; null when one's script is
31
+ * missing, waiting for approval, or runs a project file (an approval seals the script's own text, never a file the
32
+ * project can change). */
33
+ export declare function browserZonesOf(store: Store, flow: FlowRow, definition: FlowDefinition): BrowserZone[] | null;
34
+ /** What an approval of this flow would be about now, read from the store; null when it can't be approved. */
35
+ export declare function browserTermsOf(store: Store, flow: FlowRow): BrowserTerms | null;
36
+ /** The flow's live approval, as recorded (whether it still stands is browserStanding's question). */
37
+ export declare function liveBrowserGrant(store: Store, flowId: number): FlowBrowserGrant | null;
38
+ /**
39
+ * Whether `zone` of this flow may start a browser now. `run`, when given, is what the caller is about to run — the
40
+ * drawing it read and the very script object it hands the launch — and the answer is about exactly that. The live
41
+ * approval stands only when its digest is the approval this flow would be now (same flow, project, drawing revision and
42
+ * digest, and every browser zone's script name, version and digest), it was a person's, and its signer is still an
43
+ * approver who reaches the project; `run`'s drawing must be that drawing and its script that zone's script. Anything
44
+ * unreadable, changed or mismatched is a no, with the plain words a card waits with.
45
+ */
46
+ export declare function browserStanding(store: Store, flowId: number, zone: string, run?: {
47
+ definition: FlowDefinition;
48
+ script: FlowScriptRow;
49
+ }): {
50
+ allowed: true;
51
+ } | {
52
+ allowed: false;
53
+ said: string;
54
+ };
55
+ /** What approving means, in words the person reads before they give their password. */
56
+ export declare function browserTerms(zones: readonly BrowserZone[]): string[];
57
+ /**
58
+ * Approve browser use for the flow as it stands. `proof` is the signer's credential method, proved by the caller just
59
+ * now; only a person's counts (a lead's or remote run's token is refused), from an approver who reaches the project.
60
+ * Re-reads the flow, its zones and scripts in the one transaction that writes; `sawDigest` (required) is the approval
61
+ * (browserApprovalDigest: drawing and scripts) the person read, and anything else is refused. Replaces any live one.
62
+ */
63
+ export declare function signFlowBrowser(store: Store, input: {
64
+ flow: number;
65
+ signer: string;
66
+ proof: string | null;
67
+ sawDigest: string;
68
+ /** The console and chat: the signer's account must still be this generation (the session that proved it) and reach every project. */
69
+ console?: {
70
+ generation: number;
71
+ };
72
+ }, now: Date): {
73
+ ok: true;
74
+ grant: FlowBrowserGrant;
75
+ } | {
76
+ ok: false;
77
+ message: string;
78
+ };
79
+ /** Any approver on the project ends it, with no password: lowering authority. False when there was none. */
80
+ export declare function revokeFlowBrowser(store: Store, flowId: number, by: string, now: Date): boolean;
81
+ /** What the flow page, `flows show` and chat say about browser use: null when no zone says it starts a browser. */
82
+ export type BrowserView = {
83
+ /** allowed: the approval stands now; ended: there is one, but it no longer stands; none: no one has approved. */
84
+ status: "allowed" | "ended" | "none";
85
+ /** The one line every surface shows. */
86
+ words: string;
87
+ signedBy: string | null;
88
+ signedAt: string | null;
89
+ /** The browser zones as they stand now, each with its title. Empty while one can't be approved (see `problem`). */
90
+ zones: (BrowserZone & {
91
+ title: string;
92
+ })[];
93
+ /** What approving means (browserTerms), and the approval digest the step-up must carry back. */
94
+ terms: string[];
95
+ digest: string | null;
96
+ /** Why it can't be approved as it stands, or null. */
97
+ problem: string | null;
98
+ };
99
+ /**
100
+ * Browser use for one flow, read from the store (no writes: page visits call it). Whether it is allowed is exactly
101
+ * browserStanding's answer for the flow's browser zones; the rest only says why in plain words.
102
+ */
103
+ export declare function browserViewOf(store: Store, flow: FlowRow): BrowserView | null;