@foldrun/core 2.14.3 → 3.0.0

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.
package/src/step-exec.ts CHANGED
@@ -87,6 +87,9 @@ export interface ExecOutcome {
87
87
  }
88
88
 
89
89
  export interface ExecOptions {
90
+ /** A person approved this step before it ran: its file tools may write
91
+ * the workspace's configuration and code (confine.ts, FOL-96). */
92
+ configWrites?: boolean;
90
93
  /** What the step reports beyond its events (FOL-54 step 4): `metadata`
91
94
  * sends each model call (`turn`); `full` also sends each tool call's input
92
95
  * and output (`io`). Unset sends neither — the consult, the probe and any
@@ -520,7 +523,7 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
520
523
  }
521
524
  }
522
525
  const verdict = isFilesystemTool(hookInput.tool_name)
523
- ? checkPaths(hookInput.tool_name, hookInput.tool_input as Record<string, unknown>, { agentDir, workspaceRoot, libraryRoot })
526
+ ? checkPaths(hookInput.tool_name, hookInput.tool_input as Record<string, unknown>, { agentDir, workspaceRoot, libraryRoot, configWrites: opts.configWrites })
524
527
  : null;
525
528
  if (verdict && !verdict.ok) {
526
529
  emit("error", verdict.reason!, via ? { subagent: via } : undefined);
@@ -560,7 +563,7 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
560
563
  const again = hookInput.tool_name === "Bash"
561
564
  ? checkBash(String(v.input.command ?? ""))
562
565
  : isFilesystemTool(hookInput.tool_name)
563
- ? checkPaths(hookInput.tool_name, v.input, { agentDir, workspaceRoot, libraryRoot })
566
+ ? checkPaths(hookInput.tool_name, v.input, { agentDir, workspaceRoot, libraryRoot, configWrites: opts.configWrites })
564
567
  : { ok: true as const };
565
568
  const granted = opts.allowed.includes(hookInput.tool_name) || opts.mcpNames.some((n) => hookInput.tool_name.startsWith(`mcp__${n}__`));
566
569
  // The sub-agent guard read the ORIGINAL input: a rewrite of a
@@ -628,7 +631,7 @@ async function executeStepInner(opts: ExecOptions, runQuery: QueryFn): Promise<E
628
631
  toolName === "Bash"
629
632
  ? checkBash(String(input.command ?? ""))
630
633
  : isFilesystemTool(toolName)
631
- ? checkPaths(toolName, input, { agentDir, workspaceRoot, libraryRoot })
634
+ ? checkPaths(toolName, input, { agentDir, workspaceRoot, libraryRoot, configWrites: opts.configWrites })
632
635
  : { ok: true as const };
633
636
  if (!verdict.ok) {
634
637
  refund();
package/src/store.ts CHANGED
@@ -3533,6 +3533,14 @@ export interface StepRecord {
3533
3533
  /** On an isolated sub-agent's run: the workspace paths its sandbox's
3534
3534
  * changes were applied to — what its delegating step is told it wrote. */
3535
3535
  wrote?: string[];
3536
+ /** What this step's sandbox changed that was not taken back to the
3537
+ * workspace: configuration and code, which a step proposes and a person
3538
+ * applies (copy-back.ts, FOL-96). */
3539
+ refused?: { path: string; kind: "config" | "code" }[];
3540
+ /** What the host handed back into this step's sandbox mid-step — its
3541
+ * `sandbox: own` jobs' files, path → sha256 — recorded HERE, on the host,
3542
+ * so the copy-out never has to take the sandbox's word for it (FOL-96). */
3543
+ handedBack?: Record<string, string | string[]>;
3536
3544
  /**
3537
3545
  * Sandbox seconds this step rented, and how many of them were cold start.
3538
3546
  * Set only on the isolated path — an in-process step rents nothing, so