toolroll 0.9.67 → 0.9.68

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 (236) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/acp-client.js +1 -3
  3. package/dist/agent-board.d.ts +0 -1
  4. package/dist/agent-board.js +0 -1
  5. package/dist/agent-fence.js +3 -0
  6. package/dist/browser/workspace.css +1 -1
  7. package/dist/browser/workspace.js +31 -29
  8. package/dist/builder.js +3 -26
  9. package/dist/canonical-json.d.ts +14 -0
  10. package/dist/canonical-json.js +36 -0
  11. package/dist/chat-actions.js +1 -2
  12. package/dist/chat-admission.d.ts +19 -0
  13. package/dist/chat-admission.js +22 -0
  14. package/dist/chat-channel.js +1 -1
  15. package/dist/chat-delivery.js +0 -1
  16. package/dist/chat-follow.d.ts +17 -0
  17. package/dist/chat-follow.js +32 -0
  18. package/dist/chat-messages.js +0 -1
  19. package/dist/chat-settings-page.d.ts +23 -0
  20. package/dist/chat-settings-page.js +41 -0
  21. package/dist/claim.js +3 -6
  22. package/dist/claude-print.d.ts +12 -0
  23. package/dist/claude-print.js +34 -0
  24. package/dist/cli-http.d.ts +0 -7
  25. package/dist/cli-http.js +0 -21
  26. package/dist/cli.d.ts +3 -10
  27. package/dist/cli.js +15 -24
  28. package/dist/coding-provider.js +2 -1
  29. package/dist/coding-workspace.js +1 -1
  30. package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
  31. package/dist/connect-cli.js +331 -0
  32. package/dist/containment.d.ts +0 -2
  33. package/dist/containment.js +2 -6
  34. package/dist/contracts/chat-actions.d.ts +0 -1
  35. package/dist/contracts/chat-content.d.ts +0 -4
  36. package/dist/contracts/chat-content.js +0 -2
  37. package/dist/contracts/chat-tables.d.ts +0 -1
  38. package/dist/contracts/cli.d.ts +0 -62
  39. package/dist/contracts/cli.js +0 -4
  40. package/dist/contracts/coding-workspace.js +1 -1
  41. package/dist/contracts/contract.js +1 -1
  42. package/dist/contracts/flow-card.d.ts +0 -1
  43. package/dist/contracts/flow-propose.d.ts +0 -1
  44. package/dist/contracts/flow-send.js +1 -1
  45. package/dist/contracts/gateway-tools.d.ts +0 -1
  46. package/dist/contracts/integration-metadata.js +1 -1
  47. package/dist/contracts/lead-tools.d.ts +0 -1
  48. package/dist/contracts/memory-pass.d.ts +0 -1
  49. package/dist/contracts/memory-pass.js +1 -1
  50. package/dist/contracts/plan.js +1 -1
  51. package/dist/contracts/project-knowledge.js +1 -1
  52. package/dist/contracts/project-skills.d.ts +0 -1
  53. package/dist/contracts/proof.js +1 -1
  54. package/dist/contracts/recipes.js +1 -1
  55. package/dist/contracts/scope.js +1 -1
  56. package/dist/contracts/scout-report.js +1 -1
  57. package/dist/contracts/teams-callback.d.ts +0 -1
  58. package/dist/converse.d.ts +0 -6
  59. package/dist/converse.js +0 -19
  60. package/dist/daemon.js +3 -2
  61. package/dist/decision.d.ts +1 -3
  62. package/dist/deprecations.d.ts +12 -4
  63. package/dist/deprecations.js +27 -4
  64. package/dist/desktop-update-recovery.js +1 -1
  65. package/dist/desktop-update.js +3 -2
  66. package/dist/discord-chat.js +4 -15
  67. package/dist/discord-settings.d.ts +2 -6
  68. package/dist/discord-settings.js +10 -38
  69. package/dist/discord.js +12 -23
  70. package/dist/evidence.d.ts +0 -1
  71. package/dist/evidence.js +0 -3
  72. package/dist/exec.d.ts +0 -9
  73. package/dist/exec.js +3 -81
  74. package/dist/flow-actions.js +0 -3
  75. package/dist/flow-draft.d.ts +1 -3
  76. package/dist/flow-draft.js +15 -28
  77. package/dist/flow-scripts.d.ts +1 -8
  78. package/dist/flow-scripts.js +0 -13
  79. package/dist/flow-send.js +1 -1
  80. package/dist/flow-share.d.ts +0 -2
  81. package/dist/flow-share.js +1 -6
  82. package/dist/flow-steps.js +1 -1
  83. package/dist/flow-triggers.d.ts +0 -1
  84. package/dist/flow-triggers.js +1 -2
  85. package/dist/flow-unattended.js +1 -12
  86. package/dist/flows.js +1 -1
  87. package/dist/health-cli.d.ts +2 -2
  88. package/dist/health-cli.js +1 -1
  89. package/dist/invoke.d.ts +0 -3
  90. package/dist/invoke.js +5 -3
  91. package/dist/is-record.d.ts +2 -0
  92. package/dist/is-record.js +2 -0
  93. package/dist/lead-doors.d.ts +1 -5
  94. package/dist/lead-doors.js +0 -4
  95. package/dist/lead-tools.js +3 -6
  96. package/dist/live-detail.d.ts +0 -1
  97. package/dist/live-detail.js +0 -6
  98. package/dist/live-terminal.js +2 -1
  99. package/dist/live.d.ts +0 -1
  100. package/dist/live.js +1 -2
  101. package/dist/mcp-core.js +0 -2
  102. package/dist/memory-pass.js +1 -1
  103. package/dist/model-catalog.js +1 -1
  104. package/dist/modes.js +1 -12
  105. package/dist/operate.js +383 -750
  106. package/dist/password-bearer.d.ts +0 -2
  107. package/dist/password-bearer.js +0 -4
  108. package/dist/peek-cli.js +2 -1
  109. package/dist/peek.d.ts +2 -26
  110. package/dist/peek.js +2 -168
  111. package/dist/phase-routing.d.ts +0 -4
  112. package/dist/phase-routing.js +2 -21
  113. package/dist/planner-source.d.ts +2 -1
  114. package/dist/planner-source.js +2 -12
  115. package/dist/planner.js +2 -14
  116. package/dist/process-tree.d.ts +0 -2
  117. package/dist/process-tree.js +0 -10
  118. package/dist/project-delete.d.ts +0 -2
  119. package/dist/project-delete.js +1 -3
  120. package/dist/project-learning.d.ts +0 -14
  121. package/dist/project-skills.d.ts +0 -1
  122. package/dist/project-skills.js +0 -14
  123. package/dist/project-tools.js +2 -10
  124. package/dist/provider.d.ts +0 -17
  125. package/dist/provider.js +0 -26
  126. package/dist/pulls.d.ts +0 -2
  127. package/dist/pulls.js +0 -9
  128. package/dist/release-adapters.d.ts +3 -0
  129. package/dist/release-adapters.js +15 -5
  130. package/dist/remote-exec.d.ts +3 -2
  131. package/dist/remote-exec.js +3 -2
  132. package/dist/repos.d.ts +0 -5
  133. package/dist/repos.js +2 -10
  134. package/dist/repository-context.d.ts +0 -2
  135. package/dist/repository-context.js +0 -4
  136. package/dist/restart-certification.d.ts +0 -2
  137. package/dist/restart-certification.js +1 -5
  138. package/dist/result-follow-ups.d.ts +1 -4
  139. package/dist/result-follow-ups.js +1 -6
  140. package/dist/result-review.d.ts +0 -2
  141. package/dist/result-review.js +0 -2
  142. package/dist/retention.d.ts +0 -1
  143. package/dist/retention.js +0 -1
  144. package/dist/review-context.d.ts +0 -1
  145. package/dist/review-context.js +1 -12
  146. package/dist/reviewer.d.ts +0 -16
  147. package/dist/reviewer.js +0 -18
  148. package/dist/run-closeout.js +2 -1
  149. package/dist/scope.js +1 -13
  150. package/dist/scout-browser.js +2 -1
  151. package/dist/serve.js +2 -2
  152. package/dist/server/console.css +5 -133
  153. package/dist/server/flows.js +2 -2
  154. package/dist/server/http.d.ts +2 -0
  155. package/dist/server/http.js +5 -0
  156. package/dist/server/pages.js +2 -2
  157. package/dist/server/people-tokens.js +3 -4
  158. package/dist/server/remote.js +1 -6
  159. package/dist/server/render-chat.d.ts +9 -2
  160. package/dist/server/render-chat.js +53 -7
  161. package/dist/server/render-results.d.ts +0 -6
  162. package/dist/server/render-results.js +3 -9
  163. package/dist/server/render-tasks.js +4 -5
  164. package/dist/server/route-table.d.ts +0 -4
  165. package/dist/server/route-table.js +0 -6
  166. package/dist/server/settings.js +23 -23
  167. package/dist/server/task-views.js +1 -1
  168. package/dist/server/tasks.js +3 -3
  169. package/dist/setup-guide.d.ts +2 -2
  170. package/dist/skills.d.ts +0 -6
  171. package/dist/skills.js +0 -8
  172. package/dist/slack-chat.d.ts +1 -7
  173. package/dist/slack-chat.js +5 -18
  174. package/dist/slack-settings.d.ts +2 -6
  175. package/dist/slack-settings.js +10 -38
  176. package/dist/slack.js +12 -23
  177. package/dist/sleep.d.ts +2 -0
  178. package/dist/sleep.js +2 -0
  179. package/dist/store.d.ts +0 -73
  180. package/dist/store.js +3 -177
  181. package/dist/structured-output.d.ts +0 -1
  182. package/dist/structured-output.js +0 -1
  183. package/dist/subagent-work.d.ts +0 -2
  184. package/dist/subagent-work.js +0 -2
  185. package/dist/subagents.d.ts +1 -3
  186. package/dist/subagents.js +20 -33
  187. package/dist/subscription-chat.d.ts +1 -3
  188. package/dist/subscription-chat.js +2 -3
  189. package/dist/task-sizing.d.ts +1 -4
  190. package/dist/task-sizing.js +3 -10
  191. package/dist/teams-chat.js +4 -11
  192. package/dist/teams.js +12 -19
  193. package/dist/telegram-lead.js +2 -2
  194. package/dist/telegram.js +1 -1
  195. package/dist/token-notices.d.ts +0 -11
  196. package/dist/token-notices.js +1 -5
  197. package/dist/tool-secret-store.d.ts +0 -3
  198. package/dist/tool-secret-store.js +2 -11
  199. package/dist/toolroll-update.d.ts +0 -2
  200. package/dist/toolroll-update.js +2 -2
  201. package/dist/verification-evidence.d.ts +0 -1
  202. package/dist/verification-evidence.js +0 -1
  203. package/dist/webhooks.d.ts +0 -1
  204. package/dist/webhooks.js +0 -1
  205. package/dist/workspace-ui.d.ts +0 -8
  206. package/dist/workspace-ui.js +1 -6
  207. package/package.json +1 -1
  208. package/dist/contracts/native-census.d.ts +0 -42
  209. package/dist/contracts/native-census.js +0 -36
  210. package/dist/process-recovery-anchor.d.ts +0 -75
  211. package/dist/process-recovery-anchor.js +0 -188
  212. package/dist/process-recovery-coalition.d.ts +0 -25
  213. package/dist/process-recovery-coalition.js +0 -105
  214. package/dist/process-recovery-eligibility.d.ts +0 -179
  215. package/dist/process-recovery-eligibility.js +0 -174
  216. package/dist/process-recovery-native.d.ts +0 -161
  217. package/dist/process-recovery-native.js +0 -595
  218. package/dist/process-recovery-provenance.d.ts +0 -163
  219. package/dist/process-recovery-provenance.js +0 -292
  220. package/dist/process-recovery-services.d.ts +0 -56
  221. package/dist/process-recovery-services.js +0 -191
  222. package/dist/process-recovery-settlement.d.ts +0 -32
  223. package/dist/process-recovery-settlement.js +0 -133
  224. package/dist/process-recovery.d.ts +0 -41
  225. package/dist/process-recovery.js +0 -91
  226. package/dist/session-brief.d.ts +0 -8
  227. package/dist/session-brief.js +0 -24
  228. package/dist/session-cli.d.ts +0 -42
  229. package/dist/session-cli.js +0 -271
  230. package/dist/session-contract.d.ts +0 -183
  231. package/dist/session-contract.js +0 -122
  232. package/dist/session-remote.d.ts +0 -19
  233. package/dist/session-remote.js +0 -86
  234. package/dist/session-service.d.ts +0 -22
  235. package/dist/session-service.js +0 -157
  236. package/dist/team-cli.js +0 -692
