@cabane/companion 0.6.204 → 0.6.206

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/cli.js CHANGED
@@ -222,7 +222,7 @@ var init_cabane_command = __esm({
222
222
  });
223
223
 
224
224
  // src/errors.ts
225
- var CompanionError, ApiError, ConfigError, PrepareHookError;
225
+ var CompanionError, ApiError, EdgeBlockError, ConfigError, PrepareHookError;
226
226
  var init_errors = __esm({
227
227
  "src/errors.ts"() {
228
228
  "use strict";
@@ -242,6 +242,16 @@ var init_errors = __esm({
242
242
  status;
243
243
  body;
244
244
  };
245
+ EdgeBlockError = class extends ApiError {
246
+ constructor(status2, ray, requestId) {
247
+ super(status2, `${status2} blocked by a proxy in front of Cabane`);
248
+ this.ray = ray;
249
+ this.requestId = requestId;
250
+ this.name = "EdgeBlockError";
251
+ }
252
+ ray;
253
+ requestId;
254
+ };
245
255
  ConfigError = class extends CompanionError {
246
256
  constructor(message2) {
247
257
  super(message2);
@@ -1326,11 +1336,23 @@ function describeCause(err) {
1326
1336
  if (text === void 0 || text.length === 0) return void 0;
1327
1337
  return text.length > CAUSE_TEXT_CAP ? `${text.slice(0, CAUSE_TEXT_CAP)}\u2026` : text;
1328
1338
  }
1339
+ function edgeBlock(status2, headers, raw) {
1340
+ if (status2 !== 403 || !(headers.get("content-type") ?? "").includes("text/html")) return null;
1341
+ const requestId = /Request ID:\s*(?:<[^>]*>\s*)*([A-Za-z0-9-]+)/i.exec(raw)?.[1];
1342
+ return new EdgeBlockError(status2, headers.get("cf-ray") ?? void 0, requestId);
1343
+ }
1344
+ function isEdgeBlock(err) {
1345
+ return err instanceof EdgeBlockError;
1346
+ }
1329
1347
  function apiErrorLogFields(err) {
1330
1348
  const fields = {
1331
1349
  err: describeError(err)
1332
1350
  };
1333
- if (err instanceof ApiError) {
1351
+ if (err instanceof EdgeBlockError) {
1352
+ fields.status = err.status;
1353
+ if (err.ray) fields.edgeRay = err.ray;
1354
+ if (err.requestId) fields.edgeRequestId = err.requestId;
1355
+ } else if (err instanceof ApiError) {
1334
1356
  fields.status = err.status;
1335
1357
  const body = describeErrorBody(err.body);
1336
1358
  if (body !== void 0) fields.responseBody = body;
@@ -1367,6 +1389,8 @@ var init_boot_id = __esm({
1367
1389
  });
1368
1390
 
1369
1391
  // src/api.ts
1392
+ import { promisify } from "util";
1393
+ import { gzip } from "zlib";
1370
1394
  function activeRunOutboxKey(conversationId, agentId) {
1371
1395
  return `activerun:${conversationId}:${agentId}`;
1372
1396
  }
@@ -1416,7 +1440,7 @@ function errorMessage(status2, body) {
1416
1440
  if (typeof body === "string" && body.length > 0) return `${status2} ${body.slice(0, 200)}`;
1417
1441
  return `${status2} error`;
1418
1442
  }
1419
- var DEVICE_HEADER, RETRY_BACKOFF_MS, TURN_CONTEXT_BACKOFF_MS, ACTIVE_RUN_OUTBOX_SEQ, LEASE_CHECK_TIMEOUT_MS, CabaneApi;
1443
+ var DEVICE_HEADER, RETRY_BACKOFF_MS, TURN_CONTEXT_BACKOFF_MS, ACTIVE_RUN_OUTBOX_SEQ, LEASE_CHECK_TIMEOUT_MS, gzipAsync, CabaneApi;
1420
1444
  var init_api = __esm({
1421
1445
  "src/api.ts"() {
1422
1446
  "use strict";
@@ -1428,6 +1452,7 @@ var init_api = __esm({
1428
1452
  TURN_CONTEXT_BACKOFF_MS = [250, 750, 2e3, 5e3];
1429
1453
  ACTIVE_RUN_OUTBOX_SEQ = 0;
1430
1454
  LEASE_CHECK_TIMEOUT_MS = 1e4;
1455
+ gzipAsync = promisify(gzip);
1431
1456
  CabaneApi = class {
1432
1457
  constructor(opts) {
1433
1458
  this.opts = opts;
@@ -1444,19 +1469,24 @@ var init_api = __esm({
1444
1469
  // rethrows transport errors (fetch reject) unchanged so the caller's retry
1445
1470
  // logic can classify them.
1446
1471
  async attempt(method, path, body, signal) {
1472
+ const json = body !== void 0 ? JSON.stringify(body) : void 0;
1473
+ const gzipped = json !== void 0 && this.opts.gzipBodies?.() === true;
1447
1474
  const res = await fetch(`${this.base}${path}`, {
1448
1475
  method,
1449
1476
  headers: {
1450
1477
  Authorization: `Bearer ${this.opts.token}`,
1451
1478
  ...this.opts.deviceToken ? { [DEVICE_HEADER]: this.opts.deviceToken } : {},
1452
- ...body !== void 0 ? { "content-type": "application/json" } : {},
1479
+ ...json !== void 0 ? { "content-type": "application/json" } : {},
1480
+ ...gzipped ? { "content-encoding": "gzip" } : {},
1453
1481
  accept: "application/json"
1454
1482
  },
1455
- ...body !== void 0 ? { body: JSON.stringify(body) } : {},
1483
+ ...json !== void 0 ? { body: gzipped ? await gzipAsync(json) : json } : {},
1456
1484
  ...signal ? { signal } : {}
1457
1485
  });
1458
1486
  if (res.status === 204) return void 0;
1459
1487
  const raw = await res.text();
1488
+ const blocked = edgeBlock(res.status, res.headers, raw);
1489
+ if (blocked) throw blocked;
1460
1490
  let parsed = void 0;
1461
1491
  if (raw.length > 0) {
1462
1492
  try {
@@ -7477,7 +7507,7 @@ var init_opencode_lock = __esm({
7477
7507
  // src/turn-containment.ts
7478
7508
  import { execFile, execFileSync, spawn as spawn3 } from "child_process";
7479
7509
  import { readFileSync as readFileSync6 } from "fs";
7480
- import { promisify } from "util";
7510
+ import { promisify as promisify2 } from "util";
7481
7511
  function turnScopeUnit(turnId, attempt) {
7482
7512
  const safe2 = turnId.replace(/[^A-Za-z0-9_.-]/g, "-");
7483
7513
  return `cabane-turn-${safe2}-${attempt}`;
@@ -7761,7 +7791,7 @@ var execFileAsync, asContained, CONTAINMENT_STOP_GRACE_SEC, FALLBACK_GRACE_MS, C
7761
7791
  var init_turn_containment = __esm({
7762
7792
  "src/turn-containment.ts"() {
7763
7793
  "use strict";
7764
- execFileAsync = promisify(execFile);
7794
+ execFileAsync = promisify2(execFile);
7765
7795
  asContained = (child) => child;
7766
7796
  CONTAINMENT_STOP_GRACE_SEC = 5;
7767
7797
  FALLBACK_GRACE_MS = CONTAINMENT_STOP_GRACE_SEC * 1e3;
@@ -7969,7 +7999,10 @@ var init_protocol = __esm({
7969
7999
  sessionWriteNoticeClaimed: z14.boolean(),
7970
8000
  // The runner's identity, minted with its credential. The start
7971
8001
  // PATCH carries it so the lease knows which runner holds the span.
7972
- runnerId: z14.string()
8002
+ runnerId: z14.string(),
8003
+ // Whether the server took gzip request bodies at the latest heartbeat, as of
8004
+ // the start. The launcher writes `gzip_bodies` when that changes mid-turn.
8005
+ gzipBodies: z14.boolean().optional()
7973
8006
  });
7974
8007
  dispatchSchema = z14.object({
7975
8008
  type: z14.literal("device:dispatch_requested"),
@@ -8892,9 +8925,10 @@ var init_turn_committer = __esm({
8892
8925
  this.deps = deps;
8893
8926
  const source = deps.executionSourceId !== void 0 ? { executionSourceId: deps.executionSourceId } : {};
8894
8927
  this.onError = (err, hook) => {
8928
+ const what = `${String(deps.log.bindings().agentName ?? "the agent")}'s ${hook === "text" ? "reply" : "reply or activity"}`;
8895
8929
  deps.log.warn(
8896
8930
  { ...apiErrorLogFields(err), hook },
8897
- `Couldn't save ${String(deps.log.bindings().agentName ?? "the agent")}'s ${hook === "text" ? "reply" : "reply or activity"} to Cabane. ${mayNeedUpdate()}`
8931
+ isEdgeBlock(err) ? `Couldn't save ${what} to Cabane: a proxy or firewall between this device and Cabane blocked the request.` : `Couldn't save ${what} to Cabane. ${mayNeedUpdate()}`
8898
8932
  );
8899
8933
  deps.onCommitFailed?.(err);
8900
8934
  };
@@ -10520,6 +10554,7 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
10520
10554
  let resolveFirst;
10521
10555
  let gotFirst = false;
10522
10556
  let stopRequested = false;
10557
+ let gzipBodies;
10523
10558
  let gone = false;
10524
10559
  const causeWaiters = /* @__PURE__ */ new Set();
10525
10560
  const first = new Promise((resolve2) => resolveFirst = resolve2);
@@ -10533,6 +10568,7 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
10533
10568
  const message2 = decodeLauncherMessage(line);
10534
10569
  if (message2?.cabaneLauncher === "granted") waiting.get(message2.id)?.();
10535
10570
  if (message2?.cabaneLauncher === "stop") stopRequested = true;
10571
+ if (message2?.cabaneLauncher === "gzip_bodies") gzipBodies = message2.on;
10536
10572
  if (message2?.cabaneLauncher === "cause") {
10537
10573
  for (const resolve2 of causeWaiters) resolve2(message2.cause);
10538
10574
  }
@@ -10548,6 +10584,9 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
10548
10584
  get stopRequested() {
10549
10585
  return stopRequested;
10550
10586
  },
10587
+ get gzipBodies() {
10588
+ return gzipBodies;
10589
+ },
10551
10590
  terminationCause(timeoutMs = CAUSE_TIMEOUT_MS) {
10552
10591
  if (gone) return Promise.resolve(null);
10553
10592
  return new Promise((resolve2) => {
@@ -10634,7 +10673,8 @@ function runnerTurnOptions(input, log2, emit, extras = {}) {
10634
10673
  baseUrl: input.baseUrl,
10635
10674
  token: input.credential,
10636
10675
  outbox,
10637
- log: log2
10676
+ log: log2,
10677
+ gzipBodies: extras.gzipBodies ?? (() => spec.gzipBodies === true)
10638
10678
  });
10639
10679
  extras.onOutbox?.(outbox, api);
10640
10680
  const health = new Map(Object.entries(spec.connectorHealth));
@@ -10787,6 +10827,7 @@ async function runTurn(env, deps) {
10787
10827
  shims,
10788
10828
  acquireLock: (key, signal) => channel.acquire(key, signal),
10789
10829
  stopRequested: () => channel.stopRequested,
10830
+ gzipBodies: () => channel.gzipBodies ?? input.spec.gzipBodies === true,
10790
10831
  terminationCause: () => channel.terminationCause(),
10791
10832
  onProcessGroup: (pgid) => {
10792
10833
  groups.push(pgid);
@@ -12771,6 +12812,22 @@ init_boot_id();
12771
12812
  init_turn_containment();
12772
12813
  init_protocol();
12773
12814
  import { createInterface } from "readline";
12815
+ function gzipBodiesSignal() {
12816
+ let on = false;
12817
+ const listeners = /* @__PURE__ */ new Set();
12818
+ return {
12819
+ current: () => on,
12820
+ subscribe(listener) {
12821
+ listeners.add(listener);
12822
+ return () => listeners.delete(listener);
12823
+ },
12824
+ set(next) {
12825
+ if (next === on) return;
12826
+ on = next;
12827
+ for (const listener of listeners) listener(on);
12828
+ }
12829
+ };
12830
+ }
12774
12831
  function selfRunnerCommand() {
12775
12832
  const script = process.argv[1];
12776
12833
  if (!script) throw new Error("runner: cannot tell which script started this companion");
@@ -12832,7 +12889,8 @@ function runnerSpec(opts, launch, duties, runnerId) {
12832
12889
  transcripts: opts.transcriptDir !== void 0,
12833
12890
  logLevel: opts.log.level,
12834
12891
  connectorHealth: healthSnapshot(launch.connectorHealth),
12835
- sessionWriteNoticeClaimed: duties.sessionWriteNoticeClaimed()
12892
+ sessionWriteNoticeClaimed: duties.sessionWriteNoticeClaimed(),
12893
+ ...launch.gzipBodies?.current() ? { gzipBodies: true } : {}
12836
12894
  };
12837
12895
  }
12838
12896
  var PINO_LEVELS = {
@@ -12956,6 +13014,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
12956
13014
  const stopRunner = new AbortController();
12957
13015
  let ready = false;
12958
13016
  let child;
13017
+ let unsubscribeGzip;
12959
13018
  let stopAnnounced = false;
12960
13019
  const announceStop = () => {
12961
13020
  if (stopAnnounced || !child?.stdin.writable) return;
@@ -13033,6 +13092,10 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
13033
13092
  stdin: runnerStdin(input)
13034
13093
  });
13035
13094
  child = running;
13095
+ unsubscribeGzip = launch.gzipBodies?.subscribe((on) => {
13096
+ if (running.stdin.writable)
13097
+ running.stdin.write(encodeLauncherMessage({ cabaneLauncher: "gzip_bodies", on }));
13098
+ });
13036
13099
  if (controller.signal.aborted) announceStop();
13037
13100
  log2.debug(
13038
13101
  { turnId, runnerPid: running.pid, provider: launch.provider.name },
@@ -13118,6 +13181,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
13118
13181
  await Promise.all([stdoutDone, stderrDone]);
13119
13182
  } finally {
13120
13183
  if (locks) giveBackLocks(locks, `${turnId}#`, remote, launch, log2);
13184
+ unsubscribeGzip?.();
13121
13185
  child?.stdin.end();
13122
13186
  controller.signal.removeEventListener("abort", forwardStop);
13123
13187
  duties.releaseAbort(turnId, controller);
@@ -14360,6 +14424,10 @@ var CompanionSupervisor = class {
14360
14424
  reconciliationAbort = new AbortController();
14361
14425
  // The version-skew warning is logged once, not every heartbeat.
14362
14426
  versionSkewWarned = false;
14427
+ // Whether the server takes gzip request bodies, as the latest accepted
14428
+ // heartbeat said; false until one has. Read per request by every agent's API
14429
+ // client and pushed to the runners in flight when it changes.
14430
+ gzipBodies = gzipBodiesSignal();
14363
14431
  // This device's id, captured from the heartbeat / assignments response. The SSE
14364
14432
  // router uses it to tell a dispatch addressed to THIS device (a freshly-assigned
14365
14433
  // agent the last poll hasn't caught yet — the assignment-change race) from one
@@ -14562,6 +14630,7 @@ var CompanionSupervisor = class {
14562
14630
  this.hub.setDevice({ deviceId: res.deviceId });
14563
14631
  this.deviceId = res.deviceId;
14564
14632
  this.checkVersionSkew(res.serverVersion);
14633
+ this.gzipBodies.set(res.acceptsGzipBodies === true);
14565
14634
  this.heartbeated = true;
14566
14635
  const view = acknowledgedDeviceSchema.safeParse(res.device);
14567
14636
  this.heartbeatStatus = {
@@ -14803,7 +14872,8 @@ var CompanionSupervisor = class {
14803
14872
  token: credential,
14804
14873
  ...this.config.deviceToken ? { deviceToken: this.config.deviceToken } : {},
14805
14874
  outbox,
14806
- log: agentLog
14875
+ log: agentLog,
14876
+ gzipBodies: () => this.gzipBodies.current()
14807
14877
  });
14808
14878
  const aborts = /* @__PURE__ */ new Map();
14809
14879
  const dispatcher = this.buildDispatcher({
@@ -15014,7 +15084,8 @@ var CompanionSupervisor = class {
15014
15084
  ...this.runnerEnv ? { env: this.runnerEnv } : {},
15015
15085
  reportRunnerGone: (report) => this.reportRunnerGone(report),
15016
15086
  connectorHealth: this.connectorHealth,
15017
- locks: this.runnerLocks
15087
+ locks: this.runnerLocks,
15088
+ gzipBodies: this.gzipBodies
15018
15089
  },
15019
15090
  observer: this.hub.observerFor(ctx2.workspaceId, ctx2.agentId, ctx2.workspaceSlug),
15020
15091
  aborts: ctx2.aborts
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);
@@ -1828,11 +1838,20 @@ function describeCause(err) {
1828
1838
  if (text === void 0 || text.length === 0) return void 0;
1829
1839
  return text.length > CAUSE_TEXT_CAP ? `${text.slice(0, CAUSE_TEXT_CAP)}\u2026` : text;
1830
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
+ }
1831
1846
  function apiErrorLogFields(err) {
1832
1847
  const fields = {
1833
1848
  err: describeError(err)
1834
1849
  };
1835
- 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) {
1836
1855
  fields.status = err.status;
1837
1856
  const body = describeErrorBody(err.body);
1838
1857
  if (body !== void 0) fields.responseBody = body;
@@ -1848,11 +1867,14 @@ var bootId = randomUUID();
1848
1867
  import { randomUUID as randomUUID5 } from "crypto";
1849
1868
 
1850
1869
  // src/api.ts
1870
+ import { promisify } from "util";
1871
+ import { gzip } from "zlib";
1851
1872
  var DEVICE_HEADER = "x-cabane-device";
1852
1873
  var RETRY_BACKOFF_MS = [250, 750];
1853
1874
  var TURN_CONTEXT_BACKOFF_MS = [250, 750, 2e3, 5e3];
1854
1875
  var ACTIVE_RUN_OUTBOX_SEQ = 0;
1855
1876
  var LEASE_CHECK_TIMEOUT_MS = 1e4;
1877
+ var gzipAsync = promisify(gzip);
1856
1878
  function activeRunOutboxKey(conversationId, agentId) {
1857
1879
  return `activerun:${conversationId}:${agentId}`;
1858
1880
  }
@@ -1872,19 +1894,24 @@ var CabaneApi = class {
1872
1894
  // rethrows transport errors (fetch reject) unchanged so the caller's retry
1873
1895
  // logic can classify them.
1874
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;
1875
1899
  const res = await fetch(`${this.base}${path}`, {
1876
1900
  method,
1877
1901
  headers: {
1878
1902
  Authorization: `Bearer ${this.opts.token}`,
1879
1903
  ...this.opts.deviceToken ? { [DEVICE_HEADER]: this.opts.deviceToken } : {},
1880
- ...body !== void 0 ? { "content-type": "application/json" } : {},
1904
+ ...json !== void 0 ? { "content-type": "application/json" } : {},
1905
+ ...gzipped ? { "content-encoding": "gzip" } : {},
1881
1906
  accept: "application/json"
1882
1907
  },
1883
- ...body !== void 0 ? { body: JSON.stringify(body) } : {},
1908
+ ...json !== void 0 ? { body: gzipped ? await gzipAsync(json) : json } : {},
1884
1909
  ...signal ? { signal } : {}
1885
1910
  });
1886
1911
  if (res.status === 204) return void 0;
1887
1912
  const raw = await res.text();
1913
+ const blocked = edgeBlock(res.status, res.headers, raw);
1914
+ if (blocked) throw blocked;
1888
1915
  let parsed = void 0;
1889
1916
  if (raw.length > 0) {
1890
1917
  try {
@@ -7564,8 +7591,8 @@ async function stopRemoteOpencodeSession(serverUrl, sessionId, opts = {}) {
7564
7591
  // src/turn-containment.ts
7565
7592
  import { execFile, execFileSync, spawn as spawn2 } from "child_process";
7566
7593
  import { readFileSync as readFileSync6 } from "fs";
7567
- import { promisify } from "util";
7568
- var execFileAsync = promisify(execFile);
7594
+ import { promisify as promisify2 } from "util";
7595
+ var execFileAsync = promisify2(execFile);
7569
7596
  var CONTAINMENT_STOP_GRACE_SEC = 5;
7570
7597
  var FALLBACK_GRACE_MS = CONTAINMENT_STOP_GRACE_SEC * 1e3;
7571
7598
  var OOM_READ_TIMEOUT_MS = 1500;
@@ -7718,7 +7745,10 @@ var runnerSpecSchema = z14.object({
7718
7745
  sessionWriteNoticeClaimed: z14.boolean(),
7719
7746
  // The runner's identity, minted with its credential. The start
7720
7747
  // PATCH carries it so the lease knows which runner holds the span.
7721
- 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()
7722
7752
  });
7723
7753
  var dispatchSchema = z14.object({
7724
7754
  type: z14.literal("device:dispatch_requested"),
@@ -7767,6 +7797,22 @@ function decodeRunnerEvent(line) {
7767
7797
  }
7768
7798
 
7769
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
+ }
7770
7816
  function selfRunnerCommand() {
7771
7817
  const script = process.argv[1];
7772
7818
  if (!script) throw new Error("runner: cannot tell which script started this companion");
@@ -7828,7 +7874,8 @@ function runnerSpec(opts, launch, duties, runnerId) {
7828
7874
  transcripts: opts.transcriptDir !== void 0,
7829
7875
  logLevel: opts.log.level,
7830
7876
  connectorHealth: healthSnapshot(launch.connectorHealth),
7831
- sessionWriteNoticeClaimed: duties.sessionWriteNoticeClaimed()
7877
+ sessionWriteNoticeClaimed: duties.sessionWriteNoticeClaimed(),
7878
+ ...launch.gzipBodies?.current() ? { gzipBodies: true } : {}
7832
7879
  };
7833
7880
  }
7834
7881
  var PINO_LEVELS = {
@@ -7952,6 +7999,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
7952
7999
  const stopRunner = new AbortController();
7953
8000
  let ready = false;
7954
8001
  let child;
8002
+ let unsubscribeGzip;
7955
8003
  let stopAnnounced = false;
7956
8004
  const announceStop = () => {
7957
8005
  if (stopAnnounced || !child?.stdin.writable) return;
@@ -8029,6 +8077,10 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8029
8077
  stdin: runnerStdin(input)
8030
8078
  });
8031
8079
  child = running;
8080
+ unsubscribeGzip = launch.gzipBodies?.subscribe((on) => {
8081
+ if (running.stdin.writable)
8082
+ running.stdin.write(encodeLauncherMessage({ cabaneLauncher: "gzip_bodies", on }));
8083
+ });
8032
8084
  if (controller.signal.aborted) announceStop();
8033
8085
  log.debug(
8034
8086
  { turnId, runnerPid: running.pid, provider: launch.provider.name },
@@ -8114,6 +8166,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8114
8166
  await Promise.all([stdoutDone, stderrDone]);
8115
8167
  } finally {
8116
8168
  if (locks) giveBackLocks(locks, `${turnId}#`, remote, launch, log);
8169
+ unsubscribeGzip?.();
8117
8170
  child?.stdin.end();
8118
8171
  controller.signal.removeEventListener("abort", forwardStop);
8119
8172
  duties.releaseAbort(turnId, controller);
@@ -10041,6 +10094,10 @@ var CompanionSupervisor = class {
10041
10094
  reconciliationAbort = new AbortController();
10042
10095
  // The version-skew warning is logged once, not every heartbeat.
10043
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();
10044
10101
  // This device's id, captured from the heartbeat / assignments response. The SSE
10045
10102
  // router uses it to tell a dispatch addressed to THIS device (a freshly-assigned
10046
10103
  // agent the last poll hasn't caught yet — the assignment-change race) from one
@@ -10243,6 +10300,7 @@ var CompanionSupervisor = class {
10243
10300
  this.hub.setDevice({ deviceId: res.deviceId });
10244
10301
  this.deviceId = res.deviceId;
10245
10302
  this.checkVersionSkew(res.serverVersion);
10303
+ this.gzipBodies.set(res.acceptsGzipBodies === true);
10246
10304
  this.heartbeated = true;
10247
10305
  const view = acknowledgedDeviceSchema.safeParse(res.device);
10248
10306
  this.heartbeatStatus = {
@@ -10484,7 +10542,8 @@ var CompanionSupervisor = class {
10484
10542
  token: credential,
10485
10543
  ...this.config.deviceToken ? { deviceToken: this.config.deviceToken } : {},
10486
10544
  outbox,
10487
- log: agentLog
10545
+ log: agentLog,
10546
+ gzipBodies: () => this.gzipBodies.current()
10488
10547
  });
10489
10548
  const aborts = /* @__PURE__ */ new Map();
10490
10549
  const dispatcher = this.buildDispatcher({
@@ -10695,7 +10754,8 @@ var CompanionSupervisor = class {
10695
10754
  ...this.runnerEnv ? { env: this.runnerEnv } : {},
10696
10755
  reportRunnerGone: (report) => this.reportRunnerGone(report),
10697
10756
  connectorHealth: this.connectorHealth,
10698
- locks: this.runnerLocks
10757
+ locks: this.runnerLocks,
10758
+ gzipBodies: this.gzipBodies
10699
10759
  },
10700
10760
  observer: this.hub.observerFor(ctx.workspaceId, ctx.agentId, ctx.workspaceSlug),
10701
10761
  aborts: ctx.aborts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.204",
3
+ "version": "0.6.206",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect an agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
6
  "license": "UNLICENSED",
@@ -30,9 +30,9 @@
30
30
  "prepublishOnly": "pnpm build"
31
31
  },
32
32
  "dependencies": {
33
- "@anthropic-ai/claude-agent-sdk": "0.3.303",
33
+ "@anthropic-ai/claude-agent-sdk": "0.3.310",
34
34
  "@clack/prompts": "1.8.1",
35
- "@openai/codex-sdk": "0.163.0",
35
+ "@openai/codex-sdk": "0.164.0",
36
36
  "@hono/node-server": "1.19.14",
37
37
  "commander": "12.1.0",
38
38
  "eventsource-parser": "3.0.8",