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