@skillcdn/console 0.4.2 → 0.4.4

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 (243) hide show
  1. package/README.md +4 -4
  2. package/dist/api.d.ts +3 -3
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +3 -3
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts.map +1 -1
  7. package/dist/cli/cli.js +120 -33
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/format.d.ts +4 -2
  10. package/dist/cli/format.d.ts.map +1 -1
  11. package/dist/cli/format.js +38 -10
  12. package/dist/cli/format.js.map +1 -1
  13. package/dist/cli/help.d.ts.map +1 -1
  14. package/dist/cli/help.js +22 -15
  15. package/dist/cli/help.js.map +1 -1
  16. package/dist/cli/plugin-check.d.ts +10 -1
  17. package/dist/cli/plugin-check.d.ts.map +1 -1
  18. package/dist/cli/plugin-check.js +45 -4
  19. package/dist/cli/plugin-check.js.map +1 -1
  20. package/dist/cli/plugin-cli.d.ts +1 -1
  21. package/dist/cli/plugin-cli.d.ts.map +1 -1
  22. package/dist/cli/plugin-cli.js +80 -5
  23. package/dist/cli/plugin-cli.js.map +1 -1
  24. package/dist/cli/plugin-init.d.ts +19 -0
  25. package/dist/cli/plugin-init.d.ts.map +1 -1
  26. package/dist/cli/plugin-init.js +141 -3
  27. package/dist/cli/plugin-init.js.map +1 -1
  28. package/dist/client.d.ts +7 -2
  29. package/dist/client.d.ts.map +1 -1
  30. package/dist/client.js +18 -5
  31. package/dist/client.js.map +1 -1
  32. package/dist/compat.js +1 -1
  33. package/dist/components/board.d.ts +8 -0
  34. package/dist/components/board.d.ts.map +1 -1
  35. package/dist/components/board.js +3 -3
  36. package/dist/components/board.js.map +1 -1
  37. package/dist/components/comments.d.ts.map +1 -1
  38. package/dist/components/comments.js +1 -1
  39. package/dist/components/comments.js.map +1 -1
  40. package/dist/components/decision-form.d.ts +8 -1
  41. package/dist/components/decision-form.d.ts.map +1 -1
  42. package/dist/components/decision-form.js +57 -7
  43. package/dist/components/decision-form.js.map +1 -1
  44. package/dist/components/decision-list.d.ts +9 -1
  45. package/dist/components/decision-list.d.ts.map +1 -1
  46. package/dist/components/decision-list.js +53 -7
  47. package/dist/components/decision-list.js.map +1 -1
  48. package/dist/components/event-feed.d.ts.map +1 -1
  49. package/dist/components/event-feed.js +7 -1
  50. package/dist/components/event-feed.js.map +1 -1
  51. package/dist/components/goals.d.ts.map +1 -1
  52. package/dist/components/goals.js +2 -2
  53. package/dist/components/goals.js.map +1 -1
  54. package/dist/components/home.d.ts +35 -0
  55. package/dist/components/home.d.ts.map +1 -0
  56. package/dist/components/home.js +326 -0
  57. package/dist/components/home.js.map +1 -0
  58. package/dist/components/my-decisions.d.ts +2 -0
  59. package/dist/components/my-decisions.d.ts.map +1 -1
  60. package/dist/components/my-decisions.js +2 -2
  61. package/dist/components/my-decisions.js.map +1 -1
  62. package/dist/components/pack-connect.d.ts.map +1 -1
  63. package/dist/components/pack-connect.js +5 -4
  64. package/dist/components/pack-connect.js.map +1 -1
  65. package/dist/components/people.d.ts +2 -0
  66. package/dist/components/people.d.ts.map +1 -1
  67. package/dist/components/people.js +15 -2
  68. package/dist/components/people.js.map +1 -1
  69. package/dist/components/plugin-frame.d.ts +3 -12
  70. package/dist/components/plugin-frame.d.ts.map +1 -1
  71. package/dist/components/plugin-frame.js +53 -20
  72. package/dist/components/plugin-frame.js.map +1 -1
  73. package/dist/components/plugin-settings.d.ts.map +1 -1
  74. package/dist/components/plugin-settings.js +151 -26
  75. package/dist/components/plugin-settings.js.map +1 -1
  76. package/dist/components/projects.d.ts +0 -8
  77. package/dist/components/projects.d.ts.map +1 -1
  78. package/dist/components/projects.js +1 -13
  79. package/dist/components/projects.js.map +1 -1
  80. package/dist/components/task-view.d.ts +2 -1
  81. package/dist/components/task-view.d.ts.map +1 -1
  82. package/dist/components/task-view.js +2 -1
  83. package/dist/components/task-view.js.map +1 -1
  84. package/dist/components/ui.d.ts.map +1 -1
  85. package/dist/components/ui.js +5 -2
  86. package/dist/components/ui.js.map +1 -1
  87. package/dist/connect-page.d.ts.map +1 -1
  88. package/dist/connect-page.js +5 -6
  89. package/dist/connect-page.js.map +1 -1
  90. package/dist/console.css +764 -192
  91. package/dist/console.d.ts +10 -3
  92. package/dist/console.d.ts.map +1 -1
  93. package/dist/console.js +49 -28
  94. package/dist/console.js.map +1 -1
  95. package/dist/data.d.ts +6 -3
  96. package/dist/data.d.ts.map +1 -1
  97. package/dist/data.js +31 -7
  98. package/dist/data.js.map +1 -1
  99. package/dist/docs-page.d.ts.map +1 -1
  100. package/dist/docs-page.js +6 -5
  101. package/dist/docs-page.js.map +1 -1
  102. package/dist/home-tiles.d.ts +92 -0
  103. package/dist/home-tiles.d.ts.map +1 -0
  104. package/dist/home-tiles.js +294 -0
  105. package/dist/home-tiles.js.map +1 -0
  106. package/dist/i18n/en.d.ts +180 -0
  107. package/dist/i18n/en.d.ts.map +1 -1
  108. package/dist/i18n/en.js +181 -1
  109. package/dist/i18n/en.js.map +1 -1
  110. package/dist/i18n/index.d.ts +4 -2
  111. package/dist/i18n/index.d.ts.map +1 -1
  112. package/dist/i18n/index.js +10 -11
  113. package/dist/i18n/index.js.map +1 -1
  114. package/dist/i18n/ko.d.ts.map +1 -1
  115. package/dist/i18n/ko.js +176 -3
  116. package/dist/i18n/ko.js.map +1 -1
  117. package/dist/index.d.ts +4 -3
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +4 -3
  120. package/dist/index.js.map +1 -1
  121. package/dist/limits.d.ts +8 -0
  122. package/dist/limits.d.ts.map +1 -1
  123. package/dist/limits.js +8 -0
  124. package/dist/limits.js.map +1 -1
  125. package/dist/plugin/bridge.d.ts +44 -3
  126. package/dist/plugin/bridge.d.ts.map +1 -1
  127. package/dist/plugin/bridge.js +26 -0
  128. package/dist/plugin/bridge.js.map +1 -1
  129. package/dist/plugin/capabilities.d.ts +7 -1
  130. package/dist/plugin/capabilities.d.ts.map +1 -1
  131. package/dist/plugin/capabilities.js +14 -1
  132. package/dist/plugin/capabilities.js.map +1 -1
  133. package/dist/plugin/context.d.ts +24 -1
  134. package/dist/plugin/context.d.ts.map +1 -1
  135. package/dist/plugin/define.d.ts +1 -0
  136. package/dist/plugin/define.d.ts.map +1 -1
  137. package/dist/plugin/define.js +1 -1
  138. package/dist/plugin/define.js.map +1 -1
  139. package/dist/plugin/limits.d.ts +5 -2
  140. package/dist/plugin/limits.d.ts.map +1 -1
  141. package/dist/plugin/limits.js +5 -2
  142. package/dist/plugin/limits.js.map +1 -1
  143. package/dist/plugin/manifest.d.ts +83 -6
  144. package/dist/plugin/manifest.d.ts.map +1 -1
  145. package/dist/plugin/manifest.js +111 -7
  146. package/dist/plugin/manifest.js.map +1 -1
  147. package/dist/plugin/rest-schemas.d.ts +76 -0
  148. package/dist/plugin/rest-schemas.d.ts.map +1 -1
  149. package/dist/plugin/rest-schemas.js +23 -2
  150. package/dist/plugin/rest-schemas.js.map +1 -1
  151. package/dist/plugin/sandbox.d.ts +4 -3
  152. package/dist/plugin/sandbox.d.ts.map +1 -1
  153. package/dist/plugin/sandbox.js +33 -6
  154. package/dist/plugin/sandbox.js.map +1 -1
  155. package/dist/plugin/semver.d.ts +26 -0
  156. package/dist/plugin/semver.d.ts.map +1 -0
  157. package/dist/plugin/semver.js +76 -0
  158. package/dist/plugin/semver.js.map +1 -0
  159. package/dist/plugin/testing.d.ts.map +1 -1
  160. package/dist/plugin/testing.js +2 -1
  161. package/dist/plugin/testing.js.map +1 -1
  162. package/dist/plugin/web.d.ts +10 -1
  163. package/dist/plugin/web.d.ts.map +1 -1
  164. package/dist/plugin/web.js +34 -0
  165. package/dist/plugin/web.js.map +1 -1
  166. package/dist/plugin.d.ts +3 -2
  167. package/dist/plugin.d.ts.map +1 -1
  168. package/dist/plugin.js +3 -2
  169. package/dist/plugin.js.map +1 -1
  170. package/dist/rules.d.ts +2 -2
  171. package/dist/rules.js +3 -3
  172. package/dist/schemas.d.ts +574 -11
  173. package/dist/schemas.d.ts.map +1 -1
  174. package/dist/schemas.js +154 -25
  175. package/dist/schemas.js.map +1 -1
  176. package/dist/use-action.d.ts +8 -2
  177. package/dist/use-action.d.ts.map +1 -1
  178. package/dist/use-action.js +13 -6
  179. package/dist/use-action.js.map +1 -1
  180. package/dist/vocabulary.d.ts +3 -1
  181. package/dist/vocabulary.d.ts.map +1 -1
  182. package/dist/vocabulary.js +3 -0
  183. package/dist/vocabulary.js.map +1 -1
  184. package/docs/specs/cli.md +4 -3
  185. package/docs/specs/plugins.md +49 -35
  186. package/docs/specs/rest.md +20 -18
  187. package/package.json +7 -7
  188. package/skills/building-a-plugin/SKILL.md +10 -9
  189. package/skills/planning-the-board/references/setting-up.md +2 -1
  190. package/skills/working-the-board/SKILL.md +3 -3
  191. package/src/api.ts +10 -0
  192. package/src/cli/cli.ts +162 -39
  193. package/src/cli/format.ts +54 -10
  194. package/src/cli/help.ts +23 -14
  195. package/src/cli/plugin-check.ts +68 -2
  196. package/src/cli/plugin-cli.ts +104 -6
  197. package/src/cli/plugin-init.ts +178 -3
  198. package/src/client.ts +36 -9
  199. package/src/compat.ts +1 -1
  200. package/src/components/board.tsx +3 -3
  201. package/src/components/comments.tsx +1 -0
  202. package/src/components/decision-form.tsx +166 -29
  203. package/src/components/decision-list.tsx +116 -15
  204. package/src/components/event-feed.tsx +7 -1
  205. package/src/components/goals.tsx +4 -1
  206. package/src/components/home.tsx +615 -0
  207. package/src/components/my-decisions.tsx +4 -1
  208. package/src/components/pack-connect.tsx +5 -4
  209. package/src/components/people.tsx +136 -54
  210. package/src/components/plugin-frame.tsx +62 -63
  211. package/src/components/plugin-settings.tsx +428 -87
  212. package/src/components/projects.tsx +0 -55
  213. package/src/components/task-view.tsx +9 -1
  214. package/src/components/ui.tsx +6 -2
  215. package/src/connect-page.tsx +5 -6
  216. package/src/console.tsx +98 -42
  217. package/src/data.ts +41 -11
  218. package/src/docs-page.tsx +6 -5
  219. package/src/home-tiles.ts +438 -0
  220. package/src/i18n/en.ts +185 -0
  221. package/src/i18n/index.ts +17 -14
  222. package/src/i18n/ko.ts +181 -3
  223. package/src/index.ts +7 -5
  224. package/src/limits.ts +8 -0
  225. package/src/plugin/bridge.ts +77 -1
  226. package/src/plugin/capabilities.ts +17 -1
  227. package/src/plugin/context.ts +24 -2
  228. package/src/plugin/define.ts +4 -1
  229. package/src/plugin/limits.ts +5 -2
  230. package/src/plugin/manifest.ts +154 -10
  231. package/src/plugin/rest-schemas.ts +27 -1
  232. package/src/plugin/sandbox.ts +36 -8
  233. package/src/plugin/semver.ts +89 -0
  234. package/src/plugin/testing.ts +2 -1
  235. package/src/plugin/web.ts +49 -0
  236. package/src/plugin.ts +18 -0
  237. package/src/rules.ts +3 -3
  238. package/src/schemas.ts +190 -28
  239. package/src/styles/console.css +764 -192
  240. package/src/use-action.ts +16 -7
  241. package/src/vocabulary.ts +4 -0
  242. package/template/plugin/console-plugin.json +3 -1
  243. package/template/plugin/package.json +3 -3
