toolroll 0.9.65 → 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 (264) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +20 -2
  3. package/dist/acp-client.js +1 -3
  4. package/dist/agent-board.d.ts +0 -1
  5. package/dist/agent-board.js +0 -1
  6. package/dist/agent-fence.d.ts +69 -2
  7. package/dist/agent-fence.js +294 -11
  8. package/dist/agentconfig.js +4 -1
  9. package/dist/batch-checks.js +3 -2
  10. package/dist/browser/THIRD_PARTY_NOTICES.txt +46 -0
  11. package/dist/browser/terminal.js +134 -23
  12. package/dist/browser/workspace.css +2 -2
  13. package/dist/browser/workspace.js +31 -29
  14. package/dist/builder.d.ts +4 -0
  15. package/dist/builder.js +56 -37
  16. package/dist/canonical-json.d.ts +14 -0
  17. package/dist/canonical-json.js +36 -0
  18. package/dist/chat-actions.js +1 -2
  19. package/dist/chat-admission.d.ts +19 -0
  20. package/dist/chat-admission.js +22 -0
  21. package/dist/chat-channel.js +1 -1
  22. package/dist/chat-delivery.js +0 -1
  23. package/dist/chat-follow.d.ts +17 -0
  24. package/dist/chat-follow.js +32 -0
  25. package/dist/chat-messages.js +0 -1
  26. package/dist/chat-settings-page.d.ts +23 -0
  27. package/dist/chat-settings-page.js +41 -0
  28. package/dist/claim.js +3 -6
  29. package/dist/claude-print.d.ts +12 -0
  30. package/dist/claude-print.js +34 -0
  31. package/dist/cli-http.d.ts +0 -7
  32. package/dist/cli-http.js +0 -21
  33. package/dist/cli.d.ts +3 -10
  34. package/dist/cli.js +15 -24
  35. package/dist/coding-provider.js +2 -1
  36. package/dist/coding-workspace.js +1 -1
  37. package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
  38. package/dist/connect-cli.js +331 -0
  39. package/dist/containment.d.ts +0 -2
  40. package/dist/containment.js +2 -6
  41. package/dist/contracts/chat-actions.d.ts +0 -1
  42. package/dist/contracts/chat-content.d.ts +0 -4
  43. package/dist/contracts/chat-content.js +0 -2
  44. package/dist/contracts/chat-tables.d.ts +0 -1
  45. package/dist/contracts/cli.d.ts +0 -62
  46. package/dist/contracts/cli.js +0 -4
  47. package/dist/contracts/coding-workspace.js +1 -1
  48. package/dist/contracts/console-api.d.ts +1 -0
  49. package/dist/contracts/console-api.js +1 -0
  50. package/dist/contracts/contract.js +1 -1
  51. package/dist/contracts/flow-card.d.ts +0 -1
  52. package/dist/contracts/flow-propose.d.ts +0 -1
  53. package/dist/contracts/flow-send.js +1 -1
  54. package/dist/contracts/gateway-tools.d.ts +0 -1
  55. package/dist/contracts/integration-metadata.js +1 -1
  56. package/dist/contracts/lead-tools.d.ts +0 -1
  57. package/dist/contracts/memory-pass.d.ts +0 -1
  58. package/dist/contracts/memory-pass.js +1 -1
  59. package/dist/contracts/plan.js +1 -1
  60. package/dist/contracts/project-knowledge.js +1 -1
  61. package/dist/contracts/project-skills.d.ts +0 -1
  62. package/dist/contracts/proof.js +1 -1
  63. package/dist/contracts/recipes.js +1 -1
  64. package/dist/contracts/scope.js +1 -1
  65. package/dist/contracts/scout-report.js +1 -1
  66. package/dist/contracts/teams-callback.d.ts +0 -1
  67. package/dist/converse.d.ts +0 -6
  68. package/dist/converse.js +0 -19
  69. package/dist/daemon.js +3 -2
  70. package/dist/decision.d.ts +1 -3
  71. package/dist/deprecations.d.ts +12 -4
  72. package/dist/deprecations.js +27 -4
  73. package/dist/desktop-update-recovery.js +1 -1
  74. package/dist/desktop-update.js +3 -2
  75. package/dist/discord-chat.js +4 -15
  76. package/dist/discord-settings.d.ts +2 -6
  77. package/dist/discord-settings.js +10 -38
  78. package/dist/discord.js +12 -23
  79. package/dist/evidence.d.ts +0 -1
  80. package/dist/evidence.js +0 -3
  81. package/dist/exec.d.ts +0 -9
  82. package/dist/exec.js +3 -81
  83. package/dist/flow-actions.js +0 -3
  84. package/dist/flow-draft.d.ts +1 -3
  85. package/dist/flow-draft.js +15 -28
  86. package/dist/flow-scripts.d.ts +1 -8
  87. package/dist/flow-scripts.js +0 -13
  88. package/dist/flow-send.js +1 -1
  89. package/dist/flow-share.d.ts +0 -2
  90. package/dist/flow-share.js +1 -6
  91. package/dist/flow-steps.js +1 -1
  92. package/dist/flow-triggers.d.ts +0 -1
  93. package/dist/flow-triggers.js +1 -2
  94. package/dist/flow-unattended.js +1 -12
  95. package/dist/flows.js +1 -1
  96. package/dist/health-cli.d.ts +2 -2
  97. package/dist/health-cli.js +1 -1
  98. package/dist/invoke.d.ts +18 -3
  99. package/dist/invoke.js +142 -17
  100. package/dist/is-record.d.ts +2 -0
  101. package/dist/is-record.js +2 -0
  102. package/dist/lead-doors.d.ts +1 -5
  103. package/dist/lead-doors.js +0 -4
  104. package/dist/lead-tools.js +3 -6
  105. package/dist/live-detail.d.ts +0 -1
  106. package/dist/live-detail.js +0 -6
  107. package/dist/live-terminal.d.ts +24 -13
  108. package/dist/live-terminal.js +81 -43
  109. package/dist/live.d.ts +0 -1
  110. package/dist/live.js +1 -2
  111. package/dist/mcp-core.js +0 -2
  112. package/dist/memory-pass.js +1 -1
  113. package/dist/model-catalog.js +1 -1
  114. package/dist/modes.js +1 -12
  115. package/dist/operate.d.ts +2 -0
  116. package/dist/operate.js +436 -745
  117. package/dist/password-bearer.d.ts +0 -2
  118. package/dist/password-bearer.js +0 -4
  119. package/dist/peek-cli.js +2 -1
  120. package/dist/peek.d.ts +2 -26
  121. package/dist/peek.js +2 -168
  122. package/dist/phase-routing.d.ts +0 -4
  123. package/dist/phase-routing.js +2 -21
  124. package/dist/planner-source.d.ts +2 -1
  125. package/dist/planner-source.js +2 -12
  126. package/dist/planner.js +2 -14
  127. package/dist/process-tree.d.ts +0 -2
  128. package/dist/process-tree.js +0 -10
  129. package/dist/project-delete-ui.d.ts +6 -0
  130. package/dist/project-delete-ui.js +20 -1
  131. package/dist/project-delete.d.ts +0 -2
  132. package/dist/project-delete.js +1 -3
  133. package/dist/project-learning.d.ts +0 -14
  134. package/dist/project-skills.d.ts +0 -1
  135. package/dist/project-skills.js +0 -14
  136. package/dist/project-tools.js +2 -10
  137. package/dist/provider.d.ts +0 -17
  138. package/dist/provider.js +0 -26
  139. package/dist/pulls.d.ts +0 -2
  140. package/dist/pulls.js +0 -9
  141. package/dist/release-adapters.d.ts +9 -0
  142. package/dist/release-adapters.js +81 -4
  143. package/dist/release.d.ts +7 -1
  144. package/dist/release.js +20 -7
  145. package/dist/remote-exec.d.ts +3 -2
  146. package/dist/remote-exec.js +3 -2
  147. package/dist/repos.d.ts +0 -5
  148. package/dist/repos.js +2 -10
  149. package/dist/repository-context.d.ts +0 -2
  150. package/dist/repository-context.js +0 -4
  151. package/dist/restart-certification.d.ts +0 -2
  152. package/dist/restart-certification.js +1 -5
  153. package/dist/result-follow-ups.d.ts +1 -4
  154. package/dist/result-follow-ups.js +4 -8
  155. package/dist/result-review.d.ts +0 -2
  156. package/dist/result-review.js +0 -2
  157. package/dist/retention.d.ts +0 -1
  158. package/dist/retention.js +0 -1
  159. package/dist/review-context.d.ts +0 -1
  160. package/dist/review-context.js +1 -12
  161. package/dist/reviewer.d.ts +0 -16
  162. package/dist/reviewer.js +0 -18
  163. package/dist/run-closeout.js +2 -1
  164. package/dist/scope.d.ts +13 -0
  165. package/dist/scope.js +11 -18
  166. package/dist/scout-browser.js +2 -1
  167. package/dist/serve.js +3 -2
  168. package/dist/server/chrome.d.ts +10 -1
  169. package/dist/server/chrome.js +45 -4
  170. package/dist/server/console.css +60 -137
  171. package/dist/server/flows.js +2 -2
  172. package/dist/server/http.d.ts +2 -0
  173. package/dist/server/http.js +5 -0
  174. package/dist/server/pages.js +2 -2
  175. package/dist/server/people-tokens.js +3 -4
  176. package/dist/server/remote.js +1 -6
  177. package/dist/server/render-chat.d.ts +9 -2
  178. package/dist/server/render-chat.js +53 -7
  179. package/dist/server/render-pages.d.ts +28 -3
  180. package/dist/server/render-pages.js +78 -9
  181. package/dist/server/render-results.d.ts +0 -6
  182. package/dist/server/render-results.js +3 -9
  183. package/dist/server/render-tasks.js +6 -6
  184. package/dist/server/route-table.d.ts +0 -4
  185. package/dist/server/route-table.js +1 -6
  186. package/dist/server/settings.js +43 -22
  187. package/dist/server/task-views.js +1 -1
  188. package/dist/server/tasks.js +10 -7
  189. package/dist/setup-guide.d.ts +2 -2
  190. package/dist/skills.d.ts +0 -6
  191. package/dist/skills.js +0 -8
  192. package/dist/slack-chat.d.ts +1 -7
  193. package/dist/slack-chat.js +5 -18
  194. package/dist/slack-settings.d.ts +2 -6
  195. package/dist/slack-settings.js +10 -38
  196. package/dist/slack.js +12 -23
  197. package/dist/sleep.d.ts +2 -0
  198. package/dist/sleep.js +2 -0
  199. package/dist/store.d.ts +11 -74
  200. package/dist/store.js +36 -179
  201. package/dist/structured-output.d.ts +0 -1
  202. package/dist/structured-output.js +0 -1
  203. package/dist/subagent-work.d.ts +0 -2
  204. package/dist/subagent-work.js +0 -2
  205. package/dist/subagents.d.ts +1 -3
  206. package/dist/subagents.js +20 -33
  207. package/dist/subscription-chat.d.ts +1 -3
  208. package/dist/subscription-chat.js +2 -3
  209. package/dist/task-sizing.d.ts +1 -4
  210. package/dist/task-sizing.js +3 -10
  211. package/dist/teams-chat.js +4 -11
  212. package/dist/teams.js +12 -19
  213. package/dist/teardown-git.d.ts +22 -0
  214. package/dist/teardown-git.js +136 -2
  215. package/dist/telegram-lead.js +2 -2
  216. package/dist/telegram.js +1 -1
  217. package/dist/token-notices.d.ts +0 -11
  218. package/dist/token-notices.js +1 -5
  219. package/dist/tool-broker.d.ts +12 -0
  220. package/dist/tool-broker.js +66 -2
  221. package/dist/tool-secret-store.d.ts +0 -3
  222. package/dist/tool-secret-store.js +2 -11
  223. package/dist/toolroll-update.d.ts +0 -2
  224. package/dist/toolroll-update.js +2 -2
  225. package/dist/unfenced-test-support.d.ts +2 -0
  226. package/dist/unfenced-test-support.js +11 -0
  227. package/dist/verification-evidence.d.ts +0 -1
  228. package/dist/verification-evidence.js +0 -1
  229. package/dist/webhooks.d.ts +0 -1
  230. package/dist/webhooks.js +0 -1
  231. package/dist/workspace-ui.d.ts +0 -8
  232. package/dist/workspace-ui.js +1 -6
  233. package/dist/writes-mode.d.ts +9 -0
  234. package/dist/writes-mode.js +8 -0
  235. package/package.json +5 -1
  236. package/dist/contracts/native-census.d.ts +0 -42
  237. package/dist/contracts/native-census.js +0 -36
  238. package/dist/process-recovery-anchor.d.ts +0 -75
  239. package/dist/process-recovery-anchor.js +0 -188
  240. package/dist/process-recovery-coalition.d.ts +0 -25
  241. package/dist/process-recovery-coalition.js +0 -105
  242. package/dist/process-recovery-eligibility.d.ts +0 -179
  243. package/dist/process-recovery-eligibility.js +0 -174
  244. package/dist/process-recovery-native.d.ts +0 -161
  245. package/dist/process-recovery-native.js +0 -595
  246. package/dist/process-recovery-provenance.d.ts +0 -163
  247. package/dist/process-recovery-provenance.js +0 -292
  248. package/dist/process-recovery-services.d.ts +0 -56
  249. package/dist/process-recovery-services.js +0 -191
  250. package/dist/process-recovery-settlement.d.ts +0 -32
  251. package/dist/process-recovery-settlement.js +0 -133
  252. package/dist/process-recovery.d.ts +0 -41
  253. package/dist/process-recovery.js +0 -91
  254. package/dist/session-brief.d.ts +0 -8
  255. package/dist/session-brief.js +0 -24
  256. package/dist/session-cli.d.ts +0 -42
  257. package/dist/session-cli.js +0 -271
  258. package/dist/session-contract.d.ts +0 -183
  259. package/dist/session-contract.js +0 -122
  260. package/dist/session-remote.d.ts +0 -19
  261. package/dist/session-remote.js +0 -86
  262. package/dist/session-service.d.ts +0 -22
  263. package/dist/session-service.js +0 -157
  264. package/dist/team-cli.js +0 -692
