@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 +84 -13
- package/dist/runtime.js +69 -9
- package/package.json +3 -3
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
|
|
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
|
-
...
|
|
1479
|
+
...json !== void 0 ? { "content-type": "application/json" } : {},
|
|
1480
|
+
...gzipped ? { "content-encoding": "gzip" } : {},
|
|
1453
1481
|
accept: "application/json"
|
|
1454
1482
|
},
|
|
1455
|
-
...
|
|
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 =
|
|
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 ${
|
|
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
|
|
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
|
-
...
|
|
1904
|
+
...json !== void 0 ? { "content-type": "application/json" } : {},
|
|
1905
|
+
...gzipped ? { "content-encoding": "gzip" } : {},
|
|
1881
1906
|
accept: "application/json"
|
|
1882
1907
|
},
|
|
1883
|
-
...
|
|
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 =
|
|
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.
|
|
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.
|
|
33
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.310",
|
|
34
34
|
"@clack/prompts": "1.8.1",
|
|
35
|
-
"@openai/codex-sdk": "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",
|