toolroll 0.9.67 → 0.9.69

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 (250) hide show
  1. package/CHANGELOG.md +70 -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.d.ts +58 -0
  6. package/dist/agent-fence.js +214 -19
  7. package/dist/agentconfig.d.ts +1 -1
  8. package/dist/agentconfig.js +6 -2
  9. package/dist/batch-checks.js +14 -26
  10. package/dist/browser/workspace.css +1 -1
  11. package/dist/browser/workspace.js +31 -29
  12. package/dist/builder.d.ts +9 -0
  13. package/dist/builder.js +30 -27
  14. package/dist/canonical-json.d.ts +14 -0
  15. package/dist/canonical-json.js +36 -0
  16. package/dist/chat-actions.js +1 -2
  17. package/dist/chat-admission.d.ts +19 -0
  18. package/dist/chat-admission.js +22 -0
  19. package/dist/chat-channel.js +1 -1
  20. package/dist/chat-delivery.js +0 -1
  21. package/dist/chat-follow.d.ts +17 -0
  22. package/dist/chat-follow.js +32 -0
  23. package/dist/chat-messages.js +0 -1
  24. package/dist/chat-settings-page.d.ts +23 -0
  25. package/dist/chat-settings-page.js +41 -0
  26. package/dist/check-access.d.ts +54 -0
  27. package/dist/check-access.js +80 -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 +39 -5
  99. package/dist/invoke.js +181 -24
  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.js +2 -1
  108. package/dist/live.d.ts +0 -1
  109. package/dist/live.js +1 -2
  110. package/dist/mcp-core.js +0 -2
  111. package/dist/memory-pass.js +1 -1
  112. package/dist/model-catalog.js +1 -1
  113. package/dist/modes.js +1 -12
  114. package/dist/operate.d.ts +4 -0
  115. package/dist/operate.js +456 -750
  116. package/dist/password-bearer.d.ts +0 -2
  117. package/dist/password-bearer.js +0 -4
  118. package/dist/peek-cli.js +2 -1
  119. package/dist/peek.d.ts +2 -26
  120. package/dist/peek.js +2 -168
  121. package/dist/phase-routing.d.ts +0 -4
  122. package/dist/phase-routing.js +2 -21
  123. package/dist/planner-source.d.ts +2 -1
  124. package/dist/planner-source.js +2 -12
  125. package/dist/planner.js +2 -14
  126. package/dist/process-tree.d.ts +0 -2
  127. package/dist/process-tree.js +0 -10
  128. package/dist/project-delete-ui.d.ts +16 -0
  129. package/dist/project-delete-ui.js +60 -1
  130. package/dist/project-delete.d.ts +0 -2
  131. package/dist/project-delete.js +1 -3
  132. package/dist/project-learning.d.ts +0 -14
  133. package/dist/project-skills.d.ts +0 -1
  134. package/dist/project-skills.js +0 -14
  135. package/dist/project-tools.js +2 -10
  136. package/dist/provider.d.ts +0 -17
  137. package/dist/provider.js +0 -26
  138. package/dist/pulls.d.ts +0 -2
  139. package/dist/pulls.js +0 -9
  140. package/dist/release-adapters.d.ts +3 -0
  141. package/dist/release-adapters.js +15 -5
  142. package/dist/remote-exec.d.ts +3 -2
  143. package/dist/remote-exec.js +3 -2
  144. package/dist/repos.d.ts +0 -5
  145. package/dist/repos.js +2 -10
  146. package/dist/repository-context.d.ts +0 -2
  147. package/dist/repository-context.js +0 -4
  148. package/dist/restart-certification.d.ts +0 -2
  149. package/dist/restart-certification.js +1 -5
  150. package/dist/result-follow-ups.d.ts +1 -4
  151. package/dist/result-follow-ups.js +11 -21
  152. package/dist/result-review.d.ts +0 -2
  153. package/dist/result-review.js +0 -2
  154. package/dist/retention.d.ts +0 -1
  155. package/dist/retention.js +0 -1
  156. package/dist/review-context.d.ts +0 -1
  157. package/dist/review-context.js +1 -12
  158. package/dist/reviewer.d.ts +0 -16
  159. package/dist/reviewer.js +0 -18
  160. package/dist/run-closeout.js +2 -1
  161. package/dist/scope.d.ts +9 -0
  162. package/dist/scope.js +13 -18
  163. package/dist/scout-browser.js +2 -1
  164. package/dist/serve.js +11 -2
  165. package/dist/server/console.css +5 -133
  166. package/dist/server/flows.js +2 -2
  167. package/dist/server/http.d.ts +4 -0
  168. package/dist/server/http.js +5 -0
  169. package/dist/server/pages.js +2 -2
  170. package/dist/server/people-tokens.js +3 -4
  171. package/dist/server/remote.js +1 -6
  172. package/dist/server/render-chat.d.ts +9 -2
  173. package/dist/server/render-chat.js +53 -7
  174. package/dist/server/render-results.d.ts +3 -6
  175. package/dist/server/render-results.js +4 -9
  176. package/dist/server/render-tasks.js +6 -6
  177. package/dist/server/route-table.d.ts +0 -4
  178. package/dist/server/route-table.js +1 -6
  179. package/dist/server/settings.js +60 -23
  180. package/dist/server/task-views.js +1 -1
  181. package/dist/server/tasks.js +5 -3
  182. package/dist/setup-guide.d.ts +2 -2
  183. package/dist/skills.d.ts +0 -6
  184. package/dist/skills.js +0 -8
  185. package/dist/slack-chat.d.ts +1 -7
  186. package/dist/slack-chat.js +5 -18
  187. package/dist/slack-settings.d.ts +2 -6
  188. package/dist/slack-settings.js +10 -38
  189. package/dist/slack.js +12 -23
  190. package/dist/sleep.d.ts +2 -0
  191. package/dist/sleep.js +2 -0
  192. package/dist/store.d.ts +29 -75
  193. package/dist/store.js +89 -180
  194. package/dist/structured-output.d.ts +0 -1
  195. package/dist/structured-output.js +0 -1
  196. package/dist/subagent-work.d.ts +0 -2
  197. package/dist/subagent-work.js +0 -2
  198. package/dist/subagents.d.ts +1 -3
  199. package/dist/subagents.js +20 -33
  200. package/dist/subscription-chat.d.ts +1 -3
  201. package/dist/subscription-chat.js +2 -3
  202. package/dist/surface.js +2 -1
  203. package/dist/task-sizing.d.ts +1 -4
  204. package/dist/task-sizing.js +3 -10
  205. package/dist/teams-chat.js +4 -11
  206. package/dist/teams.js +12 -19
  207. package/dist/telegram-lead.js +2 -2
  208. package/dist/telegram.js +1 -1
  209. package/dist/token-notices.d.ts +0 -11
  210. package/dist/token-notices.js +1 -5
  211. package/dist/tool-secret-store.d.ts +0 -3
  212. package/dist/tool-secret-store.js +2 -11
  213. package/dist/toolroll-update.d.ts +0 -2
  214. package/dist/toolroll-update.js +2 -2
  215. package/dist/verification-evidence.d.ts +0 -1
  216. package/dist/verification-evidence.js +0 -1
  217. package/dist/webhooks.d.ts +0 -1
  218. package/dist/webhooks.js +0 -1
  219. package/dist/workspace-ui.d.ts +0 -8
  220. package/dist/workspace-ui.js +1 -6
  221. package/package.json +1 -1
  222. package/dist/contracts/native-census.d.ts +0 -42
  223. package/dist/contracts/native-census.js +0 -36
  224. package/dist/process-recovery-anchor.d.ts +0 -75
  225. package/dist/process-recovery-anchor.js +0 -188
  226. package/dist/process-recovery-coalition.d.ts +0 -25
  227. package/dist/process-recovery-coalition.js +0 -105
  228. package/dist/process-recovery-eligibility.d.ts +0 -179
  229. package/dist/process-recovery-eligibility.js +0 -174
  230. package/dist/process-recovery-native.d.ts +0 -161
  231. package/dist/process-recovery-native.js +0 -595
  232. package/dist/process-recovery-provenance.d.ts +0 -163
  233. package/dist/process-recovery-provenance.js +0 -292
  234. package/dist/process-recovery-services.d.ts +0 -56
  235. package/dist/process-recovery-services.js +0 -191
  236. package/dist/process-recovery-settlement.d.ts +0 -32
  237. package/dist/process-recovery-settlement.js +0 -133
  238. package/dist/process-recovery.d.ts +0 -41
  239. package/dist/process-recovery.js +0 -91
  240. package/dist/session-brief.d.ts +0 -8
  241. package/dist/session-brief.js +0 -24
  242. package/dist/session-cli.d.ts +0 -42
  243. package/dist/session-cli.js +0 -271
  244. package/dist/session-contract.d.ts +0 -183
  245. package/dist/session-contract.js +0 -122
  246. package/dist/session-remote.d.ts +0 -19
  247. package/dist/session-remote.js +0 -86
  248. package/dist/session-service.d.ts +0 -22
  249. package/dist/session-service.js +0 -157
  250. package/dist/team-cli.js +0 -692