@@ -20,6 +20,8 @@
20
20
  import { spawn } from "node:child_process";
21
21
  import { randomBytes, timingSafeEqual } from "node:crypto";
22
22
  import { createServer } from "node:http";
23
+ import { accessSync, constants, existsSync, realpathSync } from "node:fs";
24
+ import { delimiter, isAbsolute, resolve, sep } from "node:path";
23
25
  const BODY_LIMIT = 4 * 1024 * 1024;
24
26
  const ANSWER_LIMIT = 32 * 1024 * 1024;
25
27
  const STOP_GRACE_MS = 2_000;
@@ -59,6 +61,54 @@ export function serverEnv(base, omit, pass, values) {
59
61
  delete env[name];
60
62
  return Object.assign(env, pass, values);
61
63
  }
64
+ /**
65
+ * The first of a server's command and path-like arguments that resolves inside an untrusted folder (a build's checkout
66
+ * or caches), or null. The command is resolved as spawn would: a path against `cwd`, a bare name through PATH; an
67
+ * argument counts when it names an existing path (relative to `cwd`) or is written as one (`./x`, `../x`, `/x`).
68
+ */
69
+ export function workspaceBytes(command, args, cwd, path, untrusted) {
70
+ if (untrusted.length === 0)
71
+ return null;
72
+ const base = cwd ?? process.cwd();
73
+ // Canonical on-disk names, folded where the filesystem ignores case (macOS, Windows), so `/Users/a/CHECKOUT` is the checkout.
74
+ const fold = process.platform === "darwin" || process.platform === "win32" ? (one) => one.toLowerCase() : (one) => one;
75
+ const real = (one) => { try {
76
+ return fold(realpathSync.native(one));
77
+ }
78
+ catch {
79
+ return fold(resolve(one));
80
+ } };
81
+ const roots = untrusted.map(real);
82
+ const within = (one) => { const at = real(one); return roots.some(root => at === root || at.startsWith(`${root}${sep}`)); };
83
+ // A root's name anywhere in an argument (inline code, a list of paths) counts, however it's spelled.
84
+ const mentions = (arg) => { const text = fold(arg); return roots.some(root => text.includes(root)) || untrusted.some(root => text.includes(fold(root))); };
85
+ const located = command.includes("/") || command.includes(sep) ? resolve(base, command)
86
+ : (path ?? "").split(delimiter).filter(Boolean).map(dir => resolve(base, dir, command)).find(one => { try {
87
+ accessSync(one, constants.X_OK);
88
+ return true;
89
+ }
90
+ catch {
91
+ return false;
92
+ } }) ?? null;
93
+ if (located !== null && within(located))
94
+ return located;
95
+ // A relative path once meant "in the checkout" (servers started there): it is refused rather than read elsewhere.
96
+ if (!isAbsolute(command) && (command.includes("/") || command.includes(sep)))
97
+ return `${command} (a relative path)`;
98
+ for (const arg of args) {
99
+ if (mentions(arg))
100
+ return `${arg} (names what this build writes)`;
101
+ // A flag's value (`--config=x`, `-c=x`) is checked as its own argument.
102
+ for (const piece of [arg, ...(arg.startsWith("-") && arg.includes("=") ? [arg.slice(arg.indexOf("=") + 1)] : [])]) {
103
+ if (piece.startsWith("./") || piece.startsWith("../"))
104
+ return `${piece} (a relative path)`;
105
+ const candidate = resolve(base, piece);
106
+ if ((isAbsolute(piece) || existsSync(candidate)) && within(candidate))
107
+ return candidate;
108
+ }
109
+ }
110
+ return null;
111
+ }
62
112
  /** A local server, started on first use and spoken to over its stdin and stdout. */
