@cabane/companion 0.6.142 → 0.6.144
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 +383 -181
- package/dist/runtime.js +336 -141
- package/package.json +1 -1
package/dist/runtime.js
CHANGED
|
@@ -17,14 +17,14 @@ import { z as z2 } from "zod";
|
|
|
17
17
|
|
|
18
18
|
// src/errors.ts
|
|
19
19
|
var CompanionError = class extends Error {
|
|
20
|
-
constructor(
|
|
21
|
-
super(
|
|
20
|
+
constructor(message2) {
|
|
21
|
+
super(message2);
|
|
22
22
|
this.name = "CompanionError";
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
var ApiError = class extends CompanionError {
|
|
26
|
-
constructor(status,
|
|
27
|
-
super(
|
|
26
|
+
constructor(status, message2, body) {
|
|
27
|
+
super(message2);
|
|
28
28
|
this.status = status;
|
|
29
29
|
this.body = body;
|
|
30
30
|
this.name = "ApiError";
|
|
@@ -33,14 +33,14 @@ var ApiError = class extends CompanionError {
|
|
|
33
33
|
body;
|
|
34
34
|
};
|
|
35
35
|
var ConfigError = class extends CompanionError {
|
|
36
|
-
constructor(
|
|
37
|
-
super(
|
|
36
|
+
constructor(message2) {
|
|
37
|
+
super(message2);
|
|
38
38
|
this.name = "ConfigError";
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
var PrepareHookError = class extends CompanionError {
|
|
42
|
-
constructor(
|
|
43
|
-
super(
|
|
42
|
+
constructor(message2) {
|
|
43
|
+
super(message2);
|
|
44
44
|
this.name = "PrepareHookError";
|
|
45
45
|
}
|
|
46
46
|
};
|
|
@@ -1202,8 +1202,8 @@ function formatHumanLine(record) {
|
|
|
1202
1202
|
let line = `${time} ${word ? `${word} ` : ""}${context ? `${context} ` : ""}${humanText(record.msg)}`;
|
|
1203
1203
|
const err = record.err;
|
|
1204
1204
|
const errorRecord = err !== null && typeof err === "object" ? err : null;
|
|
1205
|
-
const
|
|
1206
|
-
const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) ||
|
|
1205
|
+
const message2 = humanText(errorRecord && "message" in errorRecord ? errorRecord.message : err);
|
|
1206
|
+
const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) || message2}` : message2;
|
|
1207
1207
|
if (detail && !line.includes(detail)) line += ` (${detail})`;
|
|
1208
1208
|
if (level < 30) {
|
|
1209
1209
|
const hidden = /* @__PURE__ */ new Set([
|
|
@@ -1337,9 +1337,9 @@ var CompanionStateHub = class {
|
|
|
1337
1337
|
if (info.deviceLabel !== void 0) this.deviceLabel = info.deviceLabel;
|
|
1338
1338
|
this.emitStatus();
|
|
1339
1339
|
}
|
|
1340
|
-
setDeviceError(
|
|
1341
|
-
if (this.deviceError ===
|
|
1342
|
-
this.deviceError =
|
|
1340
|
+
setDeviceError(message2) {
|
|
1341
|
+
if (this.deviceError === message2) return;
|
|
1342
|
+
this.deviceError = message2;
|
|
1343
1343
|
this.emitStatus();
|
|
1344
1344
|
}
|
|
1345
1345
|
clearDeviceError() {
|
|
@@ -1960,7 +1960,7 @@ async function warnAboutHarnessReadiness(cfg, deps = {}) {
|
|
|
1960
1960
|
const probeClaude = deps.probeClaude ?? claudeOnPath;
|
|
1961
1961
|
const probeCodex = deps.probeCodex ?? codexOnPath;
|
|
1962
1962
|
const resolveBundled = deps.resolveBundled ?? ((runtime) => resolveBundledBinary(runtime));
|
|
1963
|
-
const warn = deps.warn ?? ((
|
|
1963
|
+
const warn = deps.warn ?? ((message2) => process.stderr.write(`${message2}
|
|
1964
1964
|
`));
|
|
1965
1965
|
const connected = [
|
|
1966
1966
|
...isClaudeCodeConnected(cfg) ? ["Claude Code"] : [],
|
|
@@ -2305,8 +2305,8 @@ function apiErrorCode(err) {
|
|
|
2305
2305
|
function apiErrorMessage(err) {
|
|
2306
2306
|
const body = err instanceof ApiError ? err.body : void 0;
|
|
2307
2307
|
if (body !== null && typeof body === "object") {
|
|
2308
|
-
const
|
|
2309
|
-
if (typeof
|
|
2308
|
+
const message2 = body.message;
|
|
2309
|
+
if (typeof message2 === "string" && message2.length > 0) return message2;
|
|
2310
2310
|
}
|
|
2311
2311
|
return err instanceof Error ? err.message : String(err);
|
|
2312
2312
|
}
|
|
@@ -2341,9 +2341,9 @@ function describeErrorBody(body) {
|
|
|
2341
2341
|
}
|
|
2342
2342
|
var CAUSE_TEXT_CAP = 200;
|
|
2343
2343
|
function describeError(err) {
|
|
2344
|
-
const
|
|
2344
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
2345
2345
|
const cause = describeCause(err);
|
|
2346
|
-
return cause === void 0 ?
|
|
2346
|
+
return cause === void 0 ? message2 : `${message2}: ${cause}`;
|
|
2347
2347
|
}
|
|
2348
2348
|
function describeCause(err) {
|
|
2349
2349
|
if (!(err instanceof Error)) return void 0;
|
|
@@ -3309,14 +3309,11 @@ var turnEventSchema = z6.discriminatedUnion("type", [
|
|
|
3309
3309
|
// claude-code/codex it's the runtime's CUMULATIVE total summed across every model
|
|
3310
3310
|
// request in the agentic turn, so it grows with the tool-call count and is NOT
|
|
3311
3311
|
// "how full is the window." `contextTokens` is the distinct CONTEXT-OCCUPANCY
|
|
3312
|
-
// read
|
|
3313
|
-
//
|
|
3314
|
-
//
|
|
3315
|
-
// runtime
|
|
3316
|
-
//
|
|
3317
|
-
// clean final-request figure (codex's cumulative-only usage) omits `contextTokens`
|
|
3318
|
-
// and the gauge falls back to the raw count; `contextWindow` falls back to the
|
|
3319
|
-
// model catalog.
|
|
3312
|
+
// read and `contextWindow` the window it is measured against, both as the
|
|
3313
|
+
// harness reported them (claude-code's `getContextUsage`, or its final
|
|
3314
|
+
// request's input and `modelUsage` window when that call gives nothing). A
|
|
3315
|
+
// runtime that can't source them (codex's cumulative-only usage) omits both,
|
|
3316
|
+
// and no surface shows a reading for that turn.
|
|
3320
3317
|
//
|
|
3321
3318
|
// CT601: two more optional carry-homes on the terminal result, alongside
|
|
3322
3319
|
// `usage`. `resolvedModel` is the CONCRETE model the runtime actually ran —
|
|
@@ -3339,7 +3336,25 @@ var turnEventSchema = z6.discriminatedUnion("type", [
|
|
|
3339
3336
|
cacheReadTokens: z6.number().optional(),
|
|
3340
3337
|
cacheCreationTokens: z6.number().optional(),
|
|
3341
3338
|
contextTokens: z6.number().optional(),
|
|
3342
|
-
contextWindow: z6.number().optional()
|
|
3339
|
+
contextWindow: z6.number().optional(),
|
|
3340
|
+
// CT1751: the rest of the harness's own context reading, when it gave
|
|
3341
|
+
// one: where it will compact, its `/context` breakdown, and every
|
|
3342
|
+
// compaction it reported during the turn. Claude Code only.
|
|
3343
|
+
contextCompactAt: z6.number().optional(),
|
|
3344
|
+
contextCategories: z6.array(
|
|
3345
|
+
z6.object({
|
|
3346
|
+
name: z6.string(),
|
|
3347
|
+
tokens: z6.number(),
|
|
3348
|
+
kind: z6.enum(["used", "free", "buffer", "deferred"])
|
|
3349
|
+
})
|
|
3350
|
+
).optional(),
|
|
3351
|
+
contextCompactions: z6.array(
|
|
3352
|
+
z6.object({
|
|
3353
|
+
trigger: z6.enum(["manual", "auto"]),
|
|
3354
|
+
preTokens: z6.number(),
|
|
3355
|
+
postTokens: z6.number().optional()
|
|
3356
|
+
})
|
|
3357
|
+
).optional()
|
|
3343
3358
|
}).optional(),
|
|
3344
3359
|
resolvedModel: z6.string().optional(),
|
|
3345
3360
|
resolvedConfig: z6.object({
|
|
@@ -4294,6 +4309,7 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
4294
4309
|
let resultReason;
|
|
4295
4310
|
let sawResult = false;
|
|
4296
4311
|
let usage;
|
|
4312
|
+
const compactions = [];
|
|
4297
4313
|
let lastRequestContextTokens;
|
|
4298
4314
|
let resolvedModel;
|
|
4299
4315
|
const signals = { sawRejectedLimit: false };
|
|
@@ -4319,6 +4335,9 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
4319
4335
|
...ctx.degraded ? { degraded: true } : {}
|
|
4320
4336
|
};
|
|
4321
4337
|
}
|
|
4338
|
+
} else if (msg.type === "system" && msg.subtype === "compact_boundary") {
|
|
4339
|
+
const compaction = readCompaction(msg);
|
|
4340
|
+
if (compaction) compactions.push(compaction);
|
|
4322
4341
|
} else if (!workspaceProven && (msg.type === "assistant" || msg.type === "user" || msg.type === "result")) {
|
|
4323
4342
|
if (preInitToolActivity(msg) && ctx.startEvidence) ctx.startEvidence.toolActivity = true;
|
|
4324
4343
|
const failure = classifyPreInitFrame(msg, signals);
|
|
@@ -4363,6 +4382,9 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
4363
4382
|
usage.contextTokens = lastRequestContextTokens;
|
|
4364
4383
|
const window = readContextWindow(msg, resolvedModel);
|
|
4365
4384
|
if (window !== void 0) usage.contextWindow = window;
|
|
4385
|
+
if (compactions.length > 0) usage.contextCompactions = compactions;
|
|
4386
|
+
const reading = await readHarnessContext(ctx.readContextUsage);
|
|
4387
|
+
if (reading) Object.assign(usage, reading);
|
|
4366
4388
|
}
|
|
4367
4389
|
const isError = msg.is_error === true;
|
|
4368
4390
|
if (msg.subtype === "success" && !isError) {
|
|
@@ -4378,15 +4400,15 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
4378
4400
|
}
|
|
4379
4401
|
} catch (err) {
|
|
4380
4402
|
if (ctx.signal.aborted) throw err;
|
|
4381
|
-
const
|
|
4382
|
-
const failure = mcpInventory.initReceived ? classifyErrorText(
|
|
4403
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
4404
|
+
const failure = mcpInventory.initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
|
|
4383
4405
|
if (!failure && (mcpInventory.initReceived || ctx.resumedSessionId === null)) throw err;
|
|
4384
4406
|
ok = false;
|
|
4385
4407
|
if (failure) {
|
|
4386
4408
|
resultReason = encodeFailureReason(failure);
|
|
4387
4409
|
} else {
|
|
4388
4410
|
resultReason = "session_start_failed";
|
|
4389
|
-
if (ctx.startEvidence) ctx.startEvidence.thrown =
|
|
4411
|
+
if (ctx.startEvidence) ctx.startEvidence.thrown = message2.slice(0, 200);
|
|
4390
4412
|
}
|
|
4391
4413
|
sawResult = true;
|
|
4392
4414
|
}
|
|
@@ -4514,6 +4536,58 @@ function readRequestContextTokens(msg) {
|
|
|
4514
4536
|
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
4515
4537
|
return num(usage.input_tokens) + num(usage.cache_read_input_tokens) + num(usage.cache_creation_input_tokens);
|
|
4516
4538
|
}
|
|
4539
|
+
var CONTEXT_READING_TIMEOUT_MS = 2e3;
|
|
4540
|
+
async function readHarnessContext(read) {
|
|
4541
|
+
if (!read) return void 0;
|
|
4542
|
+
let timer;
|
|
4543
|
+
const timeout = new Promise((resolve2) => {
|
|
4544
|
+
timer = setTimeout(() => resolve2(void 0), CONTEXT_READING_TIMEOUT_MS);
|
|
4545
|
+
});
|
|
4546
|
+
try {
|
|
4547
|
+
return parseContextUsage(await Promise.race([read(), timeout]));
|
|
4548
|
+
} catch {
|
|
4549
|
+
return void 0;
|
|
4550
|
+
} finally {
|
|
4551
|
+
clearTimeout(timer);
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
var CATEGORY_KINDS = /* @__PURE__ */ new Set(["used", "free", "buffer", "deferred"]);
|
|
4555
|
+
function parseContextUsage(response) {
|
|
4556
|
+
const r = response;
|
|
4557
|
+
if (!r || typeof r !== "object") return void 0;
|
|
4558
|
+
const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
|
|
4559
|
+
const total = count(r.totalTokens);
|
|
4560
|
+
const window = count(r.maxTokens);
|
|
4561
|
+
if (total === void 0 || window === void 0 || window === 0) return void 0;
|
|
4562
|
+
const categories = [];
|
|
4563
|
+
for (const c of Array.isArray(r.categories) ? r.categories : []) {
|
|
4564
|
+
const row = c;
|
|
4565
|
+
const tokens = count(row?.tokens);
|
|
4566
|
+
if (typeof row?.name === "string" && tokens !== void 0 && typeof row.kind === "string" && CATEGORY_KINDS.has(row.kind)) {
|
|
4567
|
+
categories.push({ name: row.name, tokens, kind: row.kind });
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
const buffer = categories.filter((c) => c.kind === "buffer").reduce((sum, c) => sum + c.tokens, 0);
|
|
4571
|
+
return {
|
|
4572
|
+
contextTokens: total,
|
|
4573
|
+
contextWindow: window,
|
|
4574
|
+
...buffer > 0 && buffer < window ? { contextCompactAt: window - buffer } : {},
|
|
4575
|
+
...categories.length > 0 ? { contextCategories: categories } : {}
|
|
4576
|
+
};
|
|
4577
|
+
}
|
|
4578
|
+
function readCompaction(msg) {
|
|
4579
|
+
const meta = msg.compact_metadata;
|
|
4580
|
+
const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
|
|
4581
|
+
const preTokens = count(meta?.pre_tokens);
|
|
4582
|
+
if (!meta || preTokens === void 0) return void 0;
|
|
4583
|
+
if (meta.trigger !== "manual" && meta.trigger !== "auto") return void 0;
|
|
4584
|
+
const postTokens = count(meta.post_tokens);
|
|
4585
|
+
return {
|
|
4586
|
+
trigger: meta.trigger,
|
|
4587
|
+
preTokens,
|
|
4588
|
+
...postTokens !== void 0 ? { postTokens } : {}
|
|
4589
|
+
};
|
|
4590
|
+
}
|
|
4517
4591
|
function readContextWindow(msg, resolvedModel) {
|
|
4518
4592
|
const modelUsage = msg.modelUsage;
|
|
4519
4593
|
if (!modelUsage || typeof modelUsage !== "object") return void 0;
|
|
@@ -4531,7 +4605,7 @@ function readContextWindow(msg, resolvedModel) {
|
|
|
4531
4605
|
}
|
|
4532
4606
|
|
|
4533
4607
|
// packages/agent-runtime/src/claude-code/prompt-input.ts
|
|
4534
|
-
function buildQueryPrompt(req) {
|
|
4608
|
+
function buildQueryPrompt(req, openUntil) {
|
|
4535
4609
|
const content = req.content;
|
|
4536
4610
|
return (async function* () {
|
|
4537
4611
|
yield {
|
|
@@ -4540,6 +4614,7 @@ function buildQueryPrompt(req) {
|
|
|
4540
4614
|
message: { role: "user", content },
|
|
4541
4615
|
parent_tool_use_id: null
|
|
4542
4616
|
};
|
|
4617
|
+
if (openUntil) await openUntil;
|
|
4543
4618
|
})();
|
|
4544
4619
|
}
|
|
4545
4620
|
|
|
@@ -4551,19 +4626,34 @@ function attachAbortController(options, signal) {
|
|
|
4551
4626
|
else signal.addEventListener("abort", () => sdkAbort.abort(), { once: true });
|
|
4552
4627
|
options.abortController = sdkAbort;
|
|
4553
4628
|
}
|
|
4554
|
-
function sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
|
|
4629
|
+
async function* sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
|
|
4555
4630
|
attachAbortController(built.options, signal);
|
|
4556
|
-
|
|
4557
|
-
const iter = {
|
|
4558
|
-
[Symbol.asyncIterator]: () => queryFn({ prompt, options: built.options })[Symbol.asyncIterator]()
|
|
4631
|
+
let release = () => {
|
|
4559
4632
|
};
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
cwd: req.local.cwd,
|
|
4563
|
-
resumedSessionId: built.resume,
|
|
4564
|
-
degraded,
|
|
4565
|
-
...startEvidence ? { startEvidence } : {}
|
|
4633
|
+
const held = new Promise((resolve2) => {
|
|
4634
|
+
release = resolve2;
|
|
4566
4635
|
});
|
|
4636
|
+
const prompt = buildQueryPrompt(req, held);
|
|
4637
|
+
let active;
|
|
4638
|
+
const iter = {
|
|
4639
|
+
[Symbol.asyncIterator]: () => {
|
|
4640
|
+
const q = queryFn({ prompt, options: built.options });
|
|
4641
|
+
active = q;
|
|
4642
|
+
return q[Symbol.asyncIterator]();
|
|
4643
|
+
}
|
|
4644
|
+
};
|
|
4645
|
+
try {
|
|
4646
|
+
yield* decodeSdkStream(iter, {
|
|
4647
|
+
signal,
|
|
4648
|
+
readContextUsage: async () => typeof active?.getContextUsage === "function" ? active.getContextUsage({ detail: "summary" }) : void 0,
|
|
4649
|
+
cwd: req.local.cwd,
|
|
4650
|
+
resumedSessionId: built.resume,
|
|
4651
|
+
degraded,
|
|
4652
|
+
...startEvidence ? { startEvidence } : {}
|
|
4653
|
+
});
|
|
4654
|
+
} finally {
|
|
4655
|
+
release();
|
|
4656
|
+
}
|
|
4567
4657
|
}
|
|
4568
4658
|
async function freshRequest(req, reason, hooks) {
|
|
4569
4659
|
const recomposed = hooks?.fresh ? await hooks.fresh(reason) : null;
|
|
@@ -5315,9 +5405,9 @@ function readSessionError(properties) {
|
|
|
5315
5405
|
if (typeof err === "string") return err;
|
|
5316
5406
|
const rec = asRecord(err);
|
|
5317
5407
|
if (!rec) return "unknown";
|
|
5318
|
-
const { name, message } = deepestError(rec);
|
|
5319
|
-
if (
|
|
5320
|
-
if (
|
|
5408
|
+
const { name, message: message2 } = deepestError(rec);
|
|
5409
|
+
if (message2 && name && !isGenericErrorName(name)) return `${name}: ${message2}`;
|
|
5410
|
+
if (message2) return message2;
|
|
5321
5411
|
if (name) return name;
|
|
5322
5412
|
if (typeof rec.name === "string") return rec.name;
|
|
5323
5413
|
return "unknown";
|
|
@@ -5330,10 +5420,10 @@ function deepestError(node) {
|
|
|
5330
5420
|
const best = {};
|
|
5331
5421
|
let current = node;
|
|
5332
5422
|
for (let depth = 0; current && depth < 8; depth++) {
|
|
5333
|
-
const
|
|
5423
|
+
const message2 = typeof current.message === "string" ? current.message : void 0;
|
|
5334
5424
|
const name = typeof current.name === "string" && !isGenericErrorName(current.name) ? current.name : void 0;
|
|
5335
|
-
if (
|
|
5336
|
-
best.message =
|
|
5425
|
+
if (message2) {
|
|
5426
|
+
best.message = message2;
|
|
5337
5427
|
if (name) best.name = name;
|
|
5338
5428
|
} else if (name && !best.name) {
|
|
5339
5429
|
best.name = name;
|
|
@@ -5924,9 +6014,9 @@ var echoedUserPart = (partId, messageID, text) => ({
|
|
|
5924
6014
|
properties: { part: { type: "text", id: partId, messageID, text } }
|
|
5925
6015
|
});
|
|
5926
6016
|
var idle = () => ({ type: "session.idle", properties: {} });
|
|
5927
|
-
var errored = (
|
|
6017
|
+
var errored = (message2) => ({
|
|
5928
6018
|
type: "session.error",
|
|
5929
|
-
properties: { error: { message } }
|
|
6019
|
+
properties: { error: { message: message2 } }
|
|
5930
6020
|
});
|
|
5931
6021
|
var sessionEvent2 = (sessionId, directory = DIR, degraded = false) => ({
|
|
5932
6022
|
type: "session",
|
|
@@ -6361,8 +6451,8 @@ function readItemText(item) {
|
|
|
6361
6451
|
function readItemMessage(item) {
|
|
6362
6452
|
return str(item.message) ?? "";
|
|
6363
6453
|
}
|
|
6364
|
-
function isModelMetadataError(
|
|
6365
|
-
return
|
|
6454
|
+
function isModelMetadataError(message2) {
|
|
6455
|
+
return message2.includes("Defaulting to fallback metadata");
|
|
6366
6456
|
}
|
|
6367
6457
|
function readToolItem(item, eventType) {
|
|
6368
6458
|
const type = str(item.type);
|
|
@@ -6597,11 +6687,11 @@ async function* decodeCodexStream(events, ctx) {
|
|
|
6597
6687
|
if (!item) continue;
|
|
6598
6688
|
const itemType = readItemType(item);
|
|
6599
6689
|
if (itemType === "error") {
|
|
6600
|
-
const
|
|
6601
|
-
if (isModelMetadataError(
|
|
6690
|
+
const message2 = readItemMessage(item);
|
|
6691
|
+
if (isModelMetadataError(message2)) {
|
|
6602
6692
|
yield* flushInterim();
|
|
6603
6693
|
ok = false;
|
|
6604
|
-
reason = `model_unavailable:${
|
|
6694
|
+
reason = `model_unavailable:${message2.slice(0, 200)}`;
|
|
6605
6695
|
settled = true;
|
|
6606
6696
|
break;
|
|
6607
6697
|
}
|
|
@@ -6758,7 +6848,7 @@ var ENV_ENVELOPE_KEYS = [
|
|
|
6758
6848
|
"CABANE_CONVERSATION_TITLE",
|
|
6759
6849
|
"CABANE_HOST_ACCESS"
|
|
6760
6850
|
];
|
|
6761
|
-
function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null) {
|
|
6851
|
+
function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null, imagePaths = []) {
|
|
6762
6852
|
const { policy, config } = req;
|
|
6763
6853
|
const directory = req.local.cwd ?? "";
|
|
6764
6854
|
const dialect = readCodexDialect(config.runtimeOptions);
|
|
@@ -6775,7 +6865,7 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromp
|
|
|
6775
6865
|
skipGitRepoCheck: true,
|
|
6776
6866
|
...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
|
|
6777
6867
|
baseInstructionsFile,
|
|
6778
|
-
input: buildInput(req, promptRidesInput),
|
|
6868
|
+
input: withImages(buildInput(req, promptRidesInput), imagePaths),
|
|
6779
6869
|
promptRidesInput,
|
|
6780
6870
|
promptFingerprint,
|
|
6781
6871
|
config: buildConfig(req, baseInstructionsFile),
|
|
@@ -6791,6 +6881,13 @@ function buildInput(req, promptRidesInput) {
|
|
|
6791
6881
|
|
|
6792
6882
|
${body}` : body;
|
|
6793
6883
|
}
|
|
6884
|
+
function withImages(text, imagePaths) {
|
|
6885
|
+
if (imagePaths.length === 0) return text;
|
|
6886
|
+
return [
|
|
6887
|
+
{ type: "text", text },
|
|
6888
|
+
...imagePaths.map((path) => ({ type: "local_image", path }))
|
|
6889
|
+
];
|
|
6890
|
+
}
|
|
6794
6891
|
function buildConfig(req, baseInstructionsFile) {
|
|
6795
6892
|
const mcp_servers = {};
|
|
6796
6893
|
for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
|
|
@@ -7032,12 +7129,35 @@ function createCodexAdapter(deps = {}) {
|
|
|
7032
7129
|
}
|
|
7033
7130
|
}
|
|
7034
7131
|
}
|
|
7132
|
+
let images = null;
|
|
7035
7133
|
try {
|
|
7134
|
+
const imageUrls = req.content.flatMap((b) => b.type === "image" ? [b.source.url] : []);
|
|
7135
|
+
if (imageUrls.length > 0) {
|
|
7136
|
+
if (!deps.prepareImages) {
|
|
7137
|
+
deps.onWarn?.(
|
|
7138
|
+
"codex adapter: no image preparer wired on this host \u2014 the turn runs without its images (CT1757)",
|
|
7139
|
+
{ images: imageUrls.length }
|
|
7140
|
+
);
|
|
7141
|
+
} else {
|
|
7142
|
+
try {
|
|
7143
|
+
images = await deps.prepareImages(imageUrls, signal);
|
|
7144
|
+
} catch (err) {
|
|
7145
|
+
if (!signal.aborted) {
|
|
7146
|
+
deps.onWarn?.(
|
|
7147
|
+
"codex adapter: preparing the images failed \u2014 the turn runs without them (CT1757)",
|
|
7148
|
+
{ err: err instanceof Error ? err.message : String(err) }
|
|
7149
|
+
);
|
|
7150
|
+
}
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
if (signal.aborted) return;
|
|
7154
|
+
}
|
|
7036
7155
|
const spec = buildRunSpec2(
|
|
7037
7156
|
req,
|
|
7038
7157
|
resumeThreadId,
|
|
7039
7158
|
instructions?.path ?? null,
|
|
7040
|
-
threadPromptFingerprint
|
|
7159
|
+
threadPromptFingerprint,
|
|
7160
|
+
images?.paths ?? []
|
|
7041
7161
|
);
|
|
7042
7162
|
if (spec.model === null) {
|
|
7043
7163
|
deps.onWarn?.("Codex is using its default model because no model was resolved", {
|
|
@@ -7066,6 +7186,15 @@ function createCodexAdapter(deps = {}) {
|
|
|
7066
7186
|
resolvedReasoningEffort: spec.modelReasoningEffort ?? null
|
|
7067
7187
|
});
|
|
7068
7188
|
} finally {
|
|
7189
|
+
if (images) {
|
|
7190
|
+
try {
|
|
7191
|
+
await images.cleanup();
|
|
7192
|
+
} catch (err) {
|
|
7193
|
+
deps.onWarn?.("codex adapter: image cleanup failed (CT1757)", {
|
|
7194
|
+
err: err instanceof Error ? err.message : String(err)
|
|
7195
|
+
});
|
|
7196
|
+
}
|
|
7197
|
+
}
|
|
7069
7198
|
if (instructions) {
|
|
7070
7199
|
try {
|
|
7071
7200
|
await instructions.cleanup();
|
|
@@ -7132,14 +7261,14 @@ var turnCompleted = (usage) => ({
|
|
|
7132
7261
|
type: "turn.completed",
|
|
7133
7262
|
...usage ? { usage } : {}
|
|
7134
7263
|
});
|
|
7135
|
-
var turnFailed = (
|
|
7264
|
+
var turnFailed = (message2) => ({
|
|
7136
7265
|
type: "turn.failed",
|
|
7137
|
-
error: { message }
|
|
7266
|
+
error: { message: message2 }
|
|
7138
7267
|
});
|
|
7139
|
-
var fatalError = (
|
|
7140
|
-
var errorItem = (id,
|
|
7268
|
+
var fatalError = (message2) => ({ type: "error", message: message2 });
|
|
7269
|
+
var errorItem = (id, message2) => ({
|
|
7141
7270
|
type: "item.completed",
|
|
7142
|
-
item: { id, type: "error", message }
|
|
7271
|
+
item: { id, type: "error", message: message2 }
|
|
7143
7272
|
});
|
|
7144
7273
|
var OP_SUCCESS = { ok: true, result: { source: "native" } };
|
|
7145
7274
|
var OP_FAILURE = {
|
|
@@ -8058,7 +8187,7 @@ var ConnectorHealthStore = class {
|
|
|
8058
8187
|
// src/dispatcher.ts
|
|
8059
8188
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8060
8189
|
import { existsSync as existsSync10, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
8061
|
-
import { join as
|
|
8190
|
+
import { join as join15 } from "path";
|
|
8062
8191
|
|
|
8063
8192
|
// src/runner/launch.ts
|
|
8064
8193
|
import { createInterface } from "readline";
|
|
@@ -8124,8 +8253,8 @@ function unenforceable(policy) {
|
|
|
8124
8253
|
deniedDomains: as === "deniedDomains" ? [host] : []
|
|
8125
8254
|
});
|
|
8126
8255
|
if (!check.success) {
|
|
8127
|
-
const
|
|
8128
|
-
return `runners.policy.${field}.${i}: ${
|
|
8256
|
+
const message2 = check.error.issues[0]?.message ?? "not a host pattern the sandbox accepts";
|
|
8257
|
+
return `runners.policy.${field}.${i}: ${message2}`;
|
|
8129
8258
|
}
|
|
8130
8259
|
}
|
|
8131
8260
|
}
|
|
@@ -8139,8 +8268,8 @@ function unenforceable(policy) {
|
|
|
8139
8268
|
try {
|
|
8140
8269
|
new RegExp(globToRegex(path));
|
|
8141
8270
|
} catch (err) {
|
|
8142
|
-
const
|
|
8143
|
-
return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${
|
|
8271
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
8272
|
+
return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${message2})`;
|
|
8144
8273
|
}
|
|
8145
8274
|
}
|
|
8146
8275
|
}
|
|
@@ -8259,8 +8388,8 @@ async function reapTurnScopes(turnId) {
|
|
|
8259
8388
|
});
|
|
8260
8389
|
return { failures: [] };
|
|
8261
8390
|
} catch (err) {
|
|
8262
|
-
const
|
|
8263
|
-
return classifyStopError(
|
|
8391
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
8392
|
+
return classifyStopError(message2) === "absent" ? { failures: [] } : { failures: [message2.slice(0, 200)] };
|
|
8264
8393
|
}
|
|
8265
8394
|
}
|
|
8266
8395
|
function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
|
|
@@ -8276,13 +8405,13 @@ function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
|
|
|
8276
8405
|
return false;
|
|
8277
8406
|
}
|
|
8278
8407
|
}
|
|
8279
|
-
function classifyStopError(
|
|
8408
|
+
function classifyStopError(message2) {
|
|
8280
8409
|
if (/failed to connect to bus|failed to get d-?bus connection|connection refused|refusing to operate|spawn systemctl|systemctl.*ENOENT|ENOENT.*systemctl/i.test(
|
|
8281
|
-
|
|
8410
|
+
message2
|
|
8282
8411
|
)) {
|
|
8283
8412
|
return "failure";
|
|
8284
8413
|
}
|
|
8285
|
-
if (/not loaded|could not be found|no such unit|unit .* not found/i.test(
|
|
8414
|
+
if (/not loaded|could not be found|no such unit|unit .* not found/i.test(message2))
|
|
8286
8415
|
return "absent";
|
|
8287
8416
|
return "failure";
|
|
8288
8417
|
}
|
|
@@ -8394,9 +8523,9 @@ function createTurnContainment(turnId, deps = {}) {
|
|
|
8394
8523
|
timeout: (CONTAINMENT_STOP_GRACE_SEC + 10) * 1e3
|
|
8395
8524
|
});
|
|
8396
8525
|
} catch (err) {
|
|
8397
|
-
const
|
|
8398
|
-
if (classifyStopError(
|
|
8399
|
-
result.failures.push({ target: unit, error:
|
|
8526
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
8527
|
+
if (classifyStopError(message2) === "failure") {
|
|
8528
|
+
result.failures.push({ target: unit, error: message2.slice(0, 200) });
|
|
8400
8529
|
continue;
|
|
8401
8530
|
}
|
|
8402
8531
|
result.reaped.push(unit);
|
|
@@ -8801,8 +8930,8 @@ function runnerStdin(input) {
|
|
|
8801
8930
|
})}
|
|
8802
8931
|
`;
|
|
8803
8932
|
}
|
|
8804
|
-
function encodeLauncherMessage(
|
|
8805
|
-
return `${JSON.stringify(
|
|
8933
|
+
function encodeLauncherMessage(message2) {
|
|
8934
|
+
return `${JSON.stringify(message2)}
|
|
8806
8935
|
`;
|
|
8807
8936
|
}
|
|
8808
8937
|
function decodeRunnerEvent(line) {
|
|
@@ -9534,6 +9663,68 @@ async function writeCodexInstructionsFile(contents) {
|
|
|
9534
9663
|
};
|
|
9535
9664
|
}
|
|
9536
9665
|
|
|
9666
|
+
// src/codex-images.ts
|
|
9667
|
+
import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
9668
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
9669
|
+
import { join as join12 } from "path";
|
|
9670
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
9671
|
+
var EXTENSIONS = {
|
|
9672
|
+
"image/png": "png",
|
|
9673
|
+
"image/jpeg": "jpg",
|
|
9674
|
+
"image/gif": "gif",
|
|
9675
|
+
"image/webp": "webp"
|
|
9676
|
+
};
|
|
9677
|
+
function createCodexImagePreparer(opts) {
|
|
9678
|
+
const doFetch = opts.fetch ?? fetch;
|
|
9679
|
+
const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
|
|
9680
|
+
return async (urls, signal) => {
|
|
9681
|
+
signal.throwIfAborted();
|
|
9682
|
+
const dir2 = await mkdtemp2(join12(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
|
|
9683
|
+
const cleanup = async () => {
|
|
9684
|
+
try {
|
|
9685
|
+
await rm2(dir2, { recursive: true, force: true });
|
|
9686
|
+
} catch (err) {
|
|
9687
|
+
opts.warn(
|
|
9688
|
+
{ dir: dir2, err: message(err) },
|
|
9689
|
+
"codex images: could not remove the scratch directory"
|
|
9690
|
+
);
|
|
9691
|
+
}
|
|
9692
|
+
};
|
|
9693
|
+
const prepareOne = async (url, index) => {
|
|
9694
|
+
try {
|
|
9695
|
+
const res = await doFetch(url, {
|
|
9696
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)])
|
|
9697
|
+
});
|
|
9698
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
9699
|
+
const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
9700
|
+
const ext = EXTENSIONS[type];
|
|
9701
|
+
if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
|
|
9702
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
9703
|
+
const path = join12(dir2, `image-${index + 1}.${ext}`);
|
|
9704
|
+
await writeFile2(path, bytes, { mode: 384 });
|
|
9705
|
+
return path;
|
|
9706
|
+
} catch (err) {
|
|
9707
|
+
if (!signal.aborted) {
|
|
9708
|
+
opts.warn(
|
|
9709
|
+
{ image: index + 1, of: urls.length, err: message(err) },
|
|
9710
|
+
"codex images: an image could not be prepared; the turn runs without it"
|
|
9711
|
+
);
|
|
9712
|
+
}
|
|
9713
|
+
return null;
|
|
9714
|
+
}
|
|
9715
|
+
};
|
|
9716
|
+
const paths = await Promise.all(urls.map(prepareOne));
|
|
9717
|
+
if (signal.aborted) {
|
|
9718
|
+
await cleanup();
|
|
9719
|
+
signal.throwIfAborted();
|
|
9720
|
+
}
|
|
9721
|
+
return { paths: paths.filter((p) => p !== null), cleanup };
|
|
9722
|
+
};
|
|
9723
|
+
}
|
|
9724
|
+
function message(err) {
|
|
9725
|
+
return err instanceof Error ? err.message : String(err);
|
|
9726
|
+
}
|
|
9727
|
+
|
|
9537
9728
|
// src/turn-seq-floor.ts
|
|
9538
9729
|
var SeqFloorUnavailable = class extends Error {
|
|
9539
9730
|
constructor(detail) {
|
|
@@ -9571,15 +9762,15 @@ function readOutboxFloor(read, turnId, log) {
|
|
|
9571
9762
|
import { spawn as spawn5 } from "child_process";
|
|
9572
9763
|
import { hostname } from "os";
|
|
9573
9764
|
function prepareFailureCause(error) {
|
|
9574
|
-
const
|
|
9575
|
-
if (/timed out/i.test(
|
|
9576
|
-
if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(
|
|
9765
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
9766
|
+
if (/timed out/i.test(message2)) return "prepare hook timed out";
|
|
9767
|
+
if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message2))
|
|
9577
9768
|
return "prepare hook module missing; check the configured command and its installation for this device";
|
|
9578
|
-
if (/not valid JSON|JSON must carry|produced no output/.test(
|
|
9769
|
+
if (/not valid JSON|JSON must carry|produced no output/.test(message2))
|
|
9579
9770
|
return "prepare hook returned malformed output; expected a cwd path or JSON with a non-empty cwd";
|
|
9580
|
-
if (/ENOENT/.test(
|
|
9581
|
-
if (/EACCES/.test(
|
|
9582
|
-
const exit =
|
|
9771
|
+
if (/ENOENT/.test(message2)) return "prepare hook executable missing (ENOENT)";
|
|
9772
|
+
if (/EACCES/.test(message2)) return "prepare hook executable is not accessible (EACCES)";
|
|
9773
|
+
const exit = message2.match(/exited with code (\d+)/)?.[1];
|
|
9583
9774
|
return exit ? `prepare hook exited with code ${exit}` : "prepare hook failed; inspect the device-local hook log";
|
|
9584
9775
|
}
|
|
9585
9776
|
async function runPrepareWithReporting(runner, hook, input, context, reportError) {
|
|
@@ -9631,15 +9822,15 @@ function reportFailure(config, body) {
|
|
|
9631
9822
|
|
|
9632
9823
|
// src/prepared.ts
|
|
9633
9824
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
|
|
9634
|
-
import { join as
|
|
9825
|
+
import { join as join13 } from "path";
|
|
9635
9826
|
function dirFor(workspaceId) {
|
|
9636
|
-
return
|
|
9827
|
+
return join13(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
|
|
9637
9828
|
}
|
|
9638
9829
|
function conversationDir(workspaceId, conversationId) {
|
|
9639
|
-
return
|
|
9830
|
+
return join13(dirFor(workspaceId), encodeURIComponent(conversationId));
|
|
9640
9831
|
}
|
|
9641
9832
|
function pathFor2(workspaceId, conversationId, agentId) {
|
|
9642
|
-
return
|
|
9833
|
+
return join13(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
|
|
9643
9834
|
}
|
|
9644
9835
|
function readPrepared(workspaceId, conversationId, agentId) {
|
|
9645
9836
|
const path = pathFor2(workspaceId, conversationId, agentId);
|
|
@@ -9671,9 +9862,9 @@ function clearPrepared(workspaceId, conversationId, agentId) {
|
|
|
9671
9862
|
|
|
9672
9863
|
// src/transcript-writer.ts
|
|
9673
9864
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
9674
|
-
import { basename, dirname as dirname6, join as
|
|
9865
|
+
import { basename, dirname as dirname6, join as join14 } from "path";
|
|
9675
9866
|
function transcriptsDir() {
|
|
9676
|
-
return
|
|
9867
|
+
return join14(runnerStateDir(), "transcripts");
|
|
9677
9868
|
}
|
|
9678
9869
|
var RETAIN = 200;
|
|
9679
9870
|
var ANOMALY_RETAIN = 50;
|
|
@@ -9683,7 +9874,7 @@ var TranscriptWriter = class {
|
|
|
9683
9874
|
onWarn;
|
|
9684
9875
|
constructor(dir2, meta, onWarn) {
|
|
9685
9876
|
this.onWarn = onWarn;
|
|
9686
|
-
this.path =
|
|
9877
|
+
this.path = join14(dir2, fileName(meta));
|
|
9687
9878
|
try {
|
|
9688
9879
|
mkdirSync8(dir2, { recursive: true });
|
|
9689
9880
|
try {
|
|
@@ -9712,10 +9903,10 @@ var TranscriptWriter = class {
|
|
|
9712
9903
|
preserveAnomaly() {
|
|
9713
9904
|
if (this.broken) return;
|
|
9714
9905
|
try {
|
|
9715
|
-
const dir2 =
|
|
9906
|
+
const dir2 = join14(dirname6(this.path), "anomalies");
|
|
9716
9907
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
9717
9908
|
chmodSync4(dir2, 448);
|
|
9718
|
-
const target =
|
|
9909
|
+
const target = join14(dir2, basename(this.path));
|
|
9719
9910
|
copyFileSync(this.path, target);
|
|
9720
9911
|
chmodSync4(target, 384);
|
|
9721
9912
|
pruneOld(dir2, ANOMALY_RETAIN);
|
|
@@ -9761,7 +9952,7 @@ function pruneOld(dir2, retain) {
|
|
|
9761
9952
|
const drop = files.sort().slice(0, files.length - retain);
|
|
9762
9953
|
for (const f of drop) {
|
|
9763
9954
|
try {
|
|
9764
|
-
rmSync6(
|
|
9955
|
+
rmSync6(join14(dir2, f), { force: true });
|
|
9765
9956
|
} catch {
|
|
9766
9957
|
}
|
|
9767
9958
|
}
|
|
@@ -10332,16 +10523,16 @@ var TurnExecution = class {
|
|
|
10332
10523
|
}
|
|
10333
10524
|
gateTrigger() {
|
|
10334
10525
|
const { payload, turnLog } = this;
|
|
10335
|
-
const
|
|
10336
|
-
const isDispatchableTrigger =
|
|
10526
|
+
const message2 = this.turnContext.message;
|
|
10527
|
+
const isDispatchableTrigger = message2.role === "user" || message2.role === "agent" || message2.role === "system";
|
|
10337
10528
|
if (!isDispatchableTrigger) {
|
|
10338
|
-
turnLog.debug({ role:
|
|
10529
|
+
turnLog.debug({ role: message2.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
|
|
10339
10530
|
throw this.concluded("unexpected_role", UNEXPECTED_ROLE_REASON);
|
|
10340
10531
|
}
|
|
10341
10532
|
this.supervisor.notifyStart({
|
|
10342
10533
|
id: this.dispatchId,
|
|
10343
10534
|
conversationId: payload.conversationId,
|
|
10344
|
-
message:
|
|
10535
|
+
message: message2.body
|
|
10345
10536
|
});
|
|
10346
10537
|
}
|
|
10347
10538
|
async resolveSecrets() {
|
|
@@ -10692,6 +10883,10 @@ var TurnExecution = class {
|
|
|
10692
10883
|
enabled: true,
|
|
10693
10884
|
onWarn,
|
|
10694
10885
|
writeInstructionsFile: writeCodexInstructionsFile,
|
|
10886
|
+
prepareImages: createCodexImagePreparer({
|
|
10887
|
+
turnId: this.turnId,
|
|
10888
|
+
warn: (meta, msg) => turnLog.warn(meta, msg)
|
|
10889
|
+
}),
|
|
10695
10890
|
...codexPath ? { transport: createSdkCodexTransport({ codexPathOverride: codexPath }) } : {}
|
|
10696
10891
|
})
|
|
10697
10892
|
);
|
|
@@ -11171,7 +11366,7 @@ function checkoutState(cwd) {
|
|
|
11171
11366
|
if (entries.length === 0) {
|
|
11172
11367
|
return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
|
|
11173
11368
|
}
|
|
11174
|
-
const gitPath =
|
|
11369
|
+
const gitPath = join15(cwd, ".git");
|
|
11175
11370
|
if (!existsSync10(gitPath)) return { ok: true, reason: "usable" };
|
|
11176
11371
|
let stat;
|
|
11177
11372
|
try {
|
|
@@ -11179,7 +11374,7 @@ function checkoutState(cwd) {
|
|
|
11179
11374
|
} catch (error) {
|
|
11180
11375
|
return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
|
|
11181
11376
|
}
|
|
11182
|
-
if (stat.isDirectory() && !existsSync10(
|
|
11377
|
+
if (stat.isDirectory() && !existsSync10(join15(gitPath, "HEAD")))
|
|
11183
11378
|
return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
|
|
11184
11379
|
return { ok: true, reason: "usable" };
|
|
11185
11380
|
}
|
|
@@ -11372,7 +11567,7 @@ function createLocalProcessProvider(opts = {}) {
|
|
|
11372
11567
|
|
|
11373
11568
|
// src/providers/docker.ts
|
|
11374
11569
|
import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
|
|
11375
|
-
import { constants as constants2, tmpdir as
|
|
11570
|
+
import { constants as constants2, tmpdir as tmpdir3 } from "os";
|
|
11376
11571
|
import {
|
|
11377
11572
|
createReadStream,
|
|
11378
11573
|
createWriteStream as createWriteStream2,
|
|
@@ -11385,7 +11580,7 @@ import {
|
|
|
11385
11580
|
statSync as statSync3,
|
|
11386
11581
|
writeFileSync as writeFileSync6
|
|
11387
11582
|
} from "fs";
|
|
11388
|
-
import { join as
|
|
11583
|
+
import { join as join18 } from "path";
|
|
11389
11584
|
import { Writable } from "stream";
|
|
11390
11585
|
|
|
11391
11586
|
// src/providers/docker-cli.ts
|
|
@@ -11579,7 +11774,7 @@ import {
|
|
|
11579
11774
|
renameSync as renameSync4,
|
|
11580
11775
|
rmSync as rmSync8
|
|
11581
11776
|
} from "fs";
|
|
11582
|
-
import { join as
|
|
11777
|
+
import { join as join16 } from "path";
|
|
11583
11778
|
function routes(t) {
|
|
11584
11779
|
const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
|
|
11585
11780
|
const agent = `${conv}/participants/agents/${t.agentId}`;
|
|
@@ -11625,7 +11820,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
11625
11820
|
const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
|
|
11626
11821
|
let entry = {};
|
|
11627
11822
|
try {
|
|
11628
|
-
entry = JSON.parse(readFileSync8(
|
|
11823
|
+
entry = JSON.parse(readFileSync8(join16(from, name), "utf8"));
|
|
11629
11824
|
} catch {
|
|
11630
11825
|
}
|
|
11631
11826
|
return { name, entry };
|
|
@@ -11638,7 +11833,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
11638
11833
|
let published = 0;
|
|
11639
11834
|
for (const { name } of staged) {
|
|
11640
11835
|
try {
|
|
11641
|
-
move(
|
|
11836
|
+
move(join16(from, name), join16(to, name));
|
|
11642
11837
|
} catch {
|
|
11643
11838
|
return { published, complete: false };
|
|
11644
11839
|
}
|
|
@@ -11651,7 +11846,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
11651
11846
|
// src/providers/runner-base.ts
|
|
11652
11847
|
import { createHash as createHash3 } from "crypto";
|
|
11653
11848
|
import { existsSync as existsSync12, realpathSync as realpathSync2 } from "fs";
|
|
11654
|
-
import { dirname as dirname7, join as
|
|
11849
|
+
import { dirname as dirname7, join as join17, sep } from "path";
|
|
11655
11850
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11656
11851
|
var COMPANION_MOUNT = "/opt/cabane/companion";
|
|
11657
11852
|
var BASE_DOCKERFILE = `FROM node:22-slim
|
|
@@ -11699,11 +11894,11 @@ async function ensureBaseImage(docker, image, log) {
|
|
|
11699
11894
|
function ownInstall(moduleFile = fileURLToPath3(import.meta.url)) {
|
|
11700
11895
|
let dir2 = dirname7(moduleFile);
|
|
11701
11896
|
for (let i = 0; i < 4; i++) {
|
|
11702
|
-
if (existsSync12(
|
|
11897
|
+
if (existsSync12(join17(dir2, "dist", "cli.js")) && existsSync12(join17(dir2, "package.json"))) break;
|
|
11703
11898
|
dir2 = dirname7(dir2);
|
|
11704
11899
|
}
|
|
11705
|
-
if (!existsSync12(
|
|
11706
|
-
const sdk =
|
|
11900
|
+
if (!existsSync12(join17(dir2, "dist", "cli.js"))) return null;
|
|
11901
|
+
const sdk = join17(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
|
|
11707
11902
|
if (!existsSync12(sdk)) return null;
|
|
11708
11903
|
const root = realpathSync2(dir2);
|
|
11709
11904
|
return realpathSync2(sdk).startsWith(root + sep) ? root : null;
|
|
@@ -11842,21 +12037,21 @@ function networkCreateError(stderr) {
|
|
|
11842
12037
|
}
|
|
11843
12038
|
function sessionArchivePath(deviceDir, attemptId) {
|
|
11844
12039
|
if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
|
|
11845
|
-
return
|
|
12040
|
+
return join18(deviceDir, "attempts", attemptId, "session.tar");
|
|
11846
12041
|
}
|
|
11847
12042
|
function pruneOtherAttempts(deviceDir, pair, keep) {
|
|
11848
|
-
const root =
|
|
12043
|
+
const root = join18(deviceDir, "attempts");
|
|
11849
12044
|
if (!existsSync13(root)) return;
|
|
11850
12045
|
for (const id of readdirSync4(root)) {
|
|
11851
12046
|
if (id === keep || !UUID_RE.test(id)) continue;
|
|
11852
12047
|
let pairOf = "";
|
|
11853
12048
|
try {
|
|
11854
|
-
pairOf = readFileSync9(
|
|
12049
|
+
pairOf = readFileSync9(join18(root, id, "pair"), "utf8");
|
|
11855
12050
|
} catch {
|
|
11856
12051
|
continue;
|
|
11857
12052
|
}
|
|
11858
12053
|
if (pairOf === `${pair.conversationId}/${pair.agentId}`) {
|
|
11859
|
-
rmSync9(
|
|
12054
|
+
rmSync9(join18(root, id), { recursive: true, force: true });
|
|
11860
12055
|
}
|
|
11861
12056
|
}
|
|
11862
12057
|
}
|
|
@@ -12056,11 +12251,11 @@ function createDockerProvider(opts) {
|
|
|
12056
12251
|
await must(["network", "disconnect", "-f", other, name], "moving the container");
|
|
12057
12252
|
if (others.includes(sharedNetwork)) await retireSharedNetwork();
|
|
12058
12253
|
};
|
|
12059
|
-
const socketRoot = opts.socketRoot ??
|
|
12060
|
-
process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR :
|
|
12254
|
+
const socketRoot = opts.socketRoot ?? join18(
|
|
12255
|
+
process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
|
|
12061
12256
|
`cabane-runners-${opts.deviceKey}`
|
|
12062
12257
|
);
|
|
12063
|
-
const socketDir = (name) =>
|
|
12258
|
+
const socketDir = (name) => join18(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
|
|
12064
12259
|
const create = async (name, attempt) => {
|
|
12065
12260
|
mkdirSync10(socketRoot, { recursive: true, mode: 448 });
|
|
12066
12261
|
mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
|
|
@@ -12202,7 +12397,7 @@ function createDockerProvider(opts) {
|
|
|
12202
12397
|
}
|
|
12203
12398
|
};
|
|
12204
12399
|
const removeIn = (name, from, names) => names.length ? execIn(name, ["sh", "-c", 'cd "$1" && shift && rm -f -- "$@"', "sh", from, ...names]) : Promise.resolve(null);
|
|
12205
|
-
const pendingPath = (name) =>
|
|
12400
|
+
const pendingPath = (name) => join18(opts.deviceDir, "runners-pending", `${name}.json`);
|
|
12206
12401
|
const readPending = (name) => {
|
|
12207
12402
|
try {
|
|
12208
12403
|
const ids = JSON.parse(readFileSync9(pendingPath(name), "utf8"));
|
|
@@ -12216,12 +12411,12 @@ function createDockerProvider(opts) {
|
|
|
12216
12411
|
rmSync9(pendingPath(name), { force: true });
|
|
12217
12412
|
return;
|
|
12218
12413
|
}
|
|
12219
|
-
mkdirSync10(
|
|
12414
|
+
mkdirSync10(join18(opts.deviceDir, "runners-pending"), { recursive: true });
|
|
12220
12415
|
writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
|
|
12221
12416
|
};
|
|
12222
12417
|
const keepSession = async (name, attempt, attemptId) => {
|
|
12223
12418
|
const archive = sessionArchivePath(opts.deviceDir, attemptId);
|
|
12224
|
-
mkdirSync10(
|
|
12419
|
+
mkdirSync10(join18(archive, ".."), { recursive: true });
|
|
12225
12420
|
const partial = `${archive}.${process.pid}.partial`;
|
|
12226
12421
|
const file = new CappedFile(partial, sessionCap);
|
|
12227
12422
|
const r = await execIn(
|
|
@@ -12238,7 +12433,7 @@ function createDockerProvider(opts) {
|
|
|
12238
12433
|
);
|
|
12239
12434
|
if (r.code === 0 && !file.over) {
|
|
12240
12435
|
renameSync5(partial, archive);
|
|
12241
|
-
writeFileSync6(
|
|
12436
|
+
writeFileSync6(join18(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
|
|
12242
12437
|
pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
|
|
12243
12438
|
await writeMarker(name, attemptId);
|
|
12244
12439
|
log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
|
|
@@ -12252,7 +12447,7 @@ function createDockerProvider(opts) {
|
|
|
12252
12447
|
file.over ? "runners: the session files are over the size cap and weren't kept; the next new container starts the session over" : "runners: couldn't keep the session files; the next new container starts the session over"
|
|
12253
12448
|
);
|
|
12254
12449
|
};
|
|
12255
|
-
const stagingDir = (name) =>
|
|
12450
|
+
const stagingDir = (name) => join18(opts.deviceDir, "runners-pending", `${name}.outbox`);
|
|
12256
12451
|
const ensureRunning = async (name) => {
|
|
12257
12452
|
const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
|
|
12258
12453
|
if (seen.code !== 0) return false;
|
|
@@ -12260,7 +12455,7 @@ function createDockerProvider(opts) {
|
|
|
12260
12455
|
return (await docker.run(["start", name])).code === 0;
|
|
12261
12456
|
};
|
|
12262
12457
|
const dropArchive = (attemptId) => {
|
|
12263
|
-
rmSync9(
|
|
12458
|
+
rmSync9(join18(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
|
|
12264
12459
|
recursive: true,
|
|
12265
12460
|
force: true
|
|
12266
12461
|
});
|
|
@@ -12329,7 +12524,7 @@ function createDockerProvider(opts) {
|
|
|
12329
12524
|
if (seen.stdout.trim().split(" ")[0] !== "true")
|
|
12330
12525
|
await must(["start", name], "starting the container");
|
|
12331
12526
|
const git = await serveGitCredentials({
|
|
12332
|
-
socketPath:
|
|
12527
|
+
socketPath: join18(socketDir(name), "git.sock"),
|
|
12333
12528
|
hosts: opts.gitHosts,
|
|
12334
12529
|
fill: opts.gitFill,
|
|
12335
12530
|
log
|
|
@@ -12478,15 +12673,15 @@ function createDockerProvider(opts) {
|
|
|
12478
12673
|
const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
|
|
12479
12674
|
if (transcripts) {
|
|
12480
12675
|
const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
|
|
12481
|
-
if (kept.length) mkdirSync10(
|
|
12482
|
-
for (const f of kept) writeFileSync6(
|
|
12676
|
+
if (kept.length) mkdirSync10(join18(opts.hostStateDir, "transcripts"), { recursive: true });
|
|
12677
|
+
for (const f of kept) writeFileSync6(join18(opts.hostStateDir, "transcripts", f.name), f.data);
|
|
12483
12678
|
await removeIn(
|
|
12484
12679
|
name,
|
|
12485
12680
|
`${CONTAINER_STATE}/transcripts`,
|
|
12486
12681
|
kept.map((f) => f.name)
|
|
12487
12682
|
);
|
|
12488
12683
|
}
|
|
12489
|
-
const outboxRel =
|
|
12684
|
+
const outboxRel = join18("outbox", encodeURIComponent(attempt.agentId));
|
|
12490
12685
|
const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
|
|
12491
12686
|
let unpublished = false;
|
|
12492
12687
|
if (outbox) {
|
|
@@ -12512,7 +12707,7 @@ function createDockerProvider(opts) {
|
|
|
12512
12707
|
const staged = stagingDir(name);
|
|
12513
12708
|
mkdirSync10(staged, { recursive: true });
|
|
12514
12709
|
writeFileSync6(
|
|
12515
|
-
|
|
12710
|
+
join18(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
|
|
12516
12711
|
`${JSON.stringify(entry)}
|
|
12517
12712
|
`
|
|
12518
12713
|
);
|
|
@@ -12525,7 +12720,7 @@ function createDockerProvider(opts) {
|
|
|
12525
12720
|
if (outbox.complete) {
|
|
12526
12721
|
const published = publishStaged(
|
|
12527
12722
|
stagingDir(name),
|
|
12528
|
-
|
|
12723
|
+
join18(opts.hostStateDir, outboxRel),
|
|
12529
12724
|
opts.moveFile
|
|
12530
12725
|
);
|
|
12531
12726
|
queued = published.published;
|
|
@@ -12740,8 +12935,8 @@ import {
|
|
|
12740
12935
|
} from "fs";
|
|
12741
12936
|
import { createRequire as createRequire2 } from "module";
|
|
12742
12937
|
import { isIP } from "net";
|
|
12743
|
-
import { homedir as homedir3, networkInterfaces, tmpdir as
|
|
12744
|
-
import { dirname as dirname8, join as
|
|
12938
|
+
import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
|
|
12939
|
+
import { dirname as dirname8, join as join19, sep as sep2 } from "path";
|
|
12745
12940
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12746
12941
|
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
12747
12942
|
function accountHome() {
|
|
@@ -12951,7 +13146,7 @@ import {
|
|
|
12951
13146
|
rmSync as rmSync11,
|
|
12952
13147
|
writeFileSync as writeFileSync8
|
|
12953
13148
|
} from "fs";
|
|
12954
|
-
import { join as
|
|
13149
|
+
import { join as join20 } from "path";
|
|
12955
13150
|
var MAX_ENTRIES = 2e3;
|
|
12956
13151
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
12957
13152
|
var Outbox = class {
|
|
@@ -12964,10 +13159,10 @@ var Outbox = class {
|
|
|
12964
13159
|
// Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
|
|
12965
13160
|
// cases route writes at the right tmpdir.
|
|
12966
13161
|
dir() {
|
|
12967
|
-
return
|
|
13162
|
+
return join20(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
|
|
12968
13163
|
}
|
|
12969
13164
|
fileFor(turnId, seq) {
|
|
12970
|
-
return
|
|
13165
|
+
return join20(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
|
|
12971
13166
|
}
|
|
12972
13167
|
// Persist a commit for later draining. Atomic (temp file + rename) so a
|
|
12973
13168
|
// concurrent `list()` never reads a half-written entry, then enforces the
|
|
@@ -13009,7 +13204,7 @@ var Outbox = class {
|
|
|
13009
13204
|
const entries = [];
|
|
13010
13205
|
for (const name of names) {
|
|
13011
13206
|
if (!name.endsWith(".json")) continue;
|
|
13012
|
-
const full =
|
|
13207
|
+
const full = join20(dir2, name);
|
|
13013
13208
|
try {
|
|
13014
13209
|
const parsed = JSON.parse(readFileSync11(full, "utf8"));
|
|
13015
13210
|
if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
|
|
@@ -14650,8 +14845,8 @@ function errorCode(err) {
|
|
|
14650
14845
|
function isRecoverableSocketError(err) {
|
|
14651
14846
|
const code = errorCode(err);
|
|
14652
14847
|
if (code && RECOVERABLE_CODES.has(code)) return true;
|
|
14653
|
-
const
|
|
14654
|
-
return /\bEPIPE\b|\bECONNRESET\b/.test(
|
|
14848
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
14849
|
+
return /\bEPIPE\b|\bECONNRESET\b/.test(message2);
|
|
14655
14850
|
}
|
|
14656
14851
|
function installProcessSafetyNet(log, opts = {}) {
|
|
14657
14852
|
const proc = opts.proc ?? process;
|
|
@@ -14666,26 +14861,26 @@ function installProcessSafetyNet(log, opts = {}) {
|
|
|
14666
14861
|
);
|
|
14667
14862
|
}
|
|
14668
14863
|
function handleUncaught(log, err, origin) {
|
|
14669
|
-
const
|
|
14864
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
14670
14865
|
const code = errorCode(err);
|
|
14671
14866
|
if (isRecoverableSocketError(err)) {
|
|
14672
14867
|
log.debug(
|
|
14673
|
-
{ origin, code, err:
|
|
14868
|
+
{ origin, code, err: message2 },
|
|
14674
14869
|
"companion: recovered from a socket error (kept running \u2014 a broken pipe never kills the companion)"
|
|
14675
14870
|
);
|
|
14676
14871
|
return;
|
|
14677
14872
|
}
|
|
14678
14873
|
log.error(
|
|
14679
|
-
{ origin, code, err:
|
|
14874
|
+
{ origin, code, err: message2, stack: err instanceof Error ? err.stack : void 0 },
|
|
14680
14875
|
"An unexpected error occurred; the companion is still running."
|
|
14681
14876
|
);
|
|
14682
14877
|
}
|
|
14683
14878
|
|
|
14684
14879
|
// src/crash-marker.ts
|
|
14685
14880
|
import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync12, rmSync as rmSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
14686
|
-
import { join as
|
|
14881
|
+
import { join as join21 } from "path";
|
|
14687
14882
|
function crashMarkerPath() {
|
|
14688
|
-
return
|
|
14883
|
+
return join21(cabaneDir(), "last-error.json");
|
|
14689
14884
|
}
|
|
14690
14885
|
function recordCrash(rec) {
|
|
14691
14886
|
try {
|