toolroll 0.9.36 → 0.9.38

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 (175) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/assignment-adapters.d.ts +12 -48
  3. package/dist/assignment-adapters.js +33 -43
  4. package/dist/browser/THIRD_PARTY_NOTICES.txt +3 -3
  5. package/dist/browser/workspace.css +1 -1
  6. package/dist/browser/workspace.js +26 -26
  7. package/dist/browser-crew.js +5 -0
  8. package/dist/browser-workspace.d.ts +2 -0
  9. package/dist/build-review.d.ts +1 -1
  10. package/dist/build-review.js +2 -1
  11. package/dist/chat-acceptance.d.ts +1 -1
  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-channel.js +20 -9
  16. package/dist/chat-decide.d.ts +18 -4
  17. package/dist/chat-decide.js +31 -11
  18. package/dist/chat-delivery-state.d.ts +12 -70
  19. package/dist/chat-delivery-state.js +17 -135
  20. package/dist/chat-delivery.js +58 -30
  21. package/dist/chat-flow.js +8 -6
  22. package/dist/chat-inbox.js +1 -1
  23. package/dist/coding-context.d.ts +2 -39
  24. package/dist/coding-context.js +2 -1
  25. package/dist/coding-handoff.d.ts +6 -25
  26. package/dist/coding-handoff.js +11 -5
  27. package/dist/coding-types.d.ts +2 -26
  28. package/dist/coding-workspace.d.ts +2 -0
  29. package/dist/coding-workspace.js +50 -14
  30. package/dist/contracts/acceptance-terms.d.ts +14 -0
  31. package/dist/contracts/acceptance-terms.js +16 -0
  32. package/dist/contracts/chat-actions.d.ts +328 -0
  33. package/dist/contracts/chat-actions.js +133 -0
  34. package/dist/contracts/chat-callback-rows.d.ts +59 -0
  35. package/dist/contracts/chat-callback-rows.js +46 -0
  36. package/dist/contracts/chat-content.d.ts +276 -0
  37. package/dist/contracts/chat-content.js +127 -0
  38. package/dist/contracts/chat-tables.d.ts +6 -0
  39. package/dist/contracts/chat-tables.js +136 -0
  40. package/dist/contracts/coding-context.d.ts +97 -0
  41. package/dist/contracts/coding-context.js +60 -0
  42. package/dist/contracts/coding-handoff.d.ts +44 -0
  43. package/dist/contracts/coding-handoff.js +47 -0
  44. package/dist/contracts/coding-workspace.d.ts +165 -0
  45. package/dist/contracts/coding-workspace.js +71 -0
  46. package/dist/contracts/contract.d.ts +7 -2
  47. package/dist/contracts/contract.js +21 -12
  48. package/dist/contracts/decision.d.ts +45 -0
  49. package/dist/contracts/decision.js +45 -0
  50. package/dist/contracts/discord-callback.d.ts +45 -0
  51. package/dist/contracts/discord-callback.js +34 -0
  52. package/dist/contracts/flow-card.d.ts +50 -0
  53. package/dist/contracts/flow-card.js +46 -0
  54. package/dist/contracts/flow-send.d.ts +119 -0
  55. package/dist/contracts/flow-send.js +112 -0
  56. package/dist/contracts/flow.d.ts +5 -0
  57. package/dist/contracts/flow.js +5 -0
  58. package/dist/contracts/gateway-tools.d.ts +374 -0
  59. package/dist/contracts/gateway-tools.js +94 -0
  60. package/dist/contracts/lead-context.d.ts +165 -0
  61. package/dist/contracts/lead-context.js +73 -0
  62. package/dist/contracts/lead-tools.d.ts +1905 -0
  63. package/dist/contracts/lead-tools.js +265 -0
  64. package/dist/contracts/memory-pass.d.ts +158 -0
  65. package/dist/contracts/memory-pass.js +102 -0
  66. package/dist/contracts/project-knowledge.d.ts +126 -0
  67. package/dist/contracts/project-knowledge.js +93 -0
  68. package/dist/contracts/project-memory.d.ts +117 -0
  69. package/dist/contracts/project-memory.js +51 -0
  70. package/dist/contracts/project-skills.d.ts +121 -0
  71. package/dist/contracts/project-skills.js +76 -0
  72. package/dist/contracts/review-findings.d.ts +46 -0
  73. package/dist/contracts/review-findings.js +48 -0
  74. package/dist/contracts/route.d.ts +484 -0
  75. package/dist/contracts/route.js +128 -0
  76. package/dist/contracts/scope.d.ts +105 -0
  77. package/dist/contracts/scope.js +114 -0
  78. package/dist/contracts/scout-report.d.ts +234 -0
  79. package/dist/contracts/scout-report.js +141 -0
  80. package/dist/contracts/slack-callback.d.ts +43 -0
  81. package/dist/contracts/slack-callback.js +35 -0
  82. package/dist/contracts/sort-answer.d.ts +83 -0
  83. package/dist/contracts/sort-answer.js +77 -0
  84. package/dist/contracts/stage-output.d.ts +127 -0
  85. package/dist/contracts/stage-output.js +166 -0
  86. package/dist/contracts/task-sizing.d.ts +24 -0
  87. package/dist/contracts/task-sizing.js +28 -0
  88. package/dist/contracts/teammate-turn.d.ts +43 -0
  89. package/dist/contracts/teammate-turn.js +51 -0
  90. package/dist/contracts/teams-callback.d.ts +14 -0
  91. package/dist/contracts/teams-callback.js +14 -0
  92. package/dist/contracts/telegram-callback.d.ts +120 -0
  93. package/dist/contracts/telegram-callback.js +97 -0
  94. package/dist/decision.d.ts +11 -20
  95. package/dist/decision.js +86 -167
  96. package/dist/desktop-update.d.ts +1 -1
  97. package/dist/discord-chat.js +31 -20
  98. package/dist/flow-engine.js +14 -17
  99. package/dist/flow-items.d.ts +4 -13
  100. package/dist/flow-items.js +1 -15
  101. package/dist/flow-send.d.ts +6 -37
  102. package/dist/flow-send.js +18 -14
  103. package/dist/flow-share.js +7 -1
  104. package/dist/flow-sort.d.ts +7 -26
  105. package/dist/flow-sort.js +25 -20
  106. package/dist/flow-steps.js +3 -2
  107. package/dist/flows.d.ts +2 -4
  108. package/dist/flows.js +9 -5
  109. package/dist/invoke.js +4 -4
  110. package/dist/lead-context.d.ts +4 -2
  111. package/dist/lead-context.js +24 -17
  112. package/dist/mate-progress.d.ts +26 -0
  113. package/dist/mate-progress.js +24 -0
  114. package/dist/mate-tools.d.ts +4 -0
  115. package/dist/mate-tools.js +108 -275
  116. package/dist/mate.js +11 -2
  117. package/dist/mcp-connect.d.ts +24 -4
  118. package/dist/mcp-connect.js +78 -15
  119. package/dist/mcp.js +84 -216
  120. package/dist/memory-pass.d.ts +17 -42
  121. package/dist/memory-pass.js +54 -26
  122. package/dist/phase-routing.d.ts +19 -72
  123. package/dist/phase-routing.js +34 -164
  124. package/dist/project-knowledge.d.ts +16 -35
  125. package/dist/project-knowledge.js +42 -21
  126. package/dist/project-memory.d.ts +2 -28
  127. package/dist/project-memory.js +28 -13
  128. package/dist/project-skills.d.ts +16 -38
  129. package/dist/project-skills.js +41 -32
  130. package/dist/project-tools.d.ts +1 -0
  131. package/dist/project-tools.js +4 -4
  132. package/dist/provider.d.ts +2 -0
  133. package/dist/provider.js +4 -16
  134. package/dist/result-shots.d.ts +1 -1
  135. package/dist/result-shots.js +2 -1
  136. package/dist/review-switch.d.ts +3 -8
  137. package/dist/reviewer.d.ts +8 -12
  138. package/dist/reviewer.js +11 -35
  139. package/dist/scope.d.ts +21 -43
  140. package/dist/scope.js +42 -128
  141. package/dist/scout-report.d.ts +9 -194
  142. package/dist/scout-report.js +113 -264
  143. package/dist/scout.js +15 -11
  144. package/dist/serve.js +23 -5
  145. package/dist/setup-guide.d.ts +2 -2
  146. package/dist/slack-chat.js +26 -20
  147. package/dist/slack-state.js +2 -1
  148. package/dist/store.d.ts +9 -32
  149. package/dist/store.js +18 -15
  150. package/dist/structured-output.js +2 -1
  151. package/dist/subscription-chat.d.ts +4 -0
  152. package/dist/subscription-chat.js +4 -1
  153. package/dist/task-review-brief.d.ts +1 -7
  154. package/dist/task-review-brief.js +6 -6
  155. package/dist/task-sizing.d.ts +3 -27
  156. package/dist/task-sizing.js +11 -26
  157. package/dist/task-text.d.ts +0 -5
  158. package/dist/task-text.js +1 -3
  159. package/dist/teammate-question.js +7 -5
  160. package/dist/teammate-work.js +9 -5
  161. package/dist/teammates.d.ts +13 -64
  162. package/dist/teammates.js +32 -33
  163. package/dist/teams-chat.js +20 -11
  164. package/dist/telegram-flow.d.ts +9 -0
  165. package/dist/telegram-flow.js +44 -8
  166. package/dist/telegram-mate.d.ts +4 -14
  167. package/dist/telegram-mate.js +9 -8
  168. package/dist/telegram-team.d.ts +2 -19
  169. package/dist/telegram.d.ts +1 -1
  170. package/dist/telegram.js +94 -68
  171. package/dist/text-limits.d.ts +142 -1
  172. package/dist/text-limits.js +144 -1
  173. package/dist/tools-ui.d.ts +2 -1
  174. package/dist/tools-ui.js +7 -4
  175. package/package.json +17 -12