63
113
  class LocalServer {
64
114
  tool;
@@ -68,6 +118,7 @@ class LocalServer {
68
118
  buffer = "";
69
119
  next = 1;
70
120
  pending = new Map();
121
+ refusal = null;
71
122
  constructor(tool, options) {
72
123
  this.tool = tool;
73
124
  this.options = options;
@@ -75,7 +126,19 @@ class LocalServer {
75
126
  pid() { return this.child?.pid ?? null; }
76
127
  start() {
77
128
  const env = serverEnv(this.options.env ?? process.env, this.options.omitEnv ?? [], this.options.pass ?? {}, this.tool.values);
129
+ const planted = workspaceBytes(this.tool.spec.command, this.tool.spec.args, this.options.cwd, env["PATH"], this.options.untrusted ?? []);
130
+ if (planted !== null) {
131
+ this.refusal = `The tool's server would run ${planted}, inside what this build can write, so it was not started.`;
132
+ return null;
133
+ }
78
134
  const wrap = this.options.wrap?.() ?? null;
135
+ if (wrap === null) {
136
+ const why = this.options.unwrapped?.() ?? null;
137
+ if (why !== null) {
138
+ this.refusal = why;
139
+ return null;
140
+ }
141
+ }
79
142
  const { file, args } = wrap === null ? { file: this.tool.spec.command, args: [...this.tool.spec.args] } : wrap(this.tool.spec.command, this.tool.spec.args);
80
143
  const child = spawn(file, args, { env, ...(this.options.cwd === undefined ? {} : { cwd: this.options.cwd }), stdio: ["pipe", "pipe", "ignore"], detached: process.platform !== "win32" });
81
144
  this.child = child;
@@ -131,8 +194,9 @@ class LocalServer {
131
194
  // A new session (a reconnect) starts a fresh server: a server answers initialize once.
132
195
  if (message.method === "initialize" && this.initialized)
133
196
  this.stop();
134
- if (this.child === null)
135
- this.start();
197
+ if (this.child === null && this.start() === null) {
198
+ return Promise.resolve(isRequest(message) ? rpcError(message.id, this.refusal ?? "The tool could not start.") : null);
199
+ }
136
200
  if (message.method === "initialize")
137
201
  this.initialized = true;
138
202
  if (!isRequest(message)) {
@@ -56,7 +56,6 @@ export declare function detectSecretStore(options?: {
56
56
  export declare function chooseSecretStore(store: SecretBackend | "files" | DetectedStore | null): void;
57
57
  /** This computer's store (found once), or null for owner-only files. */
58
58
  export declare function secretBackend(env?: NodeJS.ProcessEnv): SecretBackend | null;
59
- export declare const secretStoreKind: () => SecretStoreKind;
60
59
  /** Where secrets rest, in the words Settings and `toolroll integrations` share; `problem` when Toolroll's folder can't be used. */
61
60
  export declare function secretStorage(): {
62
61
  kind: SecretStoreKind;
@@ -166,5 +165,3 @@ export declare function writeSealed(backend: SecretBackend, folder: SecretFolder
166
165
  export declare function readHeldSecret(state: string, name: string, file: readonly string[]): string | null;
167
166
  /** Keep (or, with null, remove) a held secret: see readHeldSecret. */
168
167
  export declare function writeHeldSecret(state: string, name: string, file: readonly string[], value: string | null): void;
169
- /** Whether a held secret has a value, without reading it where that can be helped. */
170
- export declare function heldSecretSet(state: string, name: string, file: readonly string[]): boolean;
@@ -32,6 +32,7 @@ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:
32
32
  import { closeSync, constants, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
33
33
  import { dirname, join } from "node:path";
34
34
  import { envValue } from "./names.js";
35
+ import { sleep } from "./sleep.js";
35
36
  export class SecretStoreError extends Error {
36
37
  }
37
38
  /** The words Settings and `toolroll integrations` use for where secrets rest. */
@@ -200,7 +201,6 @@ function currentStore(env) {
200
201
  export function secretBackend(env = process.env) {
201
202
  return currentStore(env).backend;
202
203
  }
203
- export const secretStoreKind = () => secretBackend()?.kind ?? "files";
204
204
  /** Where secrets rest, in the words Settings and `toolroll integrations` share; `problem` when Toolroll's folder can't be used. */
205
205
  export function secretStorage() {
206
206
  const found = currentStore(process.env);
@@ -701,7 +701,7 @@ export async function withSecretLockAsync(folder, name, work, options = {}) {
701
701
  while (!takeLock(lock, options.staleMs ?? 120_000)) {
702
702
  if (Date.now() - started > (options.waitMs ?? 120_000))
703
703
  throw new SecretStoreError("A sign-in is being renewed elsewhere. Try again in a moment.");
704
- await new Promise(resolve => setTimeout(resolve, 25));
704
+ await sleep(25);
705
705
  }
706
706
  try {
707
707
  return await work();
@@ -823,12 +823,3 @@ export function writeHeldSecret(state, name, file, value) {
823
823
  writeSealed(backend, folder, item, value);
824
824
  });
825
825
  }
826
- /** Whether a held secret has a value, without reading it where that can be helped. */
827
- export function heldSecretSet(state, name, file) {
828
- try {
829
- return readHeldSecret(state, name, file) !== null;
830
- }
831
- catch {
832
- return false;
833
- }
834
- }
@@ -162,7 +162,6 @@ export type StartOptions = {
162
162
  /** An older version replaces the current one only when asked for by name. */
163
163
  allowDowngrade?: boolean;
164
164
  };
165
- declare function nextAt(hhmm: string, now: Date): Date;
166
165
  /** Record an update without running it: the console's job resumes exactly this journal id. */
167
166
  export declare function prepareRuntimeUpdate(o: StartOptions, now: Date): RuntimeUpdateJournal | {
168
167
  refused: string;
@@ -257,4 +256,3 @@ export declare function launchRuntimeUpdate(args: {
257
256
  }): Promise<void>;
258
257
  /** The job's last act: its definition goes, so nothing can start it again. Only the definition for this id. */
259
258
  export declare function retireUpdateJob(id: string, home?: string): void;
260
- export declare const nextScheduledAt: typeof nextAt;
@@ -40,6 +40,7 @@ import { orphanKey, updateSafeSchema, V117_RENAMED_COLUMNS, V117_RENAMED_TABLES,
40
40
  import { isNewer, REGISTRY } from "./releases.js";
41
41
  import { markNeverIndex } from "./never-index.js";
42
42
  import { readRuntimeUpdateJournal, RUNTIME_PHASES, RUNTIME_UPDATE_STEPS, stagedStartedAt, updaterStartingOf } from "./contracts/update-journal.js";
43
+ import { sleep } from "./sleep.js";
43
44
  /** Loaded on first use (as backup.ts and store.ts do), so modules that only import this one (the console,
44
45
  * and tests that load it in a browser-like environment) never need `node:sqlite` itself. */
45
46
  function sqlite() {
@@ -1583,7 +1584,7 @@ export function machineSystem(home = homedir(), env = process.env, seams = {}) {
1583
1584
  };
1584
1585
  return {
1585
1586
  now: () => new Date(),
1586
- sleep: ms => new Promise(done => setTimeout(done, ms)),
1587
+ sleep,
1587
1588
  release: async (version) => {
1588
1589
  const body = await json(`${REGISTRY}/${NAME}/${version}`);
1589
1590
  return { version: String(body.version ?? ""), tarball: String(body.dist?.tarball ?? ""), integrity: String(body.dist?.integrity ?? ""), attestations: body.dist?.attestations?.url ?? null };
@@ -1805,4 +1806,3 @@ export function retireUpdateJob(id, home = homedir()) {
1805
1806
  }
1806
1807
  catch { /* none */ }
1807
1808
  }
1808
- export const nextScheduledAt = nextAt;
@@ -0,0 +1,2 @@
1
+ import type { Store } from "./store.js";
2
+ export declare function allowWhereUnfenced<T extends Store>(store: T): T;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tests that run real commands on a machine with no OS fence (CI's Ubuntu runners, where unprivileged namespaces are
3
+ * off) allow unconfined agents on their own store, as an instance operator there would; anywhere a fence exists,
4
+ * nothing changes and the fence is what the test runs under.
5
+ */
6
+ import { linuxFenceAvailable, macosFenceAvailable } from "./agent-fence.js";
7
+ export function allowWhereUnfenced(store) {
8
+ if (!macosFenceAvailable() && !linuxFenceAvailable())
9
+ store.setUnfencedAgentsAllowed(true, "test", new Date(0));
10
+ return store;
11
+ }
@@ -2,7 +2,6 @@ import type { Artifact, Store, VerifyCommand } from "./store.js";
2
2
  import type { VerifyCommandFacts } from "./proof.js";
3
3
  import { type AdjudicateResult } from "./proof.js";
4
4
  export declare const VERIFICATION_RECEIPT_CAPTURE = "machine verification receipt v1";
5
- export declare const REVIEW_GATE_NAME = "REVIEW-VERIFICATION.json";
6
5
  export declare const isVerificationReceipt: (a: Artifact) => boolean;
7
6
  export declare function sealVerificationReceipt(store: Store, root: string, runId: number, head: string, command: VerifyCommand, result: VerifyCommandFacts, now: Date, reusedFrom?: {
8
7
  run: number;
@@ -12,7 +12,6 @@ import { LEGACY_GATE_SOURCE, readVerificationView } from "./contracts/verificati
12
12
  import { readCandidateEndpoints, savedInventoryPaths } from "./contracts/diff-stat.js";
13
13
  import { liveQuickCommand, quickVerifyKey, runCheckLevel } from "./check-levels.js";
14
14
  export const VERIFICATION_RECEIPT_CAPTURE = "machine verification receipt v1";
15
- export const REVIEW_GATE_NAME = "REVIEW-VERIFICATION.json";
16
15
  const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
17
16
  const binding = (a) => ({ artifactId: a.id, sha256: a.sha256, bytesStored: a.bytesStored, bytesOriginal: a.bytesOriginal, truncated: a.truncated, redacted: a.redacted, captureStatus: a.captureStatus });
18
17
  export const isVerificationReceipt = (a) => a.kind === "structured-output" && (a.capture === VERIFICATION_RECEIPT_CAPTURE || a.capture === "machine verification reuse v1");
@@ -6,7 +6,6 @@ export type WebhookTarget = {
6
6
  };
7
7
  export declare const LEGACY_WEBHOOK_WARNING = "Legacy webhooks are deprecated. Connect Slack or Discord in Chat settings.";
8
8
  export declare const CONSOLE_URL_ENV = "TOOLROLL_CONSOLE_URL";
9
- export declare const PRIMARY_ENV = "TOOLROLL_MESSAGING_PRIMARY";
10
9
  export type MessagingChannel = "telegram" | "slack" | "discord" | "teams";
11
10
  export declare const MESSAGING_CHANNELS: readonly MessagingChannel[];
12
11
  export declare function isMessagingChannel(value: string): value is MessagingChannel;
package/dist/webhooks.js CHANGED
@@ -17,7 +17,6 @@ const FILE_OF = { slack: "slack-webhook", discord: "discord-webhook" };
17
17
  const POST_TIMEOUT_MS = 10_000;
18
18
  export const LEGACY_WEBHOOK_WARNING = "Legacy webhooks are deprecated. Connect Slack or Discord in Chat settings.";
19
19
  export const CONSOLE_URL_ENV = "TOOLROLL_CONSOLE_URL";
20
- export const PRIMARY_ENV = "TOOLROLL_MESSAGING_PRIMARY";
21
20
  const CONSOLE_FILE = "console-url";
22
21
  const PRIMARY_FILE = "messaging-primary";
23
22
  export const MESSAGING_CHANNELS = ["telegram", "slack", "discord", "teams"];
@@ -261,14 +261,6 @@ export declare function workStatusOf(facts: WorkFacts, resultDisplay?: DisplaySt
261
261
  export declare function workCounts(rows: readonly {
262
262
  views: readonly WorkView[];
263
263
  }[]): Record<WorkView, number>;
264
- /** All's order: rank, then most recently updated. */
265
- export declare function compareWorkRows(a: {
266
- rank: number;
267
- updatedAt: string;
268
- }, b: {
269
- rank: number;
270
- updatedAt: string;
271
- }): number;
272
264
  /** The shell's three primary destinations, from the page's own active
273
265
  * key: every old page keeps its key and lights the destination it now
274
266
  * lives under. */
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { ACCEPT_NEEDS_REASON, FAILED_CHECK, cantAcceptYetOf, evidenceProblemOf } from "./result-acts.js";
20
20
  export { ACCEPT_NEEDS_REASON, cantAcceptYetOf, evidenceProblemOf };
21
- import { GOAL_ASSESSMENT_PENDING, manualReviewOnly, plainReasonWords } from "./proof.js";
21
+ import { manualReviewOnly, plainReasonWords } from "./proof.js";
22
22
  import { OPEN_RESULT, plainReasonOf, stageOfCode, taskStatusOf, workToneOf } from "./task-status.js";
23
23
  import { whenUtc } from "./when-html.js";
24
24
  import { html, postForm } from "./html.js";
@@ -54,7 +54,6 @@ export function reviewFactsOf(retry, reviewerAlive, automaticPending = false) {
54
54
  }
55
55
  /** The tokens a review in flight wears — the dispatch codes, unchanged. */
56
56
  export const REVIEW_TOKENS = new Set(["reviewing", "review-pending", "review-failed", "review-exhausted"]);
57
- const retriesLeft = (count, explicit = true) => `${count}${explicit ? " explicit" : ""} ${count === 1 ? "retry" : "retries"}`;
58
57
  /**
59
58
  * A review that is queued, running, failed with a retry left, or
60
59
  * exhausted is the result's PRIMARY status — the machine is still
@@ -504,10 +503,6 @@ export function workCounts(rows) {
504
503
  counts[view] += 1;
505
504
  return counts;
506
505
  }
507
- /** All's order: rank, then most recently updated. */
508
- export function compareWorkRows(a, b) {
509
- return a.rank - b.rank || (a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0);
510
- }
511
506
  const WORK_KEYS = new Set(["inbox", "board", "queue", "work", "done", "activity", "review", "tasks", "runs", "workbench", "recipes", "ledger"]);
512
507
  const SETTINGS_KEYS = new Set(["fleet", "caps", "people", "mode", "system", "settings"]);
513
508
  export function primaryDestinationOf(active) {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Where a build may write (docs/plans/agent-baseline-hardening.md arc 2). `workspace`: only its allowlist (agent-fence.ts
3
+ * WriteAllowlist), whatever its permission mode; `anywhere`: anywhere its fence doesn't deny, as builds did before.
4
+ */
5
+ export type WritesMode = "workspace" | "anywhere";
6
+ export declare const WRITES_DEFAULT: WritesMode;
7
+ export declare const isWritesMode: (value: unknown) => value is WritesMode;
8
+ /** What an approval card and the CLI say of where a build may write (absent: sealed before write allowlists). */
9
+ export declare function writesWords(writes: WritesMode | undefined): string;
@@ -0,0 +1,8 @@
1
+ export const WRITES_DEFAULT = "workspace";
2
+ export const isWritesMode = (value) => value === "workspace" || value === "anywhere";
3
+ /** What an approval card and the CLI say of where a build may write (absent: sealed before write allowlists). */
4
+ export function writesWords(writes) {
5
+ return writes === "workspace" ? "only its checkout, its caches and its temp folder"
6
+ : writes === "anywhere" ? "anywhere outside Toolroll's fenced folders (an approver's choice)"
7
+ : "anywhere outside Toolroll's fenced folders (approved before write limits)";
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolroll",
3
- "version": "0.9.65",
3
+ "version": "0.9.68",
4
4
  "description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
5
5
  "directories": {
6
6
  "doc": "docs"
@@ -84,6 +84,8 @@
84
84
  "@types/react-dom": "19.3.0",
85
85
  "@types/ws": "8.18.2",
86
86
  "@xterm/addon-fit": "0.11.0",
87
+ "@xterm/addon-unicode11": "0.9.0",
88
+ "@xterm/addon-webgl": "0.19.0",
87
89
  "@xterm/xterm": "6.0.0",
88
90
  "@xyflow/react": "12.12.0",
89
91
  "class-variance-authority": "0.7.1",
@@ -124,6 +126,8 @@
124
126
  },
125
127
  "dependencies": {
126
128
  "@slack/socket-mode": "3.1.0",
129
+ "@xterm/addon-serialize": "0.14.0",
130
+ "@xterm/headless": "6.0.0",
127
131
  "discord.js": "14.27.0",
128
132
  "imapflow": "2.2.5",
129
133
  "mailparser": "3.9.36",
@@ -1,42 +0,0 @@
1
- /**
2
- * The macOS process census the recovery helper prints (docs/plans/zod-revamp.md, item 19): `schema: 1`, the boot,
3
- * the collector, and one identity per process. The parser in process-recovery-native.ts reads it with these schemas
4
- * and keeps its fail-closed reason codes: a top-level problem is `malformed-native-census`, one in a process row
5
- * `malformed-process-identity`. Duplicate pids and unique ids, the 64-bit bound on ids and completeness run in plain
6
- * code after parsing. Unknown keys are dropped, and each identity is rebuilt in its sealed key order.
7
- */
8
- import { z } from "zod";
9
- export declare const NATIVE_CENSUS_ERRORS: readonly ["kernel-process-table-unreadable", "process-membership-unreadable", "process-identity-unreadable", "boot-identity-unreadable", "coalition-counters-unreadable", "coalition-counters-changed", "coalition-count-mismatch", "anchor-identity-unreadable"];
10
- export declare const darwinProcessIdentitySchema: z.ZodObject<{
11
- pid: z.ZodInt;
12
- ppid: z.ZodNullable<z.ZodInt>;
13
- uid: z.ZodNullable<z.ZodInt>;
14
- birthMs: z.ZodNullable<z.ZodNumber>;
15
- uniqueId: z.ZodNullable<z.ZodString>;
16
- parentUniqueId: z.ZodNullable<z.ZodString>;
17
- traced: z.ZodNullable<z.ZodBoolean>;
18
- executable: z.ZodNullable<z.ZodString>;
19
- originalParentVersion: z.ZodNullable<z.ZodInt>;
20
- pidVersion: z.ZodExactOptional<z.ZodInt>;
21
- }, z.core.$strip>;
22
- export type DarwinProcessIdentity = z.infer<typeof darwinProcessIdentitySchema>;
23
- /** Top level only: process rows are read one by one, so a bad row gets its own reason. */
24
- export declare const darwinNativeCensusSchema: z.ZodObject<{
25
- schema: z.ZodLiteral<1>;
26
- bootId: z.ZodNullable<z.ZodString>;
27
- collectorPid: z.ZodInt;
28
- complete: z.ZodBoolean;
29
- processes: z.ZodArray<z.ZodUnknown>;
30
- errors: z.ZodArray<z.ZodEnum<{
31
- "anchor-identity-unreadable": "anchor-identity-unreadable";
32
- "boot-identity-unreadable": "boot-identity-unreadable";
33
- "coalition-count-mismatch": "coalition-count-mismatch";
34
- "coalition-counters-changed": "coalition-counters-changed";
35
- "coalition-counters-unreadable": "coalition-counters-unreadable";
36
- "kernel-process-table-unreadable": "kernel-process-table-unreadable";
37
- "process-identity-unreadable": "process-identity-unreadable";
38
- "process-membership-unreadable": "process-membership-unreadable";
39
- }>>;
40
- }, z.core.$strip>;
41
- /** A kernel id within 64 bits (the schema checks the digits). */
42
- export declare const kernelIdInRange: (id: string) => boolean;
@@ -1,36 +0,0 @@
1
- /**
2
- * The macOS process census the recovery helper prints (docs/plans/zod-revamp.md, item 19): `schema: 1`, the boot,
3
- * the collector, and one identity per process. The parser in process-recovery-native.ts reads it with these schemas
4
- * and keeps its fail-closed reason codes: a top-level problem is `malformed-native-census`, one in a process row
5
- * `malformed-process-identity`. Duplicate pids and unique ids, the 64-bit bound on ids and completeness run in plain
6
- * code after parsing. Unknown keys are dropped, and each identity is rebuilt in its sealed key order.
7
- */
8
- import { z } from "zod";
9
- export const NATIVE_CENSUS_ERRORS = ["kernel-process-table-unreadable", "process-membership-unreadable", "process-identity-unreadable", "boot-identity-unreadable", "coalition-counters-unreadable", "coalition-counters-changed", "coalition-count-mismatch", "anchor-identity-unreadable"];
10
- /** A kernel unique id: a decimal unsigned 64-bit integer as text (the bound itself is checked after parsing). */
11
- const kernelId = z.string().regex(/^(0|[1-9][0-9]{0,19})$/);
12
- const count = z.int().min(0);
13
- const version32 = z.int().min(0).max(0xffffffff);
14
- export const darwinProcessIdentitySchema = z.object({
15
- pid: z.int().min(1),
16
- ppid: count.nullable(),
17
- uid: count.nullable(),
18
- birthMs: z.number().positive().nullable(),
19
- uniqueId: kernelId.nullable(),
20
- parentUniqueId: kernelId.nullable(),
21
- traced: z.boolean().nullable(),
22
- executable: z.string().max(4096).regex(/^[^\x00-\x1f\x7f]*$/).nullable(),
23
- originalParentVersion: version32.nullable(),
24
- pidVersion: version32.exactOptional(),
25
- });
26
- /** Top level only: process rows are read one by one, so a bad row gets its own reason. */
27
- export const darwinNativeCensusSchema = z.object({
28
- schema: z.literal(1),
29
- bootId: z.string().regex(/^[a-fA-F0-9-]{36}$/).nullable(),
30
- collectorPid: z.int().min(2),
31
- complete: z.boolean(),
32
- processes: z.array(z.unknown()).max(100_000),
33
- errors: z.array(z.enum(NATIVE_CENSUS_ERRORS)),
34
- });
35
- /** A kernel id within 64 bits (the schema checks the digits). */
36
- export const kernelIdInRange = (id) => BigInt(id) <= 0xffffffffffffffffn;
@@ -1,75 +0,0 @@
1
- import type { SQLInputValue } from "node:sqlite";
2
- import type { DarwinCoalitionProcessIdentity, PreparedDarwinProcessCensus } from "./process-recovery-native.js";
3
- /** This version's causal argument was audited against these installed bytes:
4
- * up registers a fresh token BEFORE listening; every watch creates a fresh UUID;
5
- * heartbeat authenticates that closure's token and exact owner transactionally.
6
- * A new runtime needs its own audit, not a caller assertion of equivalent code. */
7
- export declare const SERVICE_ANCHOR_RUNTIME_V1_SHA256 = "9641efcb3b46717a7f122aa025e3945a08fa55ca046605276825a9d69eefd01d";
8
- export type DarwinServiceAnchorInput = {
9
- databasePath: string;
10
- targetRun: number;
11
- anchorReviewerRun: number;
12
- runtime: string;
13
- runtimeSha256: string;
14
- runner: string;
15
- repo: string;
16
- host: string;
17
- bootId: string;
18
- resourceCoalitionId: string;
19
- service: string;
20
- supervisorPid: number;
21
- workerPid: number;
22
- port: number;
23
- /** Full expected arrays; compared privately, never included in a receipt. */
24
- supervisorArgv: string[];
25
- workerArgv: string[];
26
- /** Recovery must prepare this helper itself from the known embedded source;
27
- * never accept an executable or this result receipt from a client request. */
28
- native: PreparedDarwinProcessCensus;
29
- maxWaitMs?: number;
30
- };
31
- export type DarwinServiceAnchorReceipt = {
32
- schema: 1;
33
- kind: "authenticated-watch-continuity";
34
- targetRun: number;
35
- anchorReviewerRun: number;
36
- host: string;
37
- bootId: string;
38
- observedAt: string;
39
- runtimeSha256: string;
40
- databasePath: string;
41
- runner: string;
42
- repo: string;
43
- owner: string;
44
- generation: number;
45
- episode: number;
46
- registeredAt: string;
47
- heartbeatBefore: string;
48
- heartbeatAfter: string;
49
- service: string;
50
- listener: string;
51
- worker: DarwinCoalitionProcessIdentity;
52
- supervisor: DarwinCoalitionProcessIdentity;
53
- nativeSourceSha256: string;
54
- nativeExecutableSha256: string;
55
- };
56
- export type DarwinServiceAnchorResult = {
57
- ok: true;
58
- receipt: DarwinServiceAnchorReceipt;
59
- } | {
60
- ok: false;
61
- reason: string;
62
- };
63
- export type RecoveryReadDatabase = {
64
- prepare(sql: string): {
65
- get(...parameters: SQLInputValue[]): Record<string, unknown> | undefined;
66
- all(...parameters: SQLInputValue[]): Record<string, unknown>[];
67
- };
68
- };
69
- /** Trusted read-only collection, not exit evidence. Reads an existing connection
70
- * (including a schema69 Store.handle), never opens/migrates or writes a database.
71
- * Under the trusted installed-code/credential model, renewing a saved pre-run
72
- * UUID while registration stays fixed establishes a pre-run live service bound.
73
- * It does NOT establish coalition inheritance, survivor exclusion, or authority
74
- * to clear process custody. Every incomplete observation returns a refusal. */
75
- export declare function collectDarwinServiceAnchor(db: RecoveryReadDatabase, input: DarwinServiceAnchorInput): Promise<DarwinServiceAnchorResult>;