toolroll 0.9.36 → 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.
Files changed (165) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/assignment-adapters.d.ts +12 -48
  3. package/dist/assignment-adapters.js +33 -43
  4. package/dist/build-review.d.ts +1 -1
  5. package/dist/build-review.js +2 -1
  6. package/dist/chat-acceptance.d.ts +1 -1
  7. package/dist/chat-actions.d.ts +18 -12
  8. package/dist/chat-actions.js +15 -65
  9. package/dist/chat-ask.js +5 -5
  10. package/dist/chat-decide.d.ts +14 -1
  11. package/dist/chat-decide.js +12 -6
  12. package/dist/chat-delivery-state.d.ts +12 -70
  13. package/dist/chat-delivery-state.js +17 -135
  14. package/dist/chat-delivery.js +58 -30
  15. package/dist/chat-flow.js +8 -6
  16. package/dist/chat-inbox.js +1 -1
  17. package/dist/coding-context.d.ts +2 -39
  18. package/dist/coding-context.js +2 -1
  19. package/dist/coding-handoff.d.ts +6 -25
  20. package/dist/coding-handoff.js +11 -5
  21. package/dist/coding-types.d.ts +2 -26
  22. package/dist/coding-workspace.d.ts +2 -0
  23. package/dist/coding-workspace.js +50 -14
  24. package/dist/contracts/acceptance-terms.d.ts +14 -0
  25. package/dist/contracts/acceptance-terms.js +16 -0
  26. package/dist/contracts/chat-actions.d.ts +328 -0
  27. package/dist/contracts/chat-actions.js +133 -0
  28. package/dist/contracts/chat-callback-rows.d.ts +59 -0
  29. package/dist/contracts/chat-callback-rows.js +46 -0
  30. package/dist/contracts/chat-content.d.ts +276 -0
  31. package/dist/contracts/chat-content.js +127 -0
  32. package/dist/contracts/chat-tables.d.ts +6 -0
  33. package/dist/contracts/chat-tables.js +136 -0
  34. package/dist/contracts/coding-context.d.ts +97 -0
  35. package/dist/contracts/coding-context.js +60 -0
  36. package/dist/contracts/coding-handoff.d.ts +44 -0
  37. package/dist/contracts/coding-handoff.js +47 -0
  38. package/dist/contracts/coding-workspace.d.ts +165 -0
  39. package/dist/contracts/coding-workspace.js +71 -0
  40. package/dist/contracts/contract.d.ts +7 -2
  41. package/dist/contracts/contract.js +21 -12
  42. package/dist/contracts/decision.d.ts +45 -0
  43. package/dist/contracts/decision.js +45 -0
  44. package/dist/contracts/discord-callback.d.ts +45 -0
  45. package/dist/contracts/discord-callback.js +34 -0
  46. package/dist/contracts/flow-card.d.ts +50 -0
  47. package/dist/contracts/flow-card.js +46 -0
  48. package/dist/contracts/flow-send.d.ts +119 -0
  49. package/dist/contracts/flow-send.js +112 -0
  50. package/dist/contracts/flow.d.ts +5 -0
  51. package/dist/contracts/flow.js +5 -0
  52. package/dist/contracts/gateway-tools.d.ts +374 -0
  53. package/dist/contracts/gateway-tools.js +94 -0
  54. package/dist/contracts/lead-context.d.ts +165 -0
  55. package/dist/contracts/lead-context.js +73 -0
  56. package/dist/contracts/lead-tools.d.ts +1905 -0
  57. package/dist/contracts/lead-tools.js +265 -0
  58. package/dist/contracts/memory-pass.d.ts +158 -0
  59. package/dist/contracts/memory-pass.js +102 -0
  60. package/dist/contracts/project-knowledge.d.ts +126 -0
  61. package/dist/contracts/project-knowledge.js +93 -0
  62. package/dist/contracts/project-memory.d.ts +117 -0
  63. package/dist/contracts/project-memory.js +51 -0
  64. package/dist/contracts/project-skills.d.ts +121 -0
  65. package/dist/contracts/project-skills.js +76 -0
  66. package/dist/contracts/review-findings.d.ts +46 -0
  67. package/dist/contracts/review-findings.js +48 -0
  68. package/dist/contracts/route.d.ts +484 -0
  69. package/dist/contracts/route.js +128 -0
  70. package/dist/contracts/scope.d.ts +105 -0
  71. package/dist/contracts/scope.js +114 -0
  72. package/dist/contracts/scout-report.d.ts +234 -0
  73. package/dist/contracts/scout-report.js +141 -0
  74. package/dist/contracts/slack-callback.d.ts +43 -0
  75. package/dist/contracts/slack-callback.js +35 -0
  76. package/dist/contracts/sort-answer.d.ts +83 -0
  77. package/dist/contracts/sort-answer.js +77 -0
  78. package/dist/contracts/stage-output.d.ts +127 -0
  79. package/dist/contracts/stage-output.js +166 -0
  80. package/dist/contracts/task-sizing.d.ts +24 -0
  81. package/dist/contracts/task-sizing.js +28 -0
  82. package/dist/contracts/teammate-turn.d.ts +43 -0
  83. package/dist/contracts/teammate-turn.js +51 -0
  84. package/dist/contracts/teams-callback.d.ts +14 -0
  85. package/dist/contracts/teams-callback.js +14 -0
  86. package/dist/contracts/telegram-callback.d.ts +120 -0
  87. package/dist/contracts/telegram-callback.js +97 -0
  88. package/dist/decision.d.ts +11 -20
  89. package/dist/decision.js +86 -167
  90. package/dist/desktop-update.d.ts +2 -2
  91. package/dist/discord-chat.js +31 -20
  92. package/dist/flow-engine.js +14 -17
  93. package/dist/flow-items.d.ts +4 -13
  94. package/dist/flow-items.js +1 -15
  95. package/dist/flow-send.d.ts +6 -37
  96. package/dist/flow-send.js +18 -14
  97. package/dist/flow-share.js +7 -1
  98. package/dist/flow-sort.d.ts +7 -26
  99. package/dist/flow-sort.js +25 -20
  100. package/dist/flow-steps.js +3 -2
  101. package/dist/flows.d.ts +2 -4
  102. package/dist/flows.js +9 -5
  103. package/dist/invoke.js +4 -4
  104. package/dist/lead-context.d.ts +4 -2
  105. package/dist/lead-context.js +24 -17
  106. package/dist/mate-tools.d.ts +4 -0
  107. package/dist/mate-tools.js +108 -275
  108. package/dist/mcp-connect.d.ts +24 -4
  109. package/dist/mcp-connect.js +78 -15
  110. package/dist/mcp.js +84 -216
  111. package/dist/memory-pass.d.ts +17 -42
  112. package/dist/memory-pass.js +54 -26
  113. package/dist/phase-routing.d.ts +19 -72
  114. package/dist/phase-routing.js +34 -164
  115. package/dist/project-knowledge.d.ts +16 -35
  116. package/dist/project-knowledge.js +42 -21
  117. package/dist/project-memory.d.ts +2 -28
  118. package/dist/project-memory.js +28 -13
  119. package/dist/project-skills.d.ts +16 -38
  120. package/dist/project-skills.js +41 -32
  121. package/dist/project-tools.d.ts +1 -0
  122. package/dist/project-tools.js +4 -4
  123. package/dist/provider.d.ts +2 -0
  124. package/dist/provider.js +4 -16
  125. package/dist/result-shots.d.ts +1 -1
  126. package/dist/result-shots.js +2 -1
  127. package/dist/review-switch.d.ts +3 -8
  128. package/dist/reviewer.d.ts +8 -12
  129. package/dist/reviewer.js +11 -35
  130. package/dist/scope.d.ts +21 -43
  131. package/dist/scope.js +42 -128
  132. package/dist/scout-report.d.ts +9 -194
  133. package/dist/scout-report.js +113 -264
  134. package/dist/scout.js +15 -11
  135. package/dist/serve.js +9 -2
  136. package/dist/setup-guide.d.ts +2 -2
  137. package/dist/slack-chat.js +26 -20
  138. package/dist/slack-state.js +2 -1
  139. package/dist/store.d.ts +9 -32
  140. package/dist/store.js +18 -15
  141. package/dist/structured-output.js +2 -1
  142. package/dist/subscription-chat.d.ts +4 -0
  143. package/dist/subscription-chat.js +4 -1
  144. package/dist/task-review-brief.d.ts +1 -7
  145. package/dist/task-review-brief.js +6 -6
  146. package/dist/task-sizing.d.ts +3 -27
  147. package/dist/task-sizing.js +11 -26
  148. package/dist/task-text.d.ts +0 -5
  149. package/dist/task-text.js +1 -3
  150. package/dist/teammate-question.js +7 -5
  151. package/dist/teammate-work.js +9 -5
  152. package/dist/teammates.d.ts +13 -64
  153. package/dist/teammates.js +32 -33
  154. package/dist/teams-chat.js +20 -11
  155. package/dist/telegram-flow.js +4 -3
  156. package/dist/telegram-mate.d.ts +4 -14
  157. package/dist/telegram-mate.js +8 -7
  158. package/dist/telegram-team.d.ts +2 -19
  159. package/dist/telegram.d.ts +1 -1
  160. package/dist/telegram.js +52 -42
  161. package/dist/text-limits.d.ts +142 -1
  162. package/dist/text-limits.js +144 -1
  163. package/dist/tools-ui.d.ts +2 -1
  164. package/dist/tools-ui.js +7 -4
  165. package/package.json +1 -1
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The managed context a desktop coding session captures once (`prepareCodingContext` in coding-context.ts): the
3
+ * frozen text the agent is given, what it was selected from, and the digest that ties the two together. It is saved
4
+ * inside the session's workspace record and checked again (`verifyCodingContext`) before every resume.
5
+ *
6
+ * `metadata` keeps its own `version: 1` because it is part of the digest; its keys are listed in the order the digest
7
+ * was taken in, so a parsed capture digests exactly as it was saved. Repository identity, digest and the materialized
8
+ * skill files are checked in plain code after parsing.
9
+ */
10
+ import { z } from "zod";
11
+ import { readVersioned, versioned } from "./contract.js";
12
+ export const codingContextMetadataSchema = z.strictObject({
13
+ version: z.literal(1),
14
+ repo: z.string(),
15
+ identity: z.string(),
16
+ baseRevision: z.string(),
17
+ knowledge: z.strictObject({
18
+ revision: z.int(),
19
+ selectionSha256: z.string(),
20
+ references: z.array(z.strictObject({
21
+ id: z.string(),
22
+ title: z.string(),
23
+ path: z.string().nullable(),
24
+ sourceSha: z.string().nullable(),
25
+ sourceRevision: z.string().nullable(),
26
+ })),
27
+ omitted: z.array(z.strictObject({ title: z.string(), reason: z.string() })),
28
+ }),
29
+ skills: z.strictObject({
30
+ revision: z.int(),
31
+ packages: z.array(z.strictObject({ name: z.string(), sha256: z.string(), skillFile: z.string() })),
32
+ }),
33
+ directory: z.string().nullable(),
34
+ files: z.array(z.strictObject({ path: z.string(), sha256: z.string() })),
35
+ });
36
+ export const CODING_CONTEXT_VERSION = 1;
37
+ export const codingContextSchema = versioned(CODING_CONTEXT_VERSION, {
38
+ text: z.string(),
39
+ metadata: codingContextMetadataSchema,
40
+ /** learningSha of `{ text, metadata }`. */
41
+ sha256: z.string(),
42
+ });
43
+ const CONTEXT_FIELDS = ["text", "metadata", "sha256"];
44
+ /**
45
+ * A capture saved before the envelope carried `version` (every session through 0.9.36), as version 1. Readers then
46
+ * read only these three fields; the metadata is kept exactly as saved, since its bytes are what the digest covers.
47
+ */
48
+ export function upgradeUnversionedContext(body) {
49
+ const out = { version: CODING_CONTEXT_VERSION };
50
+ for (const field of CONTEXT_FIELDS) {
51
+ if (Object.prototype.hasOwnProperty.call(body, field) && body[field] !== undefined)
52
+ out[field] = body[field];
53
+ }
54
+ return out;
55
+ }
56
+ export const CODING_CONTEXT_UPGRADES = { 0: upgradeUnversionedContext };
57
+ /** Read a saved capture: version 1 as itself, an unversioned one upgraded, a newer one refused plainly. */
58
+ export function readCodingContext(input) {
59
+ return readVersioned(codingContextSchema, input, CODING_CONTEXT_UPGRADES, { distinguishNull: true });
60
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The coding handoff receipt: the immutable terms under which a committed desktop coding session became an ordinary
3
+ * review task (`coding_handoff.payload`, coding-handoff.ts). Its bytes are hashed and sealed, and its id is the hash of
4
+ * its terms, so the keys are listed in the order the receipt has always been written in — `version` after the
5
+ * preview — and a parsed receipt serializes to exactly the bytes it was read from. The saved hash, derived identity,
6
+ * branch and scope seal are checked in plain code: the hash before parsing, the rest after.
7
+ */
8
+ import { z } from "zod";
9
+ import { type ContractResult } from "./contract.js";
10
+ export declare const CODING_HANDOFF_VERSION = 1;
11
+ export declare const codingHandoffReceiptSchema: z.ZodObject<{
12
+ sessionId: z.ZodString;
13
+ repo: z.ZodString;
14
+ base: z.ZodString;
15
+ candidate: z.ZodString;
16
+ title: z.ZodString;
17
+ originalPrompt: z.ZodString;
18
+ changedPaths: z.ZodArray<z.ZodString>;
19
+ version: z.ZodLiteral<1>;
20
+ identity: z.ZodString;
21
+ actor: z.ZodString;
22
+ generation: z.ZodInt;
23
+ goal: z.ZodString;
24
+ acceptance: z.ZodArray<z.ZodObject<{
25
+ id: z.ZodString;
26
+ statement: z.ZodString;
27
+ how: z.ZodOptional<z.ZodNullable<z.ZodString>>;
28
+ evidence: z.ZodArray<z.ZodEnum<{
29
+ "changed-path": "changed-path";
30
+ check: "check";
31
+ "manual-review": "manual-review";
32
+ screenshot: "screenshot";
33
+ }>>;
34
+ }, z.core.$strict>>;
35
+ outOfScope: z.ZodString;
36
+ id: z.ZodString;
37
+ taskId: z.ZodString;
38
+ branch: z.ZodString;
39
+ }, z.core.$strict>;
40
+ export type CodingHandoffReceipt = z.infer<typeof codingHandoffReceiptSchema>;
41
+ /** Read a receipt: version 1 as itself, a newer one refused plainly. Receipts have always carried `version`. */
42
+ export declare function readCodingHandoffReceipt(input: unknown): ContractResult<CodingHandoffReceipt>;
43
+ /** Read a receipt's saved bytes: JSON first, then the schema. */
44
+ export declare function parseCodingHandoffReceipt(raw: string): ContractResult<CodingHandoffReceipt>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The coding handoff receipt: the immutable terms under which a committed desktop coding session became an ordinary
3
+ * review task (`coding_handoff.payload`, coding-handoff.ts). Its bytes are hashed and sealed, and its id is the hash of
4
+ * its terms, so the keys are listed in the order the receipt has always been written in — `version` after the
5
+ * preview — and a parsed receipt serializes to exactly the bytes it was read from. The saved hash, derived identity,
6
+ * branch and scope seal are checked in plain code: the hash before parsing, the rest after.
7
+ */
8
+ import { z } from "zod";
9
+ import { readVersioned } from "./contract.js";
10
+ import { acceptanceCriterionSchema } from "./plan.js";
11
+ export const CODING_HANDOFF_VERSION = 1;
12
+ export const codingHandoffReceiptSchema = z.strictObject({
13
+ sessionId: z.string(),
14
+ repo: z.string(),
15
+ base: z.string(),
16
+ candidate: z.string(),
17
+ title: z.string(),
18
+ /** The session's first message, as the person wrote it. */
19
+ originalPrompt: z.string(),
20
+ changedPaths: z.array(z.string()),
21
+ version: z.literal(CODING_HANDOFF_VERSION),
22
+ identity: z.string(),
23
+ actor: z.string(),
24
+ generation: z.int(),
25
+ goal: z.string(),
26
+ acceptance: z.array(acceptanceCriterionSchema),
27
+ outOfScope: z.string(),
28
+ /** The first 32 hex of learningSha over every field above, in this order. */
29
+ id: z.string(),
30
+ taskId: z.string(),
31
+ branch: z.string(),
32
+ });
33
+ /** Read a receipt: version 1 as itself, a newer one refused plainly. Receipts have always carried `version`. */
34
+ export function readCodingHandoffReceipt(input) {
35
+ return readVersioned(codingHandoffReceiptSchema, input, {}, { distinguishNull: true });
36
+ }
37
+ /** Read a receipt's saved bytes: JSON first, then the schema. */
38
+ export function parseCodingHandoffReceipt(raw) {
39
+ let body;
40
+ try {
41
+ body = JSON.parse(raw);
42
+ }
43
+ catch {
44
+ return { ok: false, issues: [{ path: "payload", kind: "invalid", line: "payload: not JSON" }] };
45
+ }
46
+ return readCodingHandoffReceipt(body);
47
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * The workspace record: one desktop coding session as the coding catalog saves it (`coding_session.document`). The
3
+ * session itself is what the console, chat and the coding handoff read; the record is that session with its
4
+ * `version`. Row ownership, generation and recovery state are checked in plain code by the workspace after parsing.
5
+ */
6
+ import { z } from "zod";
7
+ import { type ContractResult } from "./contract.js";
8
+ export declare const CODING_STATUSES: readonly ["starting", "ready", "working", "needs-input", "stopping", "interrupted", "failed", "uncertain", "closed"];
9
+ export declare const codingSessionSchema: z.ZodObject<{
10
+ id: z.ZodString;
11
+ owner: z.ZodString;
12
+ generation: z.ZodInt;
13
+ repo: z.ZodString;
14
+ title: z.ZodString;
15
+ provider: z.ZodLiteral<"codex">;
16
+ model: z.ZodNullable<z.ZodString>;
17
+ branch: z.ZodString;
18
+ base: z.ZodString;
19
+ worktree: z.ZodString;
20
+ nativeThreadId: z.ZodNullable<z.ZodString>;
21
+ turnId: z.ZodNullable<z.ZodString>;
22
+ status: z.ZodEnum<{
23
+ closed: "closed";
24
+ failed: "failed";
25
+ interrupted: "interrupted";
26
+ "needs-input": "needs-input";
27
+ ready: "ready";
28
+ starting: "starting";
29
+ stopping: "stopping";
30
+ uncertain: "uncertain";
31
+ working: "working";
32
+ }>;
33
+ error: z.ZodNullable<z.ZodString>;
34
+ createdAt: z.ZodString;
35
+ updatedAt: z.ZodString;
36
+ deliveryReviewRequired: z.ZodOptional<z.ZodBoolean>;
37
+ initialRequest: z.ZodOptional<z.ZodObject<{
38
+ requestId: z.ZodString;
39
+ prompt: z.ZodString;
40
+ }, z.core.$strict>>;
41
+ context: z.ZodOptional<z.ZodObject<{
42
+ version: z.ZodLiteral<1>;
43
+ text: z.ZodString;
44
+ metadata: z.ZodObject<{
45
+ version: z.ZodLiteral<1>;
46
+ repo: z.ZodString;
47
+ identity: z.ZodString;
48
+ baseRevision: z.ZodString;
49
+ knowledge: z.ZodObject<{
50
+ revision: z.ZodInt;
51
+ selectionSha256: z.ZodString;
52
+ references: z.ZodArray<z.ZodObject<{
53
+ id: z.ZodString;
54
+ title: z.ZodString;
55
+ path: z.ZodNullable<z.ZodString>;
56
+ sourceSha: z.ZodNullable<z.ZodString>;
57
+ sourceRevision: z.ZodNullable<z.ZodString>;
58
+ }, z.core.$strict>>;
59
+ omitted: z.ZodArray<z.ZodObject<{
60
+ title: z.ZodString;
61
+ reason: z.ZodString;
62
+ }, z.core.$strict>>;
63
+ }, z.core.$strict>;
64
+ skills: z.ZodObject<{
65
+ revision: z.ZodInt;
66
+ packages: z.ZodArray<z.ZodObject<{
67
+ name: z.ZodString;
68
+ sha256: z.ZodString;
69
+ skillFile: z.ZodString;
70
+ }, z.core.$strict>>;
71
+ }, z.core.$strict>;
72
+ directory: z.ZodNullable<z.ZodString>;
73
+ files: z.ZodArray<z.ZodObject<{
74
+ path: z.ZodString;
75
+ sha256: z.ZodString;
76
+ }, z.core.$strict>>;
77
+ }, z.core.$strict>;
78
+ sha256: z.ZodString;
79
+ }, z.core.$strict>>;
80
+ }, z.core.$strict>;
81
+ export declare const CODING_SESSION_VERSION = 1;
82
+ export declare const codingSessionRecordSchema: z.ZodObject<{
83
+ version: z.ZodLiteral<1>;
84
+ id: z.ZodString;
85
+ owner: z.ZodString;
86
+ generation: z.ZodInt;
87
+ repo: z.ZodString;
88
+ title: z.ZodString;
89
+ provider: z.ZodLiteral<"codex">;
90
+ model: z.ZodNullable<z.ZodString>;
91
+ branch: z.ZodString;
92
+ base: z.ZodString;
93
+ worktree: z.ZodString;
94
+ nativeThreadId: z.ZodNullable<z.ZodString>;
95
+ turnId: z.ZodNullable<z.ZodString>;
96
+ status: z.ZodEnum<{
97
+ closed: "closed";
98
+ failed: "failed";
99
+ interrupted: "interrupted";
100
+ "needs-input": "needs-input";
101
+ ready: "ready";
102
+ starting: "starting";
103
+ stopping: "stopping";
104
+ uncertain: "uncertain";
105
+ working: "working";
106
+ }>;
107
+ error: z.ZodNullable<z.ZodString>;
108
+ createdAt: z.ZodString;
109
+ updatedAt: z.ZodString;
110
+ deliveryReviewRequired: z.ZodOptional<z.ZodBoolean>;
111
+ initialRequest: z.ZodOptional<z.ZodObject<{
112
+ requestId: z.ZodString;
113
+ prompt: z.ZodString;
114
+ }, z.core.$strict>>;
115
+ context: z.ZodOptional<z.ZodObject<{
116
+ version: z.ZodLiteral<1>;
117
+ text: z.ZodString;
118
+ metadata: z.ZodObject<{
119
+ version: z.ZodLiteral<1>;
120
+ repo: z.ZodString;
121
+ identity: z.ZodString;
122
+ baseRevision: z.ZodString;
123
+ knowledge: z.ZodObject<{
124
+ revision: z.ZodInt;
125
+ selectionSha256: z.ZodString;
126
+ references: z.ZodArray<z.ZodObject<{
127
+ id: z.ZodString;
128
+ title: z.ZodString;
129
+ path: z.ZodNullable<z.ZodString>;
130
+ sourceSha: z.ZodNullable<z.ZodString>;
131
+ sourceRevision: z.ZodNullable<z.ZodString>;
132
+ }, z.core.$strict>>;
133
+ omitted: z.ZodArray<z.ZodObject<{
134
+ title: z.ZodString;
135
+ reason: z.ZodString;
136
+ }, z.core.$strict>>;
137
+ }, z.core.$strict>;
138
+ skills: z.ZodObject<{
139
+ revision: z.ZodInt;
140
+ packages: z.ZodArray<z.ZodObject<{
141
+ name: z.ZodString;
142
+ sha256: z.ZodString;
143
+ skillFile: z.ZodString;
144
+ }, z.core.$strict>>;
145
+ }, z.core.$strict>;
146
+ directory: z.ZodNullable<z.ZodString>;
147
+ files: z.ZodArray<z.ZodObject<{
148
+ path: z.ZodString;
149
+ sha256: z.ZodString;
150
+ }, z.core.$strict>>;
151
+ }, z.core.$strict>;
152
+ sha256: z.ZodString;
153
+ }, z.core.$strict>>;
154
+ }, z.core.$strict>;
155
+ export type CodingStatus = z.infer<typeof codingSessionSchema>["status"];
156
+ export type CodingSession = z.infer<typeof codingSessionSchema>;
157
+ /**
158
+ * Unversioned records were read as a cast, including partial fields, nulls and unknown nested keys. Preserve that
159
+ * boundary exactly; only records explicitly written as version 1 are subject to its strict schema.
160
+ */
161
+ export declare function readCodingSessionRecord(input: unknown): ContractResult<CodingSession>;
162
+ /** Read a saved `coding_session.document`, throwing a path-named refusal; the catalog was written by Toolroll alone. */
163
+ export declare function parseCodingSessionDocument(document: string): CodingSession;
164
+ /** Stamp the current version only if the entire record fits, without dropping any legacy fields. */
165
+ export declare function codingSessionDocument(session: CodingSession): string;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The workspace record: one desktop coding session as the coding catalog saves it (`coding_session.document`). The
3
+ * session itself is what the console, chat and the coding handoff read; the record is that session with its
4
+ * `version`. Row ownership, generation and recovery state are checked in plain code by the workspace after parsing.
5
+ */
6
+ import { z } from "zod";
7
+ import { readVersioned, versioned } from "./contract.js";
8
+ import { CODING_CONTEXT_VERSION, codingContextSchema } from "./coding-context.js";
9
+ export const CODING_STATUSES = ["starting", "ready", "working", "needs-input", "stopping", "interrupted", "failed", "uncertain", "closed"];
10
+ export const codingSessionSchema = z.strictObject({
11
+ id: z.string(),
12
+ owner: z.string(),
13
+ generation: z.int(),
14
+ repo: z.string(),
15
+ title: z.string(),
16
+ provider: z.literal("codex"),
17
+ model: z.string().nullable(),
18
+ branch: z.string(),
19
+ base: z.string(),
20
+ worktree: z.string(),
21
+ nativeThreadId: z.string().nullable(),
22
+ turnId: z.string().nullable(),
23
+ status: z.enum(CODING_STATUSES),
24
+ error: z.string().nullable(),
25
+ createdAt: z.string(),
26
+ updatedAt: z.string(),
27
+ /** Set once startup or the last message could not be confirmed; absent on sessions that never needed it. */
28
+ deliveryReviewRequired: z.boolean().optional(),
29
+ /** The request the session started with, kept so it is never resent. */
30
+ initialRequest: z.strictObject({ requestId: z.string(), prompt: z.string() }).optional(),
31
+ /** Absent when no managed context was captured. */
32
+ context: codingContextSchema.optional(),
33
+ });
34
+ export const CODING_SESSION_VERSION = 1;
35
+ export const codingSessionRecordSchema = versioned(CODING_SESSION_VERSION, codingSessionSchema.shape);
36
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
37
+ /**
38
+ * Unversioned records were read as a cast, including partial fields, nulls and unknown nested keys. Preserve that
39
+ * boundary exactly; only records explicitly written as version 1 are subject to its strict schema.
40
+ */
41
+ export function readCodingSessionRecord(input) {
42
+ if (isRecord(input) && !Object.prototype.hasOwnProperty.call(input, "version"))
43
+ return { ok: true, value: input };
44
+ const read = readVersioned(codingSessionRecordSchema, input, {}, { distinguishNull: true });
45
+ if (!read.ok)
46
+ return read;
47
+ const { version: _version, ...session } = read.value;
48
+ return { ok: true, value: session };
49
+ }
50
+ /** Read a saved `coding_session.document`, throwing a path-named refusal; the catalog was written by Toolroll alone. */
51
+ export function parseCodingSessionDocument(document) {
52
+ let body;
53
+ try {
54
+ body = JSON.parse(document);
55
+ }
56
+ catch {
57
+ throw Error("A saved coding session could not be read: payload: not JSON");
58
+ }
59
+ const read = readCodingSessionRecord(body);
60
+ if (!read.ok)
61
+ throw Error(`A saved coding session could not be read: ${read.issues.map(issue => issue.line).join("; ")}`);
62
+ return read.value;
63
+ }
64
+ /** Stamp the current version only if the entire record fits, without dropping any legacy fields. */
65
+ export function codingSessionDocument(session) {
66
+ const record = { version: CODING_SESSION_VERSION, ...session };
67
+ if (isRecord(record.context) && !Object.prototype.hasOwnProperty.call(record.context, "version")) {
68
+ record.context = { ...record.context, version: CODING_CONTEXT_VERSION };
69
+ }
70
+ return JSON.stringify(codingSessionRecordSchema.safeParse(record).success ? record : session);
71
+ }
@@ -12,9 +12,12 @@ import { type TextLimitKey } 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 declare function limited(field: string, limitKey: TextLimitKey): z.ZodString;
18
+ export declare function limited(field: string, limitKey: TextLimitKey, options?: {
19
+ shorten?: boolean;
20
+ }): z.ZodString;
18
21
  /** A versioned payload envelope: a strict object whose `version` is exactly `version`. */
19
22
  export declare function versioned<V extends number, S extends z.ZodRawShape>(version: V, shape: S): z.ZodObject<{
20
23
  version: z.ZodLiteral<V>;
@@ -36,6 +39,8 @@ export declare function pathOf(path: readonly PropertyKey[]): string;
36
39
  */
37
40
  export type ContractOptions = {
38
41
  aliases?: Readonly<Record<string, readonly string[]>>;
42
+ /** Report explicit null as a wrong value; omitted keeps the historical wording for existing contracts. */
43
+ distinguishNull?: boolean;
39
44
  };
40
45
  type Where = {
41
46
  schema?: z.ZodType;
@@ -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>;