@@ -12,12 +12,13 @@ import { limitRule, TEXT_LIMITS } from "../text-limits.js";
12
12
  * A string bounded by its TEXT_LIMITS entry, never a literal. The limit rule rides along as the field's description,
13
13
  * so a model reads the bound before it writes. Zod counts UTF-16 code units; for a limit named in bytes this is the
14
14
  * schema's necessary bound (a string within N bytes is within N code units), and the byte count itself is checked in
15
- * plain code after parsing.
15
+ * plain code after parsing. `shorten: false` is for a contract with no shorten turn (a parked decision): its
16
+ * description states the bound without promising one.
16
17
  */
17
- export function limited(field, limitKey) {
18
+ export function limited(field, limitKey, options = {}) {
18
19
  const limit = TEXT_LIMITS[limitKey];
19
20
  const unit = limitKey.endsWith("Bytes") ? "bytes" : "characters";
20
- return z.string().max(limit, { error: `over ${limit.toLocaleString("en-US")} ${unit}` }).describe(limitRule(field, limit, unit));
21
+ return z.string().max(limit, { error: `over ${limit.toLocaleString("en-US")} ${unit}` }).describe(limitRule(field, limit, unit, options.shorten ?? true));
21
22
  }
22
23
  /** A versioned payload envelope: a strict object whose `version` is exactly `version`. */
23
24
  export function versioned(version, shape) {
@@ -42,12 +43,15 @@ function got(input) {
42
43
  return typeof input === "object" ? "an object" : `a ${typeof input}`;
43
44
  }
44
45
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
45
- /** The keys an object schema allows at `path` in `input`, following arrays, wrappers and (by its discriminator) a union. */
46
- function keysAt(schema, input, path) {
46
+ /** The schema at `path`, including whether that value may be omitted, following arrays, wrappers and unions. */
47
+ function nodeAt(schema, input, path) {
47
48
  let node = schema, value = input;
49
+ let optional = false;
48
50
  for (let at = 0; node !== undefined;) {
49
51
  const def = node._zod.def;
50
52
  if (def.type === "optional" || def.type === "nullable" || def.type === "readonly") {
53
+ if (def.type === "optional")
54
+ optional = true;
51
55
  node = def["innerType"];
52
56
  continue;
53
57
  }
@@ -71,8 +75,9 @@ function keysAt(schema, input, path) {
71
75
  continue;
72
76
  }
73
77
  if (at === path.length)
74
- return def.type === "object" ? Object.keys(def["shape"]) : null;
78
+ return { def, optional };
75
79
  const part = path[at++];
80
+ optional = false;
76
81
  if (def.type === "array" && typeof part === "number") {
77
82
  node = def["element"];
78
83
  value = Array.isArray(value) ? value[part] : undefined;
@@ -120,7 +125,8 @@ function issuesOf(issue, where = {}) {
120
125
  const one = (kind, what) => ({ path: at, kind, line: `${at}: ${what}` });
121
126
  switch (issue.code) {
122
127
  case "unrecognized_keys": {
123
- const known = where.schema === undefined ? null : keysAt(where.schema, where.input, issue.path);
128
+ const def = where.schema === undefined ? undefined : nodeAt(where.schema, where.input, issue.path)?.def;
129
+ const known = def?.type === "object" ? Object.keys(def["shape"]) : null;
124
130
  return issue.keys.map(key => {
125
131
  const meant = suggestion(key, known, where.options ?? {});
126
132
  return { path: at, kind: "unknown-key", line: `${at}: unknown key '${key}'${meant === null ? "" : ` (did you mean ${meant}?)`}` };
@@ -128,10 +134,12 @@ function issuesOf(issue, where = {}) {
128
134
  }
129
135
  case "invalid_type": {
130
136
  const missing = "input" in issue ? issue.input === undefined : / received undefined$/.test(issue.message);
131
- // A null where a value belongs is a value left out.
132
- if (missing || ("input" in issue && issue.input === null && issue.expected !== "null"))
137
+ // Keep required-null wording for required fields; an optional field given null has the wrong type, and so does
138
+ // any null when the caller distinguishes null from missing.
139
+ const optional = where.schema !== undefined && nodeAt(where.schema, where.input, issue.path)?.optional === true;
140
+ if (missing || (!optional && !where.options?.distinguishNull && "input" in issue && issue.input === null && issue.expected !== "null"))
133
141
  return [one("required", "required")];
134
- const expected = issue.expected === "array" ? "an array" : issue.expected === "object" ? "an object" : issue.expected === "null" ? "null" : `a ${issue.expected}`;
142
+ const expected = issue.expected === "array" ? "an array" : issue.expected === "object" ? "an object" : issue.expected === "int" ? "an integer" : issue.expected === "null" ? "null" : `a ${issue.expected}`;
135
143
  return [one("wrong-type", `must be ${expected}${"input" in issue ? ` (got ${got(issue.input)})` : ""}`)];
136
144
  }
137
145
  case "too_big":
@@ -156,8 +164,9 @@ function issuesOf(issue, where = {}) {
156
164
  if (typeof by === "string" && Array.isArray(options)) {
157
165
  const given = "input" in issue && isRecord(issue.input) ? issue.input[by] : undefined;
158
166
  const path = at === "payload" || issue.path.at(-1) === by ? at : `${at}.${by}`;
159
- const line = given === undefined || given === null ? "required" : `must be one of ${options.map(value => JSON.stringify(value)).join(", ")}`;
160
- return [{ path, kind: given === undefined || given === null ? "required" : "bad-value", line: `${path}: ${line}` }];
167
+ const missing = given === undefined || (given === null && !where.options?.distinguishNull);
168
+ const line = missing ? "required" : `must be one of ${options.map(value => JSON.stringify(value)).join(", ")}`;
169
+ return [{ path, kind: missing ? "required" : "bad-value", line: `${path}: ${line}` }];
161
170
  }
162
171
  return [one("invalid", "does not match any allowed shape")];
163
172
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * A parked decision (§7): one schema for the question an agent puts to a person — its recap, the question, two to six
3
+ * options each saying whether it can be undone, and the option it recommends. Agents hand it back as a file (the park
4
+ * mailbox); `DECISION_MODEL_SCHEMA` is exported for the scout contract migration to wire into structured output.
5
+ * `parseDecision` (decision.ts) reads decisions through `readDecisionPayload`. The options are what the `decision`
6
+ * row keeps. The existing unversioned format ignores unknown fields, including a field named `version`.
7
+ *
8
+ * What JSON Schema cannot say runs after parsing, in decision.ts, each with a path-named error: text is not blank once
9
+ * trimmed, carries no control characters (a label or assignee is one line), option ids are unique, the recommendation
10
+ * names one of them, and a deadline is a timestamp.
11
+ */
12
+ import { z } from "zod";
13
+ import { type ContractResult } from "./contract.js";
14
+ /** How many options fit on one screen, and the fewest that make a decision. */
15
+ export declare const DECISION_OPTIONS_MIN = 2;
16
+ export declare const DECISION_OPTIONS_MAX = 6;
17
+ /** Option ids travel in URLs, CLI arguments, and CAS updates — they are identifiers, not prose. */
18
+ export declare const OPTION_ID: RegExp;
19
+ /** One option: its id, a one-line label, what choosing it does, and whether it can be undone (never defaulted). */
20
+ export declare const decisionOptionSchema: z.ZodObject<{
21
+ id: z.ZodString;
22
+ label: z.ZodString;
23
+ consequence: z.ZodString;
24
+ reversible: z.ZodBoolean;
25
+ }, z.core.$strip>;
26
+ export type DecisionOption = z.infer<typeof decisionOptionSchema>;
27
+ export declare const decisionSchema: z.ZodObject<{
28
+ urgency: z.ZodLiteral<"blocking">;
29
+ recap: z.ZodString;
30
+ question: z.ZodString;
31
+ options: z.ZodArray<z.ZodObject<{
32
+ id: z.ZodString;
33
+ label: z.ZodString;
34
+ consequence: z.ZodString;
35
+ reversible: z.ZodBoolean;
36
+ }, z.core.$strip>>;
37
+ recommendation: z.ZodString;
38
+ assignee: z.ZodOptional<z.ZodNullable<z.ZodString>>;
39
+ deadline: z.ZodOptional<z.ZodNullable<z.ZodString>>;
40
+ }, z.core.$strip>;
41
+ export type DecisionPayload = z.infer<typeof decisionSchema>;
42
+ /** The decision schema for the scout migration's question branch. */
43
+ export declare const DECISION_MODEL_SCHEMA: Readonly<Record<string, unknown>>;
44
+ /** Read the existing decision format, ignoring keys it never used. */
45
+ export declare function readDecisionPayload(input: unknown): ContractResult<DecisionPayload>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * A parked decision (§7): one schema for the question an agent puts to a person — its recap, the question, two to six
3
+ * options each saying whether it can be undone, and the option it recommends. Agents hand it back as a file (the park
4
+ * mailbox); `DECISION_MODEL_SCHEMA` is exported for the scout contract migration to wire into structured output.
5
+ * `parseDecision` (decision.ts) reads decisions through `readDecisionPayload`. The options are what the `decision`
6
+ * row keeps. The existing unversioned format ignores unknown fields, including a field named `version`.
7
+ *
8
+ * What JSON Schema cannot say runs after parsing, in decision.ts, each with a path-named error: text is not blank once
9
+ * trimmed, carries no control characters (a label or assignee is one line), option ids are unique, the recommendation
10
+ * names one of them, and a deadline is a timestamp.
11
+ */
12
+ import { z } from "zod";
13
+ import { limited, parseContract, toModelSchema } from "./contract.js";
14
+ /** How many options fit on one screen, and the fewest that make a decision. */
15
+ export const DECISION_OPTIONS_MIN = 2;
16
+ export const DECISION_OPTIONS_MAX = 6;
17
+ /** Option ids travel in URLs, CLI arguments, and CAS updates — they are identifiers, not prose. */
18
+ export const OPTION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
19
+ /** Decisions have no shorten turn: an over-long field is refused, so the model is not told it will be asked. */
20
+ const NO_SHORTEN = { shorten: false };
21
+ /** One option: its id, a one-line label, what choosing it does, and whether it can be undone (never defaulted). */
22
+ export const decisionOptionSchema = z.object({
23
+ id: limited("option id", "decisionOptionId", NO_SHORTEN).regex(OPTION_ID, { error: "must be letters, digits, - or _, starting with a letter or digit" }),
24
+ label: limited("option label", "decisionLabel", NO_SHORTEN).min(1),
25
+ consequence: limited("option consequence", "decisionConsequence", NO_SHORTEN).min(1),
26
+ reversible: z.boolean(),
27
+ });
28
+ export const decisionSchema = z.object({
29
+ /** Required rather than defaulted: an agent that did not say whether the loop can continue has not composed a decision. */
30
+ urgency: z.literal("blocking"),
31
+ recap: limited("recap", "decisionRecap", NO_SHORTEN).min(1),
32
+ question: limited("question", "decisionQuestion", NO_SHORTEN).min(1),
33
+ options: z.array(decisionOptionSchema).min(DECISION_OPTIONS_MIN).max(DECISION_OPTIONS_MAX),
34
+ /** An option's id, alone or as "<id>: why". */
35
+ recommendation: limited("recommendation (an option's id, or \"<id>: why\")", "decisionRecommendation", NO_SHORTEN).min(1),
36
+ assignee: limited("assignee", "decisionAssignee", NO_SHORTEN).min(1).nullable().optional(),
37
+ /** An ISO 8601 timestamp. */
38
+ deadline: z.string().nullable().optional(),
39
+ });
40
+ /** The decision schema for the scout migration's question branch. */
41
+ export const DECISION_MODEL_SCHEMA = toModelSchema(decisionSchema);
42
+ /** Read the existing decision format, ignoring keys it never used. */
43
+ export function readDecisionPayload(input) {
44
+ return parseContract(decisionSchema, input);
45
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * A Discord button tap (discord-chat.ts `receiveDiscord`): the component interaction Discord sends over the gateway,
3
+ * and the button data Toolroll put on it. The interaction is Discord's, so fields Toolroll doesn't read are ignored;
4
+ * the button's `custom_id` (`so_` and a one-time token) is Toolroll's. A tap from the paired person on Toolroll's own
5
+ * message whose button can't be read is answered with why (`data.custom_id: not a Toolroll button`), and nothing is done.
6
+ */
7
+ import { z } from "zod";
8
+ import { type ContractResult } from "./contract.js";
9
+ /** The interaction around a tap (type 3: a message component): the app, the person, the channel, the message. */
10
+ export declare const discordComponentInteractionSchema: z.ZodObject<{
11
+ type: z.ZodLiteral<3>;
12
+ id: z.ZodString;
13
+ application_id: z.ZodString;
14
+ channel_id: z.ZodString;
15
+ guild_id: z.ZodOptional<z.ZodString>;
16
+ channel: z.ZodObject<{
17
+ type: z.ZodInt;
18
+ }, z.core.$strip>;
19
+ member: z.ZodOptional<z.ZodObject<{
20
+ user: z.ZodObject<{
21
+ id: z.ZodString;
22
+ }, z.core.$strip>;
23
+ }, z.core.$strip>>;
24
+ user: z.ZodOptional<z.ZodObject<{
25
+ id: z.ZodString;
26
+ }, z.core.$strip>>;
27
+ message: z.ZodObject<{
28
+ id: z.ZodString;
29
+ channel_id: z.ZodString;
30
+ author: z.ZodObject<{
31
+ id: z.ZodString;
32
+ }, z.core.$strip>;
33
+ }, z.core.$strip>;
34
+ data: z.ZodObject<{
35
+ custom_id: z.ZodOptional<z.ZodUnknown>;
36
+ }, z.core.$strip>;
37
+ }, z.core.$strip>;
38
+ export type DiscordComponentInteraction = z.infer<typeof discordComponentInteractionSchema>;
39
+ /** Toolroll's button data: `so_` and a one-time token. */
40
+ export declare const discordButtonSchema: z.ZodObject<{
41
+ custom_id: z.ZodString;
42
+ }, z.core.$strip>;
43
+ export declare function readDiscordComponentInteraction(body: unknown): ContractResult<DiscordComponentInteraction>;
44
+ /** The tapped button's one-time token. */
45
+ export declare function readDiscordButtonToken(interaction: DiscordComponentInteraction): ContractResult<string>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A Discord button tap (discord-chat.ts `receiveDiscord`): the component interaction Discord sends over the gateway,
3
+ * and the button data Toolroll put on it. The interaction is Discord's, so fields Toolroll doesn't read are ignored;
4
+ * the button's `custom_id` (`so_` and a one-time token) is Toolroll's. A tap from the paired person on Toolroll's own
5
+ * message whose button can't be read is answered with why (`data.custom_id: not a Toolroll button`), and nothing is done.
6
+ */
7
+ import { z } from "zod";
8
+ import { parseContract } from "./contract.js";
9
+ const user = z.object({ id: z.string() });
10
+ /** The interaction around a tap (type 3: a message component): the app, the person, the channel, the message. */
11
+ export const discordComponentInteractionSchema = z.object({
12
+ type: z.literal(3),
13
+ id: z.string(),
14
+ application_id: z.string(),
15
+ channel_id: z.string(),
16
+ guild_id: z.string().optional(),
17
+ channel: z.object({ type: z.int() }),
18
+ member: z.object({ user }).optional(),
19
+ user: user.optional(),
20
+ message: z.object({ id: z.string(), channel_id: z.string(), author: user }),
21
+ data: z.object({ custom_id: z.unknown().optional() }),
22
+ });
23
+ /** Toolroll's button data: `so_` and a one-time token. */
24
+ export const discordButtonSchema = z.object({
25
+ custom_id: z.string().regex(/^so_[a-f0-9]{32}$/, { error: "not a Toolroll button" }),
26
+ });
27
+ export function readDiscordComponentInteraction(body) {
28
+ return parseContract(discordComponentInteractionSchema, body);
29
+ }
30
+ /** The tapped button's one-time token. */
31
+ export function readDiscordButtonToken(interaction) {
32
+ const read = parseContract(z.object({ data: discordButtonSchema }), { data: interaction.data });
33
+ return read.ok ? { ok: true, value: read.value.data.custom_id.slice(3) } : read;
34
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A flow card's state (docs/plans/zod-revamp.md, item 8): what the flow engine, its steps and its Send to me and
3
+ * Person chooses zones read and write (flow-engine.ts, flow-steps.ts, flow-send.ts), as the store keeps it in
4
+ * `flow_card`. Its columns are versioned by the database's own schema version; the one payload in it, what finished
5
+ * zones handed on (`outputs_json`), carries its own version (stage-output.ts).
6
+ *
7
+ * `flowCardSchema` is the card as read; `flowCardChangeSchema` what one update may change. Where it came from
8
+ * (`source_json`, written by triggers) keeps its own type in the store.
9
+ */
10
+ import { z } from "zod";
11
+ export declare const FLOW_CARD_STATES: readonly ["active", "done", "cancelled"];
12
+ export type FlowCardState = (typeof FLOW_CARD_STATES)[number];
13
+ export declare const flowCardSchema: z.ZodObject<{
14
+ id: z.ZodInt;
15
+ flow: z.ZodInt;
16
+ title: z.ZodString;
17
+ description: z.ZodNullable<z.ZodString>;
18
+ stage: z.ZodString;
19
+ entry: z.ZodInt;
20
+ state: z.ZodEnum<{
21
+ active: "active";
22
+ cancelled: "cancelled";
23
+ done: "done";
24
+ }>;
25
+ task: z.ZodNullable<z.ZodString>;
26
+ primaryTask: z.ZodNullable<z.ZodString>;
27
+ note: z.ZodNullable<z.ZodString>;
28
+ waiting: z.ZodNullable<z.ZodString>;
29
+ outputs: z.ZodObject<{}, z.core.$catchall<z.ZodString>>;
30
+ attached: z.ZodObject<{}, z.core.$catchall<z.ZodString>>;
31
+ createdBy: z.ZodString;
32
+ createdAt: z.ZodString;
33
+ updatedAt: z.ZodString;
34
+ owner: z.ZodNullable<z.ZodString>;
35
+ }, z.core.$strict>;
36
+ export type FlowCardFields = z.infer<typeof flowCardSchema>;
37
+ /** What one update of a card may change; whatever is left out stays as it is. Outputs replace the card's whole;
38
+ * an attachment goes with the output it belongs to. */
39
+ export declare const flowCardChangeSchema: z.ZodObject<{
40
+ task: z.ZodOptional<z.ZodNullable<z.ZodString>>;
41
+ primaryTask: z.ZodOptional<z.ZodString>;
42
+ waiting: z.ZodOptional<z.ZodNullable<z.ZodString>>;
43
+ outputs: z.ZodOptional<z.ZodObject<{}, z.core.$catchall<z.ZodString>>>;
44
+ attached: z.ZodOptional<z.ZodObject<{}, z.core.$catchall<z.ZodString>>>;
45
+ state: z.ZodOptional<z.ZodEnum<{
46
+ cancelled: "cancelled";
47
+ done: "done";
48
+ }>>;
49
+ }, z.core.$strict>;
50
+ export type FlowCardChange = z.infer<typeof flowCardChangeSchema>;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A flow card's state (docs/plans/zod-revamp.md, item 8): what the flow engine, its steps and its Send to me and
3
+ * Person chooses zones read and write (flow-engine.ts, flow-steps.ts, flow-send.ts), as the store keeps it in
4
+ * `flow_card`. Its columns are versioned by the database's own schema version; the one payload in it, what finished
5
+ * zones handed on (`outputs_json`), carries its own version (stage-output.ts).
6
+ *
7
+ * `flowCardSchema` is the card as read; `flowCardChangeSchema` what one update may change. Where it came from
8
+ * (`source_json`, written by triggers) keeps its own type in the store.
9
+ */
10
+ import { z } from "zod";
11
+ import { cardOutputsSchema } from "./stage-output.js";
12
+ export const FLOW_CARD_STATES = ["active", "done", "cancelled"];
13
+ export const flowCardSchema = z.strictObject({
14
+ id: z.int().min(1),
15
+ flow: z.int().min(1),
16
+ title: z.string(),
17
+ description: z.string().nullable(),
18
+ /** The zone it is in, and which visit to that zone this is (each move is a fresh one). */
19
+ stage: z.string(),
20
+ entry: z.int().min(1),
21
+ state: z.enum(FLOW_CARD_STATES),
22
+ /** The task its zone filed this visit, and the card's main build. */
23
+ task: z.string().nullable(),
24
+ primaryTask: z.string().nullable(),
25
+ /** The latest note it was sent back or replied to with ({{note}}). */
26
+ note: z.string().nullable(),
27
+ /** What it waits for, in words. */
28
+ waiting: z.string().nullable(),
29
+ /** What finished zones handed on ({{stage.…}}), and any too long to pass on, whole. */
30
+ outputs: cardOutputsSchema.shape.outputs,
31
+ attached: cardOutputsSchema.shape.attached,
32
+ createdBy: z.string(),
33
+ createdAt: z.string(),
34
+ updatedAt: z.string(),
35
+ owner: z.string().nullable(),
36
+ });
37
+ /** What one update of a card may change; whatever is left out stays as it is. Outputs replace the card's whole;
38
+ * an attachment goes with the output it belongs to. */
39
+ export const flowCardChangeSchema = z.strictObject({
40
+ task: flowCardSchema.shape.task.optional(),
41
+ primaryTask: z.string().optional(),
42
+ waiting: flowCardSchema.shape.waiting.optional(),
43
+ outputs: flowCardSchema.shape.outputs.optional(),
44
+ attached: flowCardSchema.shape.attached.optional(),
45
+ state: z.enum(["done", "cancelled"]).optional(),
46
+ });
@@ -0,0 +1,119 @@
1
+ /**
2
+ * What a "Send to me" or "Person chooses" visit sends its person (docs/plans/zod-revamp.md, item 8): worked out once
3
+ * per visit and kept (`flow_send.content_json`), so every chat app and the card show the same (src/flow-send.ts).
4
+ *
5
+ * - `flowSendPayloadSchema`: a send's — the title, summary, links, screenshots and a research report's items.
6
+ * - `flowChoosePayloadSchema`: a choice's — the same, with the options as they were offered and whether a reply is taken.
7
+ * - `flowChoiceAnswerSchema`: the person's answer to a choice, by whichever door it comes (chooseFlowCard).
8
+ *
9
+ * Kept before this contract (version 0), a visit's content had no `version`; it reads as the 0.9.36 reader read it:
10
+ * a title, a summary and a list of links make it readable, items that can't be shown are left out, and a link or
11
+ * option that isn't one is skipped.
12
+ */
13
+ import { z } from "zod";
14
+ import { type ContractResult } from "./contract.js";
15
+ /** A link the message carries: a page of Toolroll's (a button) or the one outside page, a pull request. */
16
+ export declare const flowSendLinkSchema: z.ZodUnion<readonly [z.ZodObject<{
17
+ label: z.ZodString;
18
+ path: z.ZodString;
19
+ }, z.core.$strict>, z.ZodObject<{
20
+ label: z.ZodString;
21
+ url: z.ZodString;
22
+ }, z.core.$strict>]>;
23
+ /** One item of a research report, as the message lists it (flow-items.ts). */
24
+ export declare const flowSendItemSchema: z.ZodObject<{
25
+ title: z.ZodString;
26
+ why: z.ZodString;
27
+ url: z.ZodString;
28
+ source: z.ZodString;
29
+ plug: z.ZodString;
30
+ shot: z.ZodNullable<z.ZodInt>;
31
+ }, z.core.$strict>;
32
+ export type FlowSendLink = z.infer<typeof flowSendLinkSchema>;
33
+ export type FlowSendItem = z.infer<typeof flowSendItemSchema>;
34
+ export declare const FLOW_SEND_VERSION = 1;
35
+ export declare const flowChoiceOptionSchema: z.ZodObject<{
36
+ choice: z.ZodInt;
37
+ label: z.ZodString;
38
+ }, z.core.$strict>;
39
+ export declare const flowSendPayloadSchema: z.ZodObject<{
40
+ version: z.ZodLiteral<1>;
41
+ title: z.ZodString;
42
+ from: z.ZodOptional<z.ZodString>;
43
+ summary: z.ZodString;
44
+ links: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
45
+ label: z.ZodString;
46
+ path: z.ZodString;
47
+ }, z.core.$strict>, z.ZodObject<{
48
+ label: z.ZodString;
49
+ url: z.ZodString;
50
+ }, z.core.$strict>]>>;
51
+ shots: z.ZodNullable<z.ZodObject<{
52
+ taskId: z.ZodString;
53
+ run: z.ZodInt;
54
+ }, z.core.$strict>>;
55
+ items: z.ZodOptional<z.ZodArray<z.ZodObject<{
56
+ title: z.ZodString;
57
+ why: z.ZodString;
58
+ url: z.ZodString;
59
+ source: z.ZodString;
60
+ plug: z.ZodString;
61
+ shot: z.ZodNullable<z.ZodInt>;
62
+ }, z.core.$strict>>>;
63
+ }, z.core.$strict>;
64
+ export declare const flowChoosePayloadSchema: z.ZodObject<{
65
+ version: z.ZodLiteral<1>;
66
+ title: z.ZodString;
67
+ from: z.ZodOptional<z.ZodString>;
68
+ summary: z.ZodString;
69
+ links: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
70
+ label: z.ZodString;
71
+ path: z.ZodString;
72
+ }, z.core.$strict>, z.ZodObject<{
73
+ label: z.ZodString;
74
+ url: z.ZodString;
75
+ }, z.core.$strict>]>>;
76
+ shots: z.ZodNullable<z.ZodObject<{
77
+ taskId: z.ZodString;
78
+ run: z.ZodInt;
79
+ }, z.core.$strict>>;
80
+ items: z.ZodOptional<z.ZodArray<z.ZodObject<{
81
+ title: z.ZodString;
82
+ why: z.ZodString;
83
+ url: z.ZodString;
84
+ source: z.ZodString;
85
+ plug: z.ZodString;
86
+ shot: z.ZodNullable<z.ZodInt>;
87
+ }, z.core.$strict>>>;
88
+ options: z.ZodArray<z.ZodObject<{
89
+ choice: z.ZodInt;
90
+ label: z.ZodString;
91
+ }, z.core.$strict>>;
92
+ reply: z.ZodBoolean;
93
+ }, z.core.$strict>;
94
+ export type FlowSendPayload = z.infer<typeof flowSendPayloadSchema>;
95
+ export type FlowChoosePayload = z.infer<typeof flowChoosePayloadSchema>;
96
+ /** A visit's content as the console and chat apps read it: a send's, or a choice's with its options. */
97
+ export type FlowSendContent = Omit<FlowSendPayload, "version"> & {
98
+ options?: FlowChoosePayload["options"];
99
+ reply?: boolean;
100
+ };
101
+ export declare function readFlowItems(raw: unknown): FlowSendItem[];
102
+ /** Version 0: content kept before it carried `version`, in this contract's shape (what can't be read is left for the schema to refuse by path). */
103
+ export declare function upgradeSavedSend(body: Record<string, unknown>): Record<string, unknown>;
104
+ /** Read a visit's kept content: a choice's when it carries options, else a send's; version 0 upgraded, newer refused. */
105
+ export declare function readFlowSendPayload(input: unknown): ContractResult<FlowSendContent>;
106
+ /** A visit's content as it is kept: the payload of its kind, with the current version. */
107
+ export declare function flowSendForStore(content: FlowSendContent): string;
108
+ /** A person's answer to a choice: an option by its place (and the words and zone they saw), or a reply (`choice` null)
109
+ * that becomes the note for where replies go. `entry` is the visit they saw; without it, the card's current one. */
110
+ export declare const flowChoiceAnswerSchema: z.ZodObject<{
111
+ card: z.ZodInt;
112
+ entry: z.ZodOptional<z.ZodInt>;
113
+ choice: z.ZodNullable<z.ZodInt>;
114
+ label: z.ZodOptional<z.ZodString>;
115
+ to: z.ZodOptional<z.ZodString>;
116
+ note: z.ZodNullable<z.ZodString>;
117
+ }, z.core.$strict>;
118
+ export type FlowChoiceAnswer = z.infer<typeof flowChoiceAnswerSchema>;
119
+ export declare function readFlowChoiceAnswer(input: unknown): ContractResult<FlowChoiceAnswer>;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * What a "Send to me" or "Person chooses" visit sends its person (docs/plans/zod-revamp.md, item 8): worked out once
3
+ * per visit and kept (`flow_send.content_json`), so every chat app and the card show the same (src/flow-send.ts).
4
+ *
5
+ * - `flowSendPayloadSchema`: a send's — the title, summary, links, screenshots and a research report's items.
6
+ * - `flowChoosePayloadSchema`: a choice's — the same, with the options as they were offered and whether a reply is taken.
7
+ * - `flowChoiceAnswerSchema`: the person's answer to a choice, by whichever door it comes (chooseFlowCard).
8
+ *
9
+ * Kept before this contract (version 0), a visit's content had no `version`; it reads as the 0.9.36 reader read it:
10
+ * a title, a summary and a list of links make it readable, items that can't be shown are left out, and a link or
11
+ * option that isn't one is skipped.
12
+ */
13
+ import { z } from "zod";
14
+ import { parseContract, readVersioned, versioned } from "./contract.js";
15
+ /** A link the message carries: a page of Toolroll's (a button) or the one outside page, a pull request. */
16
+ export const flowSendLinkSchema = z.union([
17
+ z.strictObject({ label: z.string(), path: z.string().regex(/^\//, { error: "must be a path in Toolroll, starting with /" }) }),
18
+ z.strictObject({ label: z.string(), url: z.string().regex(/^https:\/\/[^\s<>"]+$/, { error: "must be an https:// address" }) }),
19
+ ]);
20
+ /** One item of a research report, as the message lists it (flow-items.ts). */
21
+ export const flowSendItemSchema = z.strictObject({
22
+ title: z.string(),
23
+ why: z.string(),
24
+ url: z.string().regex(/^https?:\/\//, { error: "must be a web address" }),
25
+ /** Where the link goes, by name: "Mobbin", "Linear", "Apple". */
26
+ source: z.string(),
27
+ /** One line: how it plugs in (from the why, else the image's own caption). */
28
+ plug: z.string(),
29
+ /** The item's screenshot (its evidence id in the report's run), when it has one. */
30
+ shot: z.int().nullable(),
31
+ });
32
+ export const FLOW_SEND_VERSION = 1;
33
+ const sendShape = {
34
+ /** The card's title and the step it comes from. */
35
+ title: z.string(),
36
+ /** The step it comes from, when known. */
37
+ from: z.string().optional(),
38
+ summary: z.string(),
39
+ links: z.array(flowSendLinkSchema),
40
+ /** The build's result whose screenshots go with it, when it saved any. */
41
+ shots: z.strictObject({ taskId: z.string(), run: z.int() }).nullable(),
42
+ /** After research: the report's items, numbered in this order in every message and screenshot caption. */
43
+ items: z.array(flowSendItemSchema).min(1).optional(),
44
+ };
45
+ export const flowChoiceOptionSchema = z.strictObject({ choice: z.int().min(0), label: z.string() });
46
+ export const flowSendPayloadSchema = versioned(FLOW_SEND_VERSION, sendShape);
47
+ export const flowChoosePayloadSchema = versioned(FLOW_SEND_VERSION, {
48
+ ...sendShape,
49
+ /** The options as they were offered, by place. */
50
+ options: z.array(flowChoiceOptionSchema),
51
+ /** Whether a reply is taken instead (the zone has somewhere replies go). */
52
+ reply: z.boolean(),
53
+ });
54
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
55
+ /** A kept item as 0.9.36 read it: its five words (a web link), and a screenshot id only when it is a whole number. */
56
+ const savedItemSchema = z.object({ ...flowSendItemSchema.shape, shot: z.unknown() });
57
+ export function readFlowItems(raw) {
58
+ if (!Array.isArray(raw))
59
+ return [];
60
+ return raw.flatMap(one => {
61
+ const read = savedItemSchema.safeParse(one);
62
+ if (!read.success)
63
+ return [];
64
+ const shot = read.data.shot;
65
+ return [{ ...read.data, shot: typeof shot === "number" && Number.isSafeInteger(shot) ? shot : null }];
66
+ });
67
+ }
68
+ /** Version 0: content kept before it carried `version`, in this contract's shape (what can't be read is left for the schema to refuse by path). */
69
+ export function upgradeSavedSend(body) {
70
+ if (typeof body["title"] !== "string" || typeof body["summary"] !== "string" || !Array.isArray(body["links"]))
71
+ return body;
72
+ const items = readFlowItems(body["items"]);
73
+ const shots = sendShape.shots.safeParse(body["shots"]);
74
+ const out = {
75
+ version: FLOW_SEND_VERSION, title: body["title"], ...(typeof body["from"] === "string" ? { from: body["from"] } : {}), summary: body["summary"],
76
+ links: body["links"].filter(one => flowSendLinkSchema.safeParse(one).success), shots: shots.success ? shots.data : null,
77
+ ...(body["items"] === undefined || items.length === 0 ? {} : { items }),
78
+ };
79
+ if (Array.isArray(body["options"])) {
80
+ out["options"] = body["options"].filter(one => flowChoiceOptionSchema.safeParse(one).success);
81
+ out["reply"] = body["reply"] === true;
82
+ }
83
+ return out;
84
+ }
85
+ /** Read a visit's kept content: a choice's when it carries options, else a send's; version 0 upgraded, newer refused. */
86
+ export function readFlowSendPayload(input) {
87
+ const choose = isRecord(input) && Object.prototype.hasOwnProperty.call(input, "options");
88
+ const read = choose ? readVersioned(flowChoosePayloadSchema, input, { 0: upgradeSavedSend }) : readVersioned(flowSendPayloadSchema, input, { 0: upgradeSavedSend });
89
+ if (!read.ok)
90
+ return read;
91
+ const { version: _version, ...content } = read.value;
92
+ return { ok: true, value: content };
93
+ }
94
+ /** A visit's content as it is kept: the payload of its kind, with the current version. */
95
+ export function flowSendForStore(content) {
96
+ const payload = { version: FLOW_SEND_VERSION, ...content };
97
+ return JSON.stringify(content.options === undefined ? flowSendPayloadSchema.parse(payload) : flowChoosePayloadSchema.parse(payload));
98
+ }
99
+ // ------------------------------------------------------------------ the person's choice
100
+ /** A person's answer to a choice: an option by its place (and the words and zone they saw), or a reply (`choice` null)
101
+ * that becomes the note for where replies go. `entry` is the visit they saw; without it, the card's current one. */
102
+ export const flowChoiceAnswerSchema = z.strictObject({
103
+ card: z.int().min(1),
104
+ entry: z.int().min(1).optional(),
105
+ choice: z.int().min(0).nullable(),
106
+ label: z.string().optional(),
107
+ to: z.string().optional(),
108
+ note: z.string().nullable(),
109
+ });
110
+ export function readFlowChoiceAnswer(input) {
111
+ return parseContract(flowChoiceAnswerSchema, input);
112
+ }
@@ -85,6 +85,11 @@ export declare const flowSortNoteSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
85
85
  question: z.ZodString;
86
86
  levels: z.ZodNull;
87
87
  }, z.core.$strict>], "kind">;
88
+ /** How a sort answer is named to Jev: its words as a short key (the request's criteria, and the choice Jev returns). */
89
+ export declare const sortKeyOf: (answer: string) => string;
90
+ /** The key of a sort zone's question in its Jev request (a choice over its answers), and of each thing it also notes. */
91
+ export declare const SORT_ROUTE_KEY = "route";
92
+ export declare const sortNoteKeyOf: (noteId: string) => string;
88
93
  /** sort: the question, its answers, how sure Jev must be to act alone (0.5–0.99), and what else it notes. */
89
94
  export declare const flowSortSchema: z.ZodObject<{
90
95
  question: z.ZodString;
@@ -72,6 +72,11 @@ export const flowSortNoteSchema = z.discriminatedUnion("kind", [
72
72
  z.strictObject({ id: zoneId, kind: z.literal("score"), question: words("question", "flowSortQuestion").min(1), levels: z.array(words("level", "flowSortLevel").min(1)).min(SORT_LEVELS_MIN).max(SORT_LEVELS_MAX) }),
73
73
  z.strictObject({ id: zoneId, kind: z.literal("yes-no"), question: words("question", "flowSortQuestion").min(1), levels: z.null() }),
74
74
  ]);
75
+ /** How a sort answer is named to Jev: its words as a short key (the request's criteria, and the choice Jev returns). */
76
+ export const sortKeyOf = (answer) => answer.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "answer";
77
+ /** The key of a sort zone's question in its Jev request (a choice over its answers), and of each thing it also notes. */
78
+ export const SORT_ROUTE_KEY = "route";
79
+ export const sortNoteKeyOf = (noteId) => `note_${noteId.replace(/-/g, "_")}`;
75
80
  /** sort: the question, its answers, how sure Jev must be to act alone (0.5–0.99), and what else it notes. */
76
81
  export const flowSortSchema = z.strictObject({
77
82
  question: words("question", "flowSortQuestion").min(1),