@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/cli.js
CHANGED
|
@@ -15,14 +15,14 @@ var init_errors = __esm({
|
|
|
15
15
|
"src/errors.ts"() {
|
|
16
16
|
"use strict";
|
|
17
17
|
CompanionError = class extends Error {
|
|
18
|
-
constructor(
|
|
19
|
-
super(
|
|
18
|
+
constructor(message2) {
|
|
19
|
+
super(message2);
|
|
20
20
|
this.name = "CompanionError";
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
ApiError = class extends CompanionError {
|
|
24
|
-
constructor(status2,
|
|
25
|
-
super(
|
|
24
|
+
constructor(status2, message2, body) {
|
|
25
|
+
super(message2);
|
|
26
26
|
this.status = status2;
|
|
27
27
|
this.body = body;
|
|
28
28
|
this.name = "ApiError";
|
|
@@ -31,14 +31,14 @@ var init_errors = __esm({
|
|
|
31
31
|
body;
|
|
32
32
|
};
|
|
33
33
|
ConfigError = class extends CompanionError {
|
|
34
|
-
constructor(
|
|
35
|
-
super(
|
|
34
|
+
constructor(message2) {
|
|
35
|
+
super(message2);
|
|
36
36
|
this.name = "ConfigError";
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
PrepareHookError = class extends CompanionError {
|
|
40
|
-
constructor(
|
|
41
|
-
super(
|
|
40
|
+
constructor(message2) {
|
|
41
|
+
super(message2);
|
|
42
42
|
this.name = "PrepareHookError";
|
|
43
43
|
}
|
|
44
44
|
};
|
|
@@ -1063,8 +1063,8 @@ function apiErrorCode(err) {
|
|
|
1063
1063
|
function apiErrorMessage(err) {
|
|
1064
1064
|
const body = err instanceof ApiError ? err.body : void 0;
|
|
1065
1065
|
if (body !== null && typeof body === "object") {
|
|
1066
|
-
const
|
|
1067
|
-
if (typeof
|
|
1066
|
+
const message2 = body.message;
|
|
1067
|
+
if (typeof message2 === "string" && message2.length > 0) return message2;
|
|
1068
1068
|
}
|
|
1069
1069
|
return err instanceof Error ? err.message : String(err);
|
|
1070
1070
|
}
|
|
@@ -1097,9 +1097,9 @@ function describeErrorBody(body) {
|
|
|
1097
1097
|
return text.length > ERROR_BODY_LOG_CAP ? `${text.slice(0, ERROR_BODY_LOG_CAP)}\u2026` : text;
|
|
1098
1098
|
}
|
|
1099
1099
|
function describeError(err) {
|
|
1100
|
-
const
|
|
1100
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
1101
1101
|
const cause = describeCause(err);
|
|
1102
|
-
return cause === void 0 ?
|
|
1102
|
+
return cause === void 0 ? message2 : `${message2}: ${cause}`;
|
|
1103
1103
|
}
|
|
1104
1104
|
function describeCause(err) {
|
|
1105
1105
|
if (!(err instanceof Error)) return void 0;
|
|
@@ -1816,14 +1816,11 @@ var init_turn_event = __esm({
|
|
|
1816
1816
|
// claude-code/codex it's the runtime's CUMULATIVE total summed across every model
|
|
1817
1817
|
// request in the agentic turn, so it grows with the tool-call count and is NOT
|
|
1818
1818
|
// "how full is the window." `contextTokens` is the distinct CONTEXT-OCCUPANCY
|
|
1819
|
-
// read
|
|
1820
|
-
//
|
|
1821
|
-
//
|
|
1822
|
-
// runtime
|
|
1823
|
-
//
|
|
1824
|
-
// clean final-request figure (codex's cumulative-only usage) omits `contextTokens`
|
|
1825
|
-
// and the gauge falls back to the raw count; `contextWindow` falls back to the
|
|
1826
|
-
// model catalog.
|
|
1819
|
+
// read and `contextWindow` the window it is measured against, both as the
|
|
1820
|
+
// harness reported them (claude-code's `getContextUsage`, or its final
|
|
1821
|
+
// request's input and `modelUsage` window when that call gives nothing). A
|
|
1822
|
+
// runtime that can't source them (codex's cumulative-only usage) omits both,
|
|
1823
|
+
// and no surface shows a reading for that turn.
|
|
1827
1824
|
//
|
|
1828
1825
|
// CT601: two more optional carry-homes on the terminal result, alongside
|
|
1829
1826
|
// `usage`. `resolvedModel` is the CONCRETE model the runtime actually ran —
|
|
@@ -1846,7 +1843,25 @@ var init_turn_event = __esm({
|
|
|
1846
1843
|
cacheReadTokens: z6.number().optional(),
|
|
1847
1844
|
cacheCreationTokens: z6.number().optional(),
|
|
1848
1845
|
contextTokens: z6.number().optional(),
|
|
1849
|
-
contextWindow: z6.number().optional()
|
|
1846
|
+
contextWindow: z6.number().optional(),
|
|
1847
|
+
// CT1751: the rest of the harness's own context reading, when it gave
|
|
1848
|
+
// one: where it will compact, its `/context` breakdown, and every
|
|
1849
|
+
// compaction it reported during the turn. Claude Code only.
|
|
1850
|
+
contextCompactAt: z6.number().optional(),
|
|
1851
|
+
contextCategories: z6.array(
|
|
1852
|
+
z6.object({
|
|
1853
|
+
name: z6.string(),
|
|
1854
|
+
tokens: z6.number(),
|
|
1855
|
+
kind: z6.enum(["used", "free", "buffer", "deferred"])
|
|
1856
|
+
})
|
|
1857
|
+
).optional(),
|
|
1858
|
+
contextCompactions: z6.array(
|
|
1859
|
+
z6.object({
|
|
1860
|
+
trigger: z6.enum(["manual", "auto"]),
|
|
1861
|
+
preTokens: z6.number(),
|
|
1862
|
+
postTokens: z6.number().optional()
|
|
1863
|
+
})
|
|
1864
|
+
).optional()
|
|
1850
1865
|
}).optional(),
|
|
1851
1866
|
resolvedModel: z6.string().optional(),
|
|
1852
1867
|
resolvedConfig: z6.object({
|
|
@@ -2902,6 +2917,7 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
2902
2917
|
let resultReason;
|
|
2903
2918
|
let sawResult = false;
|
|
2904
2919
|
let usage;
|
|
2920
|
+
const compactions = [];
|
|
2905
2921
|
let lastRequestContextTokens;
|
|
2906
2922
|
let resolvedModel;
|
|
2907
2923
|
const signals = { sawRejectedLimit: false };
|
|
@@ -2927,6 +2943,9 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
2927
2943
|
...ctx.degraded ? { degraded: true } : {}
|
|
2928
2944
|
};
|
|
2929
2945
|
}
|
|
2946
|
+
} else if (msg.type === "system" && msg.subtype === "compact_boundary") {
|
|
2947
|
+
const compaction = readCompaction(msg);
|
|
2948
|
+
if (compaction) compactions.push(compaction);
|
|
2930
2949
|
} else if (!workspaceProven && (msg.type === "assistant" || msg.type === "user" || msg.type === "result")) {
|
|
2931
2950
|
if (preInitToolActivity(msg) && ctx.startEvidence) ctx.startEvidence.toolActivity = true;
|
|
2932
2951
|
const failure = classifyPreInitFrame(msg, signals);
|
|
@@ -2971,6 +2990,9 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
2971
2990
|
usage.contextTokens = lastRequestContextTokens;
|
|
2972
2991
|
const window = readContextWindow(msg, resolvedModel);
|
|
2973
2992
|
if (window !== void 0) usage.contextWindow = window;
|
|
2993
|
+
if (compactions.length > 0) usage.contextCompactions = compactions;
|
|
2994
|
+
const reading = await readHarnessContext(ctx.readContextUsage);
|
|
2995
|
+
if (reading) Object.assign(usage, reading);
|
|
2974
2996
|
}
|
|
2975
2997
|
const isError = msg.is_error === true;
|
|
2976
2998
|
if (msg.subtype === "success" && !isError) {
|
|
@@ -2986,15 +3008,15 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
2986
3008
|
}
|
|
2987
3009
|
} catch (err) {
|
|
2988
3010
|
if (ctx.signal.aborted) throw err;
|
|
2989
|
-
const
|
|
2990
|
-
const failure = mcpInventory.initReceived ? classifyErrorText(
|
|
3011
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
3012
|
+
const failure = mcpInventory.initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
|
|
2991
3013
|
if (!failure && (mcpInventory.initReceived || ctx.resumedSessionId === null)) throw err;
|
|
2992
3014
|
ok = false;
|
|
2993
3015
|
if (failure) {
|
|
2994
3016
|
resultReason = encodeFailureReason(failure);
|
|
2995
3017
|
} else {
|
|
2996
3018
|
resultReason = "session_start_failed";
|
|
2997
|
-
if (ctx.startEvidence) ctx.startEvidence.thrown =
|
|
3019
|
+
if (ctx.startEvidence) ctx.startEvidence.thrown = message2.slice(0, 200);
|
|
2998
3020
|
}
|
|
2999
3021
|
sawResult = true;
|
|
3000
3022
|
}
|
|
@@ -3121,6 +3143,56 @@ function readRequestContextTokens(msg) {
|
|
|
3121
3143
|
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
3122
3144
|
return num(usage.input_tokens) + num(usage.cache_read_input_tokens) + num(usage.cache_creation_input_tokens);
|
|
3123
3145
|
}
|
|
3146
|
+
async function readHarnessContext(read) {
|
|
3147
|
+
if (!read) return void 0;
|
|
3148
|
+
let timer;
|
|
3149
|
+
const timeout = new Promise((resolve2) => {
|
|
3150
|
+
timer = setTimeout(() => resolve2(void 0), CONTEXT_READING_TIMEOUT_MS);
|
|
3151
|
+
});
|
|
3152
|
+
try {
|
|
3153
|
+
return parseContextUsage(await Promise.race([read(), timeout]));
|
|
3154
|
+
} catch {
|
|
3155
|
+
return void 0;
|
|
3156
|
+
} finally {
|
|
3157
|
+
clearTimeout(timer);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
function parseContextUsage(response) {
|
|
3161
|
+
const r = response;
|
|
3162
|
+
if (!r || typeof r !== "object") return void 0;
|
|
3163
|
+
const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
|
|
3164
|
+
const total = count(r.totalTokens);
|
|
3165
|
+
const window = count(r.maxTokens);
|
|
3166
|
+
if (total === void 0 || window === void 0 || window === 0) return void 0;
|
|
3167
|
+
const categories = [];
|
|
3168
|
+
for (const c of Array.isArray(r.categories) ? r.categories : []) {
|
|
3169
|
+
const row = c;
|
|
3170
|
+
const tokens = count(row?.tokens);
|
|
3171
|
+
if (typeof row?.name === "string" && tokens !== void 0 && typeof row.kind === "string" && CATEGORY_KINDS.has(row.kind)) {
|
|
3172
|
+
categories.push({ name: row.name, tokens, kind: row.kind });
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
const buffer = categories.filter((c) => c.kind === "buffer").reduce((sum, c) => sum + c.tokens, 0);
|
|
3176
|
+
return {
|
|
3177
|
+
contextTokens: total,
|
|
3178
|
+
contextWindow: window,
|
|
3179
|
+
...buffer > 0 && buffer < window ? { contextCompactAt: window - buffer } : {},
|
|
3180
|
+
...categories.length > 0 ? { contextCategories: categories } : {}
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
function readCompaction(msg) {
|
|
3184
|
+
const meta = msg.compact_metadata;
|
|
3185
|
+
const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
|
|
3186
|
+
const preTokens = count(meta?.pre_tokens);
|
|
3187
|
+
if (!meta || preTokens === void 0) return void 0;
|
|
3188
|
+
if (meta.trigger !== "manual" && meta.trigger !== "auto") return void 0;
|
|
3189
|
+
const postTokens = count(meta.post_tokens);
|
|
3190
|
+
return {
|
|
3191
|
+
trigger: meta.trigger,
|
|
3192
|
+
preTokens,
|
|
3193
|
+
...postTokens !== void 0 ? { postTokens } : {}
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3124
3196
|
function readContextWindow(msg, resolvedModel) {
|
|
3125
3197
|
const modelUsage = msg.modelUsage;
|
|
3126
3198
|
if (!modelUsage || typeof modelUsage !== "object") return void 0;
|
|
@@ -3136,7 +3208,7 @@ function readContextWindow(msg, resolvedModel) {
|
|
|
3136
3208
|
}
|
|
3137
3209
|
return max;
|
|
3138
3210
|
}
|
|
3139
|
-
var HARNESS_PLACEHOLDERS;
|
|
3211
|
+
var HARNESS_PLACEHOLDERS, CONTEXT_READING_TIMEOUT_MS, CATEGORY_KINDS;
|
|
3140
3212
|
var init_loop = __esm({
|
|
3141
3213
|
"packages/agent-runtime/src/claude-code/loop.ts"() {
|
|
3142
3214
|
"use strict";
|
|
@@ -3144,11 +3216,13 @@ var init_loop = __esm({
|
|
|
3144
3216
|
init_failure();
|
|
3145
3217
|
init_session();
|
|
3146
3218
|
HARNESS_PLACEHOLDERS = /* @__PURE__ */ new Set(["No response requested.", "(no content)"]);
|
|
3219
|
+
CONTEXT_READING_TIMEOUT_MS = 2e3;
|
|
3220
|
+
CATEGORY_KINDS = /* @__PURE__ */ new Set(["used", "free", "buffer", "deferred"]);
|
|
3147
3221
|
}
|
|
3148
3222
|
});
|
|
3149
3223
|
|
|
3150
3224
|
// packages/agent-runtime/src/claude-code/prompt-input.ts
|
|
3151
|
-
function buildQueryPrompt(req) {
|
|
3225
|
+
function buildQueryPrompt(req, openUntil) {
|
|
3152
3226
|
const content = req.content;
|
|
3153
3227
|
return (async function* () {
|
|
3154
3228
|
yield {
|
|
@@ -3157,6 +3231,7 @@ function buildQueryPrompt(req) {
|
|
|
3157
3231
|
message: { role: "user", content },
|
|
3158
3232
|
parent_tool_use_id: null
|
|
3159
3233
|
};
|
|
3234
|
+
if (openUntil) await openUntil;
|
|
3160
3235
|
})();
|
|
3161
3236
|
}
|
|
3162
3237
|
var init_prompt_input = __esm({
|
|
@@ -3176,19 +3251,34 @@ function attachAbortController(options, signal) {
|
|
|
3176
3251
|
else signal.addEventListener("abort", () => sdkAbort.abort(), { once: true });
|
|
3177
3252
|
options.abortController = sdkAbort;
|
|
3178
3253
|
}
|
|
3179
|
-
function sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
|
|
3254
|
+
async function* sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
|
|
3180
3255
|
attachAbortController(built.options, signal);
|
|
3181
|
-
|
|
3182
|
-
const iter = {
|
|
3183
|
-
[Symbol.asyncIterator]: () => queryFn({ prompt, options: built.options })[Symbol.asyncIterator]()
|
|
3256
|
+
let release = () => {
|
|
3184
3257
|
};
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
cwd: req.local.cwd,
|
|
3188
|
-
resumedSessionId: built.resume,
|
|
3189
|
-
degraded,
|
|
3190
|
-
...startEvidence ? { startEvidence } : {}
|
|
3258
|
+
const held = new Promise((resolve2) => {
|
|
3259
|
+
release = resolve2;
|
|
3191
3260
|
});
|
|
3261
|
+
const prompt = buildQueryPrompt(req, held);
|
|
3262
|
+
let active;
|
|
3263
|
+
const iter = {
|
|
3264
|
+
[Symbol.asyncIterator]: () => {
|
|
3265
|
+
const q = queryFn({ prompt, options: built.options });
|
|
3266
|
+
active = q;
|
|
3267
|
+
return q[Symbol.asyncIterator]();
|
|
3268
|
+
}
|
|
3269
|
+
};
|
|
3270
|
+
try {
|
|
3271
|
+
yield* decodeSdkStream(iter, {
|
|
3272
|
+
signal,
|
|
3273
|
+
readContextUsage: async () => typeof active?.getContextUsage === "function" ? active.getContextUsage({ detail: "summary" }) : void 0,
|
|
3274
|
+
cwd: req.local.cwd,
|
|
3275
|
+
resumedSessionId: built.resume,
|
|
3276
|
+
degraded,
|
|
3277
|
+
...startEvidence ? { startEvidence } : {}
|
|
3278
|
+
});
|
|
3279
|
+
} finally {
|
|
3280
|
+
release();
|
|
3281
|
+
}
|
|
3192
3282
|
}
|
|
3193
3283
|
async function freshRequest(req, reason, hooks) {
|
|
3194
3284
|
const recomposed = hooks?.fresh ? await hooks.fresh(reason) : null;
|
|
@@ -3979,9 +4069,9 @@ function readSessionError(properties) {
|
|
|
3979
4069
|
if (typeof err === "string") return err;
|
|
3980
4070
|
const rec2 = asRecord(err);
|
|
3981
4071
|
if (!rec2) return "unknown";
|
|
3982
|
-
const { name, message } = deepestError(rec2);
|
|
3983
|
-
if (
|
|
3984
|
-
if (
|
|
4072
|
+
const { name, message: message2 } = deepestError(rec2);
|
|
4073
|
+
if (message2 && name && !isGenericErrorName(name)) return `${name}: ${message2}`;
|
|
4074
|
+
if (message2) return message2;
|
|
3985
4075
|
if (name) return name;
|
|
3986
4076
|
if (typeof rec2.name === "string") return rec2.name;
|
|
3987
4077
|
return "unknown";
|
|
@@ -3993,10 +4083,10 @@ function deepestError(node) {
|
|
|
3993
4083
|
const best = {};
|
|
3994
4084
|
let current = node;
|
|
3995
4085
|
for (let depth = 0; current && depth < 8; depth++) {
|
|
3996
|
-
const
|
|
4086
|
+
const message2 = typeof current.message === "string" ? current.message : void 0;
|
|
3997
4087
|
const name = typeof current.name === "string" && !isGenericErrorName(current.name) ? current.name : void 0;
|
|
3998
|
-
if (
|
|
3999
|
-
best.message =
|
|
4088
|
+
if (message2) {
|
|
4089
|
+
best.message = message2;
|
|
4000
4090
|
if (name) best.name = name;
|
|
4001
4091
|
} else if (name && !best.name) {
|
|
4002
4092
|
best.name = name;
|
|
@@ -4659,9 +4749,9 @@ var init_conformance3 = __esm({
|
|
|
4659
4749
|
properties: { part: { type: "text", id: partId, messageID, text } }
|
|
4660
4750
|
});
|
|
4661
4751
|
idle = () => ({ type: "session.idle", properties: {} });
|
|
4662
|
-
errored = (
|
|
4752
|
+
errored = (message2) => ({
|
|
4663
4753
|
type: "session.error",
|
|
4664
|
-
properties: { error: { message } }
|
|
4754
|
+
properties: { error: { message: message2 } }
|
|
4665
4755
|
});
|
|
4666
4756
|
sessionEvent2 = (sessionId, directory = DIR, degraded = false) => ({
|
|
4667
4757
|
type: "session",
|
|
@@ -5105,8 +5195,8 @@ function readItemText(item) {
|
|
|
5105
5195
|
function readItemMessage(item) {
|
|
5106
5196
|
return str(item.message) ?? "";
|
|
5107
5197
|
}
|
|
5108
|
-
function isModelMetadataError(
|
|
5109
|
-
return
|
|
5198
|
+
function isModelMetadataError(message2) {
|
|
5199
|
+
return message2.includes("Defaulting to fallback metadata");
|
|
5110
5200
|
}
|
|
5111
5201
|
function readToolItem(item, eventType) {
|
|
5112
5202
|
const type = str(item.type);
|
|
@@ -5351,11 +5441,11 @@ async function* decodeCodexStream(events, ctx) {
|
|
|
5351
5441
|
if (!item) continue;
|
|
5352
5442
|
const itemType = readItemType(item);
|
|
5353
5443
|
if (itemType === "error") {
|
|
5354
|
-
const
|
|
5355
|
-
if (isModelMetadataError(
|
|
5444
|
+
const message2 = readItemMessage(item);
|
|
5445
|
+
if (isModelMetadataError(message2)) {
|
|
5356
5446
|
yield* flushInterim();
|
|
5357
5447
|
ok = false;
|
|
5358
|
-
reason = `model_unavailable:${
|
|
5448
|
+
reason = `model_unavailable:${message2.slice(0, 200)}`;
|
|
5359
5449
|
settled = true;
|
|
5360
5450
|
break;
|
|
5361
5451
|
}
|
|
@@ -5522,7 +5612,7 @@ var init_model3 = __esm({
|
|
|
5522
5612
|
});
|
|
5523
5613
|
|
|
5524
5614
|
// packages/agent-runtime/src/codex/run-spec.ts
|
|
5525
|
-
function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null) {
|
|
5615
|
+
function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null, imagePaths = []) {
|
|
5526
5616
|
const { policy, config } = req;
|
|
5527
5617
|
const directory = req.local.cwd ?? "";
|
|
5528
5618
|
const dialect = readCodexDialect(config.runtimeOptions);
|
|
@@ -5539,7 +5629,7 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromp
|
|
|
5539
5629
|
skipGitRepoCheck: true,
|
|
5540
5630
|
...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
|
|
5541
5631
|
baseInstructionsFile,
|
|
5542
|
-
input: buildInput(req, promptRidesInput),
|
|
5632
|
+
input: withImages(buildInput(req, promptRidesInput), imagePaths),
|
|
5543
5633
|
promptRidesInput,
|
|
5544
5634
|
promptFingerprint,
|
|
5545
5635
|
config: buildConfig(req, baseInstructionsFile),
|
|
@@ -5555,6 +5645,13 @@ function buildInput(req, promptRidesInput) {
|
|
|
5555
5645
|
|
|
5556
5646
|
${body}` : body;
|
|
5557
5647
|
}
|
|
5648
|
+
function withImages(text, imagePaths) {
|
|
5649
|
+
if (imagePaths.length === 0) return text;
|
|
5650
|
+
return [
|
|
5651
|
+
{ type: "text", text },
|
|
5652
|
+
...imagePaths.map((path) => ({ type: "local_image", path }))
|
|
5653
|
+
];
|
|
5654
|
+
}
|
|
5558
5655
|
function buildConfig(req, baseInstructionsFile) {
|
|
5559
5656
|
const mcp_servers = {};
|
|
5560
5657
|
for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
|
|
@@ -5826,12 +5923,35 @@ function createCodexAdapter(deps = {}) {
|
|
|
5826
5923
|
}
|
|
5827
5924
|
}
|
|
5828
5925
|
}
|
|
5926
|
+
let images = null;
|
|
5829
5927
|
try {
|
|
5928
|
+
const imageUrls = req.content.flatMap((b) => b.type === "image" ? [b.source.url] : []);
|
|
5929
|
+
if (imageUrls.length > 0) {
|
|
5930
|
+
if (!deps.prepareImages) {
|
|
5931
|
+
deps.onWarn?.(
|
|
5932
|
+
"codex adapter: no image preparer wired on this host \u2014 the turn runs without its images (CT1757)",
|
|
5933
|
+
{ images: imageUrls.length }
|
|
5934
|
+
);
|
|
5935
|
+
} else {
|
|
5936
|
+
try {
|
|
5937
|
+
images = await deps.prepareImages(imageUrls, signal);
|
|
5938
|
+
} catch (err) {
|
|
5939
|
+
if (!signal.aborted) {
|
|
5940
|
+
deps.onWarn?.(
|
|
5941
|
+
"codex adapter: preparing the images failed \u2014 the turn runs without them (CT1757)",
|
|
5942
|
+
{ err: err instanceof Error ? err.message : String(err) }
|
|
5943
|
+
);
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
if (signal.aborted) return;
|
|
5948
|
+
}
|
|
5830
5949
|
const spec = buildRunSpec2(
|
|
5831
5950
|
req,
|
|
5832
5951
|
resumeThreadId,
|
|
5833
5952
|
instructions?.path ?? null,
|
|
5834
|
-
threadPromptFingerprint
|
|
5953
|
+
threadPromptFingerprint,
|
|
5954
|
+
images?.paths ?? []
|
|
5835
5955
|
);
|
|
5836
5956
|
if (spec.model === null) {
|
|
5837
5957
|
deps.onWarn?.("Codex is using its default model because no model was resolved", {
|
|
@@ -5860,6 +5980,15 @@ function createCodexAdapter(deps = {}) {
|
|
|
5860
5980
|
resolvedReasoningEffort: spec.modelReasoningEffort ?? null
|
|
5861
5981
|
});
|
|
5862
5982
|
} finally {
|
|
5983
|
+
if (images) {
|
|
5984
|
+
try {
|
|
5985
|
+
await images.cleanup();
|
|
5986
|
+
} catch (err) {
|
|
5987
|
+
deps.onWarn?.("codex adapter: image cleanup failed (CT1757)", {
|
|
5988
|
+
err: err instanceof Error ? err.message : String(err)
|
|
5989
|
+
});
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5863
5992
|
if (instructions) {
|
|
5864
5993
|
try {
|
|
5865
5994
|
await instructions.cleanup();
|
|
@@ -5952,14 +6081,14 @@ var init_conformance4 = __esm({
|
|
|
5952
6081
|
type: "turn.completed",
|
|
5953
6082
|
...usage ? { usage } : {}
|
|
5954
6083
|
});
|
|
5955
|
-
turnFailed = (
|
|
6084
|
+
turnFailed = (message2) => ({
|
|
5956
6085
|
type: "turn.failed",
|
|
5957
|
-
error: { message }
|
|
6086
|
+
error: { message: message2 }
|
|
5958
6087
|
});
|
|
5959
|
-
fatalError = (
|
|
5960
|
-
errorItem = (id,
|
|
6088
|
+
fatalError = (message2) => ({ type: "error", message: message2 });
|
|
6089
|
+
errorItem = (id, message2) => ({
|
|
5961
6090
|
type: "item.completed",
|
|
5962
|
-
item: { id, type: "error", message }
|
|
6091
|
+
item: { id, type: "error", message: message2 }
|
|
5963
6092
|
});
|
|
5964
6093
|
OP_SUCCESS = { ok: true, result: { source: "native" } };
|
|
5965
6094
|
OP_FAILURE = {
|
|
@@ -6897,8 +7026,8 @@ function unenforceable(policy) {
|
|
|
6897
7026
|
deniedDomains: as === "deniedDomains" ? [host] : []
|
|
6898
7027
|
});
|
|
6899
7028
|
if (!check.success) {
|
|
6900
|
-
const
|
|
6901
|
-
return `runners.policy.${field}.${i}: ${
|
|
7029
|
+
const message2 = check.error.issues[0]?.message ?? "not a host pattern the sandbox accepts";
|
|
7030
|
+
return `runners.policy.${field}.${i}: ${message2}`;
|
|
6902
7031
|
}
|
|
6903
7032
|
}
|
|
6904
7033
|
}
|
|
@@ -6912,8 +7041,8 @@ function unenforceable(policy) {
|
|
|
6912
7041
|
try {
|
|
6913
7042
|
new RegExp(globToRegex(path));
|
|
6914
7043
|
} catch (err) {
|
|
6915
|
-
const
|
|
6916
|
-
return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${
|
|
7044
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
7045
|
+
return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${message2})`;
|
|
6917
7046
|
}
|
|
6918
7047
|
}
|
|
6919
7048
|
}
|
|
@@ -7118,8 +7247,8 @@ async function reapTurnScopes(turnId) {
|
|
|
7118
7247
|
});
|
|
7119
7248
|
return { failures: [] };
|
|
7120
7249
|
} catch (err) {
|
|
7121
|
-
const
|
|
7122
|
-
return classifyStopError(
|
|
7250
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
7251
|
+
return classifyStopError(message2) === "absent" ? { failures: [] } : { failures: [message2.slice(0, 200)] };
|
|
7123
7252
|
}
|
|
7124
7253
|
}
|
|
7125
7254
|
function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
|
|
@@ -7135,13 +7264,13 @@ function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
|
|
|
7135
7264
|
return false;
|
|
7136
7265
|
}
|
|
7137
7266
|
}
|
|
7138
|
-
function classifyStopError(
|
|
7267
|
+
function classifyStopError(message2) {
|
|
7139
7268
|
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(
|
|
7140
|
-
|
|
7269
|
+
message2
|
|
7141
7270
|
)) {
|
|
7142
7271
|
return "failure";
|
|
7143
7272
|
}
|
|
7144
|
-
if (/not loaded|could not be found|no such unit|unit .* not found/i.test(
|
|
7273
|
+
if (/not loaded|could not be found|no such unit|unit .* not found/i.test(message2))
|
|
7145
7274
|
return "absent";
|
|
7146
7275
|
return "failure";
|
|
7147
7276
|
}
|
|
@@ -7250,9 +7379,9 @@ function createTurnContainment(turnId, deps = {}) {
|
|
|
7250
7379
|
timeout: (CONTAINMENT_STOP_GRACE_SEC + 10) * 1e3
|
|
7251
7380
|
});
|
|
7252
7381
|
} catch (err) {
|
|
7253
|
-
const
|
|
7254
|
-
if (classifyStopError(
|
|
7255
|
-
result.failures.push({ target: unit, error:
|
|
7382
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
7383
|
+
if (classifyStopError(message2) === "failure") {
|
|
7384
|
+
result.failures.push({ target: unit, error: message2.slice(0, 200) });
|
|
7256
7385
|
continue;
|
|
7257
7386
|
}
|
|
7258
7387
|
result.reaped.push(unit);
|
|
@@ -7616,8 +7745,8 @@ function runnerStdin(input) {
|
|
|
7616
7745
|
})}
|
|
7617
7746
|
`;
|
|
7618
7747
|
}
|
|
7619
|
-
function encodeLauncherMessage(
|
|
7620
|
-
return `${JSON.stringify(
|
|
7748
|
+
function encodeLauncherMessage(message2) {
|
|
7749
|
+
return `${JSON.stringify(message2)}
|
|
7621
7750
|
`;
|
|
7622
7751
|
}
|
|
7623
7752
|
function decodeLauncherMessage(line) {
|
|
@@ -8479,6 +8608,74 @@ var init_codex_instructions = __esm({
|
|
|
8479
8608
|
}
|
|
8480
8609
|
});
|
|
8481
8610
|
|
|
8611
|
+
// src/codex-images.ts
|
|
8612
|
+
import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
8613
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
8614
|
+
import { join as join12 } from "path";
|
|
8615
|
+
function createCodexImagePreparer(opts) {
|
|
8616
|
+
const doFetch = opts.fetch ?? fetch;
|
|
8617
|
+
const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
|
|
8618
|
+
return async (urls, signal) => {
|
|
8619
|
+
signal.throwIfAborted();
|
|
8620
|
+
const dir2 = await mkdtemp2(join12(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
|
|
8621
|
+
const cleanup = async () => {
|
|
8622
|
+
try {
|
|
8623
|
+
await rm2(dir2, { recursive: true, force: true });
|
|
8624
|
+
} catch (err) {
|
|
8625
|
+
opts.warn(
|
|
8626
|
+
{ dir: dir2, err: message(err) },
|
|
8627
|
+
"codex images: could not remove the scratch directory"
|
|
8628
|
+
);
|
|
8629
|
+
}
|
|
8630
|
+
};
|
|
8631
|
+
const prepareOne = async (url, index) => {
|
|
8632
|
+
try {
|
|
8633
|
+
const res = await doFetch(url, {
|
|
8634
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)])
|
|
8635
|
+
});
|
|
8636
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
8637
|
+
const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
8638
|
+
const ext = EXTENSIONS[type];
|
|
8639
|
+
if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
|
|
8640
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
8641
|
+
const path = join12(dir2, `image-${index + 1}.${ext}`);
|
|
8642
|
+
await writeFile2(path, bytes, { mode: 384 });
|
|
8643
|
+
return path;
|
|
8644
|
+
} catch (err) {
|
|
8645
|
+
if (!signal.aborted) {
|
|
8646
|
+
opts.warn(
|
|
8647
|
+
{ image: index + 1, of: urls.length, err: message(err) },
|
|
8648
|
+
"codex images: an image could not be prepared; the turn runs without it"
|
|
8649
|
+
);
|
|
8650
|
+
}
|
|
8651
|
+
return null;
|
|
8652
|
+
}
|
|
8653
|
+
};
|
|
8654
|
+
const paths = await Promise.all(urls.map(prepareOne));
|
|
8655
|
+
if (signal.aborted) {
|
|
8656
|
+
await cleanup();
|
|
8657
|
+
signal.throwIfAborted();
|
|
8658
|
+
}
|
|
8659
|
+
return { paths: paths.filter((p) => p !== null), cleanup };
|
|
8660
|
+
};
|
|
8661
|
+
}
|
|
8662
|
+
function message(err) {
|
|
8663
|
+
return err instanceof Error ? err.message : String(err);
|
|
8664
|
+
}
|
|
8665
|
+
var FETCH_TIMEOUT_MS, EXTENSIONS;
|
|
8666
|
+
var init_codex_images = __esm({
|
|
8667
|
+
"src/codex-images.ts"() {
|
|
8668
|
+
"use strict";
|
|
8669
|
+
FETCH_TIMEOUT_MS = 3e4;
|
|
8670
|
+
EXTENSIONS = {
|
|
8671
|
+
"image/png": "png",
|
|
8672
|
+
"image/jpeg": "jpg",
|
|
8673
|
+
"image/gif": "gif",
|
|
8674
|
+
"image/webp": "webp"
|
|
8675
|
+
};
|
|
8676
|
+
}
|
|
8677
|
+
});
|
|
8678
|
+
|
|
8482
8679
|
// src/turn-seq-floor.ts
|
|
8483
8680
|
function resolveSeqFloor(sources, ctx) {
|
|
8484
8681
|
const { serverFloor, outboxFloor } = sources;
|
|
@@ -8522,15 +8719,15 @@ var init_turn_seq_floor = __esm({
|
|
|
8522
8719
|
import { spawn as spawn5 } from "child_process";
|
|
8523
8720
|
import { hostname as hostname2 } from "os";
|
|
8524
8721
|
function prepareFailureCause(error) {
|
|
8525
|
-
const
|
|
8526
|
-
if (/timed out/i.test(
|
|
8527
|
-
if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(
|
|
8722
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
8723
|
+
if (/timed out/i.test(message2)) return "prepare hook timed out";
|
|
8724
|
+
if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message2))
|
|
8528
8725
|
return "prepare hook module missing; check the configured command and its installation for this device";
|
|
8529
|
-
if (/not valid JSON|JSON must carry|produced no output/.test(
|
|
8726
|
+
if (/not valid JSON|JSON must carry|produced no output/.test(message2))
|
|
8530
8727
|
return "prepare hook returned malformed output; expected a cwd path or JSON with a non-empty cwd";
|
|
8531
|
-
if (/ENOENT/.test(
|
|
8532
|
-
if (/EACCES/.test(
|
|
8533
|
-
const exit =
|
|
8728
|
+
if (/ENOENT/.test(message2)) return "prepare hook executable missing (ENOENT)";
|
|
8729
|
+
if (/EACCES/.test(message2)) return "prepare hook executable is not accessible (EACCES)";
|
|
8730
|
+
const exit = message2.match(/exited with code (\d+)/)?.[1];
|
|
8534
8731
|
return exit ? `prepare hook exited with code ${exit}` : "prepare hook failed; inspect the device-local hook log";
|
|
8535
8732
|
}
|
|
8536
8733
|
async function runPrepareWithReporting(runner, hook, input, context, reportError) {
|
|
@@ -8587,15 +8784,15 @@ var init_prepare_failure = __esm({
|
|
|
8587
8784
|
|
|
8588
8785
|
// src/prepared.ts
|
|
8589
8786
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
|
|
8590
|
-
import { join as
|
|
8787
|
+
import { join as join13 } from "path";
|
|
8591
8788
|
function dirFor(workspaceId) {
|
|
8592
|
-
return
|
|
8789
|
+
return join13(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
|
|
8593
8790
|
}
|
|
8594
8791
|
function conversationDir(workspaceId, conversationId) {
|
|
8595
|
-
return
|
|
8792
|
+
return join13(dirFor(workspaceId), encodeURIComponent(conversationId));
|
|
8596
8793
|
}
|
|
8597
8794
|
function pathFor2(workspaceId, conversationId, agentId) {
|
|
8598
|
-
return
|
|
8795
|
+
return join13(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
|
|
8599
8796
|
}
|
|
8600
8797
|
function readPrepared(workspaceId, conversationId, agentId) {
|
|
8601
8798
|
const path = pathFor2(workspaceId, conversationId, agentId);
|
|
@@ -8633,9 +8830,9 @@ var init_prepared = __esm({
|
|
|
8633
8830
|
|
|
8634
8831
|
// src/transcript-writer.ts
|
|
8635
8832
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
8636
|
-
import { basename, dirname as dirname6, join as
|
|
8833
|
+
import { basename, dirname as dirname6, join as join14 } from "path";
|
|
8637
8834
|
function transcriptsDir() {
|
|
8638
|
-
return
|
|
8835
|
+
return join14(runnerStateDir(), "transcripts");
|
|
8639
8836
|
}
|
|
8640
8837
|
function fileName(meta) {
|
|
8641
8838
|
const d = new Date(meta.ts);
|
|
@@ -8661,7 +8858,7 @@ function pruneOld(dir2, retain) {
|
|
|
8661
8858
|
const drop = files.sort().slice(0, files.length - retain);
|
|
8662
8859
|
for (const f of drop) {
|
|
8663
8860
|
try {
|
|
8664
|
-
rmSync6(
|
|
8861
|
+
rmSync6(join14(dir2, f), { force: true });
|
|
8665
8862
|
} catch {
|
|
8666
8863
|
}
|
|
8667
8864
|
}
|
|
@@ -8679,7 +8876,7 @@ var init_transcript_writer = __esm({
|
|
|
8679
8876
|
onWarn;
|
|
8680
8877
|
constructor(dir2, meta, onWarn) {
|
|
8681
8878
|
this.onWarn = onWarn;
|
|
8682
|
-
this.path =
|
|
8879
|
+
this.path = join14(dir2, fileName(meta));
|
|
8683
8880
|
try {
|
|
8684
8881
|
mkdirSync8(dir2, { recursive: true });
|
|
8685
8882
|
try {
|
|
@@ -8708,10 +8905,10 @@ var init_transcript_writer = __esm({
|
|
|
8708
8905
|
preserveAnomaly() {
|
|
8709
8906
|
if (this.broken) return;
|
|
8710
8907
|
try {
|
|
8711
|
-
const dir2 =
|
|
8908
|
+
const dir2 = join14(dirname6(this.path), "anomalies");
|
|
8712
8909
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
8713
8910
|
chmodSync4(dir2, 448);
|
|
8714
|
-
const target =
|
|
8911
|
+
const target = join14(dir2, basename(this.path));
|
|
8715
8912
|
copyFileSync(this.path, target);
|
|
8716
8913
|
chmodSync4(target, 384);
|
|
8717
8914
|
pruneOld(dir2, ANOMALY_RETAIN);
|
|
@@ -9006,6 +9203,7 @@ var init_turn_execution = __esm({
|
|
|
9006
9203
|
init_build_options();
|
|
9007
9204
|
init_bundled_binaries();
|
|
9008
9205
|
init_codex_instructions();
|
|
9206
|
+
init_codex_images();
|
|
9009
9207
|
init_config();
|
|
9010
9208
|
init_api_error_shape();
|
|
9011
9209
|
init_errors();
|
|
@@ -9354,16 +9552,16 @@ var init_turn_execution = __esm({
|
|
|
9354
9552
|
}
|
|
9355
9553
|
gateTrigger() {
|
|
9356
9554
|
const { payload, turnLog } = this;
|
|
9357
|
-
const
|
|
9358
|
-
const isDispatchableTrigger =
|
|
9555
|
+
const message2 = this.turnContext.message;
|
|
9556
|
+
const isDispatchableTrigger = message2.role === "user" || message2.role === "agent" || message2.role === "system";
|
|
9359
9557
|
if (!isDispatchableTrigger) {
|
|
9360
|
-
turnLog.debug({ role:
|
|
9558
|
+
turnLog.debug({ role: message2.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
|
|
9361
9559
|
throw this.concluded("unexpected_role", UNEXPECTED_ROLE_REASON);
|
|
9362
9560
|
}
|
|
9363
9561
|
this.supervisor.notifyStart({
|
|
9364
9562
|
id: this.dispatchId,
|
|
9365
9563
|
conversationId: payload.conversationId,
|
|
9366
|
-
message:
|
|
9564
|
+
message: message2.body
|
|
9367
9565
|
});
|
|
9368
9566
|
}
|
|
9369
9567
|
async resolveSecrets() {
|
|
@@ -9714,6 +9912,10 @@ var init_turn_execution = __esm({
|
|
|
9714
9912
|
enabled: true,
|
|
9715
9913
|
onWarn,
|
|
9716
9914
|
writeInstructionsFile: writeCodexInstructionsFile,
|
|
9915
|
+
prepareImages: createCodexImagePreparer({
|
|
9916
|
+
turnId: this.turnId,
|
|
9917
|
+
warn: (meta, msg) => turnLog.warn(meta, msg)
|
|
9918
|
+
}),
|
|
9717
9919
|
...codexPath ? { transport: createSdkCodexTransport({ codexPathOverride: codexPath }) } : {}
|
|
9718
9920
|
})
|
|
9719
9921
|
);
|
|
@@ -10174,7 +10376,7 @@ var init_turn_execution = __esm({
|
|
|
10174
10376
|
// src/dispatcher.ts
|
|
10175
10377
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
10176
10378
|
import { existsSync as existsSync10, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
10177
|
-
import { join as
|
|
10379
|
+
import { join as join15 } from "path";
|
|
10178
10380
|
function checkoutState(cwd) {
|
|
10179
10381
|
if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
|
|
10180
10382
|
if (!existsSync10(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
|
|
@@ -10187,7 +10389,7 @@ function checkoutState(cwd) {
|
|
|
10187
10389
|
if (entries.length === 0) {
|
|
10188
10390
|
return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
|
|
10189
10391
|
}
|
|
10190
|
-
const gitPath =
|
|
10392
|
+
const gitPath = join15(cwd, ".git");
|
|
10191
10393
|
if (!existsSync10(gitPath)) return { ok: true, reason: "usable" };
|
|
10192
10394
|
let stat;
|
|
10193
10395
|
try {
|
|
@@ -10195,7 +10397,7 @@ function checkoutState(cwd) {
|
|
|
10195
10397
|
} catch (error) {
|
|
10196
10398
|
return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
|
|
10197
10399
|
}
|
|
10198
|
-
if (stat.isDirectory() && !existsSync10(
|
|
10400
|
+
if (stat.isDirectory() && !existsSync10(join15(gitPath, "HEAD")))
|
|
10199
10401
|
return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
|
|
10200
10402
|
return { ok: true, reason: "usable" };
|
|
10201
10403
|
}
|
|
@@ -10561,8 +10763,8 @@ import {
|
|
|
10561
10763
|
} from "fs";
|
|
10562
10764
|
import { createRequire as createRequire2 } from "module";
|
|
10563
10765
|
import { isIP } from "net";
|
|
10564
|
-
import { homedir as homedir3, networkInterfaces, tmpdir as
|
|
10565
|
-
import { dirname as dirname8, join as
|
|
10766
|
+
import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
|
|
10767
|
+
import { dirname as dirname8, join as join19, sep as sep2 } from "path";
|
|
10566
10768
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10567
10769
|
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
10568
10770
|
function accountHome() {
|
|
@@ -10596,7 +10798,7 @@ async function resolvedAddresses(baseUrl) {
|
|
|
10596
10798
|
function companionPackageRoot(modulePath) {
|
|
10597
10799
|
for (let dir2 = dirname8(modulePath); ; dir2 = dirname8(dir2)) {
|
|
10598
10800
|
try {
|
|
10599
|
-
const manifest = JSON.parse(readFileSync10(
|
|
10801
|
+
const manifest = JSON.parse(readFileSync10(join19(dir2, "package.json"), "utf8"));
|
|
10600
10802
|
if (manifest.name === "@cabane/companion") return dir2;
|
|
10601
10803
|
} catch {
|
|
10602
10804
|
}
|
|
@@ -10673,15 +10875,15 @@ function companionStoreDenies(input) {
|
|
|
10673
10875
|
const always = [
|
|
10674
10876
|
cabaneDir2,
|
|
10675
10877
|
...homes.flatMap((home) => [
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10878
|
+
join19(home, ".cabane"),
|
|
10879
|
+
join19(home, "*", ".cabane"),
|
|
10880
|
+
join19(home, "*", "*", ".cabane")
|
|
10679
10881
|
]),
|
|
10680
10882
|
// A glob anchored at the root has no directory to start from.
|
|
10681
|
-
...dirname8(parent) === parent ? [] : [
|
|
10883
|
+
...dirname8(parent) === parent ? [] : [join19(parent, "*", ".cabane")]
|
|
10682
10884
|
];
|
|
10683
10885
|
const companionHome = dirname8(cabaneDir2);
|
|
10684
|
-
const otherLogins = foundStores.map((store) => dirname8(store)).filter((home) => home !== companionHome).flatMap((home) => [
|
|
10886
|
+
const otherLogins = foundStores.map((store) => dirname8(store)).filter((home) => home !== companionHome).flatMap((home) => [join19(home, ".claude"), join19(home, ".codex")]).filter((path) => !input.ownState.some((own) => isAtOrUnder(own, path)));
|
|
10685
10887
|
return [.../* @__PURE__ */ new Set([...always, ...foundStores, ...otherLogins])];
|
|
10686
10888
|
}
|
|
10687
10889
|
function companionStoreWatch(log) {
|
|
@@ -10698,7 +10900,7 @@ function isAtOrUnder(path, dir2) {
|
|
|
10698
10900
|
}
|
|
10699
10901
|
function expandHome(path, home) {
|
|
10700
10902
|
if (path === "~") return home;
|
|
10701
|
-
return path.startsWith("~/") ?
|
|
10903
|
+
return path.startsWith("~/") ? join19(home, path.slice(2)) : path;
|
|
10702
10904
|
}
|
|
10703
10905
|
function stripPort(pattern) {
|
|
10704
10906
|
return pattern.startsWith("[") ? pattern.replace(/^\[([^\]]+)\](:\d+)?$/, "$1") : pattern.replace(/:\d+$/, "");
|
|
@@ -10716,8 +10918,8 @@ function patternsOverlap(a, b) {
|
|
|
10716
10918
|
}
|
|
10717
10919
|
function harnessSandboxConfig(input) {
|
|
10718
10920
|
const { policy } = input;
|
|
10719
|
-
const claudeDir = input.claudeConfigDir ??
|
|
10720
|
-
const codexDir = input.codexHome ??
|
|
10921
|
+
const claudeDir = input.claudeConfigDir ?? join19(input.home, ".claude");
|
|
10922
|
+
const codexDir = input.codexHome ?? join19(input.home, ".codex");
|
|
10721
10923
|
const homes = [.../* @__PURE__ */ new Set([input.home, input.accountHome ?? input.home])];
|
|
10722
10924
|
const expand = (paths) => (paths ?? []).map((p) => expandHome(p, input.home));
|
|
10723
10925
|
const cabane = cabaneHostEntries(input.baseUrl, input.resolved, input.ownAddresses);
|
|
@@ -10734,7 +10936,7 @@ function harnessSandboxConfig(input) {
|
|
|
10734
10936
|
foundStores: input.foundStores,
|
|
10735
10937
|
ownState: [claudeDir, codexDir]
|
|
10736
10938
|
}),
|
|
10737
|
-
...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) =>
|
|
10939
|
+
...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join19(home, p))),
|
|
10738
10940
|
...expand(policy.filesystem?.denyRead)
|
|
10739
10941
|
];
|
|
10740
10942
|
const strict = policy.filesystem?.mode === "strict";
|
|
@@ -10742,7 +10944,7 @@ function harnessSandboxConfig(input) {
|
|
|
10742
10944
|
const allowRead = strict ? [
|
|
10743
10945
|
...input.cwd ? [input.cwd] : [],
|
|
10744
10946
|
claudeDir,
|
|
10745
|
-
|
|
10947
|
+
join19(input.home, ".claude.json"),
|
|
10746
10948
|
codexDir,
|
|
10747
10949
|
...input.runtimePaths ?? [],
|
|
10748
10950
|
...expand(policy.filesystem?.allowRead)
|
|
@@ -10782,7 +10984,7 @@ function harnessSandboxConfig(input) {
|
|
|
10782
10984
|
...input.cwd ? [input.cwd] : [],
|
|
10783
10985
|
input.tmp,
|
|
10784
10986
|
claudeDir,
|
|
10785
|
-
|
|
10987
|
+
join19(input.home, ".claude.json"),
|
|
10786
10988
|
codexDir,
|
|
10787
10989
|
...expand(policy.filesystem?.allowWrite)
|
|
10788
10990
|
],
|
|
@@ -10835,7 +11037,7 @@ async function prepareHarnessSandbox(input) {
|
|
|
10835
11037
|
ownAddresses: ownAddresses(),
|
|
10836
11038
|
home: homedir3(),
|
|
10837
11039
|
accountHome: accountHome(),
|
|
10838
|
-
tmp:
|
|
11040
|
+
tmp: tmpdir4(),
|
|
10839
11041
|
cabaneDir: cabaneDir(),
|
|
10840
11042
|
claudeConfigDir: process.env.CLAUDE_CONFIG_DIR,
|
|
10841
11043
|
codexHome: process.env.CODEX_HOME,
|
|
@@ -10866,27 +11068,27 @@ async function prepareHarnessSandbox(input) {
|
|
|
10866
11068
|
"export NO_PROXY= no_proxy=; " + gitCredentialExports(input.credentials) + 'eval "exec $CABANE_SANDBOXED_ARGV"'
|
|
10867
11069
|
);
|
|
10868
11070
|
} catch (err) {
|
|
10869
|
-
const
|
|
11071
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
10870
11072
|
await SandboxManager.reset().catch(() => {
|
|
10871
11073
|
});
|
|
10872
11074
|
if (held.length || Object.keys(input.policy).length) {
|
|
10873
11075
|
input.log.warn(
|
|
10874
|
-
{ err:
|
|
11076
|
+
{ err: message2, masked: held },
|
|
10875
11077
|
"runners: the OS sandbox could not start under this device's runners.policy; refusing the turn rather than running it unsandboxed"
|
|
10876
11078
|
);
|
|
10877
|
-
throw new HarnessSandboxRefused(`the OS sandbox could not start: ${
|
|
11079
|
+
throw new HarnessSandboxRefused(`the OS sandbox could not start: ${message2}`);
|
|
10878
11080
|
}
|
|
10879
11081
|
input.log.warn(
|
|
10880
|
-
{ err:
|
|
11082
|
+
{ err: message2 },
|
|
10881
11083
|
"runners: the OS sandbox could not start; running the turn unsandboxed"
|
|
10882
11084
|
);
|
|
10883
11085
|
return null;
|
|
10884
11086
|
} finally {
|
|
10885
11087
|
for (const name of held) delete process.env[name];
|
|
10886
11088
|
}
|
|
10887
|
-
mkdirSync11(
|
|
10888
|
-
const dir2 = mkdtempSync(
|
|
10889
|
-
const script =
|
|
11089
|
+
mkdirSync11(join19(cabaneDir(), "sandbox"), { recursive: true });
|
|
11090
|
+
const dir2 = mkdtempSync(join19(cabaneDir(), "sandbox", "turn-"));
|
|
11091
|
+
const script = join19(dir2, "exec-sandboxed");
|
|
10890
11092
|
writeFileSync7(
|
|
10891
11093
|
script,
|
|
10892
11094
|
[
|
|
@@ -10902,7 +11104,7 @@ async function prepareHarnessSandbox(input) {
|
|
|
10902
11104
|
return {
|
|
10903
11105
|
wrap: (command, args) => ({ command: script, args: [command, ...args] }),
|
|
10904
11106
|
executableFor: (real) => {
|
|
10905
|
-
const shim =
|
|
11107
|
+
const shim = join19(dir2, `exec-${++shims}`);
|
|
10906
11108
|
writeFileSync7(shim, `#!/bin/bash
|
|
10907
11109
|
exec ${shellQuote(script)} ${shellQuote(real)} "$@"
|
|
10908
11110
|
`);
|
|
@@ -10995,7 +11197,7 @@ import {
|
|
|
10995
11197
|
rmSync as rmSync11,
|
|
10996
11198
|
writeFileSync as writeFileSync8
|
|
10997
11199
|
} from "fs";
|
|
10998
|
-
import { join as
|
|
11200
|
+
import { join as join20 } from "path";
|
|
10999
11201
|
var MAX_ENTRIES, MAX_AGE_MS, Outbox;
|
|
11000
11202
|
var init_outbox = __esm({
|
|
11001
11203
|
"src/outbox.ts"() {
|
|
@@ -11013,10 +11215,10 @@ var init_outbox = __esm({
|
|
|
11013
11215
|
// Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
|
|
11014
11216
|
// cases route writes at the right tmpdir.
|
|
11015
11217
|
dir() {
|
|
11016
|
-
return
|
|
11218
|
+
return join20(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
|
|
11017
11219
|
}
|
|
11018
11220
|
fileFor(turnId, seq) {
|
|
11019
|
-
return
|
|
11221
|
+
return join20(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
|
|
11020
11222
|
}
|
|
11021
11223
|
// Persist a commit for later draining. Atomic (temp file + rename) so a
|
|
11022
11224
|
// concurrent `list()` never reads a half-written entry, then enforces the
|
|
@@ -11058,7 +11260,7 @@ var init_outbox = __esm({
|
|
|
11058
11260
|
const entries = [];
|
|
11059
11261
|
for (const name of names) {
|
|
11060
11262
|
if (!name.endsWith(".json")) continue;
|
|
11061
|
-
const full =
|
|
11263
|
+
const full = join20(dir2, name);
|
|
11062
11264
|
try {
|
|
11063
11265
|
const parsed = JSON.parse(readFileSync11(full, "utf8"));
|
|
11064
11266
|
if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
|
|
@@ -11198,7 +11400,7 @@ var init_image_manifest = __esm({
|
|
|
11198
11400
|
|
|
11199
11401
|
// src/runner/exec-shim.ts
|
|
11200
11402
|
import { chmodSync as chmodSync7, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync2, rmSync as rmSync13, writeFileSync as writeFileSync10 } from "fs";
|
|
11201
|
-
import { join as
|
|
11403
|
+
import { join as join23 } from "path";
|
|
11202
11404
|
function shellQuote2(value) {
|
|
11203
11405
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11204
11406
|
}
|
|
@@ -11208,10 +11410,10 @@ function createShimDir() {
|
|
|
11208
11410
|
return {
|
|
11209
11411
|
write(argv) {
|
|
11210
11412
|
if (!dir2) {
|
|
11211
|
-
mkdirSync15(
|
|
11212
|
-
dir2 = mkdtempSync2(
|
|
11413
|
+
mkdirSync15(join23(cabaneDir(), "runner"), { recursive: true });
|
|
11414
|
+
dir2 = mkdtempSync2(join23(cabaneDir(), "runner", "turn-"));
|
|
11213
11415
|
}
|
|
11214
|
-
const path =
|
|
11416
|
+
const path = join23(dir2, `exec-${++count}`);
|
|
11215
11417
|
writeFileSync10(path, `#!/bin/sh
|
|
11216
11418
|
exec ${argv.map(shellQuote2).join(" ")} "$@"
|
|
11217
11419
|
`);
|
|
@@ -11247,9 +11449,9 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
|
|
|
11247
11449
|
resolveFirst(line);
|
|
11248
11450
|
return;
|
|
11249
11451
|
}
|
|
11250
|
-
const
|
|
11251
|
-
if (
|
|
11252
|
-
else if (
|
|
11452
|
+
const message2 = decodeLauncherMessage(line);
|
|
11453
|
+
if (message2?.cabaneLauncher === "granted") waiting.get(message2.id)?.();
|
|
11454
|
+
else if (message2) replies.get(message2.id)?.(message2);
|
|
11253
11455
|
});
|
|
11254
11456
|
let gone = false;
|
|
11255
11457
|
lines2.once("close", () => {
|
|
@@ -11262,9 +11464,9 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
|
|
|
11262
11464
|
if (gone) return Promise.resolve(null);
|
|
11263
11465
|
const id = String(++next);
|
|
11264
11466
|
return new Promise((resolve2) => {
|
|
11265
|
-
replies.set(id, (
|
|
11467
|
+
replies.set(id, (message2) => {
|
|
11266
11468
|
replies.delete(id);
|
|
11267
|
-
resolve2(
|
|
11469
|
+
resolve2(message2);
|
|
11268
11470
|
});
|
|
11269
11471
|
emit(event(id));
|
|
11270
11472
|
});
|
|
@@ -12474,8 +12676,8 @@ async function logout(opts = {}) {
|
|
|
12474
12676
|
return;
|
|
12475
12677
|
}
|
|
12476
12678
|
if (!opts.yes) {
|
|
12477
|
-
const
|
|
12478
|
-
const ok = await confirm2({ message, default: false });
|
|
12679
|
+
const message2 = opts.purge ? "Purge the entire local companion config (device + agent overrides + settings)?" : "Log out this device (removes the device token + cached agent credentials; keeps your config)?";
|
|
12680
|
+
const ok = await confirm2({ message: message2, default: false });
|
|
12479
12681
|
if (!ok) {
|
|
12480
12682
|
process.stdout.write("cancelled\n");
|
|
12481
12683
|
return;
|
|
@@ -12731,8 +12933,8 @@ function formatHumanLine(record) {
|
|
|
12731
12933
|
let line = `${time} ${word ? `${word} ` : ""}${context ? `${context} ` : ""}${humanText(record.msg)}`;
|
|
12732
12934
|
const err = record.err;
|
|
12733
12935
|
const errorRecord = err !== null && typeof err === "object" ? err : null;
|
|
12734
|
-
const
|
|
12735
|
-
const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) ||
|
|
12936
|
+
const message2 = humanText(errorRecord && "message" in errorRecord ? errorRecord.message : err);
|
|
12937
|
+
const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) || message2}` : message2;
|
|
12736
12938
|
if (detail && !line.includes(detail)) line += ` (${detail})`;
|
|
12737
12939
|
if (level < 30) {
|
|
12738
12940
|
const hidden = /* @__PURE__ */ new Set([
|
|
@@ -12966,9 +13168,9 @@ var CompanionStateHub = class {
|
|
|
12966
13168
|
if (info.deviceLabel !== void 0) this.deviceLabel = info.deviceLabel;
|
|
12967
13169
|
this.emitStatus();
|
|
12968
13170
|
}
|
|
12969
|
-
setDeviceError(
|
|
12970
|
-
if (this.deviceError ===
|
|
12971
|
-
this.deviceError =
|
|
13171
|
+
setDeviceError(message2) {
|
|
13172
|
+
if (this.deviceError === message2) return;
|
|
13173
|
+
this.deviceError = message2;
|
|
12972
13174
|
this.emitStatus();
|
|
12973
13175
|
}
|
|
12974
13176
|
clearDeviceError() {
|
|
@@ -13420,7 +13622,7 @@ async function warnAboutHarnessReadiness(cfg, deps = {}) {
|
|
|
13420
13622
|
const probeClaude = deps.probeClaude ?? claudeOnPath;
|
|
13421
13623
|
const probeCodex = deps.probeCodex ?? codexOnPath;
|
|
13422
13624
|
const resolveBundled = deps.resolveBundled ?? ((runtime) => resolveBundledBinary(runtime));
|
|
13423
|
-
const warn = deps.warn ?? ((
|
|
13625
|
+
const warn = deps.warn ?? ((message2) => process.stderr.write(`${message2}
|
|
13424
13626
|
`));
|
|
13425
13627
|
const connected = [
|
|
13426
13628
|
...isClaudeCodeConnected(cfg) ? ["Claude Code"] : [],
|
|
@@ -13912,7 +14114,7 @@ function createLocalProcessProvider(opts = {}) {
|
|
|
13912
14114
|
|
|
13913
14115
|
// src/providers/docker.ts
|
|
13914
14116
|
import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
|
|
13915
|
-
import { constants as constants2, tmpdir as
|
|
14117
|
+
import { constants as constants2, tmpdir as tmpdir3 } from "os";
|
|
13916
14118
|
import {
|
|
13917
14119
|
createReadStream,
|
|
13918
14120
|
createWriteStream as createWriteStream2,
|
|
@@ -13925,7 +14127,7 @@ import {
|
|
|
13925
14127
|
statSync as statSync3,
|
|
13926
14128
|
writeFileSync as writeFileSync6
|
|
13927
14129
|
} from "fs";
|
|
13928
|
-
import { join as
|
|
14130
|
+
import { join as join18 } from "path";
|
|
13929
14131
|
import { Writable } from "stream";
|
|
13930
14132
|
|
|
13931
14133
|
// src/providers/docker-cli.ts
|
|
@@ -13998,7 +14200,7 @@ import {
|
|
|
13998
14200
|
renameSync as renameSync4,
|
|
13999
14201
|
rmSync as rmSync8
|
|
14000
14202
|
} from "fs";
|
|
14001
|
-
import { join as
|
|
14203
|
+
import { join as join16 } from "path";
|
|
14002
14204
|
function routes(t) {
|
|
14003
14205
|
const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
|
|
14004
14206
|
const agent = `${conv}/participants/agents/${t.agentId}`;
|
|
@@ -14044,7 +14246,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
14044
14246
|
const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
|
|
14045
14247
|
let entry = {};
|
|
14046
14248
|
try {
|
|
14047
|
-
entry = JSON.parse(readFileSync8(
|
|
14249
|
+
entry = JSON.parse(readFileSync8(join16(from, name), "utf8"));
|
|
14048
14250
|
} catch {
|
|
14049
14251
|
}
|
|
14050
14252
|
return { name, entry };
|
|
@@ -14057,7 +14259,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
14057
14259
|
let published = 0;
|
|
14058
14260
|
for (const { name } of staged) {
|
|
14059
14261
|
try {
|
|
14060
|
-
move(
|
|
14262
|
+
move(join16(from, name), join16(to, name));
|
|
14061
14263
|
} catch {
|
|
14062
14264
|
return { published, complete: false };
|
|
14063
14265
|
}
|
|
@@ -14070,7 +14272,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
14070
14272
|
// src/providers/runner-base.ts
|
|
14071
14273
|
import { createHash as createHash3 } from "crypto";
|
|
14072
14274
|
import { existsSync as existsSync12, realpathSync as realpathSync2 } from "fs";
|
|
14073
|
-
import { dirname as dirname7, join as
|
|
14275
|
+
import { dirname as dirname7, join as join17, sep } from "path";
|
|
14074
14276
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14075
14277
|
var COMPANION_MOUNT = "/opt/cabane/companion";
|
|
14076
14278
|
var BASE_DOCKERFILE = `FROM node:22-slim
|
|
@@ -14118,11 +14320,11 @@ async function ensureBaseImage(docker, image, log) {
|
|
|
14118
14320
|
function ownInstall(moduleFile = fileURLToPath3(import.meta.url)) {
|
|
14119
14321
|
let dir2 = dirname7(moduleFile);
|
|
14120
14322
|
for (let i = 0; i < 4; i++) {
|
|
14121
|
-
if (existsSync12(
|
|
14323
|
+
if (existsSync12(join17(dir2, "dist", "cli.js")) && existsSync12(join17(dir2, "package.json"))) break;
|
|
14122
14324
|
dir2 = dirname7(dir2);
|
|
14123
14325
|
}
|
|
14124
|
-
if (!existsSync12(
|
|
14125
|
-
const sdk =
|
|
14326
|
+
if (!existsSync12(join17(dir2, "dist", "cli.js"))) return null;
|
|
14327
|
+
const sdk = join17(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
|
|
14126
14328
|
if (!existsSync12(sdk)) return null;
|
|
14127
14329
|
const root = realpathSync2(dir2);
|
|
14128
14330
|
return realpathSync2(sdk).startsWith(root + sep) ? root : null;
|
|
@@ -14261,21 +14463,21 @@ function networkCreateError(stderr) {
|
|
|
14261
14463
|
}
|
|
14262
14464
|
function sessionArchivePath(deviceDir, attemptId) {
|
|
14263
14465
|
if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
|
|
14264
|
-
return
|
|
14466
|
+
return join18(deviceDir, "attempts", attemptId, "session.tar");
|
|
14265
14467
|
}
|
|
14266
14468
|
function pruneOtherAttempts(deviceDir, pair2, keep) {
|
|
14267
|
-
const root =
|
|
14469
|
+
const root = join18(deviceDir, "attempts");
|
|
14268
14470
|
if (!existsSync13(root)) return;
|
|
14269
14471
|
for (const id of readdirSync4(root)) {
|
|
14270
14472
|
if (id === keep || !UUID_RE.test(id)) continue;
|
|
14271
14473
|
let pairOf = "";
|
|
14272
14474
|
try {
|
|
14273
|
-
pairOf = readFileSync9(
|
|
14475
|
+
pairOf = readFileSync9(join18(root, id, "pair"), "utf8");
|
|
14274
14476
|
} catch {
|
|
14275
14477
|
continue;
|
|
14276
14478
|
}
|
|
14277
14479
|
if (pairOf === `${pair2.conversationId}/${pair2.agentId}`) {
|
|
14278
|
-
rmSync9(
|
|
14480
|
+
rmSync9(join18(root, id), { recursive: true, force: true });
|
|
14279
14481
|
}
|
|
14280
14482
|
}
|
|
14281
14483
|
}
|
|
@@ -14475,11 +14677,11 @@ function createDockerProvider(opts) {
|
|
|
14475
14677
|
await must(["network", "disconnect", "-f", other, name], "moving the container");
|
|
14476
14678
|
if (others.includes(sharedNetwork)) await retireSharedNetwork();
|
|
14477
14679
|
};
|
|
14478
|
-
const socketRoot = opts.socketRoot ??
|
|
14479
|
-
process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR :
|
|
14680
|
+
const socketRoot = opts.socketRoot ?? join18(
|
|
14681
|
+
process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
|
|
14480
14682
|
`cabane-runners-${opts.deviceKey}`
|
|
14481
14683
|
);
|
|
14482
|
-
const socketDir = (name) =>
|
|
14684
|
+
const socketDir = (name) => join18(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
|
|
14483
14685
|
const create = async (name, attempt) => {
|
|
14484
14686
|
mkdirSync10(socketRoot, { recursive: true, mode: 448 });
|
|
14485
14687
|
mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
|
|
@@ -14621,7 +14823,7 @@ function createDockerProvider(opts) {
|
|
|
14621
14823
|
}
|
|
14622
14824
|
};
|
|
14623
14825
|
const removeIn = (name, from, names) => names.length ? execIn(name, ["sh", "-c", 'cd "$1" && shift && rm -f -- "$@"', "sh", from, ...names]) : Promise.resolve(null);
|
|
14624
|
-
const pendingPath = (name) =>
|
|
14826
|
+
const pendingPath = (name) => join18(opts.deviceDir, "runners-pending", `${name}.json`);
|
|
14625
14827
|
const readPending = (name) => {
|
|
14626
14828
|
try {
|
|
14627
14829
|
const ids = JSON.parse(readFileSync9(pendingPath(name), "utf8"));
|
|
@@ -14635,12 +14837,12 @@ function createDockerProvider(opts) {
|
|
|
14635
14837
|
rmSync9(pendingPath(name), { force: true });
|
|
14636
14838
|
return;
|
|
14637
14839
|
}
|
|
14638
|
-
mkdirSync10(
|
|
14840
|
+
mkdirSync10(join18(opts.deviceDir, "runners-pending"), { recursive: true });
|
|
14639
14841
|
writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
|
|
14640
14842
|
};
|
|
14641
14843
|
const keepSession = async (name, attempt, attemptId) => {
|
|
14642
14844
|
const archive = sessionArchivePath(opts.deviceDir, attemptId);
|
|
14643
|
-
mkdirSync10(
|
|
14845
|
+
mkdirSync10(join18(archive, ".."), { recursive: true });
|
|
14644
14846
|
const partial = `${archive}.${process.pid}.partial`;
|
|
14645
14847
|
const file = new CappedFile(partial, sessionCap);
|
|
14646
14848
|
const r = await execIn(
|
|
@@ -14657,7 +14859,7 @@ function createDockerProvider(opts) {
|
|
|
14657
14859
|
);
|
|
14658
14860
|
if (r.code === 0 && !file.over) {
|
|
14659
14861
|
renameSync5(partial, archive);
|
|
14660
|
-
writeFileSync6(
|
|
14862
|
+
writeFileSync6(join18(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
|
|
14661
14863
|
pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
|
|
14662
14864
|
await writeMarker(name, attemptId);
|
|
14663
14865
|
log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
|
|
@@ -14671,7 +14873,7 @@ function createDockerProvider(opts) {
|
|
|
14671
14873
|
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"
|
|
14672
14874
|
);
|
|
14673
14875
|
};
|
|
14674
|
-
const stagingDir = (name) =>
|
|
14876
|
+
const stagingDir = (name) => join18(opts.deviceDir, "runners-pending", `${name}.outbox`);
|
|
14675
14877
|
const ensureRunning = async (name) => {
|
|
14676
14878
|
const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
|
|
14677
14879
|
if (seen.code !== 0) return false;
|
|
@@ -14679,7 +14881,7 @@ function createDockerProvider(opts) {
|
|
|
14679
14881
|
return (await docker.run(["start", name])).code === 0;
|
|
14680
14882
|
};
|
|
14681
14883
|
const dropArchive = (attemptId) => {
|
|
14682
|
-
rmSync9(
|
|
14884
|
+
rmSync9(join18(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
|
|
14683
14885
|
recursive: true,
|
|
14684
14886
|
force: true
|
|
14685
14887
|
});
|
|
@@ -14748,7 +14950,7 @@ function createDockerProvider(opts) {
|
|
|
14748
14950
|
if (seen.stdout.trim().split(" ")[0] !== "true")
|
|
14749
14951
|
await must(["start", name], "starting the container");
|
|
14750
14952
|
const git = await serveGitCredentials({
|
|
14751
|
-
socketPath:
|
|
14953
|
+
socketPath: join18(socketDir(name), "git.sock"),
|
|
14752
14954
|
hosts: opts.gitHosts,
|
|
14753
14955
|
fill: opts.gitFill,
|
|
14754
14956
|
log
|
|
@@ -14897,15 +15099,15 @@ function createDockerProvider(opts) {
|
|
|
14897
15099
|
const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
|
|
14898
15100
|
if (transcripts) {
|
|
14899
15101
|
const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
|
|
14900
|
-
if (kept.length) mkdirSync10(
|
|
14901
|
-
for (const f of kept) writeFileSync6(
|
|
15102
|
+
if (kept.length) mkdirSync10(join18(opts.hostStateDir, "transcripts"), { recursive: true });
|
|
15103
|
+
for (const f of kept) writeFileSync6(join18(opts.hostStateDir, "transcripts", f.name), f.data);
|
|
14902
15104
|
await removeIn(
|
|
14903
15105
|
name,
|
|
14904
15106
|
`${CONTAINER_STATE}/transcripts`,
|
|
14905
15107
|
kept.map((f) => f.name)
|
|
14906
15108
|
);
|
|
14907
15109
|
}
|
|
14908
|
-
const outboxRel =
|
|
15110
|
+
const outboxRel = join18("outbox", encodeURIComponent(attempt.agentId));
|
|
14909
15111
|
const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
|
|
14910
15112
|
let unpublished = false;
|
|
14911
15113
|
if (outbox) {
|
|
@@ -14931,7 +15133,7 @@ function createDockerProvider(opts) {
|
|
|
14931
15133
|
const staged = stagingDir(name);
|
|
14932
15134
|
mkdirSync10(staged, { recursive: true });
|
|
14933
15135
|
writeFileSync6(
|
|
14934
|
-
|
|
15136
|
+
join18(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
|
|
14935
15137
|
`${JSON.stringify(entry)}
|
|
14936
15138
|
`
|
|
14937
15139
|
);
|
|
@@ -14944,7 +15146,7 @@ function createDockerProvider(opts) {
|
|
|
14944
15146
|
if (outbox.complete) {
|
|
14945
15147
|
const published = publishStaged(
|
|
14946
15148
|
stagingDir(name),
|
|
14947
|
-
|
|
15149
|
+
join18(opts.hostStateDir, outboxRel),
|
|
14948
15150
|
opts.moveFile
|
|
14949
15151
|
);
|
|
14950
15152
|
queued = published.published;
|
|
@@ -16774,8 +16976,8 @@ function errorCode(err) {
|
|
|
16774
16976
|
function isRecoverableSocketError(err) {
|
|
16775
16977
|
const code = errorCode(err);
|
|
16776
16978
|
if (code && RECOVERABLE_CODES.has(code)) return true;
|
|
16777
|
-
const
|
|
16778
|
-
return /\bEPIPE\b|\bECONNRESET\b/.test(
|
|
16979
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
16980
|
+
return /\bEPIPE\b|\bECONNRESET\b/.test(message2);
|
|
16779
16981
|
}
|
|
16780
16982
|
function installProcessSafetyNet(log, opts = {}) {
|
|
16781
16983
|
const proc = opts.proc ?? process;
|
|
@@ -16790,17 +16992,17 @@ function installProcessSafetyNet(log, opts = {}) {
|
|
|
16790
16992
|
);
|
|
16791
16993
|
}
|
|
16792
16994
|
function handleUncaught(log, err, origin) {
|
|
16793
|
-
const
|
|
16995
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
16794
16996
|
const code = errorCode(err);
|
|
16795
16997
|
if (isRecoverableSocketError(err)) {
|
|
16796
16998
|
log.debug(
|
|
16797
|
-
{ origin, code, err:
|
|
16999
|
+
{ origin, code, err: message2 },
|
|
16798
17000
|
"companion: recovered from a socket error (kept running \u2014 a broken pipe never kills the companion)"
|
|
16799
17001
|
);
|
|
16800
17002
|
return;
|
|
16801
17003
|
}
|
|
16802
17004
|
log.error(
|
|
16803
|
-
{ origin, code, err:
|
|
17005
|
+
{ origin, code, err: message2, stack: err instanceof Error ? err.stack : void 0 },
|
|
16804
17006
|
"An unexpected error occurred; the companion is still running."
|
|
16805
17007
|
);
|
|
16806
17008
|
}
|
|
@@ -16808,9 +17010,9 @@ function handleUncaught(log, err, origin) {
|
|
|
16808
17010
|
// src/crash-marker.ts
|
|
16809
17011
|
init_config();
|
|
16810
17012
|
import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync12, rmSync as rmSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
16811
|
-
import { join as
|
|
17013
|
+
import { join as join21 } from "path";
|
|
16812
17014
|
function crashMarkerPath() {
|
|
16813
|
-
return
|
|
17015
|
+
return join21(cabaneDir(), "last-error.json");
|
|
16814
17016
|
}
|
|
16815
17017
|
function recordCrash(rec2) {
|
|
16816
17018
|
try {
|
|
@@ -17161,7 +17363,7 @@ async function startScript(opts, interactive) {
|
|
|
17161
17363
|
const result = await createCompanionRuntime({
|
|
17162
17364
|
// The script says what's connectable in its own words below; a stderr warning
|
|
17163
17365
|
// in the middle of it would be the same news, worse.
|
|
17164
|
-
onReadinessWarning: (
|
|
17366
|
+
onReadinessWarning: (message2) => log.warn(message2)
|
|
17165
17367
|
});
|
|
17166
17368
|
if (!result.ok) {
|
|
17167
17369
|
reportAlreadyRunning(result.existing?.pid);
|
|
@@ -17566,7 +17768,7 @@ init_errors();
|
|
|
17566
17768
|
init_src();
|
|
17567
17769
|
init_transcript_writer();
|
|
17568
17770
|
import { existsSync as existsSync17, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
|
|
17569
|
-
import { isAbsolute, join as
|
|
17771
|
+
import { isAbsolute, join as join22 } from "path";
|
|
17570
17772
|
async function transcript(opts = {}) {
|
|
17571
17773
|
const dir2 = transcriptsDir();
|
|
17572
17774
|
if (opts.follow) {
|
|
@@ -17583,7 +17785,7 @@ async function transcript(opts = {}) {
|
|
|
17583
17785
|
process.stdout.write(emptyMessage(dir2));
|
|
17584
17786
|
return;
|
|
17585
17787
|
}
|
|
17586
|
-
process.stdout.write(renderFile(
|
|
17788
|
+
process.stdout.write(renderFile(join22(dir2, newest)) + "\n");
|
|
17587
17789
|
return;
|
|
17588
17790
|
}
|
|
17589
17791
|
printList(dir2);
|
|
@@ -17666,7 +17868,7 @@ function isComplete(content) {
|
|
|
17666
17868
|
async function followTranscripts(dir2) {
|
|
17667
17869
|
const follower = new TranscriptFollower({
|
|
17668
17870
|
listFiles: () => listFiles(dir2),
|
|
17669
|
-
read: (f) => readFileSync13(
|
|
17871
|
+
read: (f) => readFileSync13(join22(dir2, f), "utf8"),
|
|
17670
17872
|
write: (s) => process.stdout.write(s),
|
|
17671
17873
|
// CSI: cursor up `n` lines, then erase from cursor to end of screen.
|
|
17672
17874
|
clearLines: (n) => process.stdout.write(`\x1B[${n}A\x1B[0J`),
|
|
@@ -17706,7 +17908,7 @@ function printList(dir2) {
|
|
|
17706
17908
|
|
|
17707
17909
|
`);
|
|
17708
17910
|
for (const f of files.slice(0, 20)) {
|
|
17709
|
-
const { meta, outcome } = peek(
|
|
17911
|
+
const { meta, outcome } = peek(join22(dir2, f));
|
|
17710
17912
|
const when = fmtTime(rec(meta)?.ts);
|
|
17711
17913
|
const ws = str2(rec(meta)?.workspaceSlug);
|
|
17712
17914
|
const o = rec(outcome);
|
|
@@ -17743,10 +17945,10 @@ function resolveTarget(dir2, target) {
|
|
|
17743
17945
|
if (existsSync17(target)) return target;
|
|
17744
17946
|
throw new CompanionError(`no transcript at ${target}.`);
|
|
17745
17947
|
}
|
|
17746
|
-
const exact =
|
|
17948
|
+
const exact = join22(dir2, target);
|
|
17747
17949
|
if (existsSync17(exact)) return exact;
|
|
17748
17950
|
const matches = listFiles(dir2).filter((f) => f.includes(target));
|
|
17749
|
-
if (matches.length === 1) return
|
|
17951
|
+
if (matches.length === 1) return join22(dir2, matches[0]);
|
|
17750
17952
|
if (matches.length === 0) {
|
|
17751
17953
|
throw new CompanionError(
|
|
17752
17954
|
`no transcript matching "${target}" in ${dir2}. Run \`cabane-companion transcript\` to list them.`
|