@cabane/companion 0.6.142 → 0.6.143
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 +272 -160
- package/dist/runtime.js +226 -121
- 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;
|
|
@@ -2986,15 +2986,15 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
2986
2986
|
}
|
|
2987
2987
|
} catch (err) {
|
|
2988
2988
|
if (ctx.signal.aborted) throw err;
|
|
2989
|
-
const
|
|
2990
|
-
const failure = mcpInventory.initReceived ? classifyErrorText(
|
|
2989
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
2990
|
+
const failure = mcpInventory.initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
|
|
2991
2991
|
if (!failure && (mcpInventory.initReceived || ctx.resumedSessionId === null)) throw err;
|
|
2992
2992
|
ok = false;
|
|
2993
2993
|
if (failure) {
|
|
2994
2994
|
resultReason = encodeFailureReason(failure);
|
|
2995
2995
|
} else {
|
|
2996
2996
|
resultReason = "session_start_failed";
|
|
2997
|
-
if (ctx.startEvidence) ctx.startEvidence.thrown =
|
|
2997
|
+
if (ctx.startEvidence) ctx.startEvidence.thrown = message2.slice(0, 200);
|
|
2998
2998
|
}
|
|
2999
2999
|
sawResult = true;
|
|
3000
3000
|
}
|
|
@@ -3979,9 +3979,9 @@ function readSessionError(properties) {
|
|
|
3979
3979
|
if (typeof err === "string") return err;
|
|
3980
3980
|
const rec2 = asRecord(err);
|
|
3981
3981
|
if (!rec2) return "unknown";
|
|
3982
|
-
const { name, message } = deepestError(rec2);
|
|
3983
|
-
if (
|
|
3984
|
-
if (
|
|
3982
|
+
const { name, message: message2 } = deepestError(rec2);
|
|
3983
|
+
if (message2 && name && !isGenericErrorName(name)) return `${name}: ${message2}`;
|
|
3984
|
+
if (message2) return message2;
|
|
3985
3985
|
if (name) return name;
|
|
3986
3986
|
if (typeof rec2.name === "string") return rec2.name;
|
|
3987
3987
|
return "unknown";
|
|
@@ -3993,10 +3993,10 @@ function deepestError(node) {
|
|
|
3993
3993
|
const best = {};
|
|
3994
3994
|
let current = node;
|
|
3995
3995
|
for (let depth = 0; current && depth < 8; depth++) {
|
|
3996
|
-
const
|
|
3996
|
+
const message2 = typeof current.message === "string" ? current.message : void 0;
|
|
3997
3997
|
const name = typeof current.name === "string" && !isGenericErrorName(current.name) ? current.name : void 0;
|
|
3998
|
-
if (
|
|
3999
|
-
best.message =
|
|
3998
|
+
if (message2) {
|
|
3999
|
+
best.message = message2;
|
|
4000
4000
|
if (name) best.name = name;
|
|
4001
4001
|
} else if (name && !best.name) {
|
|
4002
4002
|
best.name = name;
|
|
@@ -4659,9 +4659,9 @@ var init_conformance3 = __esm({
|
|
|
4659
4659
|
properties: { part: { type: "text", id: partId, messageID, text } }
|
|
4660
4660
|
});
|
|
4661
4661
|
idle = () => ({ type: "session.idle", properties: {} });
|
|
4662
|
-
errored = (
|
|
4662
|
+
errored = (message2) => ({
|
|
4663
4663
|
type: "session.error",
|
|
4664
|
-
properties: { error: { message } }
|
|
4664
|
+
properties: { error: { message: message2 } }
|
|
4665
4665
|
});
|
|
4666
4666
|
sessionEvent2 = (sessionId, directory = DIR, degraded = false) => ({
|
|
4667
4667
|
type: "session",
|
|
@@ -5105,8 +5105,8 @@ function readItemText(item) {
|
|
|
5105
5105
|
function readItemMessage(item) {
|
|
5106
5106
|
return str(item.message) ?? "";
|
|
5107
5107
|
}
|
|
5108
|
-
function isModelMetadataError(
|
|
5109
|
-
return
|
|
5108
|
+
function isModelMetadataError(message2) {
|
|
5109
|
+
return message2.includes("Defaulting to fallback metadata");
|
|
5110
5110
|
}
|
|
5111
5111
|
function readToolItem(item, eventType) {
|
|
5112
5112
|
const type = str(item.type);
|
|
@@ -5351,11 +5351,11 @@ async function* decodeCodexStream(events, ctx) {
|
|
|
5351
5351
|
if (!item) continue;
|
|
5352
5352
|
const itemType = readItemType(item);
|
|
5353
5353
|
if (itemType === "error") {
|
|
5354
|
-
const
|
|
5355
|
-
if (isModelMetadataError(
|
|
5354
|
+
const message2 = readItemMessage(item);
|
|
5355
|
+
if (isModelMetadataError(message2)) {
|
|
5356
5356
|
yield* flushInterim();
|
|
5357
5357
|
ok = false;
|
|
5358
|
-
reason = `model_unavailable:${
|
|
5358
|
+
reason = `model_unavailable:${message2.slice(0, 200)}`;
|
|
5359
5359
|
settled = true;
|
|
5360
5360
|
break;
|
|
5361
5361
|
}
|
|
@@ -5522,7 +5522,7 @@ var init_model3 = __esm({
|
|
|
5522
5522
|
});
|
|
5523
5523
|
|
|
5524
5524
|
// packages/agent-runtime/src/codex/run-spec.ts
|
|
5525
|
-
function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null) {
|
|
5525
|
+
function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null, imagePaths = []) {
|
|
5526
5526
|
const { policy, config } = req;
|
|
5527
5527
|
const directory = req.local.cwd ?? "";
|
|
5528
5528
|
const dialect = readCodexDialect(config.runtimeOptions);
|
|
@@ -5539,7 +5539,7 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromp
|
|
|
5539
5539
|
skipGitRepoCheck: true,
|
|
5540
5540
|
...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
|
|
5541
5541
|
baseInstructionsFile,
|
|
5542
|
-
input: buildInput(req, promptRidesInput),
|
|
5542
|
+
input: withImages(buildInput(req, promptRidesInput), imagePaths),
|
|
5543
5543
|
promptRidesInput,
|
|
5544
5544
|
promptFingerprint,
|
|
5545
5545
|
config: buildConfig(req, baseInstructionsFile),
|
|
@@ -5555,6 +5555,13 @@ function buildInput(req, promptRidesInput) {
|
|
|
5555
5555
|
|
|
5556
5556
|
${body}` : body;
|
|
5557
5557
|
}
|
|
5558
|
+
function withImages(text, imagePaths) {
|
|
5559
|
+
if (imagePaths.length === 0) return text;
|
|
5560
|
+
return [
|
|
5561
|
+
{ type: "text", text },
|
|
5562
|
+
...imagePaths.map((path) => ({ type: "local_image", path }))
|
|
5563
|
+
];
|
|
5564
|
+
}
|
|
5558
5565
|
function buildConfig(req, baseInstructionsFile) {
|
|
5559
5566
|
const mcp_servers = {};
|
|
5560
5567
|
for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
|
|
@@ -5826,12 +5833,35 @@ function createCodexAdapter(deps = {}) {
|
|
|
5826
5833
|
}
|
|
5827
5834
|
}
|
|
5828
5835
|
}
|
|
5836
|
+
let images = null;
|
|
5829
5837
|
try {
|
|
5838
|
+
const imageUrls = req.content.flatMap((b) => b.type === "image" ? [b.source.url] : []);
|
|
5839
|
+
if (imageUrls.length > 0) {
|
|
5840
|
+
if (!deps.prepareImages) {
|
|
5841
|
+
deps.onWarn?.(
|
|
5842
|
+
"codex adapter: no image preparer wired on this host \u2014 the turn runs without its images (CT1757)",
|
|
5843
|
+
{ images: imageUrls.length }
|
|
5844
|
+
);
|
|
5845
|
+
} else {
|
|
5846
|
+
try {
|
|
5847
|
+
images = await deps.prepareImages(imageUrls, signal);
|
|
5848
|
+
} catch (err) {
|
|
5849
|
+
if (!signal.aborted) {
|
|
5850
|
+
deps.onWarn?.(
|
|
5851
|
+
"codex adapter: preparing the images failed \u2014 the turn runs without them (CT1757)",
|
|
5852
|
+
{ err: err instanceof Error ? err.message : String(err) }
|
|
5853
|
+
);
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
if (signal.aborted) return;
|
|
5858
|
+
}
|
|
5830
5859
|
const spec = buildRunSpec2(
|
|
5831
5860
|
req,
|
|
5832
5861
|
resumeThreadId,
|
|
5833
5862
|
instructions?.path ?? null,
|
|
5834
|
-
threadPromptFingerprint
|
|
5863
|
+
threadPromptFingerprint,
|
|
5864
|
+
images?.paths ?? []
|
|
5835
5865
|
);
|
|
5836
5866
|
if (spec.model === null) {
|
|
5837
5867
|
deps.onWarn?.("Codex is using its default model because no model was resolved", {
|
|
@@ -5860,6 +5890,15 @@ function createCodexAdapter(deps = {}) {
|
|
|
5860
5890
|
resolvedReasoningEffort: spec.modelReasoningEffort ?? null
|
|
5861
5891
|
});
|
|
5862
5892
|
} finally {
|
|
5893
|
+
if (images) {
|
|
5894
|
+
try {
|
|
5895
|
+
await images.cleanup();
|
|
5896
|
+
} catch (err) {
|
|
5897
|
+
deps.onWarn?.("codex adapter: image cleanup failed (CT1757)", {
|
|
5898
|
+
err: err instanceof Error ? err.message : String(err)
|
|
5899
|
+
});
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5863
5902
|
if (instructions) {
|
|
5864
5903
|
try {
|
|
5865
5904
|
await instructions.cleanup();
|
|
@@ -5952,14 +5991,14 @@ var init_conformance4 = __esm({
|
|
|
5952
5991
|
type: "turn.completed",
|
|
5953
5992
|
...usage ? { usage } : {}
|
|
5954
5993
|
});
|
|
5955
|
-
turnFailed = (
|
|
5994
|
+
turnFailed = (message2) => ({
|
|
5956
5995
|
type: "turn.failed",
|
|
5957
|
-
error: { message }
|
|
5996
|
+
error: { message: message2 }
|
|
5958
5997
|
});
|
|
5959
|
-
fatalError = (
|
|
5960
|
-
errorItem = (id,
|
|
5998
|
+
fatalError = (message2) => ({ type: "error", message: message2 });
|
|
5999
|
+
errorItem = (id, message2) => ({
|
|
5961
6000
|
type: "item.completed",
|
|
5962
|
-
item: { id, type: "error", message }
|
|
6001
|
+
item: { id, type: "error", message: message2 }
|
|
5963
6002
|
});
|
|
5964
6003
|
OP_SUCCESS = { ok: true, result: { source: "native" } };
|
|
5965
6004
|
OP_FAILURE = {
|
|
@@ -6897,8 +6936,8 @@ function unenforceable(policy) {
|
|
|
6897
6936
|
deniedDomains: as === "deniedDomains" ? [host] : []
|
|
6898
6937
|
});
|
|
6899
6938
|
if (!check.success) {
|
|
6900
|
-
const
|
|
6901
|
-
return `runners.policy.${field}.${i}: ${
|
|
6939
|
+
const message2 = check.error.issues[0]?.message ?? "not a host pattern the sandbox accepts";
|
|
6940
|
+
return `runners.policy.${field}.${i}: ${message2}`;
|
|
6902
6941
|
}
|
|
6903
6942
|
}
|
|
6904
6943
|
}
|
|
@@ -6912,8 +6951,8 @@ function unenforceable(policy) {
|
|
|
6912
6951
|
try {
|
|
6913
6952
|
new RegExp(globToRegex(path));
|
|
6914
6953
|
} catch (err) {
|
|
6915
|
-
const
|
|
6916
|
-
return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${
|
|
6954
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
6955
|
+
return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${message2})`;
|
|
6917
6956
|
}
|
|
6918
6957
|
}
|
|
6919
6958
|
}
|
|
@@ -7118,8 +7157,8 @@ async function reapTurnScopes(turnId) {
|
|
|
7118
7157
|
});
|
|
7119
7158
|
return { failures: [] };
|
|
7120
7159
|
} catch (err) {
|
|
7121
|
-
const
|
|
7122
|
-
return classifyStopError(
|
|
7160
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
7161
|
+
return classifyStopError(message2) === "absent" ? { failures: [] } : { failures: [message2.slice(0, 200)] };
|
|
7123
7162
|
}
|
|
7124
7163
|
}
|
|
7125
7164
|
function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
|
|
@@ -7135,13 +7174,13 @@ function scopeCapabilityUsable(env, timeoutMs = CAPABILITY_PROBE_TIMEOUT_MS) {
|
|
|
7135
7174
|
return false;
|
|
7136
7175
|
}
|
|
7137
7176
|
}
|
|
7138
|
-
function classifyStopError(
|
|
7177
|
+
function classifyStopError(message2) {
|
|
7139
7178
|
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
|
-
|
|
7179
|
+
message2
|
|
7141
7180
|
)) {
|
|
7142
7181
|
return "failure";
|
|
7143
7182
|
}
|
|
7144
|
-
if (/not loaded|could not be found|no such unit|unit .* not found/i.test(
|
|
7183
|
+
if (/not loaded|could not be found|no such unit|unit .* not found/i.test(message2))
|
|
7145
7184
|
return "absent";
|
|
7146
7185
|
return "failure";
|
|
7147
7186
|
}
|
|
@@ -7250,9 +7289,9 @@ function createTurnContainment(turnId, deps = {}) {
|
|
|
7250
7289
|
timeout: (CONTAINMENT_STOP_GRACE_SEC + 10) * 1e3
|
|
7251
7290
|
});
|
|
7252
7291
|
} catch (err) {
|
|
7253
|
-
const
|
|
7254
|
-
if (classifyStopError(
|
|
7255
|
-
result.failures.push({ target: unit, error:
|
|
7292
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
7293
|
+
if (classifyStopError(message2) === "failure") {
|
|
7294
|
+
result.failures.push({ target: unit, error: message2.slice(0, 200) });
|
|
7256
7295
|
continue;
|
|
7257
7296
|
}
|
|
7258
7297
|
result.reaped.push(unit);
|
|
@@ -7616,8 +7655,8 @@ function runnerStdin(input) {
|
|
|
7616
7655
|
})}
|
|
7617
7656
|
`;
|
|
7618
7657
|
}
|
|
7619
|
-
function encodeLauncherMessage(
|
|
7620
|
-
return `${JSON.stringify(
|
|
7658
|
+
function encodeLauncherMessage(message2) {
|
|
7659
|
+
return `${JSON.stringify(message2)}
|
|
7621
7660
|
`;
|
|
7622
7661
|
}
|
|
7623
7662
|
function decodeLauncherMessage(line) {
|
|
@@ -8479,6 +8518,74 @@ var init_codex_instructions = __esm({
|
|
|
8479
8518
|
}
|
|
8480
8519
|
});
|
|
8481
8520
|
|
|
8521
|
+
// src/codex-images.ts
|
|
8522
|
+
import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
8523
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
8524
|
+
import { join as join12 } from "path";
|
|
8525
|
+
function createCodexImagePreparer(opts) {
|
|
8526
|
+
const doFetch = opts.fetch ?? fetch;
|
|
8527
|
+
const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
|
|
8528
|
+
return async (urls, signal) => {
|
|
8529
|
+
signal.throwIfAborted();
|
|
8530
|
+
const dir2 = await mkdtemp2(join12(tmpdir2(), `cabane-codex-images-${opts.turnId}-`));
|
|
8531
|
+
const cleanup = async () => {
|
|
8532
|
+
try {
|
|
8533
|
+
await rm2(dir2, { recursive: true, force: true });
|
|
8534
|
+
} catch (err) {
|
|
8535
|
+
opts.warn(
|
|
8536
|
+
{ dir: dir2, err: message(err) },
|
|
8537
|
+
"codex images: could not remove the scratch directory"
|
|
8538
|
+
);
|
|
8539
|
+
}
|
|
8540
|
+
};
|
|
8541
|
+
const prepareOne = async (url, index) => {
|
|
8542
|
+
try {
|
|
8543
|
+
const res = await doFetch(url, {
|
|
8544
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)])
|
|
8545
|
+
});
|
|
8546
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
8547
|
+
const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
8548
|
+
const ext = EXTENSIONS[type];
|
|
8549
|
+
if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
|
|
8550
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
8551
|
+
const path = join12(dir2, `image-${index + 1}.${ext}`);
|
|
8552
|
+
await writeFile2(path, bytes, { mode: 384 });
|
|
8553
|
+
return path;
|
|
8554
|
+
} catch (err) {
|
|
8555
|
+
if (!signal.aborted) {
|
|
8556
|
+
opts.warn(
|
|
8557
|
+
{ image: index + 1, of: urls.length, err: message(err) },
|
|
8558
|
+
"codex images: an image could not be prepared; the turn runs without it"
|
|
8559
|
+
);
|
|
8560
|
+
}
|
|
8561
|
+
return null;
|
|
8562
|
+
}
|
|
8563
|
+
};
|
|
8564
|
+
const paths = await Promise.all(urls.map(prepareOne));
|
|
8565
|
+
if (signal.aborted) {
|
|
8566
|
+
await cleanup();
|
|
8567
|
+
signal.throwIfAborted();
|
|
8568
|
+
}
|
|
8569
|
+
return { paths: paths.filter((p) => p !== null), cleanup };
|
|
8570
|
+
};
|
|
8571
|
+
}
|
|
8572
|
+
function message(err) {
|
|
8573
|
+
return err instanceof Error ? err.message : String(err);
|
|
8574
|
+
}
|
|
8575
|
+
var FETCH_TIMEOUT_MS, EXTENSIONS;
|
|
8576
|
+
var init_codex_images = __esm({
|
|
8577
|
+
"src/codex-images.ts"() {
|
|
8578
|
+
"use strict";
|
|
8579
|
+
FETCH_TIMEOUT_MS = 3e4;
|
|
8580
|
+
EXTENSIONS = {
|
|
8581
|
+
"image/png": "png",
|
|
8582
|
+
"image/jpeg": "jpg",
|
|
8583
|
+
"image/gif": "gif",
|
|
8584
|
+
"image/webp": "webp"
|
|
8585
|
+
};
|
|
8586
|
+
}
|
|
8587
|
+
});
|
|
8588
|
+
|
|
8482
8589
|
// src/turn-seq-floor.ts
|
|
8483
8590
|
function resolveSeqFloor(sources, ctx) {
|
|
8484
8591
|
const { serverFloor, outboxFloor } = sources;
|
|
@@ -8522,15 +8629,15 @@ var init_turn_seq_floor = __esm({
|
|
|
8522
8629
|
import { spawn as spawn5 } from "child_process";
|
|
8523
8630
|
import { hostname as hostname2 } from "os";
|
|
8524
8631
|
function prepareFailureCause(error) {
|
|
8525
|
-
const
|
|
8526
|
-
if (/timed out/i.test(
|
|
8527
|
-
if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(
|
|
8632
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
8633
|
+
if (/timed out/i.test(message2)) return "prepare hook timed out";
|
|
8634
|
+
if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message2))
|
|
8528
8635
|
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(
|
|
8636
|
+
if (/not valid JSON|JSON must carry|produced no output/.test(message2))
|
|
8530
8637
|
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 =
|
|
8638
|
+
if (/ENOENT/.test(message2)) return "prepare hook executable missing (ENOENT)";
|
|
8639
|
+
if (/EACCES/.test(message2)) return "prepare hook executable is not accessible (EACCES)";
|
|
8640
|
+
const exit = message2.match(/exited with code (\d+)/)?.[1];
|
|
8534
8641
|
return exit ? `prepare hook exited with code ${exit}` : "prepare hook failed; inspect the device-local hook log";
|
|
8535
8642
|
}
|
|
8536
8643
|
async function runPrepareWithReporting(runner, hook, input, context, reportError) {
|
|
@@ -8587,15 +8694,15 @@ var init_prepare_failure = __esm({
|
|
|
8587
8694
|
|
|
8588
8695
|
// src/prepared.ts
|
|
8589
8696
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
|
|
8590
|
-
import { join as
|
|
8697
|
+
import { join as join13 } from "path";
|
|
8591
8698
|
function dirFor(workspaceId) {
|
|
8592
|
-
return
|
|
8699
|
+
return join13(cabaneDir(), "prepared", encodeURIComponent(workspaceId));
|
|
8593
8700
|
}
|
|
8594
8701
|
function conversationDir(workspaceId, conversationId) {
|
|
8595
|
-
return
|
|
8702
|
+
return join13(dirFor(workspaceId), encodeURIComponent(conversationId));
|
|
8596
8703
|
}
|
|
8597
8704
|
function pathFor2(workspaceId, conversationId, agentId) {
|
|
8598
|
-
return
|
|
8705
|
+
return join13(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
|
|
8599
8706
|
}
|
|
8600
8707
|
function readPrepared(workspaceId, conversationId, agentId) {
|
|
8601
8708
|
const path = pathFor2(workspaceId, conversationId, agentId);
|
|
@@ -8633,9 +8740,9 @@ var init_prepared = __esm({
|
|
|
8633
8740
|
|
|
8634
8741
|
// src/transcript-writer.ts
|
|
8635
8742
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
8636
|
-
import { basename, dirname as dirname6, join as
|
|
8743
|
+
import { basename, dirname as dirname6, join as join14 } from "path";
|
|
8637
8744
|
function transcriptsDir() {
|
|
8638
|
-
return
|
|
8745
|
+
return join14(runnerStateDir(), "transcripts");
|
|
8639
8746
|
}
|
|
8640
8747
|
function fileName(meta) {
|
|
8641
8748
|
const d = new Date(meta.ts);
|
|
@@ -8661,7 +8768,7 @@ function pruneOld(dir2, retain) {
|
|
|
8661
8768
|
const drop = files.sort().slice(0, files.length - retain);
|
|
8662
8769
|
for (const f of drop) {
|
|
8663
8770
|
try {
|
|
8664
|
-
rmSync6(
|
|
8771
|
+
rmSync6(join14(dir2, f), { force: true });
|
|
8665
8772
|
} catch {
|
|
8666
8773
|
}
|
|
8667
8774
|
}
|
|
@@ -8679,7 +8786,7 @@ var init_transcript_writer = __esm({
|
|
|
8679
8786
|
onWarn;
|
|
8680
8787
|
constructor(dir2, meta, onWarn) {
|
|
8681
8788
|
this.onWarn = onWarn;
|
|
8682
|
-
this.path =
|
|
8789
|
+
this.path = join14(dir2, fileName(meta));
|
|
8683
8790
|
try {
|
|
8684
8791
|
mkdirSync8(dir2, { recursive: true });
|
|
8685
8792
|
try {
|
|
@@ -8708,10 +8815,10 @@ var init_transcript_writer = __esm({
|
|
|
8708
8815
|
preserveAnomaly() {
|
|
8709
8816
|
if (this.broken) return;
|
|
8710
8817
|
try {
|
|
8711
|
-
const dir2 =
|
|
8818
|
+
const dir2 = join14(dirname6(this.path), "anomalies");
|
|
8712
8819
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
8713
8820
|
chmodSync4(dir2, 448);
|
|
8714
|
-
const target =
|
|
8821
|
+
const target = join14(dir2, basename(this.path));
|
|
8715
8822
|
copyFileSync(this.path, target);
|
|
8716
8823
|
chmodSync4(target, 384);
|
|
8717
8824
|
pruneOld(dir2, ANOMALY_RETAIN);
|
|
@@ -9006,6 +9113,7 @@ var init_turn_execution = __esm({
|
|
|
9006
9113
|
init_build_options();
|
|
9007
9114
|
init_bundled_binaries();
|
|
9008
9115
|
init_codex_instructions();
|
|
9116
|
+
init_codex_images();
|
|
9009
9117
|
init_config();
|
|
9010
9118
|
init_api_error_shape();
|
|
9011
9119
|
init_errors();
|
|
@@ -9354,16 +9462,16 @@ var init_turn_execution = __esm({
|
|
|
9354
9462
|
}
|
|
9355
9463
|
gateTrigger() {
|
|
9356
9464
|
const { payload, turnLog } = this;
|
|
9357
|
-
const
|
|
9358
|
-
const isDispatchableTrigger =
|
|
9465
|
+
const message2 = this.turnContext.message;
|
|
9466
|
+
const isDispatchableTrigger = message2.role === "user" || message2.role === "agent" || message2.role === "system";
|
|
9359
9467
|
if (!isDispatchableTrigger) {
|
|
9360
|
-
turnLog.debug({ role:
|
|
9468
|
+
turnLog.debug({ role: message2.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
|
|
9361
9469
|
throw this.concluded("unexpected_role", UNEXPECTED_ROLE_REASON);
|
|
9362
9470
|
}
|
|
9363
9471
|
this.supervisor.notifyStart({
|
|
9364
9472
|
id: this.dispatchId,
|
|
9365
9473
|
conversationId: payload.conversationId,
|
|
9366
|
-
message:
|
|
9474
|
+
message: message2.body
|
|
9367
9475
|
});
|
|
9368
9476
|
}
|
|
9369
9477
|
async resolveSecrets() {
|
|
@@ -9714,6 +9822,10 @@ var init_turn_execution = __esm({
|
|
|
9714
9822
|
enabled: true,
|
|
9715
9823
|
onWarn,
|
|
9716
9824
|
writeInstructionsFile: writeCodexInstructionsFile,
|
|
9825
|
+
prepareImages: createCodexImagePreparer({
|
|
9826
|
+
turnId: this.turnId,
|
|
9827
|
+
warn: (meta, msg) => turnLog.warn(meta, msg)
|
|
9828
|
+
}),
|
|
9717
9829
|
...codexPath ? { transport: createSdkCodexTransport({ codexPathOverride: codexPath }) } : {}
|
|
9718
9830
|
})
|
|
9719
9831
|
);
|
|
@@ -10174,7 +10286,7 @@ var init_turn_execution = __esm({
|
|
|
10174
10286
|
// src/dispatcher.ts
|
|
10175
10287
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
10176
10288
|
import { existsSync as existsSync10, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
10177
|
-
import { join as
|
|
10289
|
+
import { join as join15 } from "path";
|
|
10178
10290
|
function checkoutState(cwd) {
|
|
10179
10291
|
if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
|
|
10180
10292
|
if (!existsSync10(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
|
|
@@ -10187,7 +10299,7 @@ function checkoutState(cwd) {
|
|
|
10187
10299
|
if (entries.length === 0) {
|
|
10188
10300
|
return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
|
|
10189
10301
|
}
|
|
10190
|
-
const gitPath =
|
|
10302
|
+
const gitPath = join15(cwd, ".git");
|
|
10191
10303
|
if (!existsSync10(gitPath)) return { ok: true, reason: "usable" };
|
|
10192
10304
|
let stat;
|
|
10193
10305
|
try {
|
|
@@ -10195,7 +10307,7 @@ function checkoutState(cwd) {
|
|
|
10195
10307
|
} catch (error) {
|
|
10196
10308
|
return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
|
|
10197
10309
|
}
|
|
10198
|
-
if (stat.isDirectory() && !existsSync10(
|
|
10310
|
+
if (stat.isDirectory() && !existsSync10(join15(gitPath, "HEAD")))
|
|
10199
10311
|
return { ok: false, reason: `${gitPath} has no HEAD \u2014 an empty shell, not a checkout` };
|
|
10200
10312
|
return { ok: true, reason: "usable" };
|
|
10201
10313
|
}
|
|
@@ -10561,8 +10673,8 @@ import {
|
|
|
10561
10673
|
} from "fs";
|
|
10562
10674
|
import { createRequire as createRequire2 } from "module";
|
|
10563
10675
|
import { isIP } from "net";
|
|
10564
|
-
import { homedir as homedir3, networkInterfaces, tmpdir as
|
|
10565
|
-
import { dirname as dirname8, join as
|
|
10676
|
+
import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir4, userInfo as userInfo2 } from "os";
|
|
10677
|
+
import { dirname as dirname8, join as join19, sep as sep2 } from "path";
|
|
10566
10678
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10567
10679
|
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
10568
10680
|
function accountHome() {
|
|
@@ -10596,7 +10708,7 @@ async function resolvedAddresses(baseUrl) {
|
|
|
10596
10708
|
function companionPackageRoot(modulePath) {
|
|
10597
10709
|
for (let dir2 = dirname8(modulePath); ; dir2 = dirname8(dir2)) {
|
|
10598
10710
|
try {
|
|
10599
|
-
const manifest = JSON.parse(readFileSync10(
|
|
10711
|
+
const manifest = JSON.parse(readFileSync10(join19(dir2, "package.json"), "utf8"));
|
|
10600
10712
|
if (manifest.name === "@cabane/companion") return dir2;
|
|
10601
10713
|
} catch {
|
|
10602
10714
|
}
|
|
@@ -10673,15 +10785,15 @@ function companionStoreDenies(input) {
|
|
|
10673
10785
|
const always = [
|
|
10674
10786
|
cabaneDir2,
|
|
10675
10787
|
...homes.flatMap((home) => [
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10788
|
+
join19(home, ".cabane"),
|
|
10789
|
+
join19(home, "*", ".cabane"),
|
|
10790
|
+
join19(home, "*", "*", ".cabane")
|
|
10679
10791
|
]),
|
|
10680
10792
|
// A glob anchored at the root has no directory to start from.
|
|
10681
|
-
...dirname8(parent) === parent ? [] : [
|
|
10793
|
+
...dirname8(parent) === parent ? [] : [join19(parent, "*", ".cabane")]
|
|
10682
10794
|
];
|
|
10683
10795
|
const companionHome = dirname8(cabaneDir2);
|
|
10684
|
-
const otherLogins = foundStores.map((store) => dirname8(store)).filter((home) => home !== companionHome).flatMap((home) => [
|
|
10796
|
+
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
10797
|
return [.../* @__PURE__ */ new Set([...always, ...foundStores, ...otherLogins])];
|
|
10686
10798
|
}
|
|
10687
10799
|
function companionStoreWatch(log) {
|
|
@@ -10698,7 +10810,7 @@ function isAtOrUnder(path, dir2) {
|
|
|
10698
10810
|
}
|
|
10699
10811
|
function expandHome(path, home) {
|
|
10700
10812
|
if (path === "~") return home;
|
|
10701
|
-
return path.startsWith("~/") ?
|
|
10813
|
+
return path.startsWith("~/") ? join19(home, path.slice(2)) : path;
|
|
10702
10814
|
}
|
|
10703
10815
|
function stripPort(pattern) {
|
|
10704
10816
|
return pattern.startsWith("[") ? pattern.replace(/^\[([^\]]+)\](:\d+)?$/, "$1") : pattern.replace(/:\d+$/, "");
|
|
@@ -10716,8 +10828,8 @@ function patternsOverlap(a, b) {
|
|
|
10716
10828
|
}
|
|
10717
10829
|
function harnessSandboxConfig(input) {
|
|
10718
10830
|
const { policy } = input;
|
|
10719
|
-
const claudeDir = input.claudeConfigDir ??
|
|
10720
|
-
const codexDir = input.codexHome ??
|
|
10831
|
+
const claudeDir = input.claudeConfigDir ?? join19(input.home, ".claude");
|
|
10832
|
+
const codexDir = input.codexHome ?? join19(input.home, ".codex");
|
|
10721
10833
|
const homes = [.../* @__PURE__ */ new Set([input.home, input.accountHome ?? input.home])];
|
|
10722
10834
|
const expand = (paths) => (paths ?? []).map((p) => expandHome(p, input.home));
|
|
10723
10835
|
const cabane = cabaneHostEntries(input.baseUrl, input.resolved, input.ownAddresses);
|
|
@@ -10734,7 +10846,7 @@ function harnessSandboxConfig(input) {
|
|
|
10734
10846
|
foundStores: input.foundStores,
|
|
10735
10847
|
ownState: [claudeDir, codexDir]
|
|
10736
10848
|
}),
|
|
10737
|
-
...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) =>
|
|
10849
|
+
...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join19(home, p))),
|
|
10738
10850
|
...expand(policy.filesystem?.denyRead)
|
|
10739
10851
|
];
|
|
10740
10852
|
const strict = policy.filesystem?.mode === "strict";
|
|
@@ -10742,7 +10854,7 @@ function harnessSandboxConfig(input) {
|
|
|
10742
10854
|
const allowRead = strict ? [
|
|
10743
10855
|
...input.cwd ? [input.cwd] : [],
|
|
10744
10856
|
claudeDir,
|
|
10745
|
-
|
|
10857
|
+
join19(input.home, ".claude.json"),
|
|
10746
10858
|
codexDir,
|
|
10747
10859
|
...input.runtimePaths ?? [],
|
|
10748
10860
|
...expand(policy.filesystem?.allowRead)
|
|
@@ -10782,7 +10894,7 @@ function harnessSandboxConfig(input) {
|
|
|
10782
10894
|
...input.cwd ? [input.cwd] : [],
|
|
10783
10895
|
input.tmp,
|
|
10784
10896
|
claudeDir,
|
|
10785
|
-
|
|
10897
|
+
join19(input.home, ".claude.json"),
|
|
10786
10898
|
codexDir,
|
|
10787
10899
|
...expand(policy.filesystem?.allowWrite)
|
|
10788
10900
|
],
|
|
@@ -10835,7 +10947,7 @@ async function prepareHarnessSandbox(input) {
|
|
|
10835
10947
|
ownAddresses: ownAddresses(),
|
|
10836
10948
|
home: homedir3(),
|
|
10837
10949
|
accountHome: accountHome(),
|
|
10838
|
-
tmp:
|
|
10950
|
+
tmp: tmpdir4(),
|
|
10839
10951
|
cabaneDir: cabaneDir(),
|
|
10840
10952
|
claudeConfigDir: process.env.CLAUDE_CONFIG_DIR,
|
|
10841
10953
|
codexHome: process.env.CODEX_HOME,
|
|
@@ -10866,27 +10978,27 @@ async function prepareHarnessSandbox(input) {
|
|
|
10866
10978
|
"export NO_PROXY= no_proxy=; " + gitCredentialExports(input.credentials) + 'eval "exec $CABANE_SANDBOXED_ARGV"'
|
|
10867
10979
|
);
|
|
10868
10980
|
} catch (err) {
|
|
10869
|
-
const
|
|
10981
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
10870
10982
|
await SandboxManager.reset().catch(() => {
|
|
10871
10983
|
});
|
|
10872
10984
|
if (held.length || Object.keys(input.policy).length) {
|
|
10873
10985
|
input.log.warn(
|
|
10874
|
-
{ err:
|
|
10986
|
+
{ err: message2, masked: held },
|
|
10875
10987
|
"runners: the OS sandbox could not start under this device's runners.policy; refusing the turn rather than running it unsandboxed"
|
|
10876
10988
|
);
|
|
10877
|
-
throw new HarnessSandboxRefused(`the OS sandbox could not start: ${
|
|
10989
|
+
throw new HarnessSandboxRefused(`the OS sandbox could not start: ${message2}`);
|
|
10878
10990
|
}
|
|
10879
10991
|
input.log.warn(
|
|
10880
|
-
{ err:
|
|
10992
|
+
{ err: message2 },
|
|
10881
10993
|
"runners: the OS sandbox could not start; running the turn unsandboxed"
|
|
10882
10994
|
);
|
|
10883
10995
|
return null;
|
|
10884
10996
|
} finally {
|
|
10885
10997
|
for (const name of held) delete process.env[name];
|
|
10886
10998
|
}
|
|
10887
|
-
mkdirSync11(
|
|
10888
|
-
const dir2 = mkdtempSync(
|
|
10889
|
-
const script =
|
|
10999
|
+
mkdirSync11(join19(cabaneDir(), "sandbox"), { recursive: true });
|
|
11000
|
+
const dir2 = mkdtempSync(join19(cabaneDir(), "sandbox", "turn-"));
|
|
11001
|
+
const script = join19(dir2, "exec-sandboxed");
|
|
10890
11002
|
writeFileSync7(
|
|
10891
11003
|
script,
|
|
10892
11004
|
[
|
|
@@ -10902,7 +11014,7 @@ async function prepareHarnessSandbox(input) {
|
|
|
10902
11014
|
return {
|
|
10903
11015
|
wrap: (command, args) => ({ command: script, args: [command, ...args] }),
|
|
10904
11016
|
executableFor: (real) => {
|
|
10905
|
-
const shim =
|
|
11017
|
+
const shim = join19(dir2, `exec-${++shims}`);
|
|
10906
11018
|
writeFileSync7(shim, `#!/bin/bash
|
|
10907
11019
|
exec ${shellQuote(script)} ${shellQuote(real)} "$@"
|
|
10908
11020
|
`);
|
|
@@ -10995,7 +11107,7 @@ import {
|
|
|
10995
11107
|
rmSync as rmSync11,
|
|
10996
11108
|
writeFileSync as writeFileSync8
|
|
10997
11109
|
} from "fs";
|
|
10998
|
-
import { join as
|
|
11110
|
+
import { join as join20 } from "path";
|
|
10999
11111
|
var MAX_ENTRIES, MAX_AGE_MS, Outbox;
|
|
11000
11112
|
var init_outbox = __esm({
|
|
11001
11113
|
"src/outbox.ts"() {
|
|
@@ -11013,10 +11125,10 @@ var init_outbox = __esm({
|
|
|
11013
11125
|
// Resolved lazily (per call, like cursor.ts) so tests that swap HOME between
|
|
11014
11126
|
// cases route writes at the right tmpdir.
|
|
11015
11127
|
dir() {
|
|
11016
|
-
return
|
|
11128
|
+
return join20(runnerStateDir(), "outbox", encodeURIComponent(this.workspaceId));
|
|
11017
11129
|
}
|
|
11018
11130
|
fileFor(turnId, seq) {
|
|
11019
|
-
return
|
|
11131
|
+
return join20(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
|
|
11020
11132
|
}
|
|
11021
11133
|
// Persist a commit for later draining. Atomic (temp file + rename) so a
|
|
11022
11134
|
// concurrent `list()` never reads a half-written entry, then enforces the
|
|
@@ -11058,7 +11170,7 @@ var init_outbox = __esm({
|
|
|
11058
11170
|
const entries = [];
|
|
11059
11171
|
for (const name of names) {
|
|
11060
11172
|
if (!name.endsWith(".json")) continue;
|
|
11061
|
-
const full =
|
|
11173
|
+
const full = join20(dir2, name);
|
|
11062
11174
|
try {
|
|
11063
11175
|
const parsed = JSON.parse(readFileSync11(full, "utf8"));
|
|
11064
11176
|
if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
|
|
@@ -11198,7 +11310,7 @@ var init_image_manifest = __esm({
|
|
|
11198
11310
|
|
|
11199
11311
|
// src/runner/exec-shim.ts
|
|
11200
11312
|
import { chmodSync as chmodSync7, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync2, rmSync as rmSync13, writeFileSync as writeFileSync10 } from "fs";
|
|
11201
|
-
import { join as
|
|
11313
|
+
import { join as join23 } from "path";
|
|
11202
11314
|
function shellQuote2(value) {
|
|
11203
11315
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11204
11316
|
}
|
|
@@ -11208,10 +11320,10 @@ function createShimDir() {
|
|
|
11208
11320
|
return {
|
|
11209
11321
|
write(argv) {
|
|
11210
11322
|
if (!dir2) {
|
|
11211
|
-
mkdirSync15(
|
|
11212
|
-
dir2 = mkdtempSync2(
|
|
11323
|
+
mkdirSync15(join23(cabaneDir(), "runner"), { recursive: true });
|
|
11324
|
+
dir2 = mkdtempSync2(join23(cabaneDir(), "runner", "turn-"));
|
|
11213
11325
|
}
|
|
11214
|
-
const path =
|
|
11326
|
+
const path = join23(dir2, `exec-${++count}`);
|
|
11215
11327
|
writeFileSync10(path, `#!/bin/sh
|
|
11216
11328
|
exec ${argv.map(shellQuote2).join(" ")} "$@"
|
|
11217
11329
|
`);
|
|
@@ -11247,9 +11359,9 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
|
|
|
11247
11359
|
resolveFirst(line);
|
|
11248
11360
|
return;
|
|
11249
11361
|
}
|
|
11250
|
-
const
|
|
11251
|
-
if (
|
|
11252
|
-
else if (
|
|
11362
|
+
const message2 = decodeLauncherMessage(line);
|
|
11363
|
+
if (message2?.cabaneLauncher === "granted") waiting.get(message2.id)?.();
|
|
11364
|
+
else if (message2) replies.get(message2.id)?.(message2);
|
|
11253
11365
|
});
|
|
11254
11366
|
let gone = false;
|
|
11255
11367
|
lines2.once("close", () => {
|
|
@@ -11262,9 +11374,9 @@ function openLauncherChannel(stdin, emit, onLauncherGone) {
|
|
|
11262
11374
|
if (gone) return Promise.resolve(null);
|
|
11263
11375
|
const id = String(++next);
|
|
11264
11376
|
return new Promise((resolve2) => {
|
|
11265
|
-
replies.set(id, (
|
|
11377
|
+
replies.set(id, (message2) => {
|
|
11266
11378
|
replies.delete(id);
|
|
11267
|
-
resolve2(
|
|
11379
|
+
resolve2(message2);
|
|
11268
11380
|
});
|
|
11269
11381
|
emit(event(id));
|
|
11270
11382
|
});
|
|
@@ -12474,8 +12586,8 @@ async function logout(opts = {}) {
|
|
|
12474
12586
|
return;
|
|
12475
12587
|
}
|
|
12476
12588
|
if (!opts.yes) {
|
|
12477
|
-
const
|
|
12478
|
-
const ok = await confirm2({ message, default: false });
|
|
12589
|
+
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)?";
|
|
12590
|
+
const ok = await confirm2({ message: message2, default: false });
|
|
12479
12591
|
if (!ok) {
|
|
12480
12592
|
process.stdout.write("cancelled\n");
|
|
12481
12593
|
return;
|
|
@@ -12731,8 +12843,8 @@ function formatHumanLine(record) {
|
|
|
12731
12843
|
let line = `${time} ${word ? `${word} ` : ""}${context ? `${context} ` : ""}${humanText(record.msg)}`;
|
|
12732
12844
|
const err = record.err;
|
|
12733
12845
|
const errorRecord = err !== null && typeof err === "object" ? err : null;
|
|
12734
|
-
const
|
|
12735
|
-
const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) ||
|
|
12846
|
+
const message2 = humanText(errorRecord && "message" in errorRecord ? errorRecord.message : err);
|
|
12847
|
+
const detail = typeof record.status === "number" ? `HTTP ${record.status}: ${humanText(record.responseBody) || message2}` : message2;
|
|
12736
12848
|
if (detail && !line.includes(detail)) line += ` (${detail})`;
|
|
12737
12849
|
if (level < 30) {
|
|
12738
12850
|
const hidden = /* @__PURE__ */ new Set([
|
|
@@ -12966,9 +13078,9 @@ var CompanionStateHub = class {
|
|
|
12966
13078
|
if (info.deviceLabel !== void 0) this.deviceLabel = info.deviceLabel;
|
|
12967
13079
|
this.emitStatus();
|
|
12968
13080
|
}
|
|
12969
|
-
setDeviceError(
|
|
12970
|
-
if (this.deviceError ===
|
|
12971
|
-
this.deviceError =
|
|
13081
|
+
setDeviceError(message2) {
|
|
13082
|
+
if (this.deviceError === message2) return;
|
|
13083
|
+
this.deviceError = message2;
|
|
12972
13084
|
this.emitStatus();
|
|
12973
13085
|
}
|
|
12974
13086
|
clearDeviceError() {
|
|
@@ -13420,7 +13532,7 @@ async function warnAboutHarnessReadiness(cfg, deps = {}) {
|
|
|
13420
13532
|
const probeClaude = deps.probeClaude ?? claudeOnPath;
|
|
13421
13533
|
const probeCodex = deps.probeCodex ?? codexOnPath;
|
|
13422
13534
|
const resolveBundled = deps.resolveBundled ?? ((runtime) => resolveBundledBinary(runtime));
|
|
13423
|
-
const warn = deps.warn ?? ((
|
|
13535
|
+
const warn = deps.warn ?? ((message2) => process.stderr.write(`${message2}
|
|
13424
13536
|
`));
|
|
13425
13537
|
const connected = [
|
|
13426
13538
|
...isClaudeCodeConnected(cfg) ? ["Claude Code"] : [],
|
|
@@ -13912,7 +14024,7 @@ function createLocalProcessProvider(opts = {}) {
|
|
|
13912
14024
|
|
|
13913
14025
|
// src/providers/docker.ts
|
|
13914
14026
|
import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
|
|
13915
|
-
import { constants as constants2, tmpdir as
|
|
14027
|
+
import { constants as constants2, tmpdir as tmpdir3 } from "os";
|
|
13916
14028
|
import {
|
|
13917
14029
|
createReadStream,
|
|
13918
14030
|
createWriteStream as createWriteStream2,
|
|
@@ -13925,7 +14037,7 @@ import {
|
|
|
13925
14037
|
statSync as statSync3,
|
|
13926
14038
|
writeFileSync as writeFileSync6
|
|
13927
14039
|
} from "fs";
|
|
13928
|
-
import { join as
|
|
14040
|
+
import { join as join18 } from "path";
|
|
13929
14041
|
import { Writable } from "stream";
|
|
13930
14042
|
|
|
13931
14043
|
// src/providers/docker-cli.ts
|
|
@@ -13998,7 +14110,7 @@ import {
|
|
|
13998
14110
|
renameSync as renameSync4,
|
|
13999
14111
|
rmSync as rmSync8
|
|
14000
14112
|
} from "fs";
|
|
14001
|
-
import { join as
|
|
14113
|
+
import { join as join16 } from "path";
|
|
14002
14114
|
function routes(t) {
|
|
14003
14115
|
const conv = `/api/workspaces/${t.workspaceId}/conversations/${t.conversationId}`;
|
|
14004
14116
|
const agent = `${conv}/participants/agents/${t.agentId}`;
|
|
@@ -14044,7 +14156,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
14044
14156
|
const staged = readdirSync3(from).filter((n) => n.endsWith(".json")).map((name) => {
|
|
14045
14157
|
let entry = {};
|
|
14046
14158
|
try {
|
|
14047
|
-
entry = JSON.parse(readFileSync8(
|
|
14159
|
+
entry = JSON.parse(readFileSync8(join16(from, name), "utf8"));
|
|
14048
14160
|
} catch {
|
|
14049
14161
|
}
|
|
14050
14162
|
return { name, entry };
|
|
@@ -14057,7 +14169,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
14057
14169
|
let published = 0;
|
|
14058
14170
|
for (const { name } of staged) {
|
|
14059
14171
|
try {
|
|
14060
|
-
move(
|
|
14172
|
+
move(join16(from, name), join16(to, name));
|
|
14061
14173
|
} catch {
|
|
14062
14174
|
return { published, complete: false };
|
|
14063
14175
|
}
|
|
@@ -14070,7 +14182,7 @@ function publishStaged(from, to, move = atomicMove) {
|
|
|
14070
14182
|
// src/providers/runner-base.ts
|
|
14071
14183
|
import { createHash as createHash3 } from "crypto";
|
|
14072
14184
|
import { existsSync as existsSync12, realpathSync as realpathSync2 } from "fs";
|
|
14073
|
-
import { dirname as dirname7, join as
|
|
14185
|
+
import { dirname as dirname7, join as join17, sep } from "path";
|
|
14074
14186
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14075
14187
|
var COMPANION_MOUNT = "/opt/cabane/companion";
|
|
14076
14188
|
var BASE_DOCKERFILE = `FROM node:22-slim
|
|
@@ -14118,11 +14230,11 @@ async function ensureBaseImage(docker, image, log) {
|
|
|
14118
14230
|
function ownInstall(moduleFile = fileURLToPath3(import.meta.url)) {
|
|
14119
14231
|
let dir2 = dirname7(moduleFile);
|
|
14120
14232
|
for (let i = 0; i < 4; i++) {
|
|
14121
|
-
if (existsSync12(
|
|
14233
|
+
if (existsSync12(join17(dir2, "dist", "cli.js")) && existsSync12(join17(dir2, "package.json"))) break;
|
|
14122
14234
|
dir2 = dirname7(dir2);
|
|
14123
14235
|
}
|
|
14124
|
-
if (!existsSync12(
|
|
14125
|
-
const sdk =
|
|
14236
|
+
if (!existsSync12(join17(dir2, "dist", "cli.js"))) return null;
|
|
14237
|
+
const sdk = join17(dir2, "node_modules", "@anthropic-ai", "claude-agent-sdk");
|
|
14126
14238
|
if (!existsSync12(sdk)) return null;
|
|
14127
14239
|
const root = realpathSync2(dir2);
|
|
14128
14240
|
return realpathSync2(sdk).startsWith(root + sep) ? root : null;
|
|
@@ -14261,21 +14373,21 @@ function networkCreateError(stderr) {
|
|
|
14261
14373
|
}
|
|
14262
14374
|
function sessionArchivePath(deviceDir, attemptId) {
|
|
14263
14375
|
if (!UUID_RE.test(attemptId)) throw new Error(`docker: not an attempt id: ${attemptId}`);
|
|
14264
|
-
return
|
|
14376
|
+
return join18(deviceDir, "attempts", attemptId, "session.tar");
|
|
14265
14377
|
}
|
|
14266
14378
|
function pruneOtherAttempts(deviceDir, pair2, keep) {
|
|
14267
|
-
const root =
|
|
14379
|
+
const root = join18(deviceDir, "attempts");
|
|
14268
14380
|
if (!existsSync13(root)) return;
|
|
14269
14381
|
for (const id of readdirSync4(root)) {
|
|
14270
14382
|
if (id === keep || !UUID_RE.test(id)) continue;
|
|
14271
14383
|
let pairOf = "";
|
|
14272
14384
|
try {
|
|
14273
|
-
pairOf = readFileSync9(
|
|
14385
|
+
pairOf = readFileSync9(join18(root, id, "pair"), "utf8");
|
|
14274
14386
|
} catch {
|
|
14275
14387
|
continue;
|
|
14276
14388
|
}
|
|
14277
14389
|
if (pairOf === `${pair2.conversationId}/${pair2.agentId}`) {
|
|
14278
|
-
rmSync9(
|
|
14390
|
+
rmSync9(join18(root, id), { recursive: true, force: true });
|
|
14279
14391
|
}
|
|
14280
14392
|
}
|
|
14281
14393
|
}
|
|
@@ -14475,11 +14587,11 @@ function createDockerProvider(opts) {
|
|
|
14475
14587
|
await must(["network", "disconnect", "-f", other, name], "moving the container");
|
|
14476
14588
|
if (others.includes(sharedNetwork)) await retireSharedNetwork();
|
|
14477
14589
|
};
|
|
14478
|
-
const socketRoot = opts.socketRoot ??
|
|
14479
|
-
process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR :
|
|
14590
|
+
const socketRoot = opts.socketRoot ?? join18(
|
|
14591
|
+
process.env.XDG_RUNTIME_DIR && existsSync13(process.env.XDG_RUNTIME_DIR) ? process.env.XDG_RUNTIME_DIR : tmpdir3(),
|
|
14480
14592
|
`cabane-runners-${opts.deviceKey}`
|
|
14481
14593
|
);
|
|
14482
|
-
const socketDir = (name) =>
|
|
14594
|
+
const socketDir = (name) => join18(socketRoot, createHash4("sha256").update(name).digest("hex").slice(0, 12));
|
|
14483
14595
|
const create = async (name, attempt) => {
|
|
14484
14596
|
mkdirSync10(socketRoot, { recursive: true, mode: 448 });
|
|
14485
14597
|
mkdirSync10(socketDir(name), { recursive: true, mode: 457 });
|
|
@@ -14621,7 +14733,7 @@ function createDockerProvider(opts) {
|
|
|
14621
14733
|
}
|
|
14622
14734
|
};
|
|
14623
14735
|
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) =>
|
|
14736
|
+
const pendingPath = (name) => join18(opts.deviceDir, "runners-pending", `${name}.json`);
|
|
14625
14737
|
const readPending = (name) => {
|
|
14626
14738
|
try {
|
|
14627
14739
|
const ids = JSON.parse(readFileSync9(pendingPath(name), "utf8"));
|
|
@@ -14635,12 +14747,12 @@ function createDockerProvider(opts) {
|
|
|
14635
14747
|
rmSync9(pendingPath(name), { force: true });
|
|
14636
14748
|
return;
|
|
14637
14749
|
}
|
|
14638
|
-
mkdirSync10(
|
|
14750
|
+
mkdirSync10(join18(opts.deviceDir, "runners-pending"), { recursive: true });
|
|
14639
14751
|
writeFileSync6(pendingPath(name), JSON.stringify([...new Set(ids)]));
|
|
14640
14752
|
};
|
|
14641
14753
|
const keepSession = async (name, attempt, attemptId) => {
|
|
14642
14754
|
const archive = sessionArchivePath(opts.deviceDir, attemptId);
|
|
14643
|
-
mkdirSync10(
|
|
14755
|
+
mkdirSync10(join18(archive, ".."), { recursive: true });
|
|
14644
14756
|
const partial = `${archive}.${process.pid}.partial`;
|
|
14645
14757
|
const file = new CappedFile(partial, sessionCap);
|
|
14646
14758
|
const r = await execIn(
|
|
@@ -14657,7 +14769,7 @@ function createDockerProvider(opts) {
|
|
|
14657
14769
|
);
|
|
14658
14770
|
if (r.code === 0 && !file.over) {
|
|
14659
14771
|
renameSync5(partial, archive);
|
|
14660
|
-
writeFileSync6(
|
|
14772
|
+
writeFileSync6(join18(archive, "..", "pair"), `${attempt.conversationId}/${attempt.agentId}`);
|
|
14661
14773
|
pruneOtherAttempts(opts.deviceDir, attempt, attemptId);
|
|
14662
14774
|
await writeMarker(name, attemptId);
|
|
14663
14775
|
log.debug({ container: name, attemptId, bytes: file.bytes }, "runners: session files kept");
|
|
@@ -14671,7 +14783,7 @@ function createDockerProvider(opts) {
|
|
|
14671
14783
|
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
14784
|
);
|
|
14673
14785
|
};
|
|
14674
|
-
const stagingDir = (name) =>
|
|
14786
|
+
const stagingDir = (name) => join18(opts.deviceDir, "runners-pending", `${name}.outbox`);
|
|
14675
14787
|
const ensureRunning = async (name) => {
|
|
14676
14788
|
const seen = await docker.run(["inspect", "-f", "{{.State.Running}}", name]);
|
|
14677
14789
|
if (seen.code !== 0) return false;
|
|
@@ -14679,7 +14791,7 @@ function createDockerProvider(opts) {
|
|
|
14679
14791
|
return (await docker.run(["start", name])).code === 0;
|
|
14680
14792
|
};
|
|
14681
14793
|
const dropArchive = (attemptId) => {
|
|
14682
|
-
rmSync9(
|
|
14794
|
+
rmSync9(join18(sessionArchivePath(opts.deviceDir, attemptId), ".."), {
|
|
14683
14795
|
recursive: true,
|
|
14684
14796
|
force: true
|
|
14685
14797
|
});
|
|
@@ -14748,7 +14860,7 @@ function createDockerProvider(opts) {
|
|
|
14748
14860
|
if (seen.stdout.trim().split(" ")[0] !== "true")
|
|
14749
14861
|
await must(["start", name], "starting the container");
|
|
14750
14862
|
const git = await serveGitCredentials({
|
|
14751
|
-
socketPath:
|
|
14863
|
+
socketPath: join18(socketDir(name), "git.sock"),
|
|
14752
14864
|
hosts: opts.gitHosts,
|
|
14753
14865
|
fill: opts.gitFill,
|
|
14754
14866
|
log
|
|
@@ -14897,15 +15009,15 @@ function createDockerProvider(opts) {
|
|
|
14897
15009
|
const transcripts = await readDir(name, `${CONTAINER_STATE}/transcripts`);
|
|
14898
15010
|
if (transcripts) {
|
|
14899
15011
|
const kept = transcripts.files.filter((f) => SAFE_NAME_RE.test(f.name));
|
|
14900
|
-
if (kept.length) mkdirSync10(
|
|
14901
|
-
for (const f of kept) writeFileSync6(
|
|
15012
|
+
if (kept.length) mkdirSync10(join18(opts.hostStateDir, "transcripts"), { recursive: true });
|
|
15013
|
+
for (const f of kept) writeFileSync6(join18(opts.hostStateDir, "transcripts", f.name), f.data);
|
|
14902
15014
|
await removeIn(
|
|
14903
15015
|
name,
|
|
14904
15016
|
`${CONTAINER_STATE}/transcripts`,
|
|
14905
15017
|
kept.map((f) => f.name)
|
|
14906
15018
|
);
|
|
14907
15019
|
}
|
|
14908
|
-
const outboxRel =
|
|
15020
|
+
const outboxRel = join18("outbox", encodeURIComponent(attempt.agentId));
|
|
14909
15021
|
const outbox = await readDir(name, `${CONTAINER_STATE}/${outboxRel}`);
|
|
14910
15022
|
let unpublished = false;
|
|
14911
15023
|
if (outbox) {
|
|
@@ -14931,7 +15043,7 @@ function createDockerProvider(opts) {
|
|
|
14931
15043
|
const staged = stagingDir(name);
|
|
14932
15044
|
mkdirSync10(staged, { recursive: true });
|
|
14933
15045
|
writeFileSync6(
|
|
14934
|
-
|
|
15046
|
+
join18(staged, `${encodeURIComponent(entry.turnId)}__${entry.seq}.json`),
|
|
14935
15047
|
`${JSON.stringify(entry)}
|
|
14936
15048
|
`
|
|
14937
15049
|
);
|
|
@@ -14944,7 +15056,7 @@ function createDockerProvider(opts) {
|
|
|
14944
15056
|
if (outbox.complete) {
|
|
14945
15057
|
const published = publishStaged(
|
|
14946
15058
|
stagingDir(name),
|
|
14947
|
-
|
|
15059
|
+
join18(opts.hostStateDir, outboxRel),
|
|
14948
15060
|
opts.moveFile
|
|
14949
15061
|
);
|
|
14950
15062
|
queued = published.published;
|
|
@@ -16774,8 +16886,8 @@ function errorCode(err) {
|
|
|
16774
16886
|
function isRecoverableSocketError(err) {
|
|
16775
16887
|
const code = errorCode(err);
|
|
16776
16888
|
if (code && RECOVERABLE_CODES.has(code)) return true;
|
|
16777
|
-
const
|
|
16778
|
-
return /\bEPIPE\b|\bECONNRESET\b/.test(
|
|
16889
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
16890
|
+
return /\bEPIPE\b|\bECONNRESET\b/.test(message2);
|
|
16779
16891
|
}
|
|
16780
16892
|
function installProcessSafetyNet(log, opts = {}) {
|
|
16781
16893
|
const proc = opts.proc ?? process;
|
|
@@ -16790,17 +16902,17 @@ function installProcessSafetyNet(log, opts = {}) {
|
|
|
16790
16902
|
);
|
|
16791
16903
|
}
|
|
16792
16904
|
function handleUncaught(log, err, origin) {
|
|
16793
|
-
const
|
|
16905
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
16794
16906
|
const code = errorCode(err);
|
|
16795
16907
|
if (isRecoverableSocketError(err)) {
|
|
16796
16908
|
log.debug(
|
|
16797
|
-
{ origin, code, err:
|
|
16909
|
+
{ origin, code, err: message2 },
|
|
16798
16910
|
"companion: recovered from a socket error (kept running \u2014 a broken pipe never kills the companion)"
|
|
16799
16911
|
);
|
|
16800
16912
|
return;
|
|
16801
16913
|
}
|
|
16802
16914
|
log.error(
|
|
16803
|
-
{ origin, code, err:
|
|
16915
|
+
{ origin, code, err: message2, stack: err instanceof Error ? err.stack : void 0 },
|
|
16804
16916
|
"An unexpected error occurred; the companion is still running."
|
|
16805
16917
|
);
|
|
16806
16918
|
}
|
|
@@ -16808,9 +16920,9 @@ function handleUncaught(log, err, origin) {
|
|
|
16808
16920
|
// src/crash-marker.ts
|
|
16809
16921
|
init_config();
|
|
16810
16922
|
import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync12, rmSync as rmSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
16811
|
-
import { join as
|
|
16923
|
+
import { join as join21 } from "path";
|
|
16812
16924
|
function crashMarkerPath() {
|
|
16813
|
-
return
|
|
16925
|
+
return join21(cabaneDir(), "last-error.json");
|
|
16814
16926
|
}
|
|
16815
16927
|
function recordCrash(rec2) {
|
|
16816
16928
|
try {
|
|
@@ -17161,7 +17273,7 @@ async function startScript(opts, interactive) {
|
|
|
17161
17273
|
const result = await createCompanionRuntime({
|
|
17162
17274
|
// The script says what's connectable in its own words below; a stderr warning
|
|
17163
17275
|
// in the middle of it would be the same news, worse.
|
|
17164
|
-
onReadinessWarning: (
|
|
17276
|
+
onReadinessWarning: (message2) => log.warn(message2)
|
|
17165
17277
|
});
|
|
17166
17278
|
if (!result.ok) {
|
|
17167
17279
|
reportAlreadyRunning(result.existing?.pid);
|
|
@@ -17566,7 +17678,7 @@ init_errors();
|
|
|
17566
17678
|
init_src();
|
|
17567
17679
|
init_transcript_writer();
|
|
17568
17680
|
import { existsSync as existsSync17, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
|
|
17569
|
-
import { isAbsolute, join as
|
|
17681
|
+
import { isAbsolute, join as join22 } from "path";
|
|
17570
17682
|
async function transcript(opts = {}) {
|
|
17571
17683
|
const dir2 = transcriptsDir();
|
|
17572
17684
|
if (opts.follow) {
|
|
@@ -17583,7 +17695,7 @@ async function transcript(opts = {}) {
|
|
|
17583
17695
|
process.stdout.write(emptyMessage(dir2));
|
|
17584
17696
|
return;
|
|
17585
17697
|
}
|
|
17586
|
-
process.stdout.write(renderFile(
|
|
17698
|
+
process.stdout.write(renderFile(join22(dir2, newest)) + "\n");
|
|
17587
17699
|
return;
|
|
17588
17700
|
}
|
|
17589
17701
|
printList(dir2);
|
|
@@ -17666,7 +17778,7 @@ function isComplete(content) {
|
|
|
17666
17778
|
async function followTranscripts(dir2) {
|
|
17667
17779
|
const follower = new TranscriptFollower({
|
|
17668
17780
|
listFiles: () => listFiles(dir2),
|
|
17669
|
-
read: (f) => readFileSync13(
|
|
17781
|
+
read: (f) => readFileSync13(join22(dir2, f), "utf8"),
|
|
17670
17782
|
write: (s) => process.stdout.write(s),
|
|
17671
17783
|
// CSI: cursor up `n` lines, then erase from cursor to end of screen.
|
|
17672
17784
|
clearLines: (n) => process.stdout.write(`\x1B[${n}A\x1B[0J`),
|
|
@@ -17706,7 +17818,7 @@ function printList(dir2) {
|
|
|
17706
17818
|
|
|
17707
17819
|
`);
|
|
17708
17820
|
for (const f of files.slice(0, 20)) {
|
|
17709
|
-
const { meta, outcome } = peek(
|
|
17821
|
+
const { meta, outcome } = peek(join22(dir2, f));
|
|
17710
17822
|
const when = fmtTime(rec(meta)?.ts);
|
|
17711
17823
|
const ws = str2(rec(meta)?.workspaceSlug);
|
|
17712
17824
|
const o = rec(outcome);
|
|
@@ -17743,10 +17855,10 @@ function resolveTarget(dir2, target) {
|
|
|
17743
17855
|
if (existsSync17(target)) return target;
|
|
17744
17856
|
throw new CompanionError(`no transcript at ${target}.`);
|
|
17745
17857
|
}
|
|
17746
|
-
const exact =
|
|
17858
|
+
const exact = join22(dir2, target);
|
|
17747
17859
|
if (existsSync17(exact)) return exact;
|
|
17748
17860
|
const matches = listFiles(dir2).filter((f) => f.includes(target));
|
|
17749
|
-
if (matches.length === 1) return
|
|
17861
|
+
if (matches.length === 1) return join22(dir2, matches[0]);
|
|
17750
17862
|
if (matches.length === 0) {
|
|
17751
17863
|
throw new CompanionError(
|
|
17752
17864
|
`no transcript matching "${target}" in ${dir2}. Run \`cabane-companion transcript\` to list them.`
|