@foldrun/core 2.7.0 → 2.9.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.
@@ -477,6 +477,7 @@ try {
477
477
  const { buildConsultTools } = await import("@foldrun/core/agent-tools");
478
478
  const { buildSearchTools, buildHistoryTools, buildDeskTools } = await import("@foldrun/core/context-tools");
479
479
  const { httpChannel, buildAskTool, buildPeerTools, inboxReader } = await import("@foldrun/core/operator");
480
+ const { buildDelegateTools } = await import("@foldrun/core/delegate-tools");
480
481
  const { startTranslator } = await import("@foldrun/core/translator");
481
482
  const { prepareRuntime } = await import("@foldrun/core/runtime");
482
483
  const { materializeFileSecrets } = await import("@foldrun/core/secret-files");
@@ -576,6 +577,9 @@ try {
576
577
  const ask = input.ask ? buildAskTool({ channel, timeoutSec: input.ask.timeoutSec, emit }) : { server: null };
577
578
  const peers = input.peers ? buildPeerTools({ channel, self: input.peers.self, siblings: input.peers.siblings, emit }) : { server: null };
578
579
  const inbox = channel ? inboxReader(channel, emit) : undefined;
580
+ // sandbox: own sub-agents: a tool each, whose job runs in its own sandbox
581
+ // on the worker, reached over the same proxy door.
582
+ const delegate = input.delegates?.length ? buildDelegateTools({ specs: input.delegates, channel, emit, timeoutSec: input.timeoutSec }) : { server: null };
579
583
 
580
584
  // Keyless cloud identities (FOLDRUN_IDENTITIES): each token file kept
581
585
  // fresh through the proxy's /identity door, which mints only what this
@@ -627,6 +631,7 @@ try {
627
631
  ...(desks.server ? { foldrun_desks: desks.server } : {}),
628
632
  ...(ask.server ? { foldrun_ask: ask.server } : {}),
629
633
  ...(peers.server ? { foldrun_peers: peers.server } : {}),
634
+ ...(delegate.server ? { foldrun_delegate: delegate.server } : {}),
630
635
  ...(install.server ? { foldrun_install: install.server } : {}),
631
636
  ...input.mcpServers,
632
637
  },
@@ -737,8 +742,11 @@ export const RETRY_SH = `retry() { what=$1; shift; n=1; until "$@"; do if [ "$n"
737
742
  * and make one browse call per engine on the full image:
738
743
  * docker buildx imagetools inspect node:22-trixie-slim --format '{{json .Manifest}}' | jq -r .digest
739
744
  * The digest names the multi-arch index, so the one pin serves amd64 and
740
- * arm64 alike. */
741
- export const NODE_BASE = "node:22-trixie-slim@sha256:b26b04c123d9ff8ab646ceb18b9d75a1173acf64b9a401094b906d27b29338d4";
745
+ * arm64 alike.
746
+ *
747
+ * 23 Oct 2026: Node 22.23.4 (the 21 Oct security release: Brotli decoder
748
+ * RCE, TLS hostname bypasses); the pin before carried 22.23.3. */
749
+ export const NODE_BASE = "node:22-trixie-slim@sha256:8eb619e402eaede1748a3c8173bffab391592812e85136452f781741c7242ddf";
742
750
  // Two images from one file, as build targets.
743
751
  //
744
752
  // full (the default, the last stage) — everything, browsers included. The
@@ -1487,8 +1495,9 @@ export async function runStepInContainer(args) {
1487
1495
  const wsOut = path.join(staging, "out");
1488
1496
  fs.mkdirSync(wsOut);
1489
1497
  const back = spawnSync(cli(), ["cp", `${containerId}:/workspace/.`, wsOut], { encoding: "utf8" });
1498
+ let applied = [];
1490
1499
  if (back.status === 0) {
1491
- applyContainerChanges(args.workspaceRoot, wsOut, wsIn, (m) => args.emit("error", m), args.divert);
1500
+ applied = applyContainerChanges(args.workspaceRoot, wsOut, wsIn, (m) => args.emit("error", m), args.divert);
1492
1501
  }
1493
1502
  else {
1494
1503
  args.emit("error", `copy-out failed — the step's file changes were lost:\n${back.stderr.slice(0, 500)}`);
@@ -1502,7 +1511,7 @@ export async function runStepInContainer(args) {
1502
1511
  phases,
1503
1512
  };
1504
1513
  args.emit("info", timingLine(timing));
1505
- return { ...outcome, timing };
1514
+ return { ...outcome, timing, applied };
1506
1515
  }
1507
1516
  finally {
1508
1517
  if (containerId)
@@ -1,4 +1,4 @@
1
- import { type SubagentSpec } from "./subagents.ts";
1
+ import { type DelegateSpec, type SubagentSpec } from "./subagents.ts";
2
2
  import { type FlowStep, type RunRecord, type StepRecord } from "./store.ts";
3
3
  import { type Catalog } from "./catalog.ts";
4
4
  import { type SearchRoot, type RunDigest } from "./context-tools.ts";
@@ -189,6 +189,26 @@ export declare function buildSubagentSpecs(o: {
189
189
  parentAllowed: string[];
190
190
  push: (type: "info" | "error", text: string) => void;
191
191
  }): SubagentSpec[];
192
+ /**
193
+ * The `sandbox: own` sub-agents a step may hand jobs to: a name and a
194
+ * description each, all the parent's sandbox needs. What they may hold is
195
+ * read from their own file when a job arrives — never cut to the parent's,
196
+ * and never seen by it. `canRun` is false where nothing can start their
197
+ * sandbox (a sub-agent's own step: depth one; a step outside a run).
198
+ */
199
+ export declare function buildDelegateSpecs(o: {
200
+ names: unknown;
201
+ self: string;
202
+ workspaceRoot: string;
203
+ canRun: boolean;
204
+ push: (type: "info" | "error", text: string) => void;
205
+ /** Told, per offered sub-agent, whether a tool it holds acts outside the
206
+ * workspace — a delegating step whose job may have sent something must
207
+ * not be re-run as if it had only read (launchStep's retry policy). */
208
+ outward?: (name: string, outward: boolean) => void;
209
+ tenant?: string;
210
+ tags?: string[];
211
+ }): DelegateSpec[];
192
212
  export declare function sharedInstructions(agentDir: string, tenant: string): string | null;
193
213
  /**
194
214
  * Which of an agent's in-scope skills load for this run.
@@ -474,6 +494,11 @@ export declare function actedThenFailedCheck(events: {
474
494
  * happened to create it.
475
495
  */
476
496
  export declare function copyTreeBytes(from: string, to: string): void;
497
+ /** Whether two agents of one workspace call the same model supply: each
498
+ * resolved the way agentContext does — its own provider: block, else the
499
+ * workspace's — and compared in a stable key order. The account key is
500
+ * shared by both, so two that resolve to no block share it (Codex review). */
501
+ export declare function sameSupply(workspaceDir: string, tenant: string, a: string, b: string): boolean;
477
502
  /** A refusal of the credential itself, in any of the wordings Claude Code
478
503
  * has used. "Failed to authenticate: OAuth token revoked" (9 Oct 2026, a
479
504
  * step started one second before the 09:07:52Z refresh) matched none of the