@@ -1,17 +1,12 @@
1
- export type TeamCliOptions = {
1
+ export type ConnectOptions = {
2
2
  fetch?: typeof fetch;
3
3
  env?: NodeJS.ProcessEnv;
4
4
  home?: string;
5
5
  profileFile?: string;
6
6
  readStdin?: () => Promise<string>;
7
- readLine?: (prompt: string) => Promise<string | null>;
8
- isTTY?: boolean;
9
7
  stderr?: (line: string) => void;
10
8
  signal?: AbortSignal;
11
- sleep?: (milliseconds: number, signal: AbortSignal) => Promise<void>;
12
9
  };
13
- /** D5: deprecated (deprecations.ts). Each still runs this release, hidden from help and the contract, with one warning on stderr. */
14
- export declare const TEAM_CLI_ACTIONS: readonly ['connect', 'lead list', 'lead create', 'lead update', 'lead member', 'lead transfer', 'conversation list', 'conversation create', 'conversation show', 'conversation member', 'conversation edit', 'conversation withdraw', 'conversation read', 'conversation follow', 'conversation stop'];
15
10
  export declare class UsageError extends Error {
16
11
  }
17
12
  /** Credentials are never sent to a URL with embedded authority, an API path, query, fragment or an insecure remote
@@ -26,13 +21,14 @@ type Profile = {
26
21
  export declare const bearerOf: (profile: Profile) => string;
27
22
  /** The saved central profile remote commands use: the named one (or the active one), and whether it holds an API token.
28
23
  * null when nothing is saved. Reads only the private profile file — never a local login or the database. */
29
- export declare function centralProfile(options: TeamCliOptions, name?: string): {
24
+ export declare function hasCentralProfile(options: ConnectOptions): boolean;
25
+ export declare function centralProfile(options: ConnectOptions, name?: string): {
30
26
  name: string;
31
27
  origin: string;
32
28
  account: string;
33
29
  token: string;
34
30
  apiToken: boolean;
35
31
  } | null;
36
- /** null means preserve the existing local command path. Nothing here opens a database. */
37
- export declare function maybeRunTeamCommand(argv: readonly string[], write: (line: string) => void, options?: TeamCliOptions): Promise<number | null>;
32
+ /** `toolroll connect …`; null for every other command. Nothing here opens a database. */
33
+ export declare function maybeRunConnectCommand(argv: readonly string[], write: (line: string) => void, options?: ConnectOptions): Promise<number | null>;
38
34
  export {};
@@ -0,0 +1,331 @@
1
+ /**
2
+ * `toolroll connect`: saves a central server's origin and your API token in a private profile file. Remote commands
3
+ * (remote-exec.ts) and `toolroll health` sign in with it; nothing here opens the local database except to find the
4
+ * existing local sign-in for --local-login.
5
+ */
6
+ import { constants, closeSync, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { dirname, join } from 'node:path';
9
+ import { randomBytes } from 'node:crypto';
10
+ import { envelopeJson } from './envelope.js';
11
+ import { databasePath } from './store.js';
12
+ import { configBase, namedPath } from './names.js';
13
+ import { parseApiToken } from './api-tokens.js';
14
+ import { deprecationWarning } from './deprecations.js';
15
+ import { PASSWORD_PROFILE_WARNING } from './password-bearer.js';
16
+ const HELP = `toolroll connect <HTTPS-origin> --as <account> --token-stdin
17
+ Pipe your API token (so_…, from Settings → API tokens) or use --token-file <private-file>;
18
+ --local-login uses your existing local sign-in.
19
+ With an API token saved, other commands run on that server as you (--local runs here).
20
+ Optional: --profile <name> (saved as the active profile).`;
21
+ export class UsageError extends Error {
22
+ }
23
+ /** Credentials are never sent to a URL with embedded authority, an API path, query, fragment or an insecure remote
24
+ * host. Fetch cannot follow redirects. */
25
+ export function serviceOrigin(raw) {
26
+ let url;
27
+ try {
28
+ url = new URL(raw);
29
+ }
30
+ catch {
31
+ throw new UsageError('The service origin must be HTTPS or loopback HTTP.');
32
+ }
33
+ if (url.username || url.password || url.search || url.hash || url.pathname !== '/')
34
+ throw new UsageError('The service origin must contain only the origin, with no credentials, path, query or fragment.');
35
+ if (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname)))
36
+ throw new UsageError('The service origin requires HTTPS except for loopback HTTP.');
37
+ return url.origin;
38
+ }
39
+ const value = (flags, name) => typeof flags[name] === 'string' ? flags[name] : '';
40
+ const text = (raw) => raw.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '');
41
+ const record = (input) => !!input && typeof input === 'object' && !Array.isArray(input);
42
+ const strings = (input) => Array.isArray(input) && input.every(one => typeof one === 'string');
43
+ const accountValid = (input) => !!input && input.length <= 200 && !/[\s:\x00-\x1f\x7f]/.test(input);
44
+ const tokenValid = (input) => !!input && input.length <= 8192 && !/[\s\x00-\x1f\x7f]/.test(input);
45
+ const profilePath = (options) => options.profileFile ?? namedPath(configBase(options.env ?? process.env, options.home ?? homedir()), ['remote', 'profiles.json']);
46
+ /** Open the file itself without following a symlink; nonblocking also rejects FIFOs safely. */
47
+ function privateFile(path, limit) {
48
+ let fd;
49
+ try {
50
+ fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
51
+ const stat = fstatSync(fd);
52
+ if (!stat.isFile() || (stat.mode & 0o777) !== 0o600 || (process.getuid && stat.uid !== process.getuid()) || stat.size > limit)
53
+ throw new Error();
54
+ const bytes = readFileSync(fd);
55
+ if (bytes.length > limit)
56
+ throw new Error();
57
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
58
+ }
59
+ catch {
60
+ throw new UsageError('The credential file must be an owned, private (chmod 600), regular UTF-8 file.');
61
+ }
62
+ finally {
63
+ if (fd !== undefined)
64
+ closeSync(fd);
65
+ }
66
+ }
67
+ function profiles(options) {
68
+ const path = profilePath(options);
69
+ // lstat distinguishes a dangling symlink from a missing profile.
70
+ try {
71
+ lstatSync(path);
72
+ }
73
+ catch (error) {
74
+ if (error.code === 'ENOENT')
75
+ return null;
76
+ throw new UsageError('The remote profile cannot be read.');
77
+ }
78
+ const raw = privateFile(path, 128 * 1024);
79
+ try {
80
+ const parsed = JSON.parse(raw);
81
+ if (!record(parsed) || parsed.version !== 1 || typeof parsed.active !== 'string' || !record(parsed.profiles))
82
+ throw new Error();
83
+ for (const [name, one] of Object.entries(parsed.profiles)) {
84
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(name) || !record(one) || typeof one.origin !== 'string' || serviceOrigin(one.origin) !== one.origin || typeof one.account !== 'string' || !accountValid(one.account) || typeof one.token !== 'string' || !tokenValid(one.token))
85
+ throw new Error();
86
+ }
87
+ if (!Object.hasOwn(parsed.profiles, parsed.active))
88
+ throw new Error();
89
+ return parsed;
90
+ }
91
+ catch {
92
+ throw new UsageError('The remote profile is invalid. Connect again using a private credential source.');
93
+ }
94
+ }
95
+ function saveProfile(options, name, profile) {
96
+ const path = profilePath(options), directory = dirname(path);
97
+ const saved = profiles(options) ?? { version: 1, active: name, profiles: {} };
98
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
99
+ const parent = lstatSync(directory);
100
+ if (!parent.isDirectory() || (parent.mode & 0o777) !== 0o700 || (process.getuid && parent.uid !== process.getuid()))
101
+ throw new UsageError('The remote profile directory must be owned and private (chmod 700).');
102
+ saved.active = name;
103
+ Object.defineProperty(saved.profiles, name, { value: profile, enumerable: true, configurable: true, writable: true });
104
+ const temporary = join(directory, `.profile-${randomBytes(16).toString('hex')}`);
105
+ let fd;
106
+ try {
107
+ fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
108
+ writeFileSync(fd, `${JSON.stringify(saved)}\n`);
109
+ fsyncSync(fd);
110
+ closeSync(fd);
111
+ fd = undefined;
112
+ renameSync(temporary, path);
113
+ fd = openSync(directory, constants.O_RDONLY);
114
+ fsyncSync(fd);
115
+ }
116
+ finally {
117
+ if (fd !== undefined)
118
+ closeSync(fd);
119
+ if (existsSync(temporary))
120
+ unlinkSync(temporary);
121
+ }
122
+ }
123
+ /** An API token (so_…) is sent bare; an older saved sign-in keeps its account:secret form. */
124
+ export const bearerOf = (profile) => parseApiToken(profile.token) !== null ? `Bearer ${profile.token}` : `Bearer ${profile.account}:${profile.token}`;
125
+ /** The saved central profile remote commands use: the named one (or the active one), and whether it holds an API token.
126
+ * null when nothing is saved. Reads only the private profile file — never a local login or the database. */
127
+ export function hasCentralProfile(options) {
128
+ // An unreadable or invalid profile file still names a central service: the caller refuses rather than run locally.
129
+ try {
130
+ return profiles(options) !== null;
131
+ }
132
+ catch {
133
+ return true;
134
+ }
135
+ }
136
+ export function centralProfile(options, name) {
137
+ const saved = profiles(options);
138
+ if (saved === null)
139
+ return null;
140
+ const chosen = name ?? saved.active;
141
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(chosen) || !Object.hasOwn(saved.profiles, chosen))
142
+ throw new UsageError(`No saved connection named ${chosen}. Use connect with your API token first.`);
143
+ const profile = saved.profiles[chosen];
144
+ return { name: chosen, ...profile, apiToken: parseApiToken(profile.token) !== null };
145
+ }
146
+ async function stdinSecret() {
147
+ if (process.stdin.isTTY)
148
+ throw new UsageError('Pipe your sign-in secret into --token-stdin, or use --token-file.');
149
+ const chunks = [];
150
+ let size = 0;
151
+ for await (const part of process.stdin) {
152
+ const chunk = Buffer.from(part);
153
+ size += chunk.length;
154
+ if (size > 8192)
155
+ throw new UsageError('The sign-in secret is too large.');
156
+ chunks.push(chunk);
157
+ }
158
+ try {
159
+ return new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks));
160
+ }
161
+ catch {
162
+ throw new UsageError('The sign-in secret must be UTF-8.');
163
+ }
164
+ }
165
+ async function responseBody(response) {
166
+ if (!response.body)
167
+ throw new Error();
168
+ const reader = response.body.getReader(), chunks = [];
169
+ let size = 0;
170
+ try {
171
+ while (true) {
172
+ const chunk = await reader.read();
173
+ if (chunk.done)
174
+ break;
175
+ size += chunk.value.byteLength;
176
+ if (size > 2 * 1024 * 1024) {
177
+ await reader.cancel();
178
+ throw new Error();
179
+ }
180
+ chunks.push(chunk.value);
181
+ }
182
+ }
183
+ finally {
184
+ reader.releaseLock();
185
+ }
186
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)));
187
+ }
188
+ function parse(argv) {
189
+ const allowed = new Set(['profile', 'json', 'help', 'as', 'token-stdin', 'token-file', 'local-login', 'login-file']);
190
+ const switches = new Set(['json', 'help', 'token-stdin', 'local-login']);
191
+ const flags = {}, positions = [];
192
+ for (let index = 1; index < argv.length; index++) {
193
+ const arg = argv[index];
194
+ if (!arg.startsWith('--')) {
195
+ positions.push(arg);
196
+ continue;
197
+ }
198
+ const name = arg.slice(2);
199
+ if (!allowed.has(name))
200
+ throw new UsageError('Unsupported option. Use --help for this command; secrets must come from stdin or a private file.');
201
+ if (Object.hasOwn(flags, name))
202
+ throw new UsageError(`Use --${name} only once.`);
203
+ if (switches.has(name))
204
+ flags[name] = true;
205
+ else {
206
+ const next = argv[++index];
207
+ if (!next || next.startsWith('--'))
208
+ throw new UsageError(`--${name} needs a value.`);
209
+ flags[name] = next;
210
+ }
211
+ }
212
+ if (positions.length !== 1 && !flags.help)
213
+ throw new UsageError('Name one service origin.');
214
+ return { flags, positions };
215
+ }
216
+ function isMessage(one) {
217
+ return record(one) && Number.isSafeInteger(one.id) && typeof one.author === 'string' && ['operator', 'assistant'].includes(String(one.role)) && typeof one.text === 'string' && ['queued', 'running', 'answered', 'failed', 'cancelled', 'uncertain'].includes(String(one.status)) && Number.isSafeInteger(one.revision) && typeof one.createdAt === 'string' && (one.requestId === null || typeof one.requestId === 'string') && (one.turnId === null || Number.isSafeInteger(one.turnId)) && (one.error === null || typeof one.error === 'string');
218
+ }
219
+ /** The listing a central service answers `list` with; a reply without a well-formed one is no proof of a service. */
220
+ function isSnapshot(one) {
221
+ if (!record(one) || !Array.isArray(one.leads) || !one.leads.every(lead => record(lead) && typeof lead.id === 'string' && typeof lead.name === 'string') || !Array.isArray(one.conversations) || !one.conversations.every(conversation => record(conversation) && typeof conversation.id === 'string' && typeof conversation.leadId === 'string' && typeof conversation.title === 'string') || !(one.selected === null || record(one.selected) && typeof one.selected.id === 'string' && typeof one.selected.leadId === 'string' && typeof one.selected.title === 'string') || !Array.isArray(one.participants) || !one.participants.every(participant => record(participant) && typeof participant.account === 'string' && ['viewer', 'contributor', 'manager'].includes(String(participant.role)) && typeof participant.active === 'boolean') || !Array.isArray(one.messages) || !one.messages.every(isMessage) || typeof one.canManage !== 'boolean' || typeof one.canSend !== 'boolean' || !Number.isSafeInteger(one.cursor) || typeof one.truncated !== 'boolean' || !strings(one.projects) || !strings(one.accounts))
222
+ return false;
223
+ if (one.chatAuthorization !== undefined) {
224
+ const grant = one.chatAuthorization;
225
+ if (!record(grant) || typeof grant.enabled !== 'boolean' || !(grant.provider === null || typeof grant.provider === 'string') || !(grant.model === null || typeof grant.model === 'string') || !Number.isSafeInteger(grant.dailyTurns) || ![grant.weeklyCeilingUsd, grant.conversationCeilingUsd].every(amount => amount === null || typeof amount === 'number' && Number.isFinite(amount) && amount >= 0) || typeof grant.termsDigest !== 'string')
226
+ return false;
227
+ }
228
+ if (one.tasks !== undefined) {
229
+ const tasks = one.tasks;
230
+ if (!record(tasks) || !record(tasks.totals) || !['all', 'needs-you', 'running', 'completed'].every(key => Number.isSafeInteger(tasks.totals[key])) || !Array.isArray(tasks.items) || !tasks.items.every(task => record(task) && typeof task.rootId === 'string' && typeof task.activeTaskId === 'string' && typeof task.title === 'string' && record(task.status) && typeof task.status.label === 'string') || !(tasks.nextCursor === null || typeof tasks.nextCursor === 'string'))
231
+ return false;
232
+ }
233
+ if (one.proposals !== undefined && (!Array.isArray(one.proposals) || !one.proposals.every(proposal => record(proposal) && Number.isSafeInteger(proposal.id) && typeof proposal.title === 'string' && typeof proposal.state === 'string' && typeof proposal.href === 'string')))
234
+ return false;
235
+ return true;
236
+ }
237
+ /** The server must accept the credential before it is saved: one read of POST /api/team, never retried. A success
238
+ * proves itself with a well-formed listing, as the central `list` it is. */
239
+ async function verify(profile, options) {
240
+ const unconfirmed = { version: 1, ok: false, code: 'service-unavailable', message: 'The service response could not be read.' };
241
+ try {
242
+ const response = await (options.fetch ?? fetch)(`${profile.origin}/api/team`, { method: 'POST', redirect: 'manual', credentials: 'omit', signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(30_000)]) : AbortSignal.timeout(30_000), headers: { authorization: bearerOf(profile), 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ operation: 'list', args: {} }) });
243
+ if (response.status >= 300 && response.status < 400) {
244
+ await response.body?.cancel();
245
+ return unconfirmed;
246
+ }
247
+ if (response.status === 429) {
248
+ await response.body?.cancel();
249
+ return { ...unconfirmed, code: 'rate-limited', message: 'Request limit reached. Try again shortly.' };
250
+ }
251
+ const payload = await responseBody(response);
252
+ if (!record(payload) || payload.version !== 1 || typeof payload.ok !== 'boolean' || typeof payload.code !== 'string' || typeof payload.message !== 'string' || payload.snapshot !== undefined && !isSnapshot(payload.snapshot) || payload.ok && !response.ok)
253
+ return unconfirmed;
254
+ if (payload.ok && payload.snapshot === undefined)
255
+ return unconfirmed;
256
+ return { version: 1, ok: payload.ok, code: payload.code, message: payload.message };
257
+ }
258
+ catch {
259
+ return unconfirmed;
260
+ }
261
+ }
262
+ /** `toolroll connect …`; null for every other command. Nothing here opens a database. */
263
+ export async function maybeRunConnectCommand(argv, write, options = {}) {
264
+ if (argv[0] !== 'connect')
265
+ return null;
266
+ const json = argv.includes('--json');
267
+ let secret = '';
268
+ const stderr = (line) => (options.stderr ?? (line => process.stderr.write(`${line}\n`)))(text(secret ? line.replaceAll(secret, '[redacted]') : line));
269
+ const emit = (reply) => {
270
+ // A server's words can echo the credential it was sent: every string it says is redacted before it is shown.
271
+ const safe = JSON.parse(JSON.stringify(reply, (_key, one) => typeof one === 'string' && secret ? one.replaceAll(secret, '[redacted]') : one));
272
+ write(json ? envelopeJson({ ...safe, command: 'team connect' }) : text(safe.message));
273
+ };
274
+ try {
275
+ // D5: `connect` belongs to the deprecated central team service; it still saves the profile remote commands use.
276
+ stderr(deprecationWarning('connect', 'team'));
277
+ if (argv.includes('--help')) {
278
+ write(HELP);
279
+ return 0;
280
+ }
281
+ const { flags, positions } = parse(argv);
282
+ if (flags.help) {
283
+ write(HELP);
284
+ return 0;
285
+ }
286
+ const profileName = value(flags, 'profile') || 'default';
287
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(profileName))
288
+ throw new UsageError('Profile names use 1–64 letters, digits, underscores or hyphens.');
289
+ let origin;
290
+ try {
291
+ origin = serviceOrigin(positions[0]);
292
+ }
293
+ catch {
294
+ throw new UsageError('Use an HTTPS service origin (loopback HTTP is allowed), without credentials, paths, query or fragment.');
295
+ }
296
+ if (['token-stdin', 'token-file', 'local-login'].filter(name => flags[name]).length !== 1)
297
+ throw new UsageError('Choose one credential source: --token-stdin, --token-file or --local-login.');
298
+ if (flags['login-file'] && !flags['local-login'])
299
+ throw new UsageError('--login-file requires --local-login.');
300
+ let account = value(flags, 'as');
301
+ if (flags['local-login']) {
302
+ const login = privateFile(value(flags, 'login-file') || join(dirname(databasePath(options.env ?? process.env, options.home ?? homedir())), 'up-login.txt'), 8192).trim().split(/\s+/);
303
+ if (login.length !== 2 || account && account !== login[0])
304
+ throw new UsageError('The local sign-in does not match the requested account.');
305
+ account = login[0];
306
+ secret = login[1];
307
+ }
308
+ else
309
+ secret = (flags['token-file'] ? privateFile(value(flags, 'token-file'), 8192) : await (options.readStdin ?? stdinSecret)()).trim();
310
+ if (!accountValid(account) || !tokenValid(secret))
311
+ throw new UsageError('Provide an account name and a valid sign-in secret from your selected source.');
312
+ if (parseApiToken(secret) === null)
313
+ stderr(PASSWORD_PROFILE_WARNING);
314
+ const profile = { origin, account, token: secret }, checked = await verify(profile, options);
315
+ if (!checked.ok) {
316
+ emit(checked);
317
+ return 1;
318
+ }
319
+ saveProfile(options, profileName, profile);
320
+ emit({ version: 1, ok: true, code: 'connected', message: `Connected to ${origin} as ${account}.`, result: { profile: profileName, origin, account } });
321
+ return 0;
322
+ }
323
+ catch (error) {
324
+ const reply = { version: 1, ok: false, code: 'usage', message: error instanceof UsageError ? error.message : 'The connection could not be prepared. Check the private profile and credential files.' };
325
+ if (json)
326
+ emit(reply);
327
+ else
328
+ stderr(text(reply.message));
329
+ return 2;
330
+ }
331
+ }
@@ -198,5 +198,3 @@ export declare function cgroup2Container(capability: ContainmentCapability, labe
198
198
  * the CI windows job exercises it.
199
199
  */
200
200
  export declare function jobObjectContainer(capability: ContainmentCapability, label: string): Container;
201
- /** Where the Windows helper lives beside this module (dist or src). */
202
- export declare function containmentModuleDir(): string;
@@ -36,9 +36,10 @@ import { randomBytes } from "node:crypto";
36
36
  import { existsSync, mkdirSync, readFileSync, rmdirSync, writeFileSync } from "node:fs";
37
37
  import { captureCgroupIdentity, cgroupEmptiness, containerEmptiness } from "./container-state.js";
38
38
  import { createServer } from "node:net";
39
- import { dirname, join } from "node:path";
39
+ import { join } from "node:path";
40
40
  import { fileURLToPath } from "node:url";
41
41
  import { envValue } from "./names.js";
42
+ import { sleep } from "./sleep.js";
42
43
  export const CONTAINMENT_POLICIES = ["observed", "preferred", "required"];
43
44
  /** The environment name the controller reads when no flag names a policy. */
44
45
  export const CONTAINMENT_ENV = "TOOLROLL_CONTAINMENT";
@@ -278,7 +279,6 @@ export function createContainer(effective, label) {
278
279
  return { refused: `native containment could not be established for this spawn (${error instanceof Error ? error.message : String(error)}) — nothing ran` };
279
280
  }
280
281
  }