@@ -3,7 +3,6 @@ import type { Store } from './store.js';
3
3
  import type { Principal } from './operate.js';
4
4
  import { type RemoteCommandLookup } from './remote-command.js';
5
5
  import { type Admission, type BudgetLimit } from './request-budget.js';
6
- import type { SessionRunner } from './session-remote.js';
7
6
  /**
8
7
  * `POST /api/cli` (remote CLI, Phase 1): a teammate's laptop sends the argv it would have run locally, and the central
9
8
  * server runs that same command as that person. There is no per-resource API here and no second policy: the transport
@@ -12,10 +11,6 @@ import type { SessionRunner } from './session-remote.js';
12
11
  *
13
12
  * Only `Authorization: Bearer so_…` is accepted — never a password, cookie or the owner's saved login — and a request
14
13
  * carrying a browser Origin is refused, so a page can never drive it.
15
- *
16
- * `session …` (D7) is the one command the server answers through its own owner rather than runOperateAs: native
17
- * session operations need the server's live coding workspace (session-remote.ts). They are admitted, budgeted and
18
- * refused here exactly like every other command.
19
14
  */
20
15
  /** Who a remote command runs as, and the runner: operate.ts's own types, so a change there breaks this build instead of
21
16
  * drifting. Type-only: the value is loaded lazily through `run` (operate.ts imports the server that imports this). */
