toolroll 0.9.69 → 0.9.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +43 -17
  3. package/THIRD_PARTY_NOTICES.md +21 -1
  4. package/dist/agent-fence.d.ts +180 -115
  5. package/dist/agent-fence.js +413 -535
  6. package/dist/agentconfig.js +6 -1
  7. package/dist/browser/THIRD_PARTY_NOTICES.txt +21 -0
  8. package/dist/builder.js +24 -4
  9. package/dist/check-access.d.ts +6 -9
  10. package/dist/check-access.js +7 -10
  11. package/dist/coding-provider.d.ts +14 -0
  12. package/dist/coding-provider.js +33 -1
  13. package/dist/coding-workspace.d.ts +15 -0
  14. package/dist/coding-workspace.js +9 -2
  15. package/dist/contracts/console-api.d.ts +1 -0
  16. package/dist/contracts/console-api.js +1 -0
  17. package/dist/doctor.d.ts +7 -0
  18. package/dist/doctor.js +4 -0
  19. package/dist/egress.d.ts +2 -0
  20. package/dist/egress.js +13 -4
  21. package/dist/exec.d.ts +17 -3
  22. package/dist/exec.js +54 -17
  23. package/dist/flow-actions.d.ts +1 -0
  24. package/dist/flow-actions.js +3 -2
  25. package/dist/flow-code.d.ts +31 -2
  26. package/dist/flow-code.js +57 -5
  27. package/dist/flow-steps.d.ts +4 -0
  28. package/dist/flow-steps.js +9 -5
  29. package/dist/flow-triggers.d.ts +4 -0
  30. package/dist/flow-triggers.js +8 -3
  31. package/dist/invoke.d.ts +18 -8
  32. package/dist/invoke.js +98 -115
  33. package/dist/operate.js +46 -0
  34. package/dist/project-delete-ui.d.ts +7 -6
  35. package/dist/project-delete-ui.js +36 -2
  36. package/dist/project-sandbox.d.ts +59 -0
  37. package/dist/project-sandbox.js +83 -0
  38. package/dist/project-tools.d.ts +29 -5
  39. package/dist/project-tools.js +43 -7
  40. package/dist/release.d.ts +1 -1
  41. package/dist/release.js +1 -0
  42. package/dist/sandbox-launch.d.ts +1 -0
  43. package/dist/sandbox-launch.js +148 -0
  44. package/dist/sandbox-runtime.d.ts +62 -0
  45. package/dist/sandbox-runtime.js +140 -0
  46. package/dist/scope.d.ts +28 -0
  47. package/dist/scope.js +25 -5
  48. package/dist/scout-browser-lock.d.ts +69 -0
  49. package/dist/scout-browser-lock.js +69 -0
  50. package/dist/scout-browser.d.ts +60 -1
  51. package/dist/scout-browser.js +200 -4
  52. package/dist/scout.d.ts +9 -4
  53. package/dist/scout.js +14 -4
  54. package/dist/serve.js +3 -1
  55. package/dist/server/render-tasks.js +4 -1
  56. package/dist/server/route-table.js +1 -0
  57. package/dist/server/settings.js +27 -0
  58. package/dist/store.d.ts +31 -1
  59. package/dist/store.js +83 -4
  60. package/dist/subagent-tools.d.ts +1 -0
  61. package/dist/subagent-tools.js +7 -5
  62. package/dist/tool-broker.d.ts +2 -1
  63. package/dist/tool-broker.js +2 -2
  64. package/dist/tool-launcher.js +10 -16
  65. package/package.json +2 -1
  66. package/scripts/provider-canary.mjs +5 -6
  67. package/dist/egress-bridge.d.ts +0 -17
  68. package/dist/egress-bridge.js +0 -111