281
- const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
282
282
  /**
283
283
  * The cgroup2 prelude: join, confirm on fd 3, close it, exec the target
284
284
  * with argv exactly as given (positional parameters, never re-parsed).
@@ -562,7 +562,3 @@ export function jobObjectContainer(capability, label) {
562
562
  },
563
563
  };
564
564
  }
565
- /** Where the Windows helper lives beside this module (dist or src). */
566
- export function containmentModuleDir() {
567
- return dirname(fileURLToPath(import.meta.url));
568
- }
@@ -267,7 +267,6 @@ export declare const chatActionRequestSchemas: {
267
267
  };
268
268
  export type ChatActionOperation = keyof typeof chatActionRequestSchemas;
269
269
  export declare const CHAT_ACTION_OPERATIONS: [ChatActionOperation, ...ChatActionOperation[]];
270
- export type ChatActionRequest<O extends ChatActionOperation = ChatActionOperation> = z.infer<(typeof chatActionRequestSchemas)[O]>;
271
270
  /** The fields each action takes, from its schema. */
272
271
  export declare const CHAT_ACTION_FIELDS: Record<ChatActionOperation, readonly string[]>;
273
272
  /** One action's data, refused by path (`card: required`, `payload: unknown key 'cards' (did you mean card?)`). */
