toolroll 0.9.34 → 0.9.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +7 -0
- package/dist/assignment-adapters.d.ts +12 -48
- package/dist/assignment-adapters.js +33 -43
- package/dist/assignment.d.ts +1 -0
- package/dist/assignment.js +2 -2
- package/dist/browser-workspace.d.ts +3 -3
- package/dist/build-review.d.ts +1 -1
- package/dist/build-review.js +2 -1
- package/dist/builder.js +4 -7
- package/dist/chat-acceptance.d.ts +2 -2
- package/dist/chat-actions.d.ts +18 -12
- package/dist/chat-actions.js +15 -65
- package/dist/chat-ask.js +5 -5
- package/dist/chat-decide.d.ts +14 -1
- package/dist/chat-decide.js +12 -6
- package/dist/chat-delivery-state.d.ts +12 -70
- package/dist/chat-delivery-state.js +17 -135
- package/dist/chat-delivery.js +58 -30
- package/dist/chat-flow.js +8 -6
- package/dist/chat-inbox.js +1 -1
- package/dist/coding-context.d.ts +2 -39
- package/dist/coding-context.js +2 -1
- package/dist/coding-handoff.d.ts +6 -25
- package/dist/coding-handoff.js +11 -5
- package/dist/coding-types.d.ts +2 -26
- package/dist/coding-workspace.d.ts +2 -0
- package/dist/coding-workspace.js +50 -14
- package/dist/contracts/acceptance-terms.d.ts +14 -0
- package/dist/contracts/acceptance-terms.js +16 -0
- package/dist/contracts/chat-actions.d.ts +328 -0
- package/dist/contracts/chat-actions.js +133 -0
- package/dist/contracts/chat-callback-rows.d.ts +59 -0
- package/dist/contracts/chat-callback-rows.js +46 -0
- package/dist/contracts/chat-content.d.ts +276 -0
- package/dist/contracts/chat-content.js +127 -0
- package/dist/contracts/chat-tables.d.ts +6 -0
- package/dist/contracts/chat-tables.js +136 -0
- package/dist/contracts/coding-context.d.ts +97 -0
- package/dist/contracts/coding-context.js +60 -0
- package/dist/contracts/coding-handoff.d.ts +44 -0
- package/dist/contracts/coding-handoff.js +47 -0
- package/dist/contracts/coding-workspace.d.ts +165 -0
- package/dist/contracts/coding-workspace.js +71 -0
- package/dist/contracts/contract.d.ts +40 -7
- package/dist/contracts/contract.js +155 -19
- package/dist/contracts/decision.d.ts +45 -0
- package/dist/contracts/decision.js +45 -0
- package/dist/contracts/discord-callback.d.ts +45 -0
- package/dist/contracts/discord-callback.js +34 -0
- package/dist/contracts/flow-card.d.ts +50 -0
- package/dist/contracts/flow-card.js +46 -0
- package/dist/contracts/flow-propose.d.ts +325 -0
- package/dist/contracts/flow-propose.js +41 -0
- package/dist/contracts/flow-send.d.ts +119 -0
- package/dist/contracts/flow-send.js +112 -0
- package/dist/contracts/flow.d.ts +19405 -0
- package/dist/contracts/flow.js +364 -0
- package/dist/contracts/gateway-tools.d.ts +374 -0
- package/dist/contracts/gateway-tools.js +94 -0
- package/dist/contracts/handoff.d.ts +91 -0
- package/dist/contracts/handoff.js +82 -0
- package/dist/contracts/lead-context.d.ts +165 -0
- package/dist/contracts/lead-context.js +73 -0
- package/dist/contracts/lead-tools.d.ts +1905 -0
- package/dist/contracts/lead-tools.js +265 -0
- package/dist/contracts/memory-pass.d.ts +158 -0
- package/dist/contracts/memory-pass.js +102 -0
- package/dist/contracts/project-knowledge.d.ts +126 -0
- package/dist/contracts/project-knowledge.js +93 -0
- package/dist/contracts/project-memory.d.ts +117 -0
- package/dist/contracts/project-memory.js +51 -0
- package/dist/contracts/project-skills.d.ts +121 -0
- package/dist/contracts/project-skills.js +76 -0
- package/dist/contracts/proof.d.ts +136 -0
- package/dist/contracts/proof.js +110 -0
- package/dist/contracts/review-findings.d.ts +46 -0
- package/dist/contracts/review-findings.js +48 -0
- package/dist/contracts/route.d.ts +484 -0
- package/dist/contracts/route.js +128 -0
- package/dist/contracts/scope.d.ts +105 -0
- package/dist/contracts/scope.js +114 -0
- package/dist/contracts/scout-report.d.ts +234 -0
- package/dist/contracts/scout-report.js +141 -0
- package/dist/contracts/slack-callback.d.ts +43 -0
- package/dist/contracts/slack-callback.js +35 -0
- package/dist/contracts/sort-answer.d.ts +83 -0
- package/dist/contracts/sort-answer.js +77 -0
- package/dist/contracts/stage-output.d.ts +127 -0
- package/dist/contracts/stage-output.js +166 -0
- package/dist/contracts/task-sizing.d.ts +24 -0
- package/dist/contracts/task-sizing.js +28 -0
- package/dist/contracts/teammate-turn.d.ts +43 -0
- package/dist/contracts/teammate-turn.js +51 -0
- package/dist/contracts/teams-callback.d.ts +14 -0
- package/dist/contracts/teams-callback.js +14 -0
- package/dist/contracts/telegram-callback.d.ts +120 -0
- package/dist/contracts/telegram-callback.js +97 -0
- package/dist/contracts/verification-receipt.d.ts +148 -0
- package/dist/contracts/verification-receipt.js +87 -0
- package/dist/decision.d.ts +11 -20
- package/dist/decision.js +86 -167
- package/dist/demo.js +28 -11
- package/dist/discord-chat.js +31 -20
- package/dist/evidence.d.ts +3 -40
- package/dist/evidence.js +5 -1
- package/dist/flow-engine.js +14 -17
- package/dist/flow-gallery.d.ts +3 -2
- package/dist/flow-items.d.ts +4 -13
- package/dist/flow-items.js +1 -15
- package/dist/flow-send.d.ts +6 -37
- package/dist/flow-send.js +18 -14
- package/dist/flow-share.d.ts +10 -32
- package/dist/flow-share.js +72 -84
- package/dist/flow-sort.d.ts +7 -26
- package/dist/flow-sort.js +25 -20
- package/dist/flow-starters.d.ts +2 -1
- package/dist/flow-steps.js +3 -2
- package/dist/flow-triggers.d.ts +15 -72
- package/dist/flow-triggers.js +94 -83
- package/dist/flows.d.ts +37 -220
- package/dist/flows.js +356 -387
- package/dist/invoke.js +4 -4
- package/dist/lead-context.d.ts +4 -2
- package/dist/lead-context.js +24 -17
- package/dist/mate-tools.d.ts +4 -0
- package/dist/mate-tools.js +111 -308
- package/dist/mcp-connect.d.ts +27 -4
- package/dist/mcp-connect.js +92 -17
- package/dist/mcp.js +84 -216
- package/dist/memory-pass.d.ts +17 -42
- package/dist/memory-pass.js +54 -26
- package/dist/operate.js +31 -2
- package/dist/phase-routing.d.ts +19 -72
- package/dist/phase-routing.js +34 -164
- package/dist/plan.d.ts +5 -5
- package/dist/plan.js +5 -25
- package/dist/process-recovery-eligibility.d.ts +2 -2
- package/dist/process-recovery-eligibility.js +13 -8
- package/dist/project-knowledge.d.ts +16 -35
- package/dist/project-knowledge.js +42 -21
- package/dist/project-memory.d.ts +2 -28
- package/dist/project-memory.js +28 -13
- package/dist/project-skills.d.ts +16 -38
- package/dist/project-skills.js +41 -32
- package/dist/project-tools.d.ts +1 -0
- package/dist/project-tools.js +4 -4
- package/dist/proof.d.ts +15 -91
- package/dist/proof.js +90 -249
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +4 -16
- package/dist/result-evidence-readers.d.ts +42 -0
- package/dist/result-evidence-readers.js +91 -0
- package/dist/result-shots.d.ts +1 -1
- package/dist/result-shots.js +2 -1
- package/dist/review-switch.d.ts +3 -8
- package/dist/reviewer.d.ts +8 -12
- package/dist/reviewer.js +11 -35
- package/dist/scope.d.ts +21 -43
- package/dist/scope.js +42 -128
- package/dist/scout-report.d.ts +9 -194
- package/dist/scout-report.js +113 -264
- package/dist/scout.js +15 -11
- package/dist/serve.js +17 -101
- package/dist/slack-chat.js +26 -20
- package/dist/slack-state.js +2 -1
- package/dist/store.d.ts +9 -32
- package/dist/store.js +18 -15
- package/dist/structured-output.js +2 -1
- package/dist/subscription-chat.d.ts +4 -0
- package/dist/subscription-chat.js +4 -1
- package/dist/surface.js +8 -1
- package/dist/task-review-brief.d.ts +69 -0
- package/dist/task-review-brief.js +179 -0
- package/dist/task-sizing.d.ts +3 -27
- package/dist/task-sizing.js +11 -26
- package/dist/task-text.d.ts +0 -5
- package/dist/task-text.js +1 -3
- package/dist/teammate-question.js +7 -5
- package/dist/teammate-work.js +9 -5
- package/dist/teammates.d.ts +13 -64
- package/dist/teammates.js +32 -33
- package/dist/teams-chat.js +20 -11
- package/dist/telegram-flow.js +4 -3
- package/dist/telegram-mate.d.ts +4 -14
- package/dist/telegram-mate.js +8 -7
- package/dist/telegram-team.d.ts +2 -19
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +52 -42
- package/dist/text-limits.d.ts +212 -1
- package/dist/text-limits.js +214 -1
- package/dist/tools-ui.d.ts +2 -1
- package/dist/tools-ui.js +7 -4
- package/dist/verification-evidence.js +24 -12
- package/package.json +1 -1
|
@@ -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) {
|
|
@@ -41,55 +42,190 @@ function got(input) {
|
|
|
41
42
|
return "an array";
|
|
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);
|
|
46
|
+
/** The schema at `path`, including whether that value may be omitted, following arrays, wrappers and unions. */
|
|
47
|
+
function nodeAt(schema, input, path) {
|
|
48
|
+
let node = schema, value = input;
|
|
49
|
+
let optional = false;
|
|
50
|
+
for (let at = 0; node !== undefined;) {
|
|
51
|
+
const def = node._zod.def;
|
|
52
|
+
if (def.type === "optional" || def.type === "nullable" || def.type === "readonly") {
|
|
53
|
+
if (def.type === "optional")
|
|
54
|
+
optional = true;
|
|
55
|
+
node = def["innerType"];
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (def.type === "lazy") {
|
|
59
|
+
node = def["getter"]();
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (def.type === "union") {
|
|
63
|
+
const options = def["options"];
|
|
64
|
+
const by = def["discriminator"];
|
|
65
|
+
const wanted = by !== undefined && isRecord(value) ? value[by] : undefined;
|
|
66
|
+
node = options.find(option => {
|
|
67
|
+
const shape = option._zod.def["shape"];
|
|
68
|
+
if (shape === undefined)
|
|
69
|
+
return false;
|
|
70
|
+
if (by === undefined)
|
|
71
|
+
return true;
|
|
72
|
+
const literal = shape[by]?._zod.def;
|
|
73
|
+
return literal !== undefined && literal["values"]?.includes(wanted) === true;
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (at === path.length)
|
|
78
|
+
return { def, optional };
|
|
79
|
+
const part = path[at++];
|
|
80
|
+
optional = false;
|
|
81
|
+
if (def.type === "array" && typeof part === "number") {
|
|
82
|
+
node = def["element"];
|
|
83
|
+
value = Array.isArray(value) ? value[part] : undefined;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (def.type === "object" && typeof part === "string") {
|
|
87
|
+
const shape = def["shape"];
|
|
88
|
+
node = shape[part] ?? def["catchall"];
|
|
89
|
+
value = isRecord(value) ? value[part] : undefined;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/** Edit distance, for "did you mean" (short strings only). */
|
|
97
|
+
function distance(a, b) {
|
|
98
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
99
|
+
for (let i = 1; i <= a.length; i++) {
|
|
100
|
+
let last = row[0];
|
|
101
|
+
row[0] = i;
|
|
102
|
+
for (let j = 1; j <= b.length; j++) {
|
|
103
|
+
const was = row[j];
|
|
104
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, last + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
105
|
+
last = was;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return row[b.length];
|
|
109
|
+
}
|
|
110
|
+
/** The allowed key an unknown one most likely meant, or null. */
|
|
111
|
+
function suggestion(key, known, options) {
|
|
112
|
+
if (known === null || known.length === 0)
|
|
113
|
+
return null;
|
|
114
|
+
const alias = options.aliases?.[key]?.find(one => known.includes(one));
|
|
115
|
+
if (alias !== undefined)
|
|
116
|
+
return alias;
|
|
117
|
+
const close = known
|
|
118
|
+
.map(one => ({ one, far: one.toLowerCase() === key.toLowerCase() ? 0 : distance(one.toLowerCase(), key.toLowerCase()) }))
|
|
119
|
+
.filter(({ one, far }) => far <= (Math.min(one.length, key.length) >= 6 ? 2 : 1))
|
|
120
|
+
.sort((a, b) => a.far - b.far);
|
|
121
|
+
return close[0]?.one ?? null;
|
|
122
|
+
}
|
|
123
|
+
function issuesOf(issue, where = {}) {
|
|
45
124
|
const at = pathOf(issue.path);
|
|
46
125
|
const one = (kind, what) => ({ path: at, kind, line: `${at}: ${what}` });
|
|
47
126
|
switch (issue.code) {
|
|
48
|
-
case "unrecognized_keys":
|
|
49
|
-
|
|
127
|
+
case "unrecognized_keys": {
|
|
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;
|
|
130
|
+
return issue.keys.map(key => {
|
|
131
|
+
const meant = suggestion(key, known, where.options ?? {});
|
|
132
|
+
return { path: at, kind: "unknown-key", line: `${at}: unknown key '${key}'${meant === null ? "" : ` (did you mean ${meant}?)`}` };
|
|
133
|
+
});
|
|
134
|
+
}
|
|
50
135
|
case "invalid_type": {
|
|
51
136
|
const missing = "input" in issue ? issue.input === undefined : / received undefined$/.test(issue.message);
|
|
52
|
-
|
|
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"))
|
|
53
141
|
return [one("required", "required")];
|
|
54
|
-
|
|
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}`;
|
|
143
|
+
return [one("wrong-type", `must be ${expected}${"input" in issue ? ` (got ${got(issue.input)})` : ""}`)];
|
|
55
144
|
}
|
|
56
145
|
case "too_big":
|
|
57
146
|
if (issue.origin === "array" || issue.origin === "set")
|
|
58
147
|
return [one("too-many", `at most ${String(issue.maximum)} items`)];
|
|
148
|
+
if (issue.origin === "number" || issue.origin === "int" || issue.origin === "bigint")
|
|
149
|
+
return [one("bad-value", `at most ${String(issue.maximum)}`)];
|
|
59
150
|
return [one("too-long", issue.origin === "string" && issue.message.startsWith("over ") ? issue.message : `at most ${String(issue.maximum)}${issue.origin === "string" ? " characters" : ""}`)];
|
|
60
151
|
case "too_small":
|
|
61
152
|
if (issue.origin === "string" && Number(issue.minimum) === 1)
|
|
62
153
|
return [one("empty", "must not be empty")];
|
|
63
154
|
if (issue.origin === "array" || issue.origin === "set")
|
|
64
155
|
return [one("too-few", `at least ${String(issue.minimum)} item${Number(issue.minimum) === 1 ? "" : "s"}`)];
|
|
156
|
+
if (issue.origin === "number" || issue.origin === "int" || issue.origin === "bigint")
|
|
157
|
+
return [one("bad-value", `at least ${String(issue.minimum)}`)];
|
|
65
158
|
return [one("too-few", `at least ${String(issue.minimum)}`)];
|
|
66
159
|
case "invalid_value":
|
|
67
160
|
return [one("bad-value", `must be ${issue.values.length === 1 ? JSON.stringify(issue.values[0]) : `one of ${issue.values.map(value => JSON.stringify(value)).join(", ")}`}`)];
|
|
68
|
-
case "invalid_union":
|
|
161
|
+
case "invalid_union": {
|
|
162
|
+
// A discriminated union names its choices (`steps[0].kind: must be one of "inbox", "task", ...`).
|
|
163
|
+
const by = issue.discriminator, options = issue.options;
|
|
164
|
+
if (typeof by === "string" && Array.isArray(options)) {
|
|
165
|
+
const given = "input" in issue && isRecord(issue.input) ? issue.input[by] : undefined;
|
|
166
|
+
const path = at === "payload" || issue.path.at(-1) === by ? at : `${at}.${by}`;
|
|
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}` }];
|
|
170
|
+
}
|
|
69
171
|
return [one("invalid", "does not match any allowed shape")];
|
|
172
|
+
}
|
|
173
|
+
case "invalid_format":
|
|
174
|
+
// A pattern's own words say what it wants (`must be a short id: lowercase letters, numbers and dashes`).
|
|
175
|
+
return [one("bad-value", issue.message.startsWith("Invalid") ? `must match ${issue.format === "regex" ? issue.pattern ?? "its pattern" : issue.format}` : issue.message)];
|
|
70
176
|
default:
|
|
71
177
|
return [one("invalid", issue.message)];
|
|
72
178
|
}
|
|
73
179
|
}
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
180
|
+
/**
|
|
181
|
+
* A Zod error as path-named issues, in the error's own order. Given the schema and input it came from, an unknown key
|
|
182
|
+
* also says which allowed key it most likely meant (`unknown key 'onFail' (did you mean ifFails?)`).
|
|
183
|
+
*/
|
|
184
|
+
export function contractIssues(error, where = {}) {
|
|
185
|
+
return error.issues.flatMap(issue => issuesOf(issue, where));
|
|
77
186
|
}
|
|
78
187
|
/** A Zod error as path-named lines: `steps[0].routes[0].goesTo: required`, `routes[0]: unknown key 'to'`. */
|
|
79
|
-
export function contractError(error) {
|
|
80
|
-
return contractIssues(error).map(issue => issue.line);
|
|
188
|
+
export function contractError(error, where = {}) {
|
|
189
|
+
return contractIssues(error, where).map(issue => issue.line);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A contract issue as a problem with a reason code built from its path — `missing-goal`, `criteria[0].how-too-long`,
|
|
193
|
+
* `checks-too-many`, `payload-unknown-key`, `bad-version`, `newer-version` — so callers that branch on a reason, and
|
|
194
|
+
* durable outcomes that recorded one, read the same as before the contract.
|
|
195
|
+
*/
|
|
196
|
+
export function contractProblemOf(issue) {
|
|
197
|
+
const at = issue.path;
|
|
198
|
+
const reason = (() => {
|
|
199
|
+
switch (issue.kind) {
|
|
200
|
+
case "required":
|
|
201
|
+
case "empty":
|
|
202
|
+
case "too-few":
|
|
203
|
+
return `missing-${at}`;
|
|
204
|
+
case "too-long":
|
|
205
|
+
return `${at}-too-long`;
|
|
206
|
+
case "too-many":
|
|
207
|
+
return `${at}-too-many`;
|
|
208
|
+
case "unknown-key":
|
|
209
|
+
return `${at}-unknown-key`;
|
|
210
|
+
case "newer-version":
|
|
211
|
+
return "newer-version";
|
|
212
|
+
default:
|
|
213
|
+
return `bad-${at}`;
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
216
|
+
return { reason, message: issue.line };
|
|
81
217
|
}
|
|
82
218
|
/** Parse with the input reported, so a missing field reads `required` rather than a wrong type. */
|
|
83
|
-
export function parseContract(schema, input) {
|
|
219
|
+
export function parseContract(schema, input, options = {}) {
|
|
84
220
|
const parsed = schema.safeParse(input, { reportInput: true });
|
|
85
|
-
return parsed.success ? { ok: true, value: parsed.data } : { ok: false, issues: contractIssues(parsed.error) };
|
|
221
|
+
return parsed.success ? { ok: true, value: parsed.data } : { ok: false, issues: contractIssues(parsed.error, { schema, input, options }) };
|
|
86
222
|
}
|
|
87
223
|
/**
|
|
88
224
|
* Read a versioned payload. The current version parses as itself; an older known version (0 for a payload saved
|
|
89
225
|
* before it carried `version`) is upgraded by its adapter and then parsed by the same schema; a newer version is
|
|
90
226
|
* refused plainly; anything else is an unknown version.
|
|
91
227
|
*/
|
|
92
|
-
export function readVersioned(schema, input, upgrades = {}) {
|
|
228
|
+
export function readVersioned(schema, input, upgrades = {}, options = {}) {
|
|
93
229
|
const current = schema.shape.version.value;
|
|
94
230
|
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
95
231
|
return { ok: false, issues: [{ path: "payload", kind: "wrong-type", line: `payload: must be an object (got ${got(input)})` }] };
|
|
@@ -97,7 +233,7 @@ export function readVersioned(schema, input, upgrades = {}) {
|
|
|
97
233
|
const body = input;
|
|
98
234
|
const version = Object.prototype.hasOwnProperty.call(body, "version") ? body["version"] : 0;
|
|
99
235
|
if (version === current)
|
|
100
|
-
return parseContract(schema, body);
|
|
236
|
+
return parseContract(schema, body, options);
|
|
101
237
|
if (typeof version === "number" && Number.isInteger(version) && version > current) {
|
|
102
238
|
return { ok: false, issues: [{ path: "version", kind: "newer-version", line: `version: made by a newer Toolroll (version ${version}; this one reads up to ${current})` }] };
|
|
103
239
|
}
|
|
@@ -105,7 +241,7 @@ export function readVersioned(schema, input, upgrades = {}) {
|
|
|
105
241
|
if (upgrade === undefined) {
|
|
106
242
|
return { ok: false, issues: [{ path: "version", kind: "bad-value", line: `version: unknown version ${JSON.stringify(version)} (this Toolroll reads ${current}${Object.keys(upgrades).length === 0 ? "" : ` and ${Object.keys(upgrades).join(", ")}`})` }] };
|
|
107
243
|
}
|
|
108
|
-
return parseContract(schema, upgrade(body));
|
|
244
|
+
return parseContract(schema, upgrade(body), options);
|
|
109
245
|
}
|
|
110
246
|
/** The Zod node kinds a model-facing schema may use: each says exactly what its JSON Schema says. */
|
|
111
247
|
const MODEL_FACING_TYPES = new Set(["string", "number", "int", "boolean", "null", "literal", "enum", "array", "object", "optional", "nullable", "union", "tuple", "record", "lazy", "readonly", "unknown", "any"]);
|
|
@@ -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
|
+
});
|