@cabane/companion 0.6.203 → 0.6.205

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/dist/runtime.js CHANGED
@@ -133,6 +133,16 @@ var ApiError = class extends CompanionError {
133
133
  status;
134
134
  body;
135
135
  };
136
+ var EdgeBlockError = class extends ApiError {
137
+ constructor(status, ray, requestId) {
138
+ super(status, `${status} blocked by a proxy in front of Cabane`);
139
+ this.ray = ray;
140
+ this.requestId = requestId;
141
+ this.name = "EdgeBlockError";
142
+ }
143
+ ray;
144
+ requestId;
145
+ };
136
146
  var ConfigError = class extends CompanionError {
137
147
  constructor(message) {
138
148
  super(message);
@@ -167,25 +177,7 @@ var prepareHookSchema = z.object({
167
177
  // Wall-clock cap for the hook. Provisioning is slow (minutes), so the
168
178
  // default is generous; a hook that hangs past this is killed and the turn
169
179
  // fails with a clear timeout message rather than pinning the companion.
170
- timeoutMs: z.number().int().positive().optional(),
171
- // Re-invoke the hook when a prepared result already exists, in VALIDATE
172
- // mode: the cached result is handed back as `prepared` and stdout is
173
- // ignored, so the cwd cannot move. Its only power is to exit non-zero and
174
- // fail the turn. The hook resolves once per (conversation, agent), so a
175
- // later message asking for a different environment is otherwise silently
176
- // ignored; this lets a hook refuse it out loud.
177
- //
178
- // Default OFF: a hook that provisions unconditionally must not start
179
- // running on every turn because the companion upgraded.
180
- validateCached: z.boolean().optional(),
181
- // Independent operator-owned reporter. Receives sanitized failure metadata
182
- // on stdin; never re-enters this hook and cannot change the turn outcome.
183
- failureReporter: z.object({
184
- command: z.string().min(1),
185
- args: z.array(z.string()).optional(),
186
- env: z.record(z.string(), z.string()).optional(),
187
- timeoutMs: z.number().int().positive().max(3e4).optional()
188
- }).strict().optional()
180
+ timeoutMs: z.number().int().positive().optional()
189
181
  }).strict();
190
182
  var DEFAULT_TIMEOUT_MS = 10 * 6e4;
191
183
  var prepareResultSchema = z.object({
@@ -260,8 +252,7 @@ var companionConfigSchema = z2.object({
260
252
  // The DEVICE-LEVEL prepare hook, for every agent assigned here unless its own
261
253
  // `agents` row names one. Per-agent hooks alone make the config a second
262
254
  // silent roster: an agent nobody listed gets no hook and no error, and starts
263
- // in the operator's `$HOME` (an agent woke in `/home/joshed` this way on
264
- // 2026-08-17).
255
+ // in the operator's `$HOME`.
265
256
  prepareHook: prepareHookSchema.optional(),
266
257
  // `dashboardPort` and `autoOpen` are IGNORED by the CLI (no port, no browser)
267
258
  // but stay in the schema so an existing file still parses; the Electron shell
@@ -298,9 +289,10 @@ var companionConfigSchema = z2.object({
298
289
  root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional(),
299
290
  // An argv prefix the operator puts in front of every turn's runner: the companion starts
300
291
  // `<prefix…> <its own run-turn command…>` instead of the runner bare. What the prefix does
301
- // is the operator's (a box of their own making, ENG225); the companion knows nothing of it
292
+ // is the operator's (a container or VM per turn, say); the companion knows nothing of it
302
293
  // beyond handing the runner over, with the turn's inputs in its environment and the
303
- // credential and secrets on its stdin (`runner/launch.ts`, `boxed`).
294
+ // credential and secrets on its stdin (`runner/launch.ts`, `wrapped`). The README lists
295
+ // what a wrapper can rely on.
304
296
  runner: z2.object({
305
297
  command: z2.array(z2.string().min(1)).min(1)
306
298
  }).strict().optional()
@@ -312,33 +304,38 @@ function workRoot(cfg) {
312
304
  if (root.startsWith("~/")) return join4(homedir5(), root.slice(2));
313
305
  return root;
314
306
  }
315
- function retiredConfigKeys(raw) {
316
- if (!raw || typeof raw !== "object") return [];
317
- const obj = raw;
307
+ var RETIRED_ROW_KEYS = ["sandbox"];
308
+ var RETIRED_HOOK_KEYS = ["validateCached", "failureReporter"];
309
+ function isObject(value) {
310
+ return !!value && typeof value === "object" && !Array.isArray(value);
311
+ }
312
+ function splitRetired(raw) {
318
313
  const keys = [];
319
- if ("runners" in obj) keys.push("runners");
320
- const agents = obj.agents;
321
- if (agents && typeof agents === "object") {
322
- for (const [key, row] of Object.entries(agents)) {
323
- if (row && typeof row === "object" && "sandbox" in row) keys.push(`agents.${key}.sandbox`);
314
+ const without = (obj, names, path) => {
315
+ const out = {};
316
+ for (const [k, v] of Object.entries(obj)) {
317
+ if (names.includes(k)) keys.push(`${path}${k}`);
318
+ else out[k] = v;
324
319
  }
320
+ return out;
321
+ };
322
+ const hook = (row, path) => isObject(row.prepareHook) ? { ...row, prepareHook: without(row.prepareHook, RETIRED_HOOK_KEYS, `${path}prepareHook.`) } : row;
323
+ const kept = hook(without(raw, ["runners"], ""), "");
324
+ if (isObject(kept.agents)) {
325
+ const agents = {};
326
+ for (const [key, row] of Object.entries(kept.agents)) {
327
+ const path = `agents.${key}.`;
328
+ agents[key] = isObject(row) ? hook(without(row, RETIRED_ROW_KEYS, path), path) : row;
329
+ }
330
+ kept.agents = agents;
325
331
  }
326
- return keys;
332
+ return { keys, kept };
333
+ }
334
+ function retiredConfigKeys(raw) {
335
+ return isObject(raw) ? splitRetired(raw).keys : [];
327
336
  }
328
337
  function withoutRetiredKeys(raw) {
329
- if (retiredConfigKeys(raw).length === 0) return raw;
330
- const { runners: _runners, ...rest } = raw;
331
- if (!rest.agents || typeof rest.agents !== "object") return rest;
332
- const agents = {};
333
- for (const [key, row] of Object.entries(rest.agents)) {
334
- if (row && typeof row === "object" && "sandbox" in row) {
335
- const { sandbox: _sandbox, ...kept } = row;
336
- agents[key] = kept;
337
- } else {
338
- agents[key] = row;
339
- }
340
- }
341
- return { ...rest, agents };
338
+ return isObject(raw) ? splitRetired(raw).kept : raw;
342
339
  }
343
340
  function retiredConfigKeysOnDisk() {
344
341
  try {
@@ -1598,7 +1595,7 @@ var emptyHeartbeatStatus = () => ({
1598
1595
  });
1599
1596
 
1600
1597
  // src/supervisor.ts
1601
- import { hostname as hostname2 } from "os";
1598
+ import { hostname } from "os";
1602
1599
 
1603
1600
  // packages/agent-runtime/src/failure.ts
1604
1601
  import { z as z4 } from "zod";
@@ -1841,11 +1838,20 @@ function describeCause(err) {
1841
1838
  if (text === void 0 || text.length === 0) return void 0;
1842
1839
  return text.length > CAUSE_TEXT_CAP ? `${text.slice(0, CAUSE_TEXT_CAP)}\u2026` : text;
1843
1840
  }
1841
+ function edgeBlock(status, headers, raw) {
1842
+ if (status !== 403 || !(headers.get("content-type") ?? "").includes("text/html")) return null;
1843
+ const requestId = /Request ID:\s*(?:<[^>]*>\s*)*([A-Za-z0-9-]+)/i.exec(raw)?.[1];
1844
+ return new EdgeBlockError(status, headers.get("cf-ray") ?? void 0, requestId);
1845
+ }
1844
1846
  function apiErrorLogFields(err) {
1845
1847
  const fields = {
1846
1848
  err: describeError(err)
1847
1849
  };
1848
- if (err instanceof ApiError) {
1850
+ if (err instanceof EdgeBlockError) {
1851
+ fields.status = err.status;
1852
+ if (err.ray) fields.edgeRay = err.ray;
1853
+ if (err.requestId) fields.edgeRequestId = err.requestId;
1854
+ } else if (err instanceof ApiError) {
1849
1855
  fields.status = err.status;
1850
1856
  const body = describeErrorBody(err.body);
1851
1857
  if (body !== void 0) fields.responseBody = body;
@@ -1861,11 +1867,14 @@ var bootId = randomUUID();
1861
1867
  import { randomUUID as randomUUID5 } from "crypto";
1862
1868
 
1863
1869
  // src/api.ts
1870
+ import { promisify } from "util";
1871
+ import { gzip } from "zlib";
1864
1872
  var DEVICE_HEADER = "x-cabane-device";
1865
1873
  var RETRY_BACKOFF_MS = [250, 750];
1866
1874
  var TURN_CONTEXT_BACKOFF_MS = [250, 750, 2e3, 5e3];
1867
1875
  var ACTIVE_RUN_OUTBOX_SEQ = 0;
1868
1876
  var LEASE_CHECK_TIMEOUT_MS = 1e4;
1877
+ var gzipAsync = promisify(gzip);
1869
1878
  function activeRunOutboxKey(conversationId, agentId) {
1870
1879
  return `activerun:${conversationId}:${agentId}`;
1871
1880
  }
@@ -1885,19 +1894,24 @@ var CabaneApi = class {
1885
1894
  // rethrows transport errors (fetch reject) unchanged so the caller's retry
1886
1895
  // logic can classify them.
1887
1896
  async attempt(method, path, body, signal) {
1897
+ const json = body !== void 0 ? JSON.stringify(body) : void 0;
1898
+ const gzipped = json !== void 0 && this.opts.gzipBodies?.() === true;
1888
1899
  const res = await fetch(`${this.base}${path}`, {
1889
1900
  method,
1890
1901
  headers: {
1891
1902
  Authorization: `Bearer ${this.opts.token}`,
1892
1903
  ...this.opts.deviceToken ? { [DEVICE_HEADER]: this.opts.deviceToken } : {},
1893
- ...body !== void 0 ? { "content-type": "application/json" } : {},
1904
+ ...json !== void 0 ? { "content-type": "application/json" } : {},
1905
+ ...gzipped ? { "content-encoding": "gzip" } : {},
1894
1906
  accept: "application/json"
1895
1907
  },
1896
- ...body !== void 0 ? { body: JSON.stringify(body) } : {},
1908
+ ...json !== void 0 ? { body: gzipped ? await gzipAsync(json) : json } : {},
1897
1909
  ...signal ? { signal } : {}
1898
1910
  });
1899
1911
  if (res.status === 204) return void 0;
1900
1912
  const raw = await res.text();
1913
+ const blocked = edgeBlock(res.status, res.headers, raw);
1914
+ if (blocked) throw blocked;
1901
1915
  let parsed = void 0;
1902
1916
  if (raw.length > 0) {
1903
1917
  try {
@@ -6378,7 +6392,7 @@ function createCodexAdapter(deps = {}) {
6378
6392
  if (signal.aborted) return;
6379
6393
  if (compactions === null) {
6380
6394
  deps.onWarn?.(
6381
- "codex adapter: the thread's compaction count could not be read \u2014 re-sending the standing instructions (ENG210)",
6395
+ "codex adapter: the thread's compaction count could not be read \u2014 re-sending the standing instructions",
6382
6396
  { threadId: decision.resume }
6383
6397
  );
6384
6398
  } else if (decision.compactions === compactions) {
@@ -6388,7 +6402,7 @@ function createCodexAdapter(deps = {}) {
6388
6402
  };
6389
6403
  } else if (decision.compactions !== null && compactions > decision.compactions) {
6390
6404
  deps.onWarn?.(
6391
- "codex adapter: the thread compacted since the last turn \u2014 re-sending the standing instructions (ENG210)",
6405
+ "codex adapter: the thread compacted since the last turn \u2014 re-sending the standing instructions",
6392
6406
  {
6393
6407
  threadId: decision.resume,
6394
6408
  compactions,
@@ -7539,8 +7553,6 @@ var ConnectorHealthStore = class {
7539
7553
 
7540
7554
  // src/dispatcher.ts
7541
7555
  import { randomUUID as randomUUID4 } from "crypto";
7542
- import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
7543
- import { join as join21 } from "path";
7544
7556
 
7545
7557
  // src/runner/launch.ts
7546
7558
  import { createInterface } from "readline";
@@ -7579,8 +7591,8 @@ async function stopRemoteOpencodeSession(serverUrl, sessionId, opts = {}) {
7579
7591
  // src/turn-containment.ts
7580
7592
  import { execFile, execFileSync, spawn as spawn2 } from "child_process";
7581
7593
  import { readFileSync as readFileSync6 } from "fs";
7582
- import { promisify } from "util";
7583
- var execFileAsync = promisify(execFile);
7594
+ import { promisify as promisify2 } from "util";
7595
+ var execFileAsync = promisify2(execFile);
7584
7596
  var CONTAINMENT_STOP_GRACE_SEC = 5;
7585
7597
  var FALLBACK_GRACE_MS = CONTAINMENT_STOP_GRACE_SEC * 1e3;
7586
7598
  var OOM_READ_TIMEOUT_MS = 1500;
@@ -7733,7 +7745,10 @@ var runnerSpecSchema = z14.object({
7733
7745
  sessionWriteNoticeClaimed: z14.boolean(),
7734
7746
  // The runner's identity, minted with its credential. The start
7735
7747
  // PATCH carries it so the lease knows which runner holds the span.
7736
- runnerId: z14.string()
7748
+ runnerId: z14.string(),
7749
+ // Whether the server took gzip request bodies at the latest heartbeat, as of
7750
+ // the start. The launcher writes `gzip_bodies` when that changes mid-turn.
7751
+ gzipBodies: z14.boolean().optional()
7737
7752
  });
7738
7753
  var dispatchSchema = z14.object({
7739
7754
  type: z14.literal("device:dispatch_requested"),
@@ -7782,6 +7797,22 @@ function decodeRunnerEvent(line) {
7782
7797
  }
7783
7798
 
7784
7799
  // src/runner/launch.ts
7800
+ function gzipBodiesSignal() {
7801
+ let on = false;
7802
+ const listeners = /* @__PURE__ */ new Set();
7803
+ return {
7804
+ current: () => on,
7805
+ subscribe(listener) {
7806
+ listeners.add(listener);
7807
+ return () => listeners.delete(listener);
7808
+ },
7809
+ set(next) {
7810
+ if (next === on) return;
7811
+ on = next;
7812
+ for (const listener of listeners) listener(on);
7813
+ }
7814
+ };
7815
+ }
7785
7816
  function selfRunnerCommand() {
7786
7817
  const script = process.argv[1];
7787
7818
  if (!script) throw new Error("runner: cannot tell which script started this companion");
@@ -7790,7 +7821,7 @@ function selfRunnerCommand() {
7790
7821
  function wrappedRunnerCommand(prefix, own) {
7791
7822
  return [...prefix ?? [], ...own];
7792
7823
  }
7793
- var BOXED_ENV_DROPPED = [
7824
+ var WRAPPED_ENV_DROPPED = [
7794
7825
  "ANTHROPIC_API_KEY",
7795
7826
  "CLAUDE_CODE_OAUTH_TOKEN",
7796
7827
  "CODEX_API_KEY",
@@ -7799,11 +7830,11 @@ var BOXED_ENV_DROPPED = [
7799
7830
  "DBUS_SESSION_BUS_ADDRESS",
7800
7831
  "XDG_RUNTIME_DIR"
7801
7832
  ];
7802
- function runnerExecEnv(base, boxed) {
7833
+ function runnerExecEnv(base, wrapped) {
7803
7834
  const env = {};
7804
7835
  for (const [k, v] of Object.entries(base)) {
7805
7836
  if (v === void 0) continue;
7806
- if (boxed && BOXED_ENV_DROPPED.includes(k)) continue;
7837
+ if (wrapped && WRAPPED_ENV_DROPPED.includes(k)) continue;
7807
7838
  env[k] = v;
7808
7839
  }
7809
7840
  return env;
@@ -7843,7 +7874,8 @@ function runnerSpec(opts, launch, duties, runnerId) {
7843
7874
  transcripts: opts.transcriptDir !== void 0,
7844
7875
  logLevel: opts.log.level,
7845
7876
  connectorHealth: healthSnapshot(launch.connectorHealth),
7846
- sessionWriteNoticeClaimed: duties.sessionWriteNoticeClaimed()
7877
+ sessionWriteNoticeClaimed: duties.sessionWriteNoticeClaimed(),
7878
+ ...launch.gzipBodies?.current() ? { gzipBodies: true } : {}
7847
7879
  };
7848
7880
  }
7849
7881
  var PINO_LEVELS = {
@@ -7967,6 +7999,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
7967
7999
  const stopRunner = new AbortController();
7968
8000
  let ready = false;
7969
8001
  let child;
8002
+ let unsubscribeGzip;
7970
8003
  let stopAnnounced = false;
7971
8004
  const announceStop = () => {
7972
8005
  if (stopAnnounced || !child?.stdin.writable) return;
@@ -8025,13 +8058,13 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8025
8058
  return { ok: false, durationMs: durationMs2, reason: reason2 };
8026
8059
  }
8027
8060
  try {
8028
- const boxed = launch.boxed === true;
8029
- const env = runnerExecEnv(launch.env ?? process.env, boxed);
8061
+ const wrapped = launch.wrapped === true;
8062
+ const env = runnerExecEnv(launch.env ?? process.env, wrapped);
8030
8063
  const grants = minted.deviceGrants.length > 0 ? launch.grants?.(minted.deviceGrants) ?? {} : {};
8031
8064
  const input = {
8032
8065
  baseUrl: opts.baseUrl,
8033
8066
  credential: minted.credential,
8034
- ...boxed ? { secrets: launch.secrets?.() ?? {} } : {},
8067
+ ...wrapped ? { secrets: launch.secrets?.() ?? {} } : {},
8035
8068
  ...Object.keys(grants).length > 0 ? { grants } : {},
8036
8069
  dispatch: payload,
8037
8070
  turnId,
@@ -8044,6 +8077,10 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8044
8077
  stdin: runnerStdin(input)
8045
8078
  });
8046
8079
  child = running;
8080
+ unsubscribeGzip = launch.gzipBodies?.subscribe((on) => {
8081
+ if (running.stdin.writable)
8082
+ running.stdin.write(encodeLauncherMessage({ cabaneLauncher: "gzip_bodies", on }));
8083
+ });
8047
8084
  if (controller.signal.aborted) announceStop();
8048
8085
  log.debug(
8049
8086
  { turnId, runnerPid: running.pid, provider: launch.provider.name },
@@ -8105,7 +8142,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8105
8142
  locks?.release(lockOwner(event.id));
8106
8143
  return;
8107
8144
  case "process_group":
8108
- if (!boxed) groups.push(event.pgid);
8145
+ if (!wrapped) groups.push(event.pgid);
8109
8146
  return;
8110
8147
  case "remote_session":
8111
8148
  remote.push({ serverUrl: event.serverUrl, sessionId: event.sessionId });
@@ -8129,6 +8166,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8129
8166
  await Promise.all([stdoutDone, stderrDone]);
8130
8167
  } finally {
8131
8168
  if (locks) giveBackLocks(locks, `${turnId}#`, remote, launch, log);
8169
+ unsubscribeGzip?.();
8132
8170
  child?.stdin.end();
8133
8171
  controller.signal.removeEventListener("abort", forwardStop);
8134
8172
  duties.releaseAbort(turnId, controller);
@@ -8180,7 +8218,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8180
8218
 
8181
8219
  // src/turn-execution.ts
8182
8220
  import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
8183
- import { mkdirSync as mkdirSync9 } from "fs";
8221
+ import { existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
8184
8222
 
8185
8223
  // src/grants.ts
8186
8224
  import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
@@ -8234,7 +8272,7 @@ function loadGrantsFile(report) {
8234
8272
  const result = grantsFileSchema.safeParse(parsed);
8235
8273
  if (!result.success) {
8236
8274
  report.unreadable(
8237
- `${path} must map a secret's variable name to an object of "VARIABLE": "value" pairs (e.g. { "HAS_PROD_ACCESS": { "RENDER_API_KEY": "rnd_\u2026" } }).`
8275
+ `${path} must map a secret's variable name to an object of "VARIABLE": "value" pairs (e.g. { "HAS_STAGING_DB": { "DATABASE_URL": "postgres://\u2026" } }).`
8238
8276
  );
8239
8277
  return {};
8240
8278
  }
@@ -8277,10 +8315,6 @@ import { homedir as homedir7 } from "os";
8277
8315
  import { join as join17 } from "path";
8278
8316
  import { createInterface as createInterface2 } from "readline";
8279
8317
 
8280
- // src/prepare-failure.ts
8281
- import { spawn as spawn3 } from "child_process";
8282
- import { hostname } from "os";
8283
-
8284
8318
  // src/prepared.ts
8285
8319
  import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "fs";
8286
8320
  import { join as join18 } from "path";
@@ -8356,7 +8390,7 @@ function requiredSecretNames(mcpServers) {
8356
8390
  }
8357
8391
 
8358
8392
  // src/harness-usable.ts
8359
- import { spawn as spawn4 } from "child_process";
8393
+ import { spawn as spawn3 } from "child_process";
8360
8394
 
8361
8395
  // src/harness-versions.ts
8362
8396
  var EMPTY = { claudeCode: null, opencode: null, codex: null };
@@ -8459,7 +8493,7 @@ function runBounded(path, args, env) {
8459
8493
  };
8460
8494
  let child;
8461
8495
  try {
8462
- child = spawn4(path, args, { stdio: ["ignore", "pipe", "pipe"], env });
8496
+ child = spawn3(path, args, { stdio: ["ignore", "pipe", "pipe"], env });
8463
8497
  } catch {
8464
8498
  resolve({ code: null, output: "", error: "spawn" });
8465
8499
  return;
@@ -8626,7 +8660,7 @@ function hostReport() {
8626
8660
  }
8627
8661
 
8628
8662
  // src/providers/local-process.ts
8629
- import { spawn as spawn5 } from "child_process";
8663
+ import { spawn as spawn4 } from "child_process";
8630
8664
  import { constants as constants2 } from "os";
8631
8665
  var DEFAULT_KILL_GRACE_MS = 3e4;
8632
8666
  var DEFAULT_REAP_GRACE_MS = 3e3;
@@ -8637,7 +8671,7 @@ function exitCode(code, signal) {
8637
8671
  }
8638
8672
  function createLocalProcessProvider(opts = {}) {
8639
8673
  const killGraceMs = opts.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
8640
- const spawnFn = opts.spawnFn ?? spawn5;
8674
+ const spawnFn = opts.spawnFn ?? spawn4;
8641
8675
  const reapGraceMs = opts.reapGraceMs ?? DEFAULT_REAP_GRACE_MS;
8642
8676
  const killFn = opts.killFn ?? ((pid, signal) => process.kill(pid, signal));
8643
8677
  const groupAlive = (pgid) => {
@@ -8841,7 +8875,7 @@ function checkReason(check) {
8841
8875
 
8842
8876
  // src/harness-copy.ts
8843
8877
  import { homedir as homedir8 } from "os";
8844
- import { join as join22 } from "path";
8878
+ import { join as join21 } from "path";
8845
8879
  var HARNESS_LABELS = {
8846
8880
  "claude-code": "Claude Code",
8847
8881
  codex: "Codex",
@@ -8855,7 +8889,7 @@ function failureText(runtime, reason) {
8855
8889
  case "spawn":
8856
8890
  return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
8857
8891
  case "config":
8858
- return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join22(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
8892
+ return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join21(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
8859
8893
  case "unreadable":
8860
8894
  return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
8861
8895
  }
@@ -9114,13 +9148,13 @@ var DispatchClaims = class {
9114
9148
  import {
9115
9149
  existsSync as existsSync14,
9116
9150
  mkdirSync as mkdirSync10,
9117
- readdirSync as readdirSync3,
9151
+ readdirSync as readdirSync2,
9118
9152
  readFileSync as readFileSync11,
9119
9153
  renameSync as renameSync5,
9120
9154
  rmSync as rmSync7,
9121
9155
  writeFileSync as writeFileSync6
9122
9156
  } from "fs";
9123
- import { join as join23 } from "path";
9157
+ import { join as join22 } from "path";
9124
9158
  var MAX_ENTRIES = 2e3;
9125
9159
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
9126
9160
  var Outbox = class {
@@ -9133,10 +9167,10 @@ var Outbox = class {
9133
9167
  // Resolved lazily (per call) so tests that swap HOME between
9134
9168
  // cases route writes at the right tmpdir.
9135
9169
  dir() {
9136
- return join23(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
9170
+ return join22(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
9137
9171
  }
9138
9172
  fileFor(turnId, seq) {
9139
- return join23(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
9173
+ return join22(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
9140
9174
  }
9141
9175
  // Persist a commit for later draining. Atomic (temp file + rename) so a
9142
9176
  // concurrent `list()` never reads a half-written entry, then enforces the
@@ -9171,14 +9205,14 @@ var Outbox = class {
9171
9205
  if (!existsSync14(dir2)) return [];
9172
9206
  let names;
9173
9207
  try {
9174
- names = readdirSync3(dir2);
9208
+ names = readdirSync2(dir2);
9175
9209
  } catch {
9176
9210
  return [];
9177
9211
  }
9178
9212
  const entries = [];
9179
9213
  for (const name of names) {
9180
9214
  if (!name.endsWith(".json")) continue;
9181
- const full = join23(dir2, name);
9215
+ const full = join22(dir2, name);
9182
9216
  try {
9183
9217
  const parsed = JSON.parse(readFileSync11(full, "utf8"));
9184
9218
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
@@ -9215,7 +9249,7 @@ var Outbox = class {
9215
9249
  maxSeqForTurn(turnId) {
9216
9250
  const dir2 = this.dir();
9217
9251
  if (!existsSync14(dir2)) return 0;
9218
- const names = readdirSync3(dir2);
9252
+ const names = readdirSync2(dir2);
9219
9253
  const prefix = `${encodeURIComponent(turnId)}__`;
9220
9254
  let max = 0;
9221
9255
  for (const name of names) {
@@ -9236,7 +9270,7 @@ var Outbox = class {
9236
9270
  const dir2 = this.dir();
9237
9271
  if (!existsSync14(dir2)) return 0;
9238
9272
  try {
9239
- return readdirSync3(dir2).filter((n) => n.endsWith(".json")).length;
9273
+ return readdirSync2(dir2).filter((n) => n.endsWith(".json")).length;
9240
9274
  } catch {
9241
9275
  return 0;
9242
9276
  }
@@ -9400,9 +9434,9 @@ var SseSubscriber = class {
9400
9434
  };
9401
9435
 
9402
9436
  // src/updater.ts
9403
- import { spawn as spawn6 } from "child_process";
9437
+ import { spawn as spawn5 } from "child_process";
9404
9438
  import { copyFileSync as copyFileSync2, existsSync as existsSync16, mkdirSync as mkdirSync14, renameSync as renameSync9, rmSync as rmSync10 } from "fs";
9405
- import { dirname as dirname11, join as join27 } from "path";
9439
+ import { dirname as dirname11, join as join26 } from "path";
9406
9440
 
9407
9441
  // src/boot-rule.ts
9408
9442
  import {
@@ -9414,7 +9448,7 @@ import {
9414
9448
  symlinkSync,
9415
9449
  writeFileSync as writeFileSync7
9416
9450
  } from "fs";
9417
- import { dirname as dirname8, join as join24 } from "path";
9451
+ import { dirname as dirname8, join as join23 } from "path";
9418
9452
  var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
9419
9453
  var MAX_STAGE_ATTEMPTS = 3;
9420
9454
  function superseded(pending, ...running) {
@@ -9441,32 +9475,32 @@ import {
9441
9475
  lstatSync,
9442
9476
  mkdirSync as mkdirSync12,
9443
9477
  readFileSync as readFileSync13,
9444
- readdirSync as readdirSync4,
9478
+ readdirSync as readdirSync3,
9445
9479
  realpathSync as realpathSync5,
9446
9480
  renameSync as renameSync7,
9447
9481
  rmSync as rmSync9,
9448
9482
  symlinkSync as symlinkSync2
9449
9483
  } from "fs";
9450
- import { dirname as dirname9, join as join25 } from "path";
9484
+ import { dirname as dirname9, join as join24 } from "path";
9451
9485
  import { fileURLToPath as fileURLToPath5 } from "url";
9452
9486
  function versionsDir() {
9453
- return join25(cabaneDir(), "versions");
9487
+ return join24(cabaneDir(), "versions");
9454
9488
  }
9455
9489
  function currentLink() {
9456
- return join25(cabaneDir(), "current");
9490
+ return join24(cabaneDir(), "current");
9457
9491
  }
9458
9492
  function bootGuardPath() {
9459
- return join25(cabaneDir(), "boot-guard.mjs");
9493
+ return join24(cabaneDir(), "boot-guard.mjs");
9460
9494
  }
9461
9495
  function versionPackageRoot(version) {
9462
- return join25(versionsDir(), version, "node_modules", "@cabane", "companion");
9496
+ return join24(versionsDir(), version, "node_modules", "@cabane", "companion");
9463
9497
  }
9464
9498
  function ownPackageRoot(moduleUrl = import.meta.url) {
9465
9499
  return dirname9(dirname9(fileURLToPath5(moduleUrl)));
9466
9500
  }
9467
9501
  function packageVersion(root) {
9468
9502
  try {
9469
- const pkg2 = JSON.parse(readFileSync13(join25(root, "package.json"), "utf8"));
9503
+ const pkg2 = JSON.parse(readFileSync13(join24(root, "package.json"), "utf8"));
9470
9504
  return typeof pkg2.version === "string" ? pkg2.version : null;
9471
9505
  } catch {
9472
9506
  return null;
@@ -9475,7 +9509,7 @@ function packageVersion(root) {
9475
9509
  function currentRoot() {
9476
9510
  try {
9477
9511
  const root = realpathSync5(currentLink());
9478
- return existsSync15(join25(root, "dist", "cli.js")) ? root : null;
9512
+ return existsSync15(join24(root, "dist", "cli.js")) ? root : null;
9479
9513
  } catch {
9480
9514
  return null;
9481
9515
  }
@@ -9496,9 +9530,9 @@ function pruneVersions(keep) {
9496
9530
  const dir2 = versionsDir();
9497
9531
  if (!existsSync15(dir2)) return [];
9498
9532
  const removed = [];
9499
- for (const name of readdirSync4(dir2)) {
9533
+ for (const name of readdirSync3(dir2)) {
9500
9534
  if (keep.includes(name)) continue;
9501
- rmSync9(join25(dir2, name), { recursive: true, force: true });
9535
+ rmSync9(join24(dir2, name), { recursive: true, force: true });
9502
9536
  removed.push(name);
9503
9537
  }
9504
9538
  return removed;
@@ -9517,7 +9551,7 @@ function compareVersions(a, b) {
9517
9551
 
9518
9552
  // src/update-state.ts
9519
9553
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, renameSync as renameSync8, writeFileSync as writeFileSync8 } from "fs";
9520
- import { dirname as dirname10, join as join26 } from "path";
9554
+ import { dirname as dirname10, join as join25 } from "path";
9521
9555
  import { z as z18 } from "zod";
9522
9556
  var resultSchema = z18.object({
9523
9557
  kind: z18.enum(["updated", "rolled_back", "failed"]),
@@ -9541,7 +9575,7 @@ var stateSchema = z18.object({
9541
9575
  failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
9542
9576
  });
9543
9577
  function updateStatePath(dir2 = cabaneDir()) {
9544
- return join26(dir2, "update.json");
9578
+ return join25(dir2, "update.json");
9545
9579
  }
9546
9580
  function readUpdateState(path = updateStatePath()) {
9547
9581
  try {
@@ -9590,7 +9624,7 @@ function rollBack(pending, reason, now = /* @__PURE__ */ new Date()) {
9590
9624
  return updateUpdateState((state) => rolledBackState(state, pending, reason, now));
9591
9625
  }
9592
9626
  function writeBootGuard(root = ownPackageRoot()) {
9593
- const built = join27(root, "dist", "boot-guard.js");
9627
+ const built = join26(root, "dist", "boot-guard.js");
9594
9628
  if (!existsSync16(built)) return false;
9595
9629
  const target = bootGuardPath();
9596
9630
  mkdirSync14(dirname11(target), { recursive: true });
@@ -9607,7 +9641,7 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve) => {
9607
9641
  let stderr = "";
9608
9642
  let child;
9609
9643
  try {
9610
- child = spawn6(command, [...args], {
9644
+ child = spawn5(command, [...args], {
9611
9645
  stdio: ["ignore", "pipe", "pipe"],
9612
9646
  env: opts.env ?? process.env
9613
9647
  });
@@ -9630,17 +9664,17 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve) => {
9630
9664
  async function stageVersion(version, harnesses, deps = {}) {
9631
9665
  const run = deps.run ?? runProcess;
9632
9666
  const node = deps.execPath ?? process.execPath;
9633
- const target = join27(versionsDir(), version);
9667
+ const target = join26(versionsDir(), version);
9634
9668
  const placed = versionPackageRoot(version);
9635
9669
  if (existsSync16(placed)) {
9636
9670
  if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
9637
9671
  return { ok: true, root: placed };
9638
9672
  }
9639
- const npm = join27(dirname11(node), "npm");
9673
+ const npm = join26(dirname11(node), "npm");
9640
9674
  if (!existsSync16(npm))
9641
9675
  return { ok: false, reason: `there is no npm beside Node at ${dirname11(node)}` };
9642
9676
  mkdirSync14(versionsDir(), { recursive: true });
9643
- const staging = join27(versionsDir(), `.${version}.staging-${process.pid}`);
9677
+ const staging = join26(versionsDir(), `.${version}.staging-${process.pid}`);
9644
9678
  rmSync10(staging, { recursive: true, force: true });
9645
9679
  const installed = await run(
9646
9680
  npm,
@@ -9669,7 +9703,7 @@ async function stageVersion(version, harnesses, deps = {}) {
9669
9703
  return { ok: false, reason: `version ${version} isn't published yet`, unpublished: true };
9670
9704
  return { ok: false, reason: `npm couldn't install it: ${lastLine(installed.stderr)}` };
9671
9705
  }
9672
- const root = join27(staging, "node_modules", "@cabane", "companion");
9706
+ const root = join26(staging, "node_modules", "@cabane", "companion");
9673
9707
  const verdict = await verifyInstall(root, version, harnesses, { run, execPath: node });
9674
9708
  if (verdict) {
9675
9709
  rmSync10(staging, { recursive: true, force: true });
@@ -9681,7 +9715,7 @@ async function stageVersion(version, harnesses, deps = {}) {
9681
9715
  }
9682
9716
  async function verifyInstall(root, version, harnesses, deps) {
9683
9717
  if (packageVersion(root) !== version) return `the installed package isn't version ${version}`;
9684
- const answered = await deps.run(deps.execPath, [join27(root, "dist", "bin.js"), "--version"], {
9718
+ const answered = await deps.run(deps.execPath, [join26(root, "dist", "bin.js"), "--version"], {
9685
9719
  timeoutMs: 6e4,
9686
9720
  env: { ...process.env, CABANE_LAUNCH_SELF: "1" }
9687
9721
  });
@@ -9689,7 +9723,7 @@ async function verifyInstall(root, version, harnesses, deps) {
9689
9723
  return `it doesn't start: ${lastLine(answered.stderr) || "no version printed"}`;
9690
9724
  }
9691
9725
  if (harnesses.length === 0) return null;
9692
- const probed = await deps.run(deps.execPath, [join27(root, "dist", "postinstall.js"), "--json"], {
9726
+ const probed = await deps.run(deps.execPath, [join26(root, "dist", "postinstall.js"), "--json"], {
9693
9727
  timeoutMs: 6e4
9694
9728
  });
9695
9729
  let resolutions = {};
@@ -10060,6 +10094,10 @@ var CompanionSupervisor = class {
10060
10094
  reconciliationAbort = new AbortController();
10061
10095
  // The version-skew warning is logged once, not every heartbeat.
10062
10096
  versionSkewWarned = false;
10097
+ // Whether the server takes gzip request bodies, as the latest accepted
10098
+ // heartbeat said; false until one has. Read per request by every agent's API
10099
+ // client and pushed to the runners in flight when it changes.
10100
+ gzipBodies = gzipBodiesSignal();
10063
10101
  // This device's id, captured from the heartbeat / assignments response. The SSE
10064
10102
  // router uses it to tell a dispatch addressed to THIS device (a freshly-assigned
10065
10103
  // agent the last poll hasn't caught yet — the assignment-change race) from one
@@ -10106,13 +10144,13 @@ var CompanionSupervisor = class {
10106
10144
  if (this.stopped) return;
10107
10145
  this.log.info(
10108
10146
  { protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
10109
- `Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname2()}`
10147
+ `Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname()}`
10110
10148
  );
10111
10149
  const retired = retiredConfigKeysOnDisk();
10112
10150
  if (retired.length > 0) {
10113
10151
  this.log.warn(
10114
10152
  { keys: retired },
10115
- `config.json: ${retired.join(", ")} ${retired.length === 1 ? "is" : "are"} no longer read and can be removed. The companion does no sandboxing, and every turn runs in a child process of its own.`
10153
+ `config.json: ${retired.join(", ")} ${retired.length === 1 ? "is" : "are"} no longer read and can be removed.`
10116
10154
  );
10117
10155
  }
10118
10156
  if (this.config.deviceToken) {
@@ -10152,7 +10190,7 @@ var CompanionSupervisor = class {
10152
10190
  log: this.log,
10153
10191
  lastEventId: null,
10154
10192
  onOpen: () => {
10155
- this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ?? hostname2()}`);
10193
+ this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ?? hostname()}`);
10156
10194
  this.deviceConnected = true;
10157
10195
  for (const wr of this.workspaces.values()) {
10158
10196
  this.hub.setConnected(wr.workspaceId, true);
@@ -10262,6 +10300,7 @@ var CompanionSupervisor = class {
10262
10300
  this.hub.setDevice({ deviceId: res.deviceId });
10263
10301
  this.deviceId = res.deviceId;
10264
10302
  this.checkVersionSkew(res.serverVersion);
10303
+ this.gzipBodies.set(res.acceptsGzipBodies === true);
10265
10304
  this.heartbeated = true;
10266
10305
  const view = acknowledgedDeviceSchema.safeParse(res.device);
10267
10306
  this.heartbeatStatus = {
@@ -10503,7 +10542,8 @@ var CompanionSupervisor = class {
10503
10542
  token: credential,
10504
10543
  ...this.config.deviceToken ? { deviceToken: this.config.deviceToken } : {},
10505
10544
  outbox,
10506
- log: agentLog
10545
+ log: agentLog,
10546
+ gzipBodies: () => this.gzipBodies.current()
10507
10547
  });
10508
10548
  const aborts = /* @__PURE__ */ new Map();
10509
10549
  const dispatcher = this.buildDispatcher({
@@ -10698,9 +10738,9 @@ var CompanionSupervisor = class {
10698
10738
  this.runnerCommand ?? selfRunnerCommand()
10699
10739
  ),
10700
10740
  ...this.config.runner?.command ? {
10701
- boxed: true,
10741
+ wrapped: true,
10702
10742
  secrets: () => loadSecretValuesTolerant(
10703
- (m) => this.log.warn({ err: m }, "Could not read the secrets file for a boxed turn.")
10743
+ (m) => this.log.warn({ err: m }, "Could not read the secrets file for a wrapped turn.")
10704
10744
  )
10705
10745
  } : {},
10706
10746
  // Read per dispatch, for the secret variables its mint named and no others.
@@ -10714,7 +10754,8 @@ var CompanionSupervisor = class {
10714
10754
  ...this.runnerEnv ? { env: this.runnerEnv } : {},
10715
10755
  reportRunnerGone: (report) => this.reportRunnerGone(report),
10716
10756
  connectorHealth: this.connectorHealth,
10717
- locks: this.runnerLocks
10757
+ locks: this.runnerLocks,
10758
+ gzipBodies: this.gzipBodies
10718
10759
  },
10719
10760
  observer: this.hub.observerFor(ctx.workspaceId, ctx.agentId, ctx.workspaceSlug),
10720
10761
  aborts: ctx.aborts
@@ -11379,9 +11420,9 @@ async function waitForBoundedHeartbeat(heartbeat) {
11379
11420
  }
11380
11421
  function defaultReexec() {
11381
11422
  clearRuntimeState();
11382
- void import("child_process").then(({ spawn: spawn7 }) => {
11423
+ void import("child_process").then(({ spawn: spawn6 }) => {
11383
11424
  try {
11384
- const child = spawn7(process.execPath, process.argv.slice(1), {
11425
+ const child = spawn6(process.execPath, process.argv.slice(1), {
11385
11426
  stdio: "inherit",
11386
11427
  detached: false
11387
11428
  });
@@ -11452,9 +11493,9 @@ function handleUncaught(log, err, origin) {
11452
11493
 
11453
11494
  // src/crash-marker.ts
11454
11495
  import { existsSync as existsSync17, mkdirSync as mkdirSync15, readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync9 } from "fs";
11455
- import { join as join28 } from "path";
11496
+ import { join as join27 } from "path";
11456
11497
  function crashMarkerPath() {
11457
- return join28(cabaneDir(), "last-error.json");
11498
+ return join27(cabaneDir(), "last-error.json");
11458
11499
  }
11459
11500
  function recordCrash(rec) {
11460
11501
  try {
@@ -11474,7 +11515,7 @@ function clearCrash() {
11474
11515
  // src/stray-companion.ts
11475
11516
  import { spawnSync } from "child_process";
11476
11517
  import { readFileSync as readFileSync16 } from "fs";
11477
- import { join as join29 } from "path";
11518
+ import { join as join28 } from "path";
11478
11519
  var COMPANION_ENTRY = /@cabane\/companion\/|cabane-companion|(^|[\s/])cabane\s|\.cabane\/(versions|current|boot-guard)/;
11479
11520
  var START = /\sstart(\s|$)/;
11480
11521
  function companionStarts(psOutput) {
@@ -11491,7 +11532,7 @@ function companionStarts(psOutput) {
11491
11532
  }
11492
11533
  function stateDirOf(env) {
11493
11534
  const home = env.CABANE_COMPANION_HOME || env.HOME;
11494
- return home ? join29(home, ".cabane") : null;
11535
+ return home ? join28(home, ".cabane") : null;
11495
11536
  }
11496
11537
  function envFromPsE(output, command) {
11497
11538
  const line = output.replace(/\n$/, "");