@@ -335,10 +335,6 @@ export declare const chatActionBodySchema: z.ZodUnion<readonly [z.ZodObject<{
335
335
  version: z.ZodLiteral<1>;
336
336
  problem: z.ZodString;
337
337
  }, z.core.$strict>]>;
338
- /** A notice carries its parts, not a body. */
339
- export declare const chatNoticeBodySchema: z.ZodObject<{
340
- version: z.ZodLiteral<1>;
341
- }, z.core.$strict>;
342
338
  export type ChatPairBody = z.infer<typeof chatPairBodySchema>;
343
339
  export type ChatMessageBody = z.infer<typeof chatMessageBodySchema>;
344
340
  export type ChatActionBody = z.infer<typeof chatActionBodySchema>;
@@ -125,8 +125,6 @@ export const chatMessageBodySchema = versioned(CHAT_EVENT_VERSION, {
125
125
  export const chatTokenBodySchema = versioned(CHAT_EVENT_VERSION, { token: z.string().regex(CHAT_BUTTON_TOKEN, { error: "must be a Toolroll button token" }) });
126
126
  export const chatProblemBodySchema = versioned(CHAT_EVENT_VERSION, { problem: z.string().min(1) });
127
127
  export const chatActionBodySchema = z.union([chatTokenBodySchema, chatProblemBodySchema]);
128
- /** A notice carries its parts, not a body. */
129
- export const chatNoticeBodySchema = versioned(CHAT_EVENT_VERSION, {});
130
128
  export function savedChatEventBody(body) {
131
129
  return JSON.stringify({ version: CHAT_EVENT_VERSION, ...body });
132
130
  }
@@ -10,4 +10,3 @@ export declare const isChatProvider: (value: unknown) => value is ChatProvider;
10
10
  export declare const CHAT_SCHEMA: string;
11
11
  /** Every shared chat table, parents before children. */
12
12
  export declare const CHAT_TABLES: readonly ["chat_binding", "chat_pair", "chat_event", "chat_part", "chat_action", "chat_progress", "chat_runtime", "chat_room", "chat_meta", "chat_flow_action", "chat_flow_prompt", "chat_flow_choice", "chat_flow_note", "chat_question_action", "chat_question_prompt", "chat_ask_action", "chat_message_ref", "chat_note_draft", "chat_digest"];
13
- export type ChatTable = (typeof CHAT_TABLES)[number];
@@ -54,37 +54,6 @@ export declare const commandRowSchema: z.ZodReadonly<z.ZodObject<{
54
54
  meaning: z.ZodString;
55
55
  }, z.core.$loose>>>>>;
56
56
  notableReasons: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
57
- inputSchema: z.ZodOptional<z.ZodCustom<{
58
- type: 'object';
59
- additionalProperties: false;
60
- required: readonly string[];
61
- properties: Record<string, {
62
- type: 'string' | 'integer' | readonly ['string', 'null'];
63
- minLength?: number;
64
- maxLength?: number;
65
- pattern?: string;
66
- minimum?: number;
67
- maximum?: number;
68
- maxBytes?: number;
69
- nonblank?: boolean;
70
- const?: number;
71
- }>;
72
- }, {
73
- type: 'object';
74
- additionalProperties: false;
75
- required: readonly string[];
76
- properties: Record<string, {
77
- type: 'string' | 'integer' | readonly ['string', 'null'];
78
- minLength?: number;
79
- maxLength?: number;
80
- pattern?: string;
81
- minimum?: number;
82
- maximum?: number;
83
- maxBytes?: number;
84
- nonblank?: boolean;
85
- const?: number;
86
- }>;
87
- }>>;
88
57
  remote: z.ZodEnum<{
89
58
  no: "no";
90
59
  "step-up": "step-up";
@@ -134,37 +103,6 @@ export declare const COMMAND_OUTPUTS: {
134
103
  meaning: z.ZodString;
135
104
  }, z.core.$loose>>>>>;
136
105
  notableReasons: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
137
- inputSchema: z.ZodOptional<z.ZodCustom<{
138
- type: 'object';
139
- additionalProperties: false;
140
- required: readonly string[];
141
- properties: Record<string, {
142
- type: 'string' | 'integer' | readonly ['string', 'null'];
143
- minLength?: number;
144
- maxLength?: number;
145
- pattern?: string;
146
- minimum?: number;
147
- maximum?: number;
148
- maxBytes?: number;
149
- nonblank?: boolean;
150
- const?: number;
151
- }>;
152
- }, {
153
- type: 'object';
154
- additionalProperties: false;
155
- required: readonly string[];
156
- properties: Record<string, {
157
- type: 'string' | 'integer' | readonly ['string', 'null'];
158
- minLength?: number;
159
- maxLength?: number;
160
- pattern?: string;
161
- minimum?: number;
162
- maximum?: number;
163
- maxBytes?: number;
164
- nonblank?: boolean;
165
- const?: number;
166
- }>;
167
- }>>;
168
106
  remote: z.ZodEnum<{
169
107
  no: "no";
170
108
  "step-up": "step-up";
@@ -38,8 +38,6 @@ function answers(command, shape) {
38
38
  export const commandFlagSchema = z.looseObject({ name: str, takesValue: z.boolean(), meaning: str }).readonly();
39
39
  /** The remote policy every command row states; see `remote` below. */
40
40
  export const REMOTE_POLICIES = ["yes", "no", "step-up"];
41
- /** A session operation's executable input schema, as session-contract.ts states it. */
42
- const sessionInputSchema = z.custom(value => typeof value === "object" && value !== null && !Array.isArray(value));
43
41
  export const commandRowSchema = z.looseObject({
44
42
  /** What you type after `toolroll` (subcommands included). */
45
43
  invocation: str,
@@ -62,8 +60,6 @@ export const commandRowSchema = z.looseObject({
62
60
  positionals: z.array(z.looseObject({ name: str, required: z.boolean(), meaning: str }).readonly()).readonly().optional(),
63
61
  flags: z.array(commandFlagSchema).readonly().optional(),
64
62
  notableReasons: z.array(str).readonly().optional(),
65
- /** Session schemas are executable input contracts, not a grant of authority. */
66
- inputSchema: sessionInputSchema.optional(),
67
63
  /** Whether a person signed in to a central server with their own API token may run it there (`runOperateAs`):
68
64
  * yes — runs as that person, within their token's scope and project access.
69
65
  * no — this machine's own infrastructure, credentials or files; never run for a remote caller.
@@ -6,6 +6,7 @@
6
6
  import { z } from "zod";
7
7
  import { readVersioned, versioned } from "./contract.js";
8
8
  import { CODING_CONTEXT_VERSION, codingContextSchema } from "./coding-context.js";
9
+ import { isRecord } from "../is-record.js";
9
10
  export const CODING_STATUSES = ["starting", "ready", "working", "needs-input", "stopping", "interrupted", "failed", "uncertain", "closed"];
10
11
  export const codingSessionSchema = z.strictObject({
11
12
  id: z.string(),
@@ -33,7 +34,6 @@ export const codingSessionSchema = z.strictObject({
33
34
  });
34
35
  export const CODING_SESSION_VERSION = 1;
35
36
  export const codingSessionRecordSchema = versioned(CODING_SESSION_VERSION, codingSessionSchema.shape);
36
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
37
37
  /**
38
38
  * Unversioned records were read as a cast, including partial fields, nulls and unknown nested keys. Preserve that
39
39
  * boundary exactly; only records explicitly written as version 1 are subject to its strict schema.
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { z } from "zod";
10
10
  import { limitRule, TEXT_LIMITS } from "../text-limits.js";
11
+ import { isRecord } from "../is-record.js";
11
12
  /**
12
13
  * A string bounded by its TEXT_LIMITS entry, never a literal. The limit rule rides along as the field's description,
13
14
  * so a model reads the bound before it writes. Zod counts UTF-16 code units; for a limit named in bytes this is the
@@ -42,7 +43,6 @@ function got(input) {
42
43
  return "an array";
43
44
  return typeof input === "object" ? "an object" : `a ${typeof input}`;
44
45
  }
45
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
46
46
  /** The schema at `path`, including whether that value may be omitted, following arrays, wrappers and unions. */
47
47
  function nodeAt(schema, input, path) {
48
48
  let node = schema, value = input;
@@ -9,7 +9,6 @@
9
9
  */
10
10
  import { z } from "zod";
11
11
  export declare const FLOW_CARD_STATES: readonly ["active", "done", "cancelled"];
12
- export type FlowCardState = (typeof FLOW_CARD_STATES)[number];
13
12
  export declare const flowCardSchema: z.ZodObject<{
14
13
  id: z.ZodInt;
15
14
  flow: z.ZodInt;
@@ -320,6 +320,5 @@ export declare const proposeFlowInputSchema: z.ZodObject<{
320
320
  timeZone: z.ZodOptional<z.ZodString>;
321
321
  }, z.core.$strict>], "kind">>;
322
322
  }, z.core.$strict>;
323
- export type ProposeFlowInput = z.infer<typeof proposeFlowInputSchema>;
324
323
  /** What the lead is told propose_flow takes: exactly the schema its call is read with. */
325
324
  export declare const PROPOSE_FLOW_MODEL_SCHEMA: Readonly<Record<string, unknown>>;
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { z } from "zod";
14
14
  import { parseContract, readVersioned, versioned } from "./contract.js";
15
+ import { isRecord } from "../is-record.js";
15
16
  /** A link the message carries: a page of Toolroll's (a button) or the one outside page, a pull request. */
16
17
  export const flowSendLinkSchema = z.union([
17
18
  z.strictObject({ label: z.string(), path: z.string().regex(/^\//, { error: "must be a path in Toolroll, starting with /" }) }),
@@ -51,7 +52,6 @@ export const flowChoosePayloadSchema = versioned(FLOW_SEND_VERSION, {
51
52
  /** Whether a reply is taken instead (the zone has somewhere replies go). */
52
53
  reply: z.boolean(),
53
54
  });
54
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
55
55
  /** A kept item as 0.9.36 read it: its five words (a web link), and a screenshot id only when it is a whole number. */
56
56
  const savedItemSchema = z.object({ ...flowSendItemSchema.shape, shot: z.unknown() });
57
57
  export function readFlowItems(raw) {
@@ -371,7 +371,6 @@ export declare const GATEWAY_TOOL_OUTPUTS: {
371
371
  admission: z.ZodString;
372
372
  }, z.core.$loose>;
373
373
  };
374
- export type GatewayToolOutput<N extends GatewayToolName> = z.infer<(typeof GATEWAY_TOOL_OUTPUTS)[N]>;
375
374
  export declare const PERSON_TOOL_INPUTS: {
376
375
  readonly status: z.ZodObject<{}, z.core.$strict>;
377
376
  readonly list_tasks: z.ZodObject<{
@@ -14,9 +14,9 @@
14
14
  */
15
15
  import { z } from "zod";
16
16
  import { parseContract } from "./contract.js";
17
+ import { isRecord } from "../is-record.js";
17
18
  /** RFC 6749 scope tokens. */
18
19
  export const SCOPE_TOKEN = /^[\x21\x23-\x5b\x5d-\x7e]{1,128}$/;
19
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20
20
  /** A field's schema without its optional wrapper. */
21
21
  const inner = (field) => (field instanceof z.ZodOptional ? field.unwrap() : field);
22
22
  /**
@@ -2003,7 +2003,6 @@ export declare const LEAD_TOOL_OUTPUTS: {
2003
2003
  awaiting: z.ZodString;
2004
2004
  }, z.core.$loose>;
2005
2005
  };
2006
- export type LeadToolOutput<N extends LeadToolName> = z.infer<(typeof LEAD_TOOL_OUTPUTS)[N]>;
2007
2006
  /**
2008
2007
  * Report a result that disagrees with its output schema without hiding the actual result, including under Vitest.
2009
2008
  * Contract tests assert output shapes directly; executing a tool always writes the diagnostic and returns its result.
@@ -131,7 +131,6 @@ export declare const memoryEvidenceSchema: z.ZodArray<z.ZodObject<{
131
131
  session: z.ZodString;
132
132
  quote: z.ZodString;
133
133
  }, z.core.$strict>>;
134
- export type MemoryEvidence = z.infer<typeof memoryEvidenceSchema>;
135
134
  /** A proposal as its row reads; its evidence is a JSON list (an older runtime reads it as one, so it has no envelope). */
136
135
  export declare const memoryProposalSchema: z.ZodObject<{
137
136
  id: z.ZodInt;