@@ -75,4 +75,5 @@ export declare function toolWaiting(store: Store, stage: FlowStage, repo: string
75
75
  export declare function useTool(store: Store, stage: FlowStage, card: FlowCardRow, repo: string, io: {
76
76
  callTool?: ToolCaller;
77
77
  toolHome?: string;
78
+ fenceAvailable?: () => boolean;
78
79
  }): Promise<ActionOutcome>;
@@ -25,7 +25,7 @@ import { googleAccessToken, googleConnected } from "./google-mail.js";
25
25
  import { ADDRESS, readEmailSettings } from "./email-settings.js";
26
26
  import { readFlowSecrets, scrubSecrets } from "./flow-secrets.js";
27
27
  export { readEmailSettings, saveEmailSettings } from "./email-settings.js";
28
- import { callProjectTool, projectToolsOf, readToolSecrets } from "./project-tools.js";
28
+ import { callProjectTool, projectToolsOf, readToolSecrets, toolServerFor } from "./project-tools.js";
29
29
  import { ALL_CREDENTIAL_ENV } from "./provider.js";
30
30
  const clip = (text, cap) => text.length <= cap ? text : `${text.slice(0, cap - 1)}…`;
31
31
  const blank = (text) => redactSecretAssignments(redactSecretLines(text, scanForSecrets(text)));
@@ -189,7 +189,8 @@ export async function useTool(store, stage, card, repo, io) {
189
189
  const text = { title: card.title, description: card.description, note: card.note, outputs: card.outputs };
190
190
  const args = fillJson(JSON.parse(call.args), one => fillFlowText(one, text));
191
191
  const values = readToolSecrets(repo, tool.name, io.toolHome);
192
- const answer = await (io.callTool ?? ((spec, secrets, name, given) => callProjectTool(spec, secrets, name, given, { timeoutMs: 120_000, omitEnv: ALL_CREDENTIAL_ENV })))(tool.spec, values, call.name, args);
192
+ const answer = await (io.callTool ?? ((spec, secrets, name, given) => callProjectTool(spec, secrets, name, given, { timeoutMs: 120_000, omitEnv: ALL_CREDENTIAL_ENV,
193
+ server: toolServerFor(store, repo, { ...(io.toolHome === undefined ? {} : { home: io.toolHome }), ...(io.fenceAvailable === undefined ? {} : { available: io.fenceAvailable }) }) })))(tool.spec, values, call.name, args);
193
194
  if (!answer.ok)
194
195
  return { state: "retry", said: `${call.server}: ${answer.problem}` };
195
196
  const said = blank(scrub(clip(answer.text, 64_000), values));
@@ -1,5 +1,6 @@
1
1
  import type { Runner } from "./backend.js";
2
- import type { FlowScriptRow } from "./store.js";
2
+ import { type FencePolicy, type LaunchFence } from "./agent-fence.js";
3
+ import type { FlowScriptRow, Store } from "./store.js";
3
4
  import { projectFile } from "./project-file.js";
4
5
  export { projectFile };
5
6
  export { LANGUAGE_WORDS, SCRIPT_LANGUAGES, type ScriptLanguage } from "./flows.js";
@@ -17,7 +18,9 @@ export type CodeRun = {
17
18
  secrets: Record<string, string>;
18
19
  scratch: string;
19
20
  shell: Runner;
20
- fence: readonly string[];
21
+ /** Where it runs from (flowLaunch): its fence, and the launch folder its input and an inline script are written to;
22
+ * null only where there is nothing to fence (a store with no database file). */
23
+ launch: FlowLaunch | null;
21
24
  };
22
25
  /** `printed`: everything it printed (secrets blanked), for a trigger to read items from; `output` is what a card keeps. */
23
26
  export type CodeResult = {
@@ -49,3 +52,29 @@ export declare function cardsFromOutput(stdout: string): {
49
52
  title: string;
50
53
  description: string | null;
51
54
  }[];
55
+ /**
56
+ * Where one flow launch runs (docs/plans/sandbox-parity.md C1): the fence `fenceFor` makes for a flow's setup, script
57
+ * or trigger, around `folder` (a copy of the card's work, bound to its project, or a clean folder), with a launch folder
58
+ * of its own for its input, an inline script and its temp files; writes as the project's writes setting allows (its
59
+ * folder, the copy's Git data, its project's caches); the scratch pool denied around its own folder; and its network
60
+ * open, as flows' scripts have always had it (they reach the services their secrets are for; no approval seals a
61
+ * network for them). The fence is required: where this machine can't confine it, it runs only when an instance operator
62
+ * allowed unconfined agents, and otherwise nothing runs and the card says why.
63
+ */
64
+ export type FlowLaunch = {
65
+ folder: string;
66
+ env: Record<string, string>;
67
+ fence?: LaunchFence;
68
+ dispose(): void;
69
+ };
70
+ export declare function flowLaunch(store: Store, options: {
71
+ repo: string;
72
+ folder: string;
73
+ scratch: string;
74
+ copy: boolean;
75
+ available?: () => boolean;
76
+ policy?: FencePolicy;
77
+ home?: string;
78
+ }): FlowLaunch | null | {
79
+ problem: string;
80
+ };
package/dist/flow-code.js CHANGED
@@ -15,9 +15,13 @@
15
15
  * keys are out of reach), under a time limit, with the setup's plain
16
16
  * environment rather than this process's.
17
17
  */
18
- import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
18
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
19
19
  import { randomBytes } from "node:crypto";
20
- import { join } from "node:path";
20
+ import { homedir, tmpdir } from "node:os";
21
+ import { dirname, join } from "node:path";
22
+ import { buildCacheEnv, buildCacheFolder, checkLaunchRoot, fenceFor, linuxFenceAvailable, macosFenceAvailable, runFolders, taskBoundary, trustedWorktreeBinding, writeAllowlist } from "./agent-fence.js";
23
+ import { readDenyPath } from "./project-sandbox.js";
24
+ import { poolRootOf } from "./worktree.js";
21
25
  import { SETUP_ENV_ALLOWLIST, SETUP_ENV_DENYLIST } from "./builder.js";
22
26
  import { redactSecretLines, scanForSecrets } from "./evidence.js";
23
27
  import { scrubSecrets } from "./flow-secrets.js";
@@ -51,7 +55,10 @@ export async function runCode(run) {
51
55
  const language = script.language;
52
56
  const tag = randomBytes(6).toString("hex");
53
57
  mkdirSync(run.scratch, { recursive: true });
54
- const inputFile = join(run.scratch, `flow-input-${tag}.json`);
58
+ // The input and an inline script live in the launch's own folder, the one place beside its working folder its fence
59
+ // gives back (every other launch folder, and every other flow copy in the scratch pool, stay out of reach).
60
+ const files = run.launch?.folder ?? run.scratch;
61
+ const inputFile = join(files, `flow-input-${tag}.json`);
55
62
  let scriptFile = null;
56
63
  try {
57
64
  writeFileSync(inputFile, JSON.stringify(run.input), { mode: 0o600 });
@@ -61,7 +68,7 @@ export async function runCode(run) {
61
68
  return { state: "failed", exitCode: null, output: "", printed: "", goTo: null, log: "", said: `${script.name} runs ${script.file}, which isn't a file in the project.` };
62
69
  }
63
70
  else {
64
- const own = join(run.scratch, `flow-script-${tag}.${EXTENSION[language]}`);
71
+ const own = join(files, `flow-script-${tag}.${EXTENSION[language]}`);
65
72
  writeFileSync(own, `${script.body}\n`, { mode: 0o700 });
66
73
  chmodSync(own, 0o700);
67
74
  scriptFile = own;
@@ -71,7 +78,7 @@ export async function runCode(run) {
71
78
  // first without breaking anything. Only our own paths are in the command; the card is only data.
72
79
  const ran = await run.shell("/bin/sh", ["-c", 'exec "$@" < "$FLOW_INPUT"', "flow-script", runtime.file, ...runtime.args], {
73
80
  cwd: run.cwd, timeoutMs: script.timeoutMinutes * 60_000, envAllowlist: SETUP_ENV_ALLOWLIST, omitEnv: SETUP_ENV_DENYLIST, processGroup: true,
74
- env: { ...run.env, ...run.secrets, FLOW_INPUT: inputFile }, fence: run.fence,
81
+ env: { ...run.env, ...run.secrets, ...(run.launch?.env ?? {}), FLOW_INPUT: inputFile }, ...(run.launch?.fence ?? {}),
75
82
  });
76
83
  const printed = codeOutput(ran.stdout, run.secrets);
77
84
  const all = scrubSecrets(ran.stdout.slice(0, 1_000_000), run.secrets);
@@ -148,3 +155,48 @@ export function cardsFromOutput(stdout) {
148
155
  return asItem(trimmed);
149
156
  }).filter((one) => one !== null);
150
157
  }
158
+ export function flowLaunch(store, options) {
159
+ const databaseFile = store.databaseFile();
160
+ if (databaseFile === null || databaseFile === "" || databaseFile === ":memory:")
161
+ return null;
162
+ const available = options.available ?? (() => macosFenceAvailable() || linuxFenceAvailable());
163
+ const fenced = available();
164
+ // Asked first: a launch with no fence of its own (an instance operator allowed it, as inside another sandbox) makes
165
+ // its folder in the temp folder it was given, never among the fenced launches' (which an outer sandbox may hold).
166
+ if (!fenced && !store.unfencedAgentsAllowed()) {
167
+ return { problem: "This machine can't confine the flow's script (no macOS sandbox, no working bubblewrap), so it didn't run. Install bubblewrap, socat and ripgrep (Linux), or an instance operator allows unconfined agents with `toolroll config unconfined-agents allow`." };
168
+ }
169
+ let own;
170
+ try {
171
+ own = realpathSync(mkdtempSync(join(fenced ? checkLaunchRoot() : tmpdir(), "so-cmd-")));
172
+ }
173
+ catch (error) {
174
+ return { problem: `The flow's launch folder couldn't be made, so nothing ran: ${error instanceof Error ? error.message : String(error)}` };
175
+ }
176
+ const dispose = () => rmSync(own, { recursive: true, force: true });
177
+ const temp = join(own, "tmp");
178
+ mkdirSync(temp, { mode: 0o700 });
179
+ const env = { TMPDIR: temp, TMP: temp, TEMP: temp };
180
+ if (!fenced)
181
+ return { folder: own, env, dispose };
182
+ try {
183
+ const poolRoots = [join(dirname(databaseFile), "worktrees"), options.scratch, ...store.listWorktrees().map(record => poolRootOf(record.path, record.repo))];
184
+ // Its scratch folder is a pool root (so another flow's copy is out of reach) and sealed: never read, at any policy.
185
+ // A copy is held to its project by the plane's own record of the repository, never what the copy says.
186
+ const binding = options.copy ? trustedWorktreeBinding(options.repo, options.folder, poolRoots) : null;
187
+ // The policy its flow sealed; nothing sealed is v1 (the home configuration denied), never a wider one implicitly.
188
+ const home = options.home ?? homedir();
189
+ const boundary = taskBoundary({ worktree: options.folder, provider: "command", home, binding, policy: options.policy ?? 1 });
190
+ const cache = buildCacheFolder(options.repo, home);
191
+ const allowed = store.projectWrites(options.repo) !== "workspace" ? null : writeAllowlist({ provider: "command", home, worktree: options.folder, binding, launchFolder: own, cache });
192
+ // The project's read denies apply to flows as to builds (read live: a flow seals no list, and these only narrow).
193
+ const readDeny = store.projectSandbox(options.repo).readDeny.map(one => readDenyPath(one, home));
194
+ const fence = fenceFor("flow", { databaseFile, worktree: options.folder, home, boundary, readDeny, policy: options.policy ?? 1, scratchRoots: [options.scratch], binding, runFolders: runFolders(own), poolRoots, extra: allowed?.deny ?? [],
195
+ writes: allowed === null ? null : { writes: allowed.writes, readOnly: allowed.readOnly }, network: { mode: "open" } });
196
+ return { folder: own, env: { ...env, ...(allowed === null ? {} : buildCacheEnv(cache)) }, fence, dispose };
197
+ }
198
+ catch {
199
+ dispose();
200
+ return { problem: "The flow's copy couldn't be bound to its project, so nothing ran." };
201
+ }
202
+ }
@@ -14,6 +14,10 @@ export type StepIo = {
14
14
  dir: string | null;
15
15
  /** Where temporary copies of a card's work are made. */
16
16
  scratch: string;
17
+ /** Whether this machine can confine a script (tests only; default: the macOS sandbox or bubblewrap is here). */
18
+ fenceAvailable?: () => boolean;
19
+ /** The home folder a launch is fenced around (tests only; default: this user's). */
20
+ home?: string;
17
21
  evidenceRoot?: string;
18
22
  /** The branch a card with no result yet is checked against. */
19
23
  base: string;
@@ -35,8 +35,7 @@ import { JEV_MODEL, askJev, readJevAnswers, sortLog, sortRequest, sortState, sor
35
35
  import { claudeDraftRunner, DRAFT_CHARS, DRAFT_TIMEOUT, draftPrompt, wholeDraft } from "./flow-draft.js";
36
36
  import { overruns, passOn, TEXT_LIMITS, writeWithin } from "./text-limits.js";
37
37
  import { readFlowSecrets, sendingReady, runRequest, sendEmail, toolWaiting, useTool } from "./flow-actions.js";
38
- import { cleanFolder, runCode } from "./flow-code.js";
39
- import { agentFence } from "./agent-fence.js";
38
+ import { cleanFolder, flowLaunch, runCode } from "./flow-code.js";
40
39
  import { readProviderKey } from "./keys.js";
41
40
  import { replyInChannel } from "./chat-inbox.js";
42
41
  import { recordSent, threadOf } from "./flow-replies.js";
@@ -362,13 +361,16 @@ async function runCheck(store, flow, stage, script, card, now, io) {
362
361
  if (added.code !== 0)
363
362
  return { state: "retry", said: `Couldn't make a copy of${where}: ${added.stderr.trim().split("\n")[0]?.slice(0, 160) ?? "git refused"}.` };
364
363
  }
364
+ const launch = flowLaunch(store, { repo: flow.repo, folder: path, scratch: io.scratch, copy, policy: store.flowFencePolicy(flow.id), ...(io.fenceAvailable === undefined ? {} : { available: io.fenceAvailable }), ...(io.home === undefined ? {} : { home: io.home }) });
365
365
  try {
366
- const fence = agentFence({ databaseFile: store.databaseFile(), worktree: path });
366
+ if (launch !== null && "problem" in launch)
367
+ return { state: "failed", said: launch.problem };
367
368
  let log = "";
368
369
  const setup = copy ? store.liveWorktreeSetup(flow.repo) : null;
369
370
  if (setup !== null) {
370
371
  const shell = approvedCommandShell(setup.command);
371
- const prepared = await io.shell(shell.file, shell.args, { cwd: path, timeoutMs: setup.timeoutMs, envAllowlist: SETUP_ENV_ALLOWLIST, omitEnv: SETUP_ENV_DENYLIST, processGroup: true, fence });
372
+ const prepared = await io.shell(shell.file, shell.args, { cwd: path, timeoutMs: setup.timeoutMs, envAllowlist: SETUP_ENV_ALLOWLIST, omitEnv: SETUP_ENV_DENYLIST, processGroup: true,
373
+ ...(launch === null ? {} : { env: launch.env, ...(launch.fence ?? {}) }) });
372
374
  log += `$ ${setup.command}\n${prepared.stdout}${prepared.stderr}\n`;
373
375
  if (prepared.code !== 0)
374
376
  return { state: "failed", said: `The project's setup failed before ${script.name} ran (exit ${prepared.code}).\n${tail(`${prepared.stdout}\n${prepared.stderr}`)}`, log, exitCode: prepared.code };
@@ -383,7 +385,7 @@ async function runCheck(store, flow, stage, script, card, now, io) {
383
385
  answers: (stage.routes ?? []).map(one => one.answer),
384
386
  };
385
387
  const env = { FLOW_NAME: flow.name, FLOW_CARD_ID: String(card.id), FLOW_CARD_TITLE: card.title, FLOW_COMMIT: commit ?? "", FLOW_SCRIPT: script.name, FLOW_PROJECT: flow.repo };
386
- const ran = await runCode({ script, cwd: path, root: copy ? path : flow.repo, input, env, secrets, scratch: io.scratch, shell: io.shell, fence });
388
+ const ran = await runCode({ script, cwd: path, root: copy ? path : flow.repo, input, env, secrets, scratch: io.scratch, shell: io.shell, launch });
387
389
  log += ran.log;
388
390
  const output = ran.output === "" ? undefined : ran.output;
389
391
  // Where it ran goes at the end of the first line: "run-tests failed (exit 1) on main."
@@ -398,6 +400,8 @@ async function runCheck(store, flow, stage, script, card, now, io) {
398
400
  return { state: "passed", said: `${script.name} ran${where} and picked ${route.answer}.`, log, exitCode: 0, to: route.to, ...(output === undefined ? {} : { output }) };
399
401
  }
400
402
  finally {
403
+ if (launch !== null && !("problem" in launch))
404
+ launch.dispose();
401
405
  if (copy)
402
406
  await io.git("git", ["-C", flow.repo, "worktree", "remove", "--force", path], { timeoutMs: 60_000 }).catch(() => undefined);
403
407
  rmSync(path, { recursive: true, force: true });
@@ -141,6 +141,10 @@ export type TriggerIo = {
141
141
  /** v90: how a schedule's script runs (default: the same runner as gh) and where (default: beside the database). */
142
142
  shell?: Runner;
143
143
  scratch?: string;
144
+ /** Whether this machine can confine a script (tests only; default: the macOS sandbox or bubblewrap is here). */
145
+ fenceAvailable?: () => boolean;
146
+ /** The home folder a launch is fenced around (tests only; default: this user's). */
147
+ home?: string;
144
148
  };
145
149
  export type TriggerPass = {
146
150
  added: number;
@@ -859,21 +859,26 @@ async function fireScript(store, trigger, config, now, io, scheduled) {
859
859
  if (missing.length > 0)
860
860
  return done(false, `${script.name} needs the secret${missing.length === 1 ? "" : "s"} ${missing.join(", ")}. Save ${missing.length === 1 ? "it" : "them"} on the flow first.`);
861
861
  // Loaded when a script first runs: the runner reaches the builder's modules, which reach back to these triggers.
862
- const { cardsFromOutput, cleanFolder, runCode } = await import("./flow-code.js");
863
- const { agentFence } = await import("./agent-fence.js");
862
+ const { cardsFromOutput, cleanFolder, flowLaunch, runCode } = await import("./flow-code.js");
864
863
  const scratch = io.scratch ?? join(io.dir ?? tmpdir(), "flow-scratch");
865
864
  const folder = cleanFolder(scratch, `flow-trigger-${trigger.id}`);
866
865
  // Its cards run on their own only if it ran the very code a grant signed: pinned just before and just after the run.
867
866
  const pin = scheduled && grantSignsScriptOf(store, flow.id, trigger.id, now) ? () => scriptProvenanceOf(store, flow.repo, script.name) : null;
868
867
  const before = pin === null ? null : await pin();
869
868
  let ran;
869
+ // A full boundary around its clean folder, though it has no checkout (flow-code.ts flowLaunch).
870
+ const launch = flowLaunch(store, { repo: flow.repo, folder, scratch, copy: false, policy: store.flowFencePolicy(flow.id), ...(io.fenceAvailable === undefined ? {} : { available: io.fenceAvailable }), ...(io.home === undefined ? {} : { home: io.home }) });
870
871
  try {
872
+ if (launch !== null && "problem" in launch)
873
+ return done(false, launch.problem);
871
874
  ran = await runCode({ script, cwd: folder, root: flow.repo, scratch, shell: io.shell ?? io.gh,
872
875
  input: { flow: { id: flow.id, name: flow.name }, trigger: trigger.id, lastRun: trigger.lastAt }, secrets: Object.fromEntries((config.secrets ?? []).map(name => [name, saved[name]])),
873
876
  env: { FLOW_NAME: flow.name, FLOW_TRIGGER_ID: String(trigger.id), FLOW_PROJECT: flow.repo, FLOW_LAST_RUN: trigger.lastAt ?? "" },
874
- fence: agentFence({ databaseFile: store.databaseFile(), worktree: folder }) });
877
+ launch });
875
878
  }
876
879
  finally {
880
+ if (launch !== null && !("problem" in launch))
881
+ launch.dispose();
877
882
  rmSync(folder, { recursive: true, force: true });
878
883
  }
879
884
  if (ran.state === "failed")
package/dist/invoke.d.ts CHANGED
@@ -23,7 +23,7 @@ import type { RunOptions } from "./exec.js";
23
23
  import type { Runner } from "./worktree.js";
24
24
  import { type NetworkFence, type SignInGrants } from "./agent-fence.js";
25
25
  import { type CheckAccess } from "./check-access.js";
26
- import { type WritesMode } from "./agent-fence.js";
26
+ import { type WritesMode, type LaunchSandbox, type FenceGrants } from "./agent-fence.js";
27
27
  import { type NetworkProfile } from "./egress.js";
28
28
  export type { ProviderRunner } from "./provider.js";
29
29
  export type ProviderUsage = {
@@ -111,6 +111,7 @@ export declare function invokeAgent(store: Store, runId: number, spec: AgentSpec
111
111
  accumulateUsage?: boolean;
112
112
  network?: LaunchNetwork;
113
113
  writes?: WritesMode;
114
+ sandbox?: LaunchSandbox;
114
115
  }): Promise<InvokeResult>;
115
116
  /**
116
117
  * A build's setup and check commands, run in its fence (arc 2): the same denies as its agent's (Toolroll's state,
@@ -119,20 +120,20 @@ export declare function invokeAgent(store: Store, runId: number, spec: AgentSpec
119
120
  * Their network stays as it was. A machine without an OS fence runs them as before; a fence that can't be prepared
120
121
  * (a checkout whose binding no longer proves) runs nothing.
121
122
  */
122
- export declare function fencedCommands(store: Store, runId: number, writes: WritesMode, inner: Runner, available?: () => boolean, access?: CheckAccess, network?: NetworkProfile): Runner;
123
+ export declare function fencedCommands(store: Store, runId: number, writes: WritesMode, inner: Runner, available?: () => boolean, access?: CheckAccess, network?: NetworkProfile, sandbox?: LaunchSandbox): Runner;
123
124
  /**
124
125
  * A project's checks run in a checkout of a result's commit made for them (result-follow-ups.ts, batch-checks.ts), in
125
126
  * the same fence as a build's commands: Toolroll's state, credentials, other projects and runs denied; under the
126
127
  * project's writes setting, only that checkout, its Git data, its own temp folder, the project's caches and its check
127
128
  * database writable. Where nothing can confine them, they run only when an instance operator allowed unconfined agents.
128
129
  */
129
- export declare function fencedCheckoutCommands(store: Store, repo: string, checkout: string, inner: Runner, available?: () => boolean, access?: CheckAccess, network?: NetworkProfile): Runner;
130
+ export declare function fencedCheckoutCommands(store: Store, repo: string, checkout: string, inner: Runner, available?: () => boolean, access?: CheckAccess, network?: NetworkProfile, sandbox?: LaunchSandbox): Runner;
130
131
  /**
131
- * A `sign-ins` check's launch (check-access.ts): its exact sign-in files and its own transcript folder
132
- * (agent-fence.ts signInGrants), Codex's config and sign-in copied into a home of the launch's own (private, gone with
133
- * the launch; ~/.codex itself stays fenced), gh pointed at its own folder, and the network the check runs under:
134
- * open, or its allowlist through a proxy of its own (on Linux, carried in by a bridge). Nothing about it is started
135
- * unless all of it is.
132
+ * A `sign-ins` check's launch (check-access.ts, sandbox parity C10): its exact sign-in files, read by the check itself
133
+ * through its sandbox (agent-fence.ts signInGrants: nothing privileged reads or copies one), its own transcript folder
134
+ * and Codex's session and log folders; Codex, gh and XDG pointed at the real folders; and the network the check runs
135
+ * under: open, or its allowlist through a proxy of its own (on Linux, relayed in by the sandbox runtime). Nothing about it is
136
+ * started unless all of it is.
136
137
  */
137
138
  export declare function signInsLaunch(store: Store, own: string, temp: string, checkout: string, policy: NetworkProfile, gitPaths: readonly string[], platform?: NodeJS.Platform): Promise<{
138
139
  grants: SignInGrants;
@@ -162,3 +163,12 @@ export declare const ranAtLine: (level: CheckAccess) => string | null;
162
163
  /** A saved result's check command runner (batch-checks.ts, result-follow-ups.ts): its level and network worked out right
163
164
  * now (checkoutLaunchOf), and how it actually runs told to `ran`. */
164
165
  export declare function checkoutRunner(store: Store, repo: string, checkout: string, inner: Runner, runIds: readonly number[], ran: (level: CheckAccess) => void, platform?: NodeJS.Platform): Runner;
166
+ /**
167
+ * A saved result's checks' sandbox terms (sandbox parity C9), never wider than any of those results' own approvals nor
168
+ * the project now: v2 only when every result's approval sealed v2; every read deny any of them sealed or the project
169
+ * lists now; mach services only those all of them sealed and the project still lists. A result with
170
+ * no readable approval counts as v1 with nothing listed.
171
+ */
172
+ export declare function checkoutSandboxOf(store: Store, repo: string, runIds: readonly number[]): LaunchSandbox;
173
+ /** The digest of a launch's whole fence (its denied paths and grants, canonical), as stamped on its run (C11). */
174
+ export declare function fenceDigest(fence: readonly string[], grants: FenceGrants): string;