@@ -50,8 +45,6 @@ export type CliHttpOptions = {
50
45
  modeOf?: RemoteCommandLookup | undefined;
51
46
  /** The token's request budget (request-budget.ts), charged once per request before its body is read. */
52
47
  admit?: ((principal: Principal) => Admission) | undefined;
53
- /** Native session operations (`session …`), run by the server's session owner; absent where none is ready. */
54
- session?: SessionRunner | undefined;
55
48
  store: Store;
56
49
  };
57
50
  /** Decode the request's shape and byte limits. The endpoint checks file references against its command policy. */
package/dist/cli-http.js CHANGED
@@ -123,8 +123,6 @@ export async function handleCliHttp(request, response, options) {
123
123
  const parsed = cliRequest(value);
124
124
  if ('problem' in parsed)
125
125
  return reject(parsed.tooLarge ? 413 : 400, 'invalid-request', parsed.problem);
126
- if (parsed.argv[0] === 'session')
127
- return runSession(response, options, principal, parsed);
128
126
  const row = (options.modeOf ?? contractRow)(parsed.argv);
129
127
  if (row?.mode === 'step-up')
130
128
  return reject(403, 'step-up', STEP_UP_MESSAGE);
@@ -161,22 +159,3 @@ export async function handleCliHttp(request, response, options) {
161
159
  send(response, 200, { exitCode, stdout: out.join(''), stderr: '' });
162
160
  return true;
163
161
  }
164
- /** `session …`: the prompt is the request's one file; the session owner answers one session envelope on stdout. */
165
- async function runSession(response, options, principal, parsed) {
166
- const refuse = (status, code, message) => { send(response, status, { ok: false, code, message }); return true; };
167
- if (options.session === undefined)
168
- return refuse(501, 'remote-unavailable', 'This server cannot run session commands. Update it, or use the console.');
169
- const at = parsed.argv.indexOf('--file'), file = at < 0 ? undefined : parsed.argv[at + 1];
170
- if (Object.keys(parsed.files).some(path => path !== file))
171
- return refuse(400, 'invalid-request', 'Each file must name a declared input file argument.');
172
- const out = [];
173
- let exitCode;
174
- try {
175
- exitCode = await options.session(principal, parsed.argv, parsed.files, line => { out.push(`${line}\n`); });
176
- }
177
- catch {
178
- return refuse(500, 'command-failed', 'The command stopped unexpectedly on the server. Check its state before running it again.');
179
- }
180
- send(response, 200, { exitCode, stdout: out.join(''), stderr: '' });
181
- return true;
182
- }
package/dist/cli.d.ts CHANGED
@@ -12,8 +12,7 @@
12
12
  */
13
13
  import { previewGithubRepo, cloneGithubRepo } from "./onboard.js";
14
14
  import { type QuickStartSeams } from "./quick-start.js";
15
- import { type SessionCliOptions } from "./session-cli.js";
16
- import { type TeamCliOptions } from "./team-cli.js";
15
+ import { type ConnectOptions } from "./connect-cli.js";
17
16
  import { type RemoteExecOptions } from "./remote-exec.js";
18
17
  import type { UpdateCliDeps } from "./toolroll-update-cli.js";
19
18
  import type { ReleaseCliDeps } from "./release-cli.js";
@@ -47,19 +46,13 @@ type Write = (line: string) => void;
47
46
  export { HELP };
48
47
  export declare function parseArgs(argv: readonly string[]): ParseResult;
49
48
  /** The commands that operate the queue rather than report on the world. */
50
- /** Every verb the CLI routes OUTSIDE runOperate. Exported (with
51
- * OPERATE_COMMANDS) so the declared command guide's drift tests compare
52
- * exact sets instead of regex-reading source. "" is the no-verb report,
53
- * which answers as `scan`. */
54
- export declare const TOP_LEVEL_COMMANDS: readonly string[];
55
49
  export declare const OPERATE_COMMANDS: Set<string>;
56
50
  /** binSource exists so tests can exercise linking without depending on a build. */
57
51
  export type MainOptions = {
58
52
  binSource?: string;
59
53
  operate?: OperateOptions;
60
- session?: SessionCliOptions;
61
- team?: TeamCliOptions;
62
- /** Injected by tests: the remote CLI's profile, fetch and output streams (defaults to `team`'s profile seams). */
54
+ connect?: ConnectOptions;
55
+ /** Injected by tests: the remote CLI's profile, fetch and output streams (defaults to `connect`'s profile seams). */
63
56
  remote?: RemoteExecOptions;
64
57
  /** Injected by tests: the gh-facing halves of `repos add-from-github` —
65
58
  * the verb's parsing, gating, and enrollment are what CLI tests prove;
package/dist/cli.js CHANGED
@@ -26,9 +26,8 @@ import { parseGithubRepo, previewGithubRepo, cloneGithubRepo, isLargeRepo } from
26
26
  import { run as execRun } from "./exec.js";
27
27
  import { isBareInvocation, runQuickStart } from "./quick-start.js";
28
28
  import { COMMAND_GUIDE, SURFACE_NOTES, SURFACE_SCHEMA_VERSION } from "./surface.js";
29
- import { runSessionCommand, SESSION_CLI_ACTIONS } from "./session-cli.js";
30
- import { maybeRunTeamCommand, TEAM_CLI_ACTIONS } from "./team-cli.js";
31
- import { deprecationWarning } from "./deprecations.js";
29
+ import { hasCentralProfile, maybeRunConnectCommand } from "./connect-cli.js";
30
+ import { removedCommand, removedMessage } from "./deprecations.js";
32
31
  import { maybeRunRemoteCommand } from "./remote-exec.js";
33
32
  import { discover, inspectAll } from "./discover.js";
34
33
  import { readPulls } from "./pulls.js";
@@ -107,16 +106,6 @@ export function parseArgs(argv) {
107
106
  };
108
107
  }
109
108
  /** The commands that operate the queue rather than report on the world. */
110
- /** Every verb the CLI routes OUTSIDE runOperate. Exported (with
111
- * OPERATE_COMMANDS) so the declared command guide's drift tests compare
112
- * exact sets instead of regex-reading source. "" is the no-verb report,
113
- * which answers as `scan`. */
114
- export const TOP_LEVEL_COMMANDS = [
115
- "", "scan", "pulls", "graph", "repos", "repos add", "repos remove", "repos add-from-github",
116
- "link", "unlink", "update", "release", "contract", "skills list", "skills get", "skills install", "demo",
117
- ...SESSION_CLI_ACTIONS.map(action => `session ${action}`),
118
- ...TEAM_CLI_ACTIONS,
119
- ];
120
109
  export const OPERATE_COMMANDS = new Set([
121
110
  "up",
122
111
  // Only `lead token` reaches here; the central service's lead verbs answer first.
@@ -310,21 +299,29 @@ async function dispatch(argv, write, mainOptions) {
310
299
  // hidden from help and the contract, before anything remote or local opens, changing nothing.
311
300
  if (argv[0] === "routine")
312
301
  return routinesMoved(argv, write);
302
+ // D5: the central team CLI and native sessions are gone; each old invocation says so and changes nothing.
303
+ const connectOptions = mainOptions.connect ?? mainOptions.remote;
304
+ const removed = removedCommand(argv, () => hasCentralProfile(connectOptions ?? {}));
305
+ if (removed !== null) {
306
+ const message = removedMessage(removed.named, removed.kind);
307
+ write(argv.includes("--json") ? envelopeJson({ ok: false, command: removed.named, reason: "removed", message }) : message);
308
+ return 2;
309
+ }
313
310
  // A saved API token sends commands marked remote to the central server, before anything local opens.
314
311
  // Command families below load when their command runs, so one command never pays for the others.
315
312
  if (argv[0] === 'health')
316
- return (await import("./health-cli.js")).runHealthCommand(argv.slice(1), write, mainOptions.team ?? mainOptions.remote);
313
+ return (await import("./health-cli.js")).runHealthCommand(argv.slice(1), write, mainOptions.connect ?? mainOptions.remote);
317
314
  // This computer's own service, read here and never on a central server.
318
315
  if (argv[0] === 'doctor')
319
316
  return (await import("./doctor.js")).runDoctorCommand(argv.slice(1), write);
320
- const central = await maybeRunRemoteCommand(argv, mainOptions.remote ?? mainOptions.team);
317
+ const central = await maybeRunRemoteCommand(argv, mainOptions.remote ?? mainOptions.connect);
321
318
  if (typeof central === "number")
322
319
  return central;
323
320
  if (central !== null)
324
321
  argv = central.local;
325
- const team = await maybeRunTeamCommand(argv, write, mainOptions.team);
326
- if (team !== null)
327
- return team;
322
+ const connected = await maybeRunConnectCommand(argv, write, mainOptions.connect);
323
+ if (connected !== null)
324
+ return connected;
328
325
  if ((argv[0] === "chat" || argv[0] === "brief") && argv.includes("--local"))
329
326
  argv = argv.filter(arg => arg !== "--local");
330
327
  const [first, ...rest] = argv;
@@ -345,12 +342,6 @@ async function dispatch(argv, write, mainOptions) {
345
342
  }
346
343
  if (first === "contract")
347
344
  return runContractCommand(rest, write);
348
- if (first === "session") {
349
- // D5: native coding sessions are deprecated. They still run this release; each use says so once, on stderr.
350
- (mainOptions.session?.stderr ?? (line => process.stderr.write(`${line}\n`)))(deprecationWarning(`session ${rest.find(arg => !arg.startsWith("-")) ?? ""}`.trim(), "session"));
351
- // D7: it runs on the saved connection over /api/cli, signed in with that profile's API token.
352
- return runSessionCommand(rest, write, { ...(mainOptions.remote ?? mainOptions.team), ...mainOptions.session });
353
- }
354
345
  if (first === "demo")
355
346
  return runDemoCommand(rest, write);
356
347
  if (first === "skills")
@@ -6,6 +6,7 @@ import { createContainer, currentContainment } from "./containment.js";
6
6
  import { processMayBeAlive } from "./process-liveness.js";
7
7
  import { observeProcessTree, sampleProcessTree, stopProcessTree } from "./process-tree.js";
8
8
  import { isOwnEnv } from "./names.js";
9
+ import { sleep } from "./sleep.js";
9
10
  /** A response from the server, distinct from an ambiguous transport failure. */
10
11
  export class CodingProviderRequestError extends Error {
11
12
  code;
@@ -374,7 +375,7 @@ export function createCodexCodingProvider(options = {}) {
374
375
  const absent = () => (owned.pid === undefined || !processMayBeAlive(owned.pid, process.platform !== "win32")) && [...descendants.values()].every(({ pid, group }) => !processMayBeAlive(pid, group));
375
376
  const until = Date.now() + EXIT_GRACE_MS;
376
377
  while (!ancestryUnknown && !absent() && Date.now() < until)
377
- await new Promise(resolve => setTimeout(resolve, 25));
378
+ await sleep(25);
378
379
  clean = clean && !ancestryUnknown && absent();
379
380
  }
380
381
  if (!clean)
@@ -1,6 +1,6 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { mkdirSync, chmodSync, realpathSync } from 'node:fs';
3
- import { dirname, join, resolve } from 'node:path';
3
+ import { dirname, join } from 'node:path';
4
4
  import { hostname } from 'node:os';
5
5
  import { provenDeadByBootChange } from './boot-identity.js';
6
6
  import { containerEmptiness } from './container-state.js';
@@ -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];