package/src/schemas.ts CHANGED
@@ -16,9 +16,12 @@ import {
16
16
  MAX_BODY_LENGTH,
17
17
  MAX_CATEGORY_KEY_LENGTH,
18
18
  MAX_CATEGORY_NAME_LENGTH,
19
+ MAX_DECIDERS,
19
20
  MAX_DOCUMENT_LENGTH,
20
21
  MAX_DOCUMENT_PATH_LENGTH,
21
22
  MAX_FILE_NAME_LENGTH,
23
+ MAX_HOME_ROW,
24
+ MAX_HOME_TILES,
22
25
  MAX_LANGUAGE_TAG_LENGTH,
23
26
  MAX_LINK_LABEL_LENGTH,
24
27
  MAX_LINKS,
@@ -42,6 +45,7 @@ import {
42
45
  MAX_WORKERS,
43
46
  MIN_OPTIONS,
44
47
  } from "./limits.js";
48
+ import { PLUGIN_WIDGET_SIZE_NAMES } from "./plugin/manifest.js";
45
49
  import { hasForbiddenCodePoint } from "./text.js";
46
50
  import {
47
51
  ARTIFACT_KINDS,
@@ -100,6 +104,14 @@ const day = z.iso.date();
100
104
  const count = z.int().check(z.nonnegative());
101
105
 
102
106
  /** A person of the workspace, as the pages show them. Nothing here is a secret. */
107
+ /** A language tag as a browser sends one (`en`, `ko-KR`). */
108
+ const languageTag = z
109
+ .string()
110
+ .check(
111
+ z.maxLength(MAX_LANGUAGE_TAG_LENGTH),
112
+ z.regex(/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/, "must be a language tag"),
113
+ );
114
+
103
115
  export const restPersonSchema = z.object({
104
116
  id: uuid,
105
117
  /** What the account is called at its provider: a login at the git host, an address at Google. */
@@ -116,6 +128,19 @@ export const restPersonSchema = z.object({
116
128
  source: z.enum(MEMBERSHIP_SOURCES),
117
129
  /** When they were deactivated, or `null`. */
118
130
  deactivatedAt: z.nullable(instant),
131
+ /**
132
+ * The id of the person they report to, the reporting line of the directory an administrator
133
+ * keeps, or `null` for nobody; what a plugin reads as the default approver, the default
134
+ * assignee, in place of a line of its own. Answered from 0.4.4 and read as optional, so that
135
+ * a console built on this package still reads an older console's people.
136
+ */
137
+ managerId: z.optional(z.nullable(uuid)),
138
+ /**
139
+ * The language the person reads in (ADR-0065), a tag: the one they chose, else the one the pages
140
+ * first saw them in; `null` until they have opened the pages. What a plugin or an agent writes
141
+ * for them in. Answered from 0.4.4 and read as optional, as `managerId` is.
142
+ */
143
+ language: z.optional(z.nullable(languageTag)),
119
144
  });
120
145
  export type RestPerson = z.infer<typeof restPersonSchema>;
121
146
 
@@ -195,16 +220,59 @@ export const timeZoneName = z
195
220
  z.regex(/^[A-Za-z0-9_+-]+(?:\/[A-Za-z0-9_+-]+)*$/, "must be a time zone name"),
196
221
  );
197
222
 
198
- /** A language tag as a browser sends one (`en`, `ko-KR`): what the pages speak, by the person's choice. */
199
- const languageTag = z
223
+ /** What `PATCH /api/v1/me` is sent: the language the person chose, or `null` to choose none. */
224
+ /** The sizes of a tile on the front page (ADR-0061): a project's icon, and the widgets' family. */
225
+ export const HOME_TILE_SIZES = ["icon", ...PLUGIN_WIDGET_SIZE_NAMES] as const;
226
+
227
+ /** A tile by what it is: a project by its id, a widget by its plugin's key and its own. */
228
+ const homeTileId = z
200
229
  .string()
201
230
  .check(
202
- z.maxLength(MAX_LANGUAGE_TAG_LENGTH),
203
- z.regex(/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/, "must be a language tag"),
231
+ z.regex(
232
+ /^(?:project:[0-9a-f-]{36}|widget:[a-z][a-z0-9-]{0,31}:[a-z][a-z0-9-]{0,31})$/,
233
+ "must name a project or a widget",
234
+ ),
204
235
  );
205
236
 
206
- /** What `PATCH /api/v1/me` is sent: the language the person chose, or `null` to choose none. */
207
- export const restMePatchSchema = z.object({ language: z.nullable(languageTag) });
237
+ /** One tile of a person's own front page, at a place of the grid and at a size. */
238
+ export const restHomeTileSchema = z.object({
239
+ tile: homeTileId,
240
+ x: z.int().check(z.gte(0), z.lte(11)),
241
+ y: z.int().check(z.gte(0), z.lte(MAX_HOME_ROW)),
242
+ size: z.enum(HOME_TILE_SIZES),
243
+ });
244
+ export type RestHomeTile = z.infer<typeof restHomeTileSchema>;
245
+
246
+ const homeLayoutSchema = z.array(restHomeTileSchema).check(z.maxLength(MAX_HOME_TILES));
247
+
248
+ /**
249
+ * A person's own front page (ADR-0061): the tiles they took off it, which stay off, and what
250
+ * they arranged at each width of the grid, by its columns; what they have not seen yet takes
251
+ * the first room.
252
+ */
253
+ export const restHomeSchema = z.object({
254
+ hidden: z.array(homeTileId).check(z.maxLength(MAX_HOME_TILES)),
255
+ layouts: z.object({
256
+ 12: z.optional(homeLayoutSchema),
257
+ 8: z.optional(homeLayoutSchema),
258
+ 6: z.optional(homeLayoutSchema),
259
+ 4: z.optional(homeLayoutSchema),
260
+ }),
261
+ });
262
+ export type RestHome = z.infer<typeof restHomeSchema>;
263
+
264
+ /** What `PATCH /api/v1/me` is sent: the language, the front page, either or both; `null` lets the console choose. */
265
+ export const restMePatchSchema = z
266
+ .object({
267
+ language: z.optional(z.nullable(languageTag)),
268
+ home: z.optional(z.nullable(restHomeSchema)),
269
+ })
270
+ .check(
271
+ z.refine(
272
+ (patch) => patch.language !== undefined || patch.home !== undefined,
273
+ "must name a language or the front page",
274
+ ),
275
+ );
208
276
  export type RestMePatch = z.infer<typeof restMePatchSchema>;
209
277
 
210
278
  /**
@@ -274,8 +342,13 @@ export const restMeSchema = z.object({
274
342
  * person's own knows it holds one. `null` on a session, and for nobody.
275
343
  */
276
344
  agent: z.nullable(z.string()),
277
- /** The language the person chose for the pages (ADR-0012), or `null` for the browser's. */
345
+ /** The language the person reads in (ADR-0065): the one they chose, else the one the pages first saw them in; `null` until then. */
278
346
  language: z.nullable(languageTag),
347
+ /**
348
+ * The person's own front page (ADR-0061), or `null` while they have arranged nothing; answered
349
+ * from 0.4.4 and read as optional.
350
+ */
351
+ home: z.optional(z.nullable(restHomeSchema)),
279
352
  signIn: z.array(restProviderSchema),
280
353
  /**
281
354
  * The console's version and the range of command versions it takes, as npm spells one
@@ -284,6 +357,13 @@ export const restMeSchema = z.object({
284
357
  * built on this package still reads an older console's answer, and says it is older.
285
358
  */
286
359
  console: z.optional(z.object({ version: z.string(), clientRange: z.string() })),
360
+ /**
361
+ * How many decisions wait for the person's answer across the projects they may see
362
+ * (ADR-0042), for the pages to say beside My decisions; `null` for nobody. Answered from
363
+ * 0.4.3 and read as optional, so that a console built on this package still reads an older
364
+ * console's answer.
365
+ */
366
+ decisions: z.optional(z.nullable(z.object({ waiting: count }))),
287
367
  });
288
368
  export type RestMe = z.infer<typeof restMeSchema>;
289
369
 
@@ -306,11 +386,14 @@ export const restPersonPatchSchema = z
306
386
  .object({
307
387
  role: z.optional(z.enum(PERSON_ROLES)),
308
388
  status: z.optional(z.enum(PERSON_STATUSES)),
389
+ /** Whom the person reports to: another active person of the workspace, or `null` for nobody. */
390
+ managerId: z.optional(z.nullable(uuid)),
309
391
  })
310
392
  .check(
311
393
  z.refine(
312
- (patch) => patch.role !== undefined || patch.status !== undefined,
313
- "must name a role or a status",
394
+ (patch) =>
395
+ patch.role !== undefined || patch.status !== undefined || patch.managerId !== undefined,
396
+ "must name a role, a status or a manager",
314
397
  ),
315
398
  );
316
399
  export type RestPersonPatch = z.infer<typeof restPersonPatchSchema>;
@@ -498,6 +581,8 @@ export const restTaskSchema = z.object({
498
581
  owner: restPersonSchema,
499
582
  /** The plugin that made it, by key (ADR-0052), or `null`; left out by a console older than the plugins. */
500
583
  plugin: z.optional(z.nullable(z.string())),
584
+ /** The agent the task was written through, or `null` when a person wrote it themselves (ADR-0056); the owner may have changed since. Left out by an older console. */
585
+ agent: z.optional(z.nullable(z.string())),
501
586
  /** The people at work on it (ADR-0037), in the order they came to it; `[]` while nobody is. */
502
587
  workers: z.array(restPersonSchema),
503
588
  /** The task this one is part of, or `null`. */
@@ -608,6 +693,8 @@ export const restGoalSchema = z.object({
608
693
  /** The day the goal is meant to be reached by, or `null`. */
609
694
  dueOn: z.nullable(day),
610
695
  createdBy: restPersonSchema,
696
+ /** The agent the goal was set through, or `null` when a person set it themselves (ADR-0056); left out by an older console. */
697
+ agent: z.optional(z.nullable(z.string())),
611
698
  /** The tasks that work towards it, counted by state. */
612
699
  taskCounts: restTaskCountsSchema,
613
700
  createdAt: instant,
@@ -643,10 +730,50 @@ export const restGoalPatchSchema = z.object({
643
730
  });
644
731
  export type RestGoalPatch = z.infer<typeof restGoalPatchSchema>;
645
732
 
733
+ /** A file a run handed in, as the console keeps it; its bytes are at `projectPath(key, "files", <artifact id>)`. */
734
+ export const restFileSchema = z.object({
735
+ /** What the agent called it: one line, without a path. */
736
+ name: z.string(),
737
+ /** In bytes. */
738
+ size: count,
739
+ /** The media type the agent said, or `application/octet-stream`. */
740
+ contentType: z.string(),
741
+ /** The SHA-256 of the bytes, in hex: what the console keeps them under. */
742
+ sha256: z.string(),
743
+ });
744
+ export type RestFile = z.infer<typeof restFileSchema>;
745
+
746
+ /** A file put on a decision when it was raised (ADR-0055), kept as a run's file is; its bytes are at `projectPath(key, "files", <id>)`. */
747
+ export const restDecisionFileSchema = z.object({
748
+ id: uuid,
749
+ file: restFileSchema,
750
+ });
751
+ export type RestDecisionFile = z.infer<typeof restDecisionFileSchema>;
752
+
753
+ /**
754
+ * The key a raiser may give an option, for a program to read the answer by instead of
755
+ * comparing labels: spelled as a plugin's key is.
756
+ */
757
+ export const OPTION_KEY_PATTERN = /^[a-z][a-z0-9-]{1,31}$/;
758
+ const optionKey = z
759
+ .string()
760
+ .check(
761
+ z.regex(
762
+ OPTION_KEY_PATTERN,
763
+ "must be a key: a lowercase letter, then lowercase letters, digits and hyphens, 2 to 32 long",
764
+ ),
765
+ );
766
+
646
767
  export const restDecisionOptionSchema = z.object({
647
768
  /** What an answer names: stable once given. */
648
769
  id: z.string(),
649
770
  label: z.string(),
771
+ /**
772
+ * The key the raiser gave the option, for a program to read the answer by (`optionKey` on the
773
+ * `decision.answered` event) without comparing labels; none on an option given without one,
774
+ * and none on the person's own answer.
775
+ */
776
+ key: z.optional(z.string()),
650
777
  /**
651
778
  * On the one option a person's own answer became, when they answered in their own words
652
779
  * instead of choosing (`POST .../answer` with `own`): added then, after the options given,
@@ -667,8 +794,15 @@ export const restDecisionSchema = z.object({
667
794
  /** The task's number, when there is a task, or `null`. */
668
795
  taskNumber: z.nullable(z.int()),
669
796
  raisedBy: restPersonSchema,
670
- /** Who is to answer (ADR-0042): the raiser, unless another person of the project was named. */
797
+ /** The first of those to answer (ADR-0042): the raiser, unless others of the project were named. */
671
798
  decider: restPersonSchema,
799
+ /**
800
+ * Everyone the decision is addressed to (ADR-0057), `decider` first: each finds it among their
801
+ * decisions, any of them may answer, and the first answer ends it. Answered from 0.4.4 and read
802
+ * as optional, so that a console built on this package still reads an older console's
803
+ * decisions, where it is `[decider]`.
804
+ */
805
+ deciders: z.optional(z.array(restPersonSchema)),
672
806
  /** The run that raised it, as the agent asked, or `null` when a person did. */
673
807
  run: z.nullable(z.object({ id: uuid, agent: z.string() })),
674
808
  /** The plugin that raised it, by key (ADR-0052), or `null`; left out by a console older than the plugins. */
@@ -682,10 +816,31 @@ export const restDecisionSchema = z.object({
682
816
  note: z.nullable(z.string()),
683
817
  by: restPersonSchema,
684
818
  at: instant,
819
+ /**
820
+ * The agent the answer came through, when the person's agent wrote it in their name
821
+ * (`console answer`, ADR-0021), or `null` when they answered themselves (ADR-0056).
822
+ * Answered from 0.4.4 and read as optional, so that a console built on this package still
823
+ * reads an older console's decisions.
824
+ */
825
+ agent: z.optional(z.nullable(z.string())),
685
826
  }),
686
827
  ),
687
828
  /** What followed the decision, in Markdown, written afterwards; `null` until someone does. */
688
829
  outcome: z.nullable(z.string()),
830
+ /**
831
+ * Who wrote what followed, the agent they wrote it through (`null` when themselves) and when
832
+ * (ADR-0056); each `null` until someone does. Answered from 0.4.4 and read as optional.
833
+ */
834
+ outcomeBy: z.optional(z.nullable(restPersonSchema)),
835
+ outcomeAgent: z.optional(z.nullable(z.string())),
836
+ outcomeAt: z.optional(z.nullable(instant)),
837
+ /**
838
+ * The files put on it when it was raised (ADR-0055), in the order they came; a picture among
839
+ * them is shown where the context names it as `![words](its name)`, else after it. Answered
840
+ * from 0.4.3 and read as optional, so that a console built on this package still reads an
841
+ * older console's decisions.
842
+ */
843
+ files: z.optional(z.array(restDecisionFileSchema)),
689
844
  createdAt: instant,
690
845
  updatedAt: instant,
691
846
  });
@@ -742,18 +897,35 @@ export const restPersonWorkSchema = z.object({
742
897
  });
743
898
  export type RestPersonWork = z.infer<typeof restPersonWorkSchema>;
744
899
 
745
- /** What `POST /api/v1/projects/<key>/decisions` is sent: the question, its context, and the options. */
900
+ /**
901
+ * What `POST /api/v1/projects/<key>/decisions` is sent: the question, its context, and the
902
+ * options. As JSON; or, with files put on the decision (ADR-0055), as the part `decision` of a
903
+ * form, with a part `file` for each file.
904
+ */
746
905
  export const restDecisionInputSchema = z.object({
747
906
  question: line(MAX_QUESTION_LENGTH),
748
907
  body: z.optional(body),
908
+ /** Each a label, and optionally a key a program reads the answer by, each key once. */
749
909
  options: z
750
- .array(z.object({ label: line(MAX_OPTION_LABEL_LENGTH) }))
751
- .check(z.minLength(MIN_OPTIONS), z.maxLength(MAX_OPTIONS)),
910
+ .array(z.object({ label: line(MAX_OPTION_LABEL_LENGTH), key: z.optional(optionKey) }))
911
+ .check(
912
+ z.minLength(MIN_OPTIONS),
913
+ z.maxLength(MAX_OPTIONS),
914
+ z.refine((options) => {
915
+ const keys = options.flatMap((option) => (option.key === undefined ? [] : [option.key]));
916
+ return new Set(keys).size === keys.length;
917
+ }, "each key once"),
918
+ ),
752
919
  taskId: z.optional(z.nullable(uuid)),
753
920
  /** The run that asks, when an agent does: the decision is about its task, and it waits for the answer. */
754
921
  runId: z.optional(z.nullable(uuid)),
755
922
  /** Who is to answer (ADR-0042): a person who may work in the project; the asker when left out. */
756
923
  deciderId: z.optional(uuid),
924
+ /**
925
+ * Several to answer (ADR-0057), in place of `deciderId`: each a person who may work in the
926
+ * project, up to `MAX_DECIDERS`; any of them may answer, and the first answer ends the decision.
927
+ */
928
+ deciderIds: z.optional(z.array(uuid).check(z.minLength(1), z.maxLength(MAX_DECIDERS))),
757
929
  });
758
930
  export type RestDecisionInput = z.infer<typeof restDecisionInputSchema>;
759
931
 
@@ -801,11 +973,14 @@ export const restEventDataSchema = z.object({
801
973
  from: z.optional(z.enum(TASK_STATES)),
802
974
  to: z.optional(z.enum(TASK_STATES)),
803
975
  question: z.optional(z.string()),
804
- /** For `decision.answered`: the label of the chosen option. */
976
+ /** For `decision.answered`: the label of the chosen option, and its key where the raiser gave one. */
805
977
  option: z.optional(z.string()),
978
+ optionKey: z.optional(z.string()),
806
979
  /** For `person.role_changed` and the events about a project's members: whose role, and what it became. */
807
980
  login: z.optional(z.string()),
808
981
  role: z.optional(z.string()),
982
+ /** For `person.manager_changed`: the login of whom the person reports to now, or `null` for nobody. */
983
+ manager: z.optional(z.nullable(z.string())),
809
984
  /** For the events about a project: its key and its name. */
810
985
  key: z.optional(z.string()),
811
986
  name: z.optional(z.string()),
@@ -818,7 +993,7 @@ export const restEventDataSchema = z.object({
818
993
  label: z.optional(z.string()),
819
994
  /** For `run.reported` and `task.commented`: the first words of what was written. */
820
995
  excerpt: z.optional(z.string()),
821
- /** For `task.commented`: how many files came with it. */
996
+ /** For `task.commented`, and for `decision.raised` when any came: how many files came with it. */
822
997
  files: z.optional(z.int()),
823
998
  /** For `plugin.command`: the command, how it ended, the refusal's code where it was refused, how long it ran, and the arguments, a secret one as `[hidden]`. */
824
999
  command: z.optional(z.string()),
@@ -867,19 +1042,6 @@ export const restReportSchema = z.object({
867
1042
  });
868
1043
  export type RestReport = z.infer<typeof restReportSchema>;
869
1044
 
870
- /** A file a run handed in, as the console keeps it; its bytes are at `projectPath(key, "files", <artifact id>)`. */
871
- export const restFileSchema = z.object({
872
- /** What the agent called it: one line, without a path. */
873
- name: z.string(),
874
- /** In bytes. */
875
- size: count,
876
- /** The media type the agent said, or `application/octet-stream`. */
877
- contentType: z.string(),
878
- /** The SHA-256 of the bytes, in hex: what the console keeps them under. */
879
- sha256: z.string(),
880
- });
881
- export type RestFile = z.infer<typeof restFileSchema>;
882
-
883
1045
  /** What a run handed in: a link to a branch, a pull request, a page; or a file the console keeps. */
884
1046
  export const restArtifactSchema = z.object({
885
1047
  id: uuid,