toolroll 0.9.40 → 0.9.42

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 (68) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/cli.js +3 -1
  3. package/dist/coding-types.d.ts +2 -24
  4. package/dist/coding-workspace.js +12 -11
  5. package/dist/contracts/cli.d.ts +736 -0
  6. package/dist/contracts/cli.js +223 -0
  7. package/dist/contracts/coding-activity.d.ts +64 -0
  8. package/dist/contracts/coding-activity.js +58 -0
  9. package/dist/contracts/console-api.d.ts +483 -0
  10. package/dist/contracts/console-api.js +273 -0
  11. package/dist/contracts/diff-stat.d.ts +58 -0
  12. package/dist/contracts/diff-stat.js +59 -0
  13. package/dist/contracts/integration-metadata.d.ts +99 -0
  14. package/dist/contracts/integration-metadata.js +101 -0
  15. package/dist/contracts/model-catalog.d.ts +21 -0
  16. package/dist/contracts/model-catalog.js +14 -0
  17. package/dist/contracts/native-census.d.ts +42 -0
  18. package/dist/contracts/native-census.js +36 -0
  19. package/dist/contracts/observation-request.d.ts +37 -0
  20. package/dist/contracts/observation-request.js +25 -0
  21. package/dist/contracts/provider.d.ts +30 -0
  22. package/dist/contracts/provider.js +15 -0
  23. package/dist/contracts/recipes.d.ts +166 -0
  24. package/dist/contracts/recipes.js +52 -0
  25. package/dist/contracts/retention.d.ts +42 -0
  26. package/dist/contracts/retention.js +24 -0
  27. package/dist/contracts/storage.d.ts +23 -0
  28. package/dist/contracts/storage.js +10 -0
  29. package/dist/contracts/store-json.d.ts +315 -0
  30. package/dist/contracts/store-json.js +196 -0
  31. package/dist/contracts/trigger-payloads.d.ts +369 -0
  32. package/dist/contracts/trigger-payloads.js +144 -0
  33. package/dist/contracts/update-journal.d.ts +254 -0
  34. package/dist/contracts/update-journal.js +155 -0
  35. package/dist/contracts/verification-view.d.ts +93 -0
  36. package/dist/contracts/verification-view.js +43 -0
  37. package/dist/control-setup.d.ts +1 -1
  38. package/dist/desktop-update.d.ts +7 -40
  39. package/dist/desktop-update.js +36 -8
  40. package/dist/evidence.d.ts +3 -19
  41. package/dist/flow-triggers.js +61 -60
  42. package/dist/mcp-connect.d.ts +0 -1
  43. package/dist/mcp-connect.js +41 -23
  44. package/dist/model-catalog.js +8 -6
  45. package/dist/observations.d.ts +2 -6
  46. package/dist/observations.js +13 -9
  47. package/dist/operate.js +2 -1
  48. package/dist/process-recovery-native.d.ts +2 -13
  49. package/dist/process-recovery-native.js +8 -5
  50. package/dist/project-tools.d.ts +6 -24
  51. package/dist/project-tools.js +22 -18
  52. package/dist/provider.d.ts +3 -8
  53. package/dist/provider.js +15 -11
  54. package/dist/recipes.d.ts +3 -28
  55. package/dist/recipes.js +79 -52
  56. package/dist/retention.d.ts +4 -5
  57. package/dist/retention.js +5 -4
  58. package/dist/serve.js +142 -28
  59. package/dist/storage.d.ts +2 -1
  60. package/dist/storage.js +12 -9
  61. package/dist/store.d.ts +3 -15
  62. package/dist/store.js +80 -158
  63. package/dist/surface.d.ts +6 -54
  64. package/dist/surface.js +6 -1
  65. package/dist/toolroll-update.d.ts +8 -99
  66. package/dist/toolroll-update.js +28 -18
  67. package/dist/verification-evidence.js +27 -12
  68. package/package.json +1 -1
@@ -0,0 +1,223 @@
1
+ /**
2
+ * The CLI's machine contract (docs/plans/zod-revamp.md, item 14): one schema for the `--json` envelope, one for each
3
+ * declared command's answer, and one for a row of the declared command guide that `contract --commands` dumps.
4
+ *
5
+ * Answers are what Toolroll writes and consumers are told to ignore keys they don't recognize, so every object here is
6
+ * loose. A command answers with its own success shape or a refusal (`ok: false` with a stable `reason`). The commands
7
+ * named in COMMAND_OUTPUTS describe their top-level fields; every other declared command is held to the envelope with
8
+ * its own `command` name. An answer that disagrees is logged on stderr and written exactly as it was: the check never
9
+ * changes, reorders or refuses the bytes a command writes.
10
+ */
11
+ import { z } from "zod";
12
+ import { ENVELOPE_VERSION, envelopeJson } from "../envelope.js";
13
+ import { parseContract } from "./contract.js";
14
+ import { LISTED_TASK_STATES } from "./lead-tools.js";
15
+ const str = z.string();
16
+ const int = z.number().int();
17
+ const list = z.array(z.unknown());
18
+ const nullable = (schema) => schema.nullable();
19
+ const rows = (shape) => z.array(z.looseObject(shape));
20
+ /** Every `--json` answer: `envelopeVersion` first, then `ok` and `command`; a refusal adds `reason` and `message`. */
21
+ export const envelopeSchema = z.looseObject({
22
+ envelopeVersion: z.literal(ENVELOPE_VERSION),
23
+ ok: z.boolean(),
24
+ command: str,
25
+ reason: str.optional(),
26
+ message: str.optional(),
27
+ });
28
+ /** A command's refusal: its stable reason, usually with a message, and whatever the command adds. */
29
+ function refusal(command) {
30
+ return z.looseObject({ envelopeVersion: z.literal(ENVELOPE_VERSION), ok: z.literal(false), command: z.literal(command), reason: str, message: str.optional() });
31
+ }
32
+ /** A command's answer: its success shape (each `shape` field as written) or its refusal. */
33
+ function answers(command, shape) {
34
+ return z.discriminatedUnion("ok", [z.looseObject({ envelopeVersion: z.literal(ENVELOPE_VERSION), ok: z.literal(true), command: z.literal(command), ...shape }), refusal(command)]);
35
+ }
36
+ // ---- the declared command guide, as `contract --commands` dumps it ----
37
+ export const commandFlagSchema = z.looseObject({ name: str, takesValue: z.boolean(), meaning: str }).readonly();
38
+ /** A session operation's executable input schema, as session-contract.ts states it. */
39
+ const sessionInputSchema = z.custom(value => typeof value === "object" && value !== null && !Array.isArray(value));
40
+ export const commandRowSchema = z.looseObject({
41
+ /** What you type after `toolroll` (subcommands included). */
42
+ invocation: str,
43
+ /** The `command` field the envelope answers with, where it differs from the invocation (the no-verb report answers
44
+ * as "scan"). */
45
+ envelopeCommand: str.optional(),
46
+ synopsis: str,
47
+ /** Who this act belongs to. "operator" rows are ceremonies or infrastructure: an agent must not invoke them even
48
+ * when credentials are within reach — the credential IS the person. The operator() helper omits flag detail on
49
+ * purpose; an explicit row may document flags when its command contract requires them. A schema is never
50
+ * permission. */
51
+ audience: z.enum(["agent", "operator"]),
52
+ agentMayInvoke: z.boolean(),
53
+ /** Truthful retry semantics, not a boolean:
54
+ * keyed — takes --key; same key returns the first answer.
55
+ * identity-idempotent — repeating it converges (same lease, same path, same managed file); no key needed.
56
+ * unkeyed — a mutation without replay protection: do not blind-retry.
57
+ * none — a read. */
58
+ mutation: z.enum(["keyed", "identity-idempotent", "unkeyed", "none"]),
59
+ positionals: z.array(z.looseObject({ name: str, required: z.boolean(), meaning: str }).readonly()).readonly().optional(),
60
+ flags: z.array(commandFlagSchema).readonly().optional(),
61
+ notableReasons: z.array(str).readonly().optional(),
62
+ /** Session schemas are executable input contracts, not a grant of authority. */
63
+ inputSchema: sessionInputSchema.optional(),
64
+ }).readonly();
65
+ // ---- the commands that describe their fields ----
66
+ const taskSchema = z.looseObject({ id: str, title: str, state: z.enum(LISTED_TASK_STATES), createdAt: str, updatedAt: str, priority: z.number() });
67
+ const checkSummary = z.looseObject({ status: str, exitCode: nullable(int), suites: z.unknown() });
68
+ const contractOutput = z.union([
69
+ z.looseObject({ envelopeVersion: z.literal(ENVELOPE_VERSION), ok: z.literal(true), command: z.literal("contract"), capabilities: z.array(str) }),
70
+ z.looseObject({ envelopeVersion: z.literal(ENVELOPE_VERSION), ok: z.literal(true), command: z.literal("contract"), schemaVersion: int, notes: z.record(str, str), commands: z.array(commandRowSchema) }),
71
+ refusal("contract"),
72
+ ]);
73
+ /** The installation report (`lead-status.ts` InstallationStatus) and what `status` adds to it. */
74
+ const statusOutput = answers("status", {
75
+ generatedAt: str,
76
+ running: z.looseObject({ count: int, phases: rows({ phase: str, count: int }), tasks: rows({ task: str, run: int, phase: str }) }),
77
+ queued: z.looseObject({ count: int, reasons: rows({ reason: str, count: int }), tasks: rows({ task: str, reason: str }) }),
78
+ waitingForReview: z.looseObject({ count: int, results: rows({ task: str, run: nullable(int), check: checkSummary }) }),
79
+ releaseCheck: nullable(z.looseObject({ task: str, run: int, finishedAt: nullable(str), check: checkSummary })),
80
+ planWindows: rows({ provider: str, plan: nullable(str), observedAt: str }),
81
+ signIn: rows({ provider: str, reason: str, command: str, since: str }),
82
+ tasks: rows({ task: str, title: str, headline: str, sentence: str }),
83
+ lead: nullable(z.looseObject({ owner: str, doing: str, at: str, task: nullable(str), line: str })),
84
+ projects: rows({ repo: str, name: str, running: int, limit: int }),
85
+ update: z.looseObject({ current: str, latest: str, security: z.boolean(), updateCommand: str, url: str }).optional(),
86
+ integrations: z.looseObject({ broken: rows({ key: str, name: str, fix: nullable(str) }) }).optional(),
87
+ updateWaiting: z.looseObject({}).optional(),
88
+ unsentReplies: rows({ since: str, error: nullable(str), retry: str }).optional(),
89
+ });
90
+ /** One integration (`integrations.ts` Integration): never a secret. */
91
+ const integrationRow = z.looseObject({
92
+ key: str,
93
+ group: z.enum(["chat", "code", "mail", "tools", "monitoring", "agents"]),
94
+ name: str,
95
+ state: z.enum(["connected", "not-set-up", "broken"]),
96
+ account: nullable(str),
97
+ detail: nullable(str),
98
+ checked: z.boolean(),
99
+ checkedAt: nullable(str),
100
+ lastSuccessAt: nullable(str),
101
+ lastError: nullable(str),
102
+ lastErrorAt: nullable(str),
103
+ usedBy: z.array(str),
104
+ action: z.looseObject({ kind: z.enum(["setup", "test", "fix"]), label: str }),
105
+ custom: z.literal(true).optional(),
106
+ });
107
+ const integrationsOutput = answers("integrations", {
108
+ integrations: z.array(integrationRow),
109
+ counts: z.looseObject({ connected: int, "not-set-up": int, broken: int }),
110
+ });
111
+ const scanOutput = answers("scan", {
112
+ scannedAt: str,
113
+ roots: z.array(str),
114
+ missingRoots: z.array(str),
115
+ remoteRead: z.boolean(),
116
+ repos: rows({ path: str, name: str }),
117
+ });
118
+ const taskListOutput = answers("task list", {
119
+ count: int,
120
+ tasks: rows({ id: str, rootId: str, activeTaskId: str, title: str, repo: nullable(str), state: str, status: z.looseObject({ token: str, label: str }) }),
121
+ totals: z.record(str, int),
122
+ nextCursor: nullable(str),
123
+ limit: int,
124
+ view: str,
125
+ });
126
+ const taskShowOutput = answers("task show", {
127
+ task: taskSchema,
128
+ work: z.looseObject({ taskId: str, state: str }),
129
+ assignment: z.unknown(),
130
+ ref: int,
131
+ blockedBy: list,
132
+ position: z.unknown(),
133
+ reservedFor: nullable(str),
134
+ approval: z.looseObject({ approved: z.boolean() }),
135
+ risk: str,
136
+ route: z.unknown(),
137
+ runs: list,
138
+ deliverable: str,
139
+ proofReasons: list,
140
+ proofMatrix: list,
141
+ proofAccepted: z.boolean(),
142
+ stops: list,
143
+ });
144
+ const flowListed = z.looseObject({ id: int, name: str, repo: str, project: str, zones: z.array(str), triggers: int, cardsWaiting: int, needDecision: int });
145
+ /**
146
+ * The commands whose answers name their fields: the most-used ones (contract, the no-verb scan, status,
147
+ * integrations, task, flows, ready) and the plain lists. Keyed by the envelope's `command`.
148
+ */
149
+ export const COMMAND_OUTPUTS = {
150
+ contract: contractOutput,
151
+ scan: scanOutput,
152
+ status: statusOutput,
153
+ integrations: integrationsOutput,
154
+ ready: z.union([
155
+ answers("ready", { count: int, tasks: rows({ id: str, title: str, state: str }) }),
156
+ z.looseObject({ envelopeVersion: z.literal(ENVELOPE_VERSION), ok: z.literal(false), command: z.literal("ready"), reason: z.literal("empty"), message: str, count: int, tasks: list }),
157
+ ]),
158
+ "task add": z.union([
159
+ answers("task add", { task: taskSchema, repo: nullable(str) }),
160
+ // Filed in an external backend: its id there, and the local mirror when one was made.
161
+ answers("task add", { id: str, backend: str, mirror: str.optional() }),
162
+ ]),
163
+ "task list": taskListOutput,
164
+ "task show": taskShowOutput,
165
+ "task hold": answers("task hold", { id: str, reason: str, until: nullable(str) }),
166
+ "task unhold": answers("task unhold", { id: str }),
167
+ "task wait": answers("task wait", { task: str, outcome: str }),
168
+ "flows list": answers("flows list", { flows: z.array(flowListed) }),
169
+ "flows show": answers("flows show", { flow: z.looseObject({ id: int, name: str, repo: str, project: str, readable: z.boolean(), zones: list, triggers: list }) }),
170
+ grants: answers("grants", { count: int, grants: list }),
171
+ "runner list": answers("runner list", { count: int, runners: list }),
172
+ "routine list": answers("routine list", { routines: list }),
173
+ "incident list": answers("incident list", { incidents: list }),
174
+ "outbox list": answers("outbox list", { notifications: rows({ id: int, kind: str, subject: str, createdAt: str }) }),
175
+ // A pass with failed deliveries answers ok: false with its counts and no reason.
176
+ "outbox deliver": z.union([
177
+ z.looseObject({ envelopeVersion: z.literal(ENVELOPE_VERSION), ok: z.boolean(), command: z.literal("outbox deliver"), delivered: int, failed: int }),
178
+ refusal("outbox deliver"),
179
+ ]),
180
+ "approver list": answers("approver list", { approvers: list }),
181
+ "coordinator list": answers("coordinator list", { coordinators: list }),
182
+ "cap list": answers("cap list", { repo: str, capabilities: list }),
183
+ gaps: answers("gaps", { repo: str, gaps: list }),
184
+ reap: answers("reap", { count: int, released: list }),
185
+ "skills list": answers("skills list", { guides: rows({ name: str, title: str, oneLiner: str }) }),
186
+ };
187
+ const declared = new Map(Object.entries(COMMAND_OUTPUTS));
188
+ /** The schema for `command`'s answer: its own when it names its fields, else the envelope under its own name. */
189
+ export function commandOutputSchema(command) {
190
+ let schema = declared.get(command);
191
+ if (schema === undefined) {
192
+ schema = answers(command, {});
193
+ declared.set(command, schema);
194
+ }
195
+ return schema;
196
+ }
197
+ /** Pair each guide row, in order, with the schema of the answer it names (the envelope command, else the invocation). */
198
+ export function commandEntries(guide) {
199
+ return guide.map(row => ({ guide: row, output: commandOutputSchema(row.envelopeCommand ?? row.invocation) }));
200
+ }
201
+ /** Path-named lines for each way `envelope` (as written, version first) disagrees with its command's schema; empty when it agrees. */
202
+ export function envelopeProblems(envelope) {
203
+ const top = parseContract(envelopeSchema, envelope);
204
+ if (!top.ok)
205
+ return top.issues.map(one => one.line);
206
+ const read = parseContract(commandOutputSchema(top.value.command), envelope);
207
+ return read.ok ? [] : read.issues.map(one => one.line);
208
+ }
209
+ /**
210
+ * Serialize one envelope with the unchanged `envelopeJson`, after checking the value it writes. A disagreement is
211
+ * logged on stderr and the envelope is written as it was; nothing here throws into a command's path.
212
+ */
213
+ export function checkedEnvelopeJson(payload) {
214
+ try {
215
+ const problems = envelopeProblems({ envelopeVersion: ENVELOPE_VERSION, ...payload });
216
+ if (problems.length > 0)
217
+ process.stderr.write(`toolroll ${String(payload.command)} --json: its answer disagrees with its schema — ${problems.join("; ")}\n`);
218
+ }
219
+ catch {
220
+ // A check that breaks never reaches the command's envelope path.
221
+ }
222
+ return envelopeJson(payload);
223
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * What the coding workspace saves beside each session (docs/plans/zod-revamp.md, item 19): its activity items
3
+ * (`coding_item.payload`), approval requests (`coding_request.payload` and `rpc_id`) and the native custody witness
4
+ * (`coding_custody.payload`). The session record itself is src/contracts/coding-workspace.ts.
5
+ *
6
+ * These rows carry no `version` and are written byte for byte as before. They were read as casts, so the readers stay
7
+ * tolerant: a row that is not JSON fails as it always did, and a row that does not match its schema is read as saved
8
+ * and logged, never refused. The custody witness is only a recovery proof: one without the shape its checks read
9
+ * proves nothing, as before.
10
+ */
11
+ import { z } from "zod";
12
+ export declare const codingItemSchema: z.ZodObject<{
13
+ id: z.ZodString;
14
+ type: z.ZodString;
15
+ text: z.ZodString;
16
+ status: z.ZodNullable<z.ZodString>;
17
+ clientId: z.ZodExactOptional<z.ZodString>;
18
+ }, z.core.$strip>;
19
+ export type CodingItem = z.infer<typeof codingItemSchema>;
20
+ export declare const codingQuestionSchema: z.ZodObject<{
21
+ id: z.ZodString;
22
+ header: z.ZodString;
23
+ question: z.ZodString;
24
+ options: z.ZodArray<z.ZodObject<{
25
+ label: z.ZodString;
26
+ description: z.ZodString;
27
+ }, z.core.$strip>>;
28
+ }, z.core.$strip>;
29
+ export type CodingQuestion = z.infer<typeof codingQuestionSchema>;
30
+ export declare const codingRequestSchema: z.ZodObject<{
31
+ id: z.ZodString;
32
+ kind: z.ZodEnum<{
33
+ command: "command";
34
+ files: "files";
35
+ questions: "questions";
36
+ }>;
37
+ method: z.ZodString;
38
+ title: z.ZodString;
39
+ detail: z.ZodString;
40
+ questions: z.ZodArray<z.ZodObject<{
41
+ id: z.ZodString;
42
+ header: z.ZodString;
43
+ question: z.ZodString;
44
+ options: z.ZodArray<z.ZodObject<{
45
+ label: z.ZodString;
46
+ description: z.ZodString;
47
+ }, z.core.$strip>>;
48
+ }, z.core.$strip>>;
49
+ }, z.core.$strip>;
50
+ export type CodingRequest = z.infer<typeof codingRequestSchema>;
51
+ /** The native JSON-RPC id a request is answered with. */
52
+ export declare const codingRpcIdSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
53
+ export type CodingRpcId = z.infer<typeof codingRpcIdSchema>;
54
+ /** The fields recovery reads from a custody witness before it may count as proof that the prior agent is gone. */
55
+ export declare const codingCustodyWitnessSchema: z.ZodObject<{
56
+ host: z.ZodString;
57
+ descendants: z.ZodArray<z.ZodUnknown>;
58
+ observationUnknown: z.ZodBoolean;
59
+ }, z.core.$strip>;
60
+ export declare const readCodingItem: (payload: string) => CodingItem;
61
+ export declare const readCodingRequest: (payload: string) => CodingRequest;
62
+ export declare const readCodingRpcId: (saved: string) => CodingRpcId;
63
+ /** The saved witness when it has the shape recovery checks, else null (it proves nothing). Not JSON throws. */
64
+ export declare function readCodingCustodyWitness<T>(payload: string): T | null;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * What the coding workspace saves beside each session (docs/plans/zod-revamp.md, item 19): its activity items
3
+ * (`coding_item.payload`), approval requests (`coding_request.payload` and `rpc_id`) and the native custody witness
4
+ * (`coding_custody.payload`). The session record itself is src/contracts/coding-workspace.ts.
5
+ *
6
+ * These rows carry no `version` and are written byte for byte as before. They were read as casts, so the readers stay
7
+ * tolerant: a row that is not JSON fails as it always did, and a row that does not match its schema is read as saved
8
+ * and logged, never refused. The custody witness is only a recovery proof: one without the shape its checks read
9
+ * proves nothing, as before.
10
+ */
11
+ import { z } from "zod";
12
+ import { contractError } from "./contract.js";
13
+ export const codingItemSchema = z.object({
14
+ id: z.string(),
15
+ type: z.string(),
16
+ text: z.string(),
17
+ status: z.string().nullable(),
18
+ /** The browser's submission key, on a user message it sent. */
19
+ clientId: z.string().exactOptional(),
20
+ });
21
+ export const codingQuestionSchema = z.object({
22
+ id: z.string(),
23
+ header: z.string(),
24
+ question: z.string(),
25
+ options: z.array(z.object({ label: z.string(), description: z.string() })),
26
+ });
27
+ export const codingRequestSchema = z.object({
28
+ id: z.string(),
29
+ kind: z.enum(["command", "files", "questions"]),
30
+ method: z.string(),
31
+ title: z.string(),
32
+ detail: z.string(),
33
+ questions: z.array(codingQuestionSchema),
34
+ });
35
+ /** The native JSON-RPC id a request is answered with. */
36
+ export const codingRpcIdSchema = z.union([z.string(), z.number()]);
37
+ /** The fields recovery reads from a custody witness before it may count as proof that the prior agent is gone. */
38
+ export const codingCustodyWitnessSchema = z.object({
39
+ host: z.string(),
40
+ descendants: z.array(z.unknown()),
41
+ observationUnknown: z.boolean(),
42
+ });
43
+ function tolerant(schema, what, payload) {
44
+ const value = JSON.parse(payload);
45
+ const read = schema.safeParse(value);
46
+ if (!read.success)
47
+ console.warn(`A saved ${what} does not match its contract and is read as saved: ${contractError(read.error, { schema, input: value }).join("; ")}`);
48
+ // The saved value itself: a row written again keeps its bytes.
49
+ return value;
50
+ }
51
+ export const readCodingItem = (payload) => tolerant(codingItemSchema, "coding item", payload);
52
+ export const readCodingRequest = (payload) => tolerant(codingRequestSchema, "coding request", payload);
53
+ export const readCodingRpcId = (saved) => tolerant(codingRpcIdSchema, "coding request id", saved);
54
+ /** The saved witness when it has the shape recovery checks, else null (it proves nothing). Not JSON throws. */
55
+ export function readCodingCustodyWitness(payload) {
56
+ const value = JSON.parse(payload);
57
+ return codingCustodyWitnessSchema.safeParse(value).success ? value : null;
58
+ }