@cabane/companion 0.6.232 → 0.6.234
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 +386 -327
- package/dist/runtime.js +151 -128
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1010,6 +1010,317 @@ var init_bundled_binaries = __esm({
|
|
|
1010
1010
|
}
|
|
1011
1011
|
});
|
|
1012
1012
|
|
|
1013
|
+
// src/device-state.ts
|
|
1014
|
+
var DEVICE_STATES, DEFAULT_DEVICE_OFFLINE_THRESHOLD_MS, HARNESS_STATES, DEVICE_HEALTH_STATES, DEVICE_STATE_LABEL, DEVICE_HEALTH_LABEL, HARNESS_STATE_LABEL;
|
|
1015
|
+
var init_device_state = __esm({
|
|
1016
|
+
"src/device-state.ts"() {
|
|
1017
|
+
"use strict";
|
|
1018
|
+
DEVICE_STATES = ["waiting", "online", "offline", "deactivated"];
|
|
1019
|
+
DEFAULT_DEVICE_OFFLINE_THRESHOLD_MS = 9e4;
|
|
1020
|
+
HARNESS_STATES = [
|
|
1021
|
+
"checking",
|
|
1022
|
+
"connected",
|
|
1023
|
+
"needs_sign_in",
|
|
1024
|
+
"limited",
|
|
1025
|
+
"problem",
|
|
1026
|
+
"off",
|
|
1027
|
+
"gone"
|
|
1028
|
+
];
|
|
1029
|
+
DEVICE_HEALTH_STATES = ["checking", "ready", "degraded"];
|
|
1030
|
+
DEVICE_STATE_LABEL = {
|
|
1031
|
+
waiting: "Waiting for its first check-in",
|
|
1032
|
+
online: "Online",
|
|
1033
|
+
offline: "Offline",
|
|
1034
|
+
deactivated: "Deactivated"
|
|
1035
|
+
};
|
|
1036
|
+
DEVICE_HEALTH_LABEL = {
|
|
1037
|
+
checking: "Checking harnesses\u2026",
|
|
1038
|
+
ready: "Ready",
|
|
1039
|
+
degraded: "Needs attention"
|
|
1040
|
+
};
|
|
1041
|
+
HARNESS_STATE_LABEL = {
|
|
1042
|
+
checking: "checking",
|
|
1043
|
+
connected: "connected",
|
|
1044
|
+
needs_sign_in: "needs sign-in",
|
|
1045
|
+
limited: "limited",
|
|
1046
|
+
problem: "problem",
|
|
1047
|
+
off: "disabled",
|
|
1048
|
+
gone: "gone"
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
// src/harness-state.ts
|
|
1054
|
+
function harnessEntry(runtime, state) {
|
|
1055
|
+
switch (state.state) {
|
|
1056
|
+
case "usable":
|
|
1057
|
+
return {
|
|
1058
|
+
runtime,
|
|
1059
|
+
state: "connected",
|
|
1060
|
+
reason: state.via,
|
|
1061
|
+
version: state.version,
|
|
1062
|
+
...state.url ? { url: state.url } : {}
|
|
1063
|
+
};
|
|
1064
|
+
case "signed_out":
|
|
1065
|
+
return { runtime, state: "needs_sign_in", reason: "signed_out", version: state.version };
|
|
1066
|
+
case "not_running":
|
|
1067
|
+
return { runtime, state: "problem", reason: "not_running", url: state.url };
|
|
1068
|
+
case "off":
|
|
1069
|
+
return { runtime, state: "off" };
|
|
1070
|
+
case "problem":
|
|
1071
|
+
return { runtime, state: "problem", reason: checkReason(state.check) };
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
function harnessEntriesFromDetection(detection) {
|
|
1075
|
+
return HARNESS_RUNTIMES.map((runtime) => harnessEntry(runtime, detection[runtime]));
|
|
1076
|
+
}
|
|
1077
|
+
function checkReason(check) {
|
|
1078
|
+
switch (check.status) {
|
|
1079
|
+
case "bundle_missing":
|
|
1080
|
+
return check.bundled;
|
|
1081
|
+
case "check_failed":
|
|
1082
|
+
return `check_failed:${check.reason}`;
|
|
1083
|
+
default:
|
|
1084
|
+
return check.status;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
var init_harness_state = __esm({
|
|
1088
|
+
"src/harness-state.ts"() {
|
|
1089
|
+
"use strict";
|
|
1090
|
+
init_config();
|
|
1091
|
+
init_device_state();
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
// src/harness-copy.ts
|
|
1096
|
+
import { homedir as homedir6 } from "os";
|
|
1097
|
+
import { join as join7, win32 } from "path";
|
|
1098
|
+
function harnessLabel(runtime) {
|
|
1099
|
+
const known = HARNESS_RUNTIMES.find((candidate) => candidate === runtime);
|
|
1100
|
+
return known ? HARNESS_LABELS[known] : "The harness";
|
|
1101
|
+
}
|
|
1102
|
+
function ctx(c = {}) {
|
|
1103
|
+
const path = "path" in c ? c.path : process.env.PATH;
|
|
1104
|
+
const home = c.home ?? homedir6();
|
|
1105
|
+
return {
|
|
1106
|
+
path,
|
|
1107
|
+
home,
|
|
1108
|
+
install: c.install ?? companionInstall(),
|
|
1109
|
+
profile: "profile" in c ? c.profile ?? null : profileFile({ home }),
|
|
1110
|
+
reload: "reload" in c ? c.reload ?? null : reloadLine(),
|
|
1111
|
+
hasNpm: c.hasNpm ?? firstOnPath("npm", path) !== null,
|
|
1112
|
+
windows: (c.platform ?? process.platform) === "win32"
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
function loginCommand(runtime) {
|
|
1116
|
+
return runtime === "codex" ? "codex login" : "claude auth login";
|
|
1117
|
+
}
|
|
1118
|
+
function failureText(runtime, reason) {
|
|
1119
|
+
const label = HARNESS_LABELS[runtime];
|
|
1120
|
+
switch (reason) {
|
|
1121
|
+
case "timeout":
|
|
1122
|
+
return `Couldn't check whether ${label} is signed in: the check didn't answer in time. Try again.`;
|
|
1123
|
+
case "spawn":
|
|
1124
|
+
return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
1125
|
+
case "config":
|
|
1126
|
+
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join7(homedir6(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
|
|
1127
|
+
case "unreadable":
|
|
1128
|
+
return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
function bundleMissingText(runtime) {
|
|
1132
|
+
const label = HARNESS_LABELS[runtime];
|
|
1133
|
+
return `This Cabane install is missing its bundled ${label}. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
1134
|
+
}
|
|
1135
|
+
function unusableReason(runtime, check) {
|
|
1136
|
+
if (check.status === "bundle_missing") return bundleMissingText(runtime);
|
|
1137
|
+
if (check.status === "check_failed") return failureText(runtime, check.reason);
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
function agentRows(detection, opts = {}) {
|
|
1141
|
+
return HARNESS_RUNTIMES.map((runtime) => {
|
|
1142
|
+
const state = detection[runtime];
|
|
1143
|
+
return {
|
|
1144
|
+
runtime,
|
|
1145
|
+
label: HARNESS_LABELS[runtime],
|
|
1146
|
+
text: agentRowText(runtime, state, opts),
|
|
1147
|
+
usable: state.state === "usable"
|
|
1148
|
+
};
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
function agentRowText(runtime, state, opts = {}) {
|
|
1152
|
+
const word = HARNESS_STATE_LABEL[harnessEntry(runtime, state).state];
|
|
1153
|
+
switch (state.state) {
|
|
1154
|
+
case "usable": {
|
|
1155
|
+
const version = state.version ? ` (${state.version})` : "";
|
|
1156
|
+
if (state.via === "api_key") return `${word} \u2014 API key configured${version}`;
|
|
1157
|
+
if (state.via === "serve") return `${word} \u2014 running at ${state.url ?? ""}`;
|
|
1158
|
+
return `${word} \u2014 signed in${version}`;
|
|
1159
|
+
}
|
|
1160
|
+
case "signed_out":
|
|
1161
|
+
return opts.signInHint && runtime !== "opencode" ? `${word} \u2014 run ${loginCommand(runtime)}` : word;
|
|
1162
|
+
case "not_running":
|
|
1163
|
+
return `${word} \u2014 not running`;
|
|
1164
|
+
case "off":
|
|
1165
|
+
return `${word} \u2014 disabled in config`;
|
|
1166
|
+
case "problem":
|
|
1167
|
+
return runtime === "opencode" ? `${word} \u2014 not running` : `${word} \u2014 ${unusableReason(runtime, state.check) ?? "could not be checked"}`;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
function agentNote(runtime, c = {}) {
|
|
1171
|
+
const k = ctx(c);
|
|
1172
|
+
if (k.windows) return windowsAgentNote(runtime, k);
|
|
1173
|
+
if (runtime === "claude-code") {
|
|
1174
|
+
const commands = ["curl -fsSL https://claude.ai/install.sh | bash"];
|
|
1175
|
+
const localBin = join7(k.home, ".local", "bin");
|
|
1176
|
+
const needsPath = !dirOnPath(localBin, k.path);
|
|
1177
|
+
const profile = k.profile ? tildePath(k.profile, k.home) : null;
|
|
1178
|
+
let sourced = null;
|
|
1179
|
+
if (needsPath) {
|
|
1180
|
+
if (profile) {
|
|
1181
|
+
commands.push(`echo 'export PATH="$HOME/.local/bin:$PATH"' >> ${profile}`);
|
|
1182
|
+
sourced = `source ${profile}`;
|
|
1183
|
+
commands.push(sourced);
|
|
1184
|
+
} else {
|
|
1185
|
+
commands.push('export PATH="$HOME/.local/bin:$PATH"');
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
if (k.reload && k.reload !== sourced) commands.push(k.reload);
|
|
1189
|
+
commands.push("claude auth login");
|
|
1190
|
+
return {
|
|
1191
|
+
title: "Set up Claude Code",
|
|
1192
|
+
lines: [
|
|
1193
|
+
"Run these lines here. The last one opens your browser",
|
|
1194
|
+
"to sign in with your Claude account.",
|
|
1195
|
+
"",
|
|
1196
|
+
...commands.map(indent),
|
|
1197
|
+
"",
|
|
1198
|
+
SETUP_AGAIN
|
|
1199
|
+
]
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
if (runtime === "codex") {
|
|
1203
|
+
const own = k.hasNpm || k.install !== "script";
|
|
1204
|
+
const on = own ? "" : 'PATH="$HOME/.cabane/node/bin:$PATH" ';
|
|
1205
|
+
const commands = [
|
|
1206
|
+
`${on}npm i -g @openai/codex`,
|
|
1207
|
+
...k.reload ? [k.reload] : [],
|
|
1208
|
+
`${on}codex login`
|
|
1209
|
+
];
|
|
1210
|
+
return {
|
|
1211
|
+
title: "Set up Codex",
|
|
1212
|
+
lines: [
|
|
1213
|
+
"Run these lines here. The last one opens your browser",
|
|
1214
|
+
"to sign in with your ChatGPT account.",
|
|
1215
|
+
"",
|
|
1216
|
+
...commands.map(indent),
|
|
1217
|
+
"",
|
|
1218
|
+
SETUP_AGAIN
|
|
1219
|
+
]
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
const here = [...k.reload ? [k.reload] : [], "curl -fsSL https://opencode.ai/install | bash"];
|
|
1223
|
+
return {
|
|
1224
|
+
title: "Set up OpenCode",
|
|
1225
|
+
lines: [
|
|
1226
|
+
`Run the ${here.length > 1 ? "first two lines" : "first line"} here. Then open a second terminal`,
|
|
1227
|
+
"window, run the last line there, and leave that window open.",
|
|
1228
|
+
"",
|
|
1229
|
+
...here.map(indent),
|
|
1230
|
+
indent("opencode serve --port 4096"),
|
|
1231
|
+
"",
|
|
1232
|
+
`Then ${cabane("status")} shows it signed in; nothing else to run.`
|
|
1233
|
+
]
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
function windowsAgentNote(runtime, k) {
|
|
1237
|
+
if (runtime === "claude-code") {
|
|
1238
|
+
const commands = ["irm https://claude.ai/install.ps1 | iex"];
|
|
1239
|
+
if (!windowsDirOnPath(win32.join(k.home, ".local", "bin"), k.path)) {
|
|
1240
|
+
commands.push(
|
|
1241
|
+
`$bin = "$env:USERPROFILE\\.local\\bin"`,
|
|
1242
|
+
`$p = [Environment]::GetEnvironmentVariable('PATH', 'User')`,
|
|
1243
|
+
`[Environment]::SetEnvironmentVariable('PATH', "$p;$bin", 'User')`,
|
|
1244
|
+
`$env:PATH += ";$bin"`
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
commands.push("claude auth login");
|
|
1248
|
+
return {
|
|
1249
|
+
title: "Set up Claude Code",
|
|
1250
|
+
lines: [
|
|
1251
|
+
"Run these lines in PowerShell. The last one opens your browser",
|
|
1252
|
+
"to sign in with your Claude account.",
|
|
1253
|
+
"",
|
|
1254
|
+
...commands.map(indent),
|
|
1255
|
+
"",
|
|
1256
|
+
SETUP_AGAIN
|
|
1257
|
+
]
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
if (runtime === "codex") {
|
|
1261
|
+
return {
|
|
1262
|
+
title: "Set up Codex",
|
|
1263
|
+
lines: [
|
|
1264
|
+
"Run these lines here. The last one opens your browser",
|
|
1265
|
+
"to sign in with your ChatGPT account.",
|
|
1266
|
+
"",
|
|
1267
|
+
indent("npm i -g @openai/codex"),
|
|
1268
|
+
indent("codex login"),
|
|
1269
|
+
"",
|
|
1270
|
+
SETUP_AGAIN
|
|
1271
|
+
]
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
return {
|
|
1275
|
+
title: "Set up OpenCode",
|
|
1276
|
+
lines: [
|
|
1277
|
+
"Run the first line here. Then open a second terminal",
|
|
1278
|
+
"window, run the last line there, and leave that window open.",
|
|
1279
|
+
"",
|
|
1280
|
+
indent("npm i -g opencode-ai"),
|
|
1281
|
+
indent("opencode serve --port 4096"),
|
|
1282
|
+
"",
|
|
1283
|
+
`Then ${cabane("status")} shows it signed in; nothing else to run.`
|
|
1284
|
+
]
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
function windowsDirOnPath(dir2, path) {
|
|
1288
|
+
const norm = (entry) => entry.replace(/[\\/]+$/, "").toLowerCase();
|
|
1289
|
+
return (path ?? "").split(";").some((entry) => norm(entry) === norm(dir2));
|
|
1290
|
+
}
|
|
1291
|
+
function indent(line) {
|
|
1292
|
+
return ` ${line}`;
|
|
1293
|
+
}
|
|
1294
|
+
function spaced(sentence) {
|
|
1295
|
+
return sentence ? ` ${sentence}` : "";
|
|
1296
|
+
}
|
|
1297
|
+
var HARNESS_LABELS, AGENT_CHOICES, CHOOSE_AGENT, SETUP_AGAIN;
|
|
1298
|
+
var init_harness_copy = __esm({
|
|
1299
|
+
"src/harness-copy.ts"() {
|
|
1300
|
+
"use strict";
|
|
1301
|
+
init_bundled_binaries();
|
|
1302
|
+
init_cabane_command();
|
|
1303
|
+
init_config();
|
|
1304
|
+
init_device_state();
|
|
1305
|
+
init_harness_state();
|
|
1306
|
+
init_install_kind();
|
|
1307
|
+
init_install_record();
|
|
1308
|
+
init_term();
|
|
1309
|
+
HARNESS_LABELS = {
|
|
1310
|
+
"claude-code": "Claude Code",
|
|
1311
|
+
codex: "Codex",
|
|
1312
|
+
opencode: "OpenCode"
|
|
1313
|
+
};
|
|
1314
|
+
AGENT_CHOICES = [
|
|
1315
|
+
{ value: "claude-code", label: "Claude Code", hint: "a Claude Pro or Max plan" },
|
|
1316
|
+
{ value: "codex", label: "Codex", hint: "a ChatGPT plan" },
|
|
1317
|
+
{ value: "opencode", label: "OpenCode" }
|
|
1318
|
+
];
|
|
1319
|
+
CHOOSE_AGENT = "Cabane needs one of them. Which will you set up?";
|
|
1320
|
+
SETUP_AGAIN = `Then ${cabane("status")} shows it signed in; nothing else to run.`;
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1013
1324
|
// src/harness-versions.ts
|
|
1014
1325
|
function parseVersionToken(raw) {
|
|
1015
1326
|
if (!raw) return null;
|
|
@@ -7204,9 +7515,12 @@ var init_turn_runtime_integrity = __esm({
|
|
|
7204
7515
|
// src/turn-execution.ts
|
|
7205
7516
|
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
7206
7517
|
import { existsSync as existsSync11, mkdirSync as mkdirSync9 } from "fs";
|
|
7207
|
-
function harnessUnavailableNotice(deviceId, deviceLabel) {
|
|
7518
|
+
function harnessUnavailableNotice(runtime, agentUsername, deviceId, deviceLabel) {
|
|
7208
7519
|
const name = deviceId && deviceLabel !== void 0 ? ` ([${deviceLabel.replace(/([\\[\]])/g, "\\$1")}](/settings/devices/${encodeURIComponent(deviceId)}))` : "";
|
|
7209
|
-
|
|
7520
|
+
const agent = `\\@${agentUsername}`;
|
|
7521
|
+
return `${harnessLabel(runtime)} isn't connected on ${agent}'s device${name}, so they can't reply. Run \`cabane setup\` in a terminal, or choose a different harness in ${agent}'s settings.
|
|
7522
|
+
|
|
7523
|
+
Reply to try again.`;
|
|
7210
7524
|
}
|
|
7211
7525
|
function resolveIdleTimeoutMs(override) {
|
|
7212
7526
|
return override ?? DEFAULT_AGENT_IDLE_TIMEOUT_MS;
|
|
@@ -7273,6 +7587,7 @@ var init_turn_execution = __esm({
|
|
|
7273
7587
|
init_turn_containment();
|
|
7274
7588
|
init_turn_committer();
|
|
7275
7589
|
init_turn_runtime_integrity();
|
|
7590
|
+
init_harness_copy();
|
|
7276
7591
|
PREPARING_TOOL_NAME = "preparing";
|
|
7277
7592
|
NO_TURN_TOKEN_REASON = "no_turn_token: the server issued no turn credential for this turn";
|
|
7278
7593
|
UNEXPECTED_ROLE_REASON = `refused the wake trigger (role "system" isn't dispatchable on this companion) \u2014 the companion is likely running outdated code; refresh it, then re-address the agent`;
|
|
@@ -7900,7 +8215,10 @@ var init_turn_execution = __esm({
|
|
|
7900
8215
|
async selectAdapter() {
|
|
7901
8216
|
const { payload, workspaceId, turnId, turnLog } = this;
|
|
7902
8217
|
await guardBundledBinary(this.integrityContext(), (r, e) => this.concluded(r, e), this.outcome);
|
|
7903
|
-
const onWarn = (msg, meta) => turnLog.
|
|
8218
|
+
const onWarn = (msg, meta) => turnLog.warn(
|
|
8219
|
+
{ ...meta, ...typeof meta?.thrown === "string" ? { err: meta.thrown } : {} },
|
|
8220
|
+
msg
|
|
8221
|
+
);
|
|
7904
8222
|
const adapters = [];
|
|
7905
8223
|
if (this.opts.claudeCodeAvailable?.() ?? true) {
|
|
7906
8224
|
this.containment = createTurnContainment(this.turnId, {
|
|
@@ -7969,7 +8287,12 @@ var init_turn_execution = __esm({
|
|
|
7969
8287
|
);
|
|
7970
8288
|
try {
|
|
7971
8289
|
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
7972
|
-
body: harnessUnavailableNotice(
|
|
8290
|
+
body: harnessUnavailableNotice(
|
|
8291
|
+
err.runtime,
|
|
8292
|
+
this.opts.agentUsername,
|
|
8293
|
+
this.opts.deviceId,
|
|
8294
|
+
this.opts.deviceLabel
|
|
8295
|
+
),
|
|
7973
8296
|
kind: "runtime_notice",
|
|
7974
8297
|
turnId,
|
|
7975
8298
|
parentMessageId: payload.messageId,
|
|
@@ -9168,295 +9491,7 @@ init_errors();
|
|
|
9168
9491
|
|
|
9169
9492
|
// src/harness-status.ts
|
|
9170
9493
|
init_config();
|
|
9171
|
-
|
|
9172
|
-
// src/harness-copy.ts
|
|
9173
|
-
init_bundled_binaries();
|
|
9174
|
-
init_cabane_command();
|
|
9175
|
-
init_config();
|
|
9176
|
-
import { homedir as homedir6 } from "os";
|
|
9177
|
-
import { join as join7, win32 } from "path";
|
|
9178
|
-
|
|
9179
|
-
// src/device-state.ts
|
|
9180
|
-
var DEVICE_STATES = ["waiting", "online", "offline", "deactivated"];
|
|
9181
|
-
var DEFAULT_DEVICE_OFFLINE_THRESHOLD_MS = 9e4;
|
|
9182
|
-
var HARNESS_STATES = [
|
|
9183
|
-
"checking",
|
|
9184
|
-
"connected",
|
|
9185
|
-
"needs_sign_in",
|
|
9186
|
-
"limited",
|
|
9187
|
-
"problem",
|
|
9188
|
-
"off",
|
|
9189
|
-
"gone"
|
|
9190
|
-
];
|
|
9191
|
-
var DEVICE_HEALTH_STATES = ["checking", "ready", "degraded"];
|
|
9192
|
-
var DEVICE_STATE_LABEL = {
|
|
9193
|
-
waiting: "Waiting for its first check-in",
|
|
9194
|
-
online: "Online",
|
|
9195
|
-
offline: "Offline",
|
|
9196
|
-
deactivated: "Deactivated"
|
|
9197
|
-
};
|
|
9198
|
-
var DEVICE_HEALTH_LABEL = {
|
|
9199
|
-
checking: "Checking harnesses\u2026",
|
|
9200
|
-
ready: "Ready",
|
|
9201
|
-
degraded: "Needs attention"
|
|
9202
|
-
};
|
|
9203
|
-
var HARNESS_STATE_LABEL = {
|
|
9204
|
-
checking: "checking",
|
|
9205
|
-
connected: "connected",
|
|
9206
|
-
needs_sign_in: "needs sign-in",
|
|
9207
|
-
limited: "limited",
|
|
9208
|
-
problem: "problem",
|
|
9209
|
-
off: "disabled",
|
|
9210
|
-
gone: "gone"
|
|
9211
|
-
};
|
|
9212
|
-
|
|
9213
|
-
// src/harness-state.ts
|
|
9214
|
-
init_config();
|
|
9215
|
-
function harnessEntry(runtime, state) {
|
|
9216
|
-
switch (state.state) {
|
|
9217
|
-
case "usable":
|
|
9218
|
-
return {
|
|
9219
|
-
runtime,
|
|
9220
|
-
state: "connected",
|
|
9221
|
-
reason: state.via,
|
|
9222
|
-
version: state.version,
|
|
9223
|
-
...state.url ? { url: state.url } : {}
|
|
9224
|
-
};
|
|
9225
|
-
case "signed_out":
|
|
9226
|
-
return { runtime, state: "needs_sign_in", reason: "signed_out", version: state.version };
|
|
9227
|
-
case "not_running":
|
|
9228
|
-
return { runtime, state: "problem", reason: "not_running", url: state.url };
|
|
9229
|
-
case "off":
|
|
9230
|
-
return { runtime, state: "off" };
|
|
9231
|
-
case "problem":
|
|
9232
|
-
return { runtime, state: "problem", reason: checkReason(state.check) };
|
|
9233
|
-
}
|
|
9234
|
-
}
|
|
9235
|
-
function harnessEntriesFromDetection(detection) {
|
|
9236
|
-
return HARNESS_RUNTIMES.map((runtime) => harnessEntry(runtime, detection[runtime]));
|
|
9237
|
-
}
|
|
9238
|
-
function checkReason(check) {
|
|
9239
|
-
switch (check.status) {
|
|
9240
|
-
case "bundle_missing":
|
|
9241
|
-
return check.bundled;
|
|
9242
|
-
case "check_failed":
|
|
9243
|
-
return `check_failed:${check.reason}`;
|
|
9244
|
-
default:
|
|
9245
|
-
return check.status;
|
|
9246
|
-
}
|
|
9247
|
-
}
|
|
9248
|
-
|
|
9249
|
-
// src/harness-copy.ts
|
|
9250
|
-
init_install_kind();
|
|
9251
|
-
init_install_record();
|
|
9252
|
-
init_term();
|
|
9253
|
-
var HARNESS_LABELS = {
|
|
9254
|
-
"claude-code": "Claude Code",
|
|
9255
|
-
codex: "Codex",
|
|
9256
|
-
opencode: "OpenCode"
|
|
9257
|
-
};
|
|
9258
|
-
function ctx(c = {}) {
|
|
9259
|
-
const path = "path" in c ? c.path : process.env.PATH;
|
|
9260
|
-
const home = c.home ?? homedir6();
|
|
9261
|
-
return {
|
|
9262
|
-
path,
|
|
9263
|
-
home,
|
|
9264
|
-
install: c.install ?? companionInstall(),
|
|
9265
|
-
profile: "profile" in c ? c.profile ?? null : profileFile({ home }),
|
|
9266
|
-
reload: "reload" in c ? c.reload ?? null : reloadLine(),
|
|
9267
|
-
hasNpm: c.hasNpm ?? firstOnPath("npm", path) !== null,
|
|
9268
|
-
windows: (c.platform ?? process.platform) === "win32"
|
|
9269
|
-
};
|
|
9270
|
-
}
|
|
9271
|
-
function loginCommand(runtime) {
|
|
9272
|
-
return runtime === "codex" ? "codex login" : "claude auth login";
|
|
9273
|
-
}
|
|
9274
|
-
function failureText(runtime, reason) {
|
|
9275
|
-
const label = HARNESS_LABELS[runtime];
|
|
9276
|
-
switch (reason) {
|
|
9277
|
-
case "timeout":
|
|
9278
|
-
return `Couldn't check whether ${label} is signed in: the check didn't answer in time. Try again.`;
|
|
9279
|
-
case "spawn":
|
|
9280
|
-
return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
9281
|
-
case "config":
|
|
9282
|
-
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join7(homedir6(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
|
|
9283
|
-
case "unreadable":
|
|
9284
|
-
return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
|
|
9285
|
-
}
|
|
9286
|
-
}
|
|
9287
|
-
function bundleMissingText(runtime) {
|
|
9288
|
-
const label = HARNESS_LABELS[runtime];
|
|
9289
|
-
return `This Cabane install is missing its bundled ${label}. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
9290
|
-
}
|
|
9291
|
-
function unusableReason(runtime, check) {
|
|
9292
|
-
if (check.status === "bundle_missing") return bundleMissingText(runtime);
|
|
9293
|
-
if (check.status === "check_failed") return failureText(runtime, check.reason);
|
|
9294
|
-
return null;
|
|
9295
|
-
}
|
|
9296
|
-
function agentRows(detection, opts = {}) {
|
|
9297
|
-
return HARNESS_RUNTIMES.map((runtime) => {
|
|
9298
|
-
const state = detection[runtime];
|
|
9299
|
-
return {
|
|
9300
|
-
runtime,
|
|
9301
|
-
label: HARNESS_LABELS[runtime],
|
|
9302
|
-
text: agentRowText(runtime, state, opts),
|
|
9303
|
-
usable: state.state === "usable"
|
|
9304
|
-
};
|
|
9305
|
-
});
|
|
9306
|
-
}
|
|
9307
|
-
function agentRowText(runtime, state, opts = {}) {
|
|
9308
|
-
const word = HARNESS_STATE_LABEL[harnessEntry(runtime, state).state];
|
|
9309
|
-
switch (state.state) {
|
|
9310
|
-
case "usable": {
|
|
9311
|
-
const version = state.version ? ` (${state.version})` : "";
|
|
9312
|
-
if (state.via === "api_key") return `${word} \u2014 API key configured${version}`;
|
|
9313
|
-
if (state.via === "serve") return `${word} \u2014 running at ${state.url ?? ""}`;
|
|
9314
|
-
return `${word} \u2014 signed in${version}`;
|
|
9315
|
-
}
|
|
9316
|
-
case "signed_out":
|
|
9317
|
-
return opts.signInHint && runtime !== "opencode" ? `${word} \u2014 run ${loginCommand(runtime)}` : word;
|
|
9318
|
-
case "not_running":
|
|
9319
|
-
return `${word} \u2014 not running`;
|
|
9320
|
-
case "off":
|
|
9321
|
-
return `${word} \u2014 disabled in config`;
|
|
9322
|
-
case "problem":
|
|
9323
|
-
return runtime === "opencode" ? `${word} \u2014 not running` : `${word} \u2014 ${unusableReason(runtime, state.check) ?? "could not be checked"}`;
|
|
9324
|
-
}
|
|
9325
|
-
}
|
|
9326
|
-
var AGENT_CHOICES = [
|
|
9327
|
-
{ value: "claude-code", label: "Claude Code", hint: "a Claude Pro or Max plan" },
|
|
9328
|
-
{ value: "codex", label: "Codex", hint: "a ChatGPT plan" },
|
|
9329
|
-
{ value: "opencode", label: "OpenCode" }
|
|
9330
|
-
];
|
|
9331
|
-
var CHOOSE_AGENT = "Cabane needs one of them. Which will you set up?";
|
|
9332
|
-
var SETUP_AGAIN = `Then ${cabane("status")} shows it signed in; nothing else to run.`;
|
|
9333
|
-
function agentNote(runtime, c = {}) {
|
|
9334
|
-
const k = ctx(c);
|
|
9335
|
-
if (k.windows) return windowsAgentNote(runtime, k);
|
|
9336
|
-
if (runtime === "claude-code") {
|
|
9337
|
-
const commands = ["curl -fsSL https://claude.ai/install.sh | bash"];
|
|
9338
|
-
const localBin = join7(k.home, ".local", "bin");
|
|
9339
|
-
const needsPath = !dirOnPath(localBin, k.path);
|
|
9340
|
-
const profile = k.profile ? tildePath(k.profile, k.home) : null;
|
|
9341
|
-
let sourced = null;
|
|
9342
|
-
if (needsPath) {
|
|
9343
|
-
if (profile) {
|
|
9344
|
-
commands.push(`echo 'export PATH="$HOME/.local/bin:$PATH"' >> ${profile}`);
|
|
9345
|
-
sourced = `source ${profile}`;
|
|
9346
|
-
commands.push(sourced);
|
|
9347
|
-
} else {
|
|
9348
|
-
commands.push('export PATH="$HOME/.local/bin:$PATH"');
|
|
9349
|
-
}
|
|
9350
|
-
}
|
|
9351
|
-
if (k.reload && k.reload !== sourced) commands.push(k.reload);
|
|
9352
|
-
commands.push("claude auth login");
|
|
9353
|
-
return {
|
|
9354
|
-
title: "Set up Claude Code",
|
|
9355
|
-
lines: [
|
|
9356
|
-
"Run these lines here. The last one opens your browser",
|
|
9357
|
-
"to sign in with your Claude account.",
|
|
9358
|
-
"",
|
|
9359
|
-
...commands.map(indent),
|
|
9360
|
-
"",
|
|
9361
|
-
SETUP_AGAIN
|
|
9362
|
-
]
|
|
9363
|
-
};
|
|
9364
|
-
}
|
|
9365
|
-
if (runtime === "codex") {
|
|
9366
|
-
const own = k.hasNpm || k.install !== "script";
|
|
9367
|
-
const on = own ? "" : 'PATH="$HOME/.cabane/node/bin:$PATH" ';
|
|
9368
|
-
const commands = [
|
|
9369
|
-
`${on}npm i -g @openai/codex`,
|
|
9370
|
-
...k.reload ? [k.reload] : [],
|
|
9371
|
-
`${on}codex login`
|
|
9372
|
-
];
|
|
9373
|
-
return {
|
|
9374
|
-
title: "Set up Codex",
|
|
9375
|
-
lines: [
|
|
9376
|
-
"Run these lines here. The last one opens your browser",
|
|
9377
|
-
"to sign in with your ChatGPT account.",
|
|
9378
|
-
"",
|
|
9379
|
-
...commands.map(indent),
|
|
9380
|
-
"",
|
|
9381
|
-
SETUP_AGAIN
|
|
9382
|
-
]
|
|
9383
|
-
};
|
|
9384
|
-
}
|
|
9385
|
-
const here = [...k.reload ? [k.reload] : [], "curl -fsSL https://opencode.ai/install | bash"];
|
|
9386
|
-
return {
|
|
9387
|
-
title: "Set up OpenCode",
|
|
9388
|
-
lines: [
|
|
9389
|
-
`Run the ${here.length > 1 ? "first two lines" : "first line"} here. Then open a second terminal`,
|
|
9390
|
-
"window, run the last line there, and leave that window open.",
|
|
9391
|
-
"",
|
|
9392
|
-
...here.map(indent),
|
|
9393
|
-
indent("opencode serve --port 4096"),
|
|
9394
|
-
"",
|
|
9395
|
-
`Then ${cabane("status")} shows it signed in; nothing else to run.`
|
|
9396
|
-
]
|
|
9397
|
-
};
|
|
9398
|
-
}
|
|
9399
|
-
function windowsAgentNote(runtime, k) {
|
|
9400
|
-
if (runtime === "claude-code") {
|
|
9401
|
-
const commands = ["irm https://claude.ai/install.ps1 | iex"];
|
|
9402
|
-
if (!windowsDirOnPath(win32.join(k.home, ".local", "bin"), k.path)) {
|
|
9403
|
-
commands.push(
|
|
9404
|
-
`$bin = "$env:USERPROFILE\\.local\\bin"`,
|
|
9405
|
-
`$p = [Environment]::GetEnvironmentVariable('PATH', 'User')`,
|
|
9406
|
-
`[Environment]::SetEnvironmentVariable('PATH', "$p;$bin", 'User')`,
|
|
9407
|
-
`$env:PATH += ";$bin"`
|
|
9408
|
-
);
|
|
9409
|
-
}
|
|
9410
|
-
commands.push("claude auth login");
|
|
9411
|
-
return {
|
|
9412
|
-
title: "Set up Claude Code",
|
|
9413
|
-
lines: [
|
|
9414
|
-
"Run these lines in PowerShell. The last one opens your browser",
|
|
9415
|
-
"to sign in with your Claude account.",
|
|
9416
|
-
"",
|
|
9417
|
-
...commands.map(indent),
|
|
9418
|
-
"",
|
|
9419
|
-
SETUP_AGAIN
|
|
9420
|
-
]
|
|
9421
|
-
};
|
|
9422
|
-
}
|
|
9423
|
-
if (runtime === "codex") {
|
|
9424
|
-
return {
|
|
9425
|
-
title: "Set up Codex",
|
|
9426
|
-
lines: [
|
|
9427
|
-
"Run these lines here. The last one opens your browser",
|
|
9428
|
-
"to sign in with your ChatGPT account.",
|
|
9429
|
-
"",
|
|
9430
|
-
indent("npm i -g @openai/codex"),
|
|
9431
|
-
indent("codex login"),
|
|
9432
|
-
"",
|
|
9433
|
-
SETUP_AGAIN
|
|
9434
|
-
]
|
|
9435
|
-
};
|
|
9436
|
-
}
|
|
9437
|
-
return {
|
|
9438
|
-
title: "Set up OpenCode",
|
|
9439
|
-
lines: [
|
|
9440
|
-
"Run the first line here. Then open a second terminal",
|
|
9441
|
-
"window, run the last line there, and leave that window open.",
|
|
9442
|
-
"",
|
|
9443
|
-
indent("npm i -g opencode-ai"),
|
|
9444
|
-
indent("opencode serve --port 4096"),
|
|
9445
|
-
"",
|
|
9446
|
-
`Then ${cabane("status")} shows it signed in; nothing else to run.`
|
|
9447
|
-
]
|
|
9448
|
-
};
|
|
9449
|
-
}
|
|
9450
|
-
function windowsDirOnPath(dir2, path) {
|
|
9451
|
-
const norm = (entry) => entry.replace(/[\\/]+$/, "").toLowerCase();
|
|
9452
|
-
return (path ?? "").split(";").some((entry) => norm(entry) === norm(dir2));
|
|
9453
|
-
}
|
|
9454
|
-
function indent(line) {
|
|
9455
|
-
return ` ${line}`;
|
|
9456
|
-
}
|
|
9457
|
-
function spaced(sentence) {
|
|
9458
|
-
return sentence ? ` ${sentence}` : "";
|
|
9459
|
-
}
|
|
9494
|
+
init_harness_copy();
|
|
9460
9495
|
|
|
9461
9496
|
// src/harness-detect.ts
|
|
9462
9497
|
init_config();
|
|
@@ -9734,6 +9769,7 @@ async function liveCompanionState() {
|
|
|
9734
9769
|
// src/commands/setup.ts
|
|
9735
9770
|
init_cabane_command();
|
|
9736
9771
|
init_config();
|
|
9772
|
+
init_harness_copy();
|
|
9737
9773
|
|
|
9738
9774
|
// src/logger.ts
|
|
9739
9775
|
init_config();
|
|
@@ -10028,6 +10064,8 @@ async function awaitEnrollment(baseUrl, code, opts = {}) {
|
|
|
10028
10064
|
|
|
10029
10065
|
// src/terminal-copy.ts
|
|
10030
10066
|
init_cabane_command();
|
|
10067
|
+
init_device_state();
|
|
10068
|
+
init_harness_copy();
|
|
10031
10069
|
var UNPAIRED = `This device isn't paired yet. Run ${cabane("setup")} on it.`;
|
|
10032
10070
|
var lease = DEFAULT_DEVICE_OFFLINE_THRESHOLD_MS;
|
|
10033
10071
|
function age(at, now) {
|
|
@@ -10547,6 +10585,7 @@ var CompanionStateHub = class {
|
|
|
10547
10585
|
init_errors();
|
|
10548
10586
|
|
|
10549
10587
|
// src/heartbeat-status.ts
|
|
10588
|
+
init_device_state();
|
|
10550
10589
|
import { z as z3 } from "zod";
|
|
10551
10590
|
var acknowledgedDeviceSchema = z3.object({
|
|
10552
10591
|
state: z3.enum(DEVICE_STATES),
|
|
@@ -10571,72 +10610,90 @@ import { hostname as hostname2 } from "os";
|
|
|
10571
10610
|
// src/log-copy.ts
|
|
10572
10611
|
init_failure();
|
|
10573
10612
|
init_bundled_binaries();
|
|
10574
|
-
|
|
10613
|
+
init_harness_copy();
|
|
10614
|
+
var RESTART = "`cabane stop` and `cabane start`";
|
|
10575
10615
|
function turnFailureCopy(reason, runtime) {
|
|
10576
10616
|
const failure = decodeFailureReason(reason);
|
|
10617
|
+
const harness = harnessLabel(runtime);
|
|
10577
10618
|
if (failure?.kind === "auth_expired") {
|
|
10578
10619
|
if (runtime === "claude-code")
|
|
10579
|
-
return `Claude Code is signed out on this device. To sign back in, run \`claude auth login\`, ${RESTART}.
|
|
10620
|
+
return `Claude Code is signed out on this device. To sign back in, run \`claude auth login\`, ${RESTART}. Reply in Cabane to try again.`;
|
|
10580
10621
|
if (runtime === "codex")
|
|
10581
|
-
return `Codex is signed out on this device. To sign back in, run \`codex login\`, ${RESTART}.
|
|
10582
|
-
return
|
|
10622
|
+
return `Codex is signed out on this device. To sign back in, run \`codex login\`, ${RESTART}. Reply in Cabane to try again.`;
|
|
10623
|
+
return `${harness} is signed out on this device. Sign in to ${runtime === "opencode" ? "OpenCode" : "it"}, then run ${RESTART}. Reply in Cabane to try again.`;
|
|
10583
10624
|
}
|
|
10584
10625
|
if (failure?.kind === "usage_capped")
|
|
10585
|
-
return failure.resetsAt ? `The usage limit is reached
|
|
10626
|
+
return failure.resetsAt ? `The usage limit is reached and resets around ${new Date(failure.resetsAt).toLocaleTimeString([], { timeStyle: "short" })}. Reply in Cabane after reset to try again.` : "The usage limit is reached. Reply in Cabane later to try again.";
|
|
10586
10627
|
if (failure?.kind === "rate_limited")
|
|
10587
10628
|
return "The provider is limiting requests. Reply in Cabane to try again.";
|
|
10588
10629
|
if (failure?.kind === "server_error")
|
|
10589
10630
|
return "The provider had a temporary error. Reply in Cabane to try again.";
|
|
10590
|
-
if (reason?.startsWith("missing_secret:"))
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10631
|
+
if (reason?.startsWith("missing_secret:")) {
|
|
10632
|
+
const tokens = humanText(reason.slice(15)).split(",").map((name) => name.trim()).filter(Boolean);
|
|
10633
|
+
const more = tokens.reduce(
|
|
10634
|
+
(n, token) => /^\+\d+$/.test(token) ? n + Number(token.slice(1)) : n,
|
|
10635
|
+
0
|
|
10636
|
+
);
|
|
10637
|
+
const names = tokens.filter((token) => !/^\+\d+$/.test(token)).map((name) => `\`${name}\``);
|
|
10638
|
+
const count = names.length + more;
|
|
10639
|
+
const needs = names.length === 0 ? count > 1 ? `${count} secrets` : "a secret" : `${count > 1 ? "secrets" : "a secret"} called ${more > 0 ? `${names.join(", ")} and ${more} more` : names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names.at(-1)}` : names[0]}`;
|
|
10640
|
+
return `The agent's tools need ${needs}, which this device doesn't have. Add ${count > 1 ? "them" : "it"} to the secrets file, \`~/.cabane/secrets.json\`. Reply in Cabane to try again.`;
|
|
10641
|
+
}
|
|
10642
|
+
if (reason?.startsWith("runtime_incomplete:")) {
|
|
10643
|
+
const commands = repairCommands(void 0, (rest) => `cabane ${rest}`);
|
|
10644
|
+
const stopsFirst = commands.length > 1;
|
|
10645
|
+
if (!stopsFirst) commands.push("cabane stop");
|
|
10646
|
+
commands.push("cabane start");
|
|
10647
|
+
const list = commands.map((command) => `\`${command}\``);
|
|
10648
|
+
return `${harnessLabel(runtime ?? reason.slice("runtime_incomplete:".length))}'s installation on this device is incomplete. Run ${list.slice(0, -1).join(", ")} and ${list.at(-1)}. ${stopsFirst ? `${STOP_WARNING} ` : ""}Reply in Cabane to try again.`;
|
|
10649
|
+
}
|
|
10650
|
+
const unavailable = /^(codex|opencode)_unavailable:/.exec(reason ?? "")?.[1] ?? (reason?.startsWith("runtime_unavailable:") ? reason.slice("runtime_unavailable:".length).trim() : void 0);
|
|
10651
|
+
if (unavailable !== void 0)
|
|
10652
|
+
return `${harnessLabel(runtime ?? unavailable)} isn't connected on this device. Run \`cabane setup\`, or choose a different harness in the agent's settings in Cabane. Reply in Cabane to try again.`;
|
|
10596
10653
|
if (reason?.startsWith("model_unavailable:"))
|
|
10597
|
-
return "The
|
|
10654
|
+
return "The agent's model isn't available to the harness on this device. Choose a different model in the agent's settings in Cabane. Reply in Cabane to try again.";
|
|
10598
10655
|
if (reason?.startsWith("prepare_failed:"))
|
|
10599
|
-
return `
|
|
10656
|
+
return `This device couldn't get a folder ready for the agent's turn. It reported: "${humanText(reason.slice(15))}". Check the agent's prepare command in \`~/.cabane/config.json\`. Reply in Cabane to try again.`;
|
|
10600
10657
|
if (reason?.startsWith("fetch_failed:"))
|
|
10601
|
-
return `
|
|
10658
|
+
return `This device couldn't load the conversation (${humanText(reason.slice(13))}), so the turn didn't run. Reply in Cabane to try again.`;
|
|
10602
10659
|
if (reason === "timeout_idle")
|
|
10603
|
-
return "The agent stopped
|
|
10660
|
+
return "The agent went quiet for too long, so the turn was stopped. Reply in Cabane to try again.";
|
|
10604
10661
|
if (reason === "timeout_total")
|
|
10605
|
-
return "The turn ran
|
|
10662
|
+
return "The turn ran too long and was stopped. Reply in Cabane to continue.";
|
|
10606
10663
|
if (reason === "result_error:error_max_turns")
|
|
10607
10664
|
return "The agent reached their step limit. Reply in Cabane to continue.";
|
|
10608
|
-
if (reason === "
|
|
10609
|
-
return
|
|
10665
|
+
if (reason === "empty_result" || reason === "empty_result_unverified")
|
|
10666
|
+
return `${harness} finished the turn without a reply. Reply in Cabane to try again.`;
|
|
10610
10667
|
if (reason === "session_start_failed")
|
|
10611
|
-
return
|
|
10612
|
-
if (reason === "no_terminal")
|
|
10613
|
-
return
|
|
10668
|
+
return `${harness} closed before the turn could start, so the turn didn't run. Reply in Cabane to try again.`;
|
|
10669
|
+
if (reason === "no_terminal" || reason === "no_result")
|
|
10670
|
+
return `${harness} stopped before the turn finished. Reply in Cabane to try again.`;
|
|
10614
10671
|
if (reason === "lease_unconfirmed")
|
|
10615
|
-
return "
|
|
10672
|
+
return "Before a turn starts, this device checks with Cabane that it should run it. That check didn't go through, so nothing ran. Check this device's connection. Reply in Cabane to try again.";
|
|
10616
10673
|
if (reason === "workspace_tools_missing")
|
|
10617
|
-
return "The Cabane workspace tools
|
|
10674
|
+
return "The agent's Cabane workspace tools didn't load, so they couldn't reply. Reply in Cabane to try again.";
|
|
10618
10675
|
if (reason?.startsWith("seq_floor_unavailable:"))
|
|
10619
|
-
return "The interrupted
|
|
10676
|
+
return "The turn was interrupted, and it couldn't safely pick back up where it left off. Reply in Cabane to continue.";
|
|
10620
10677
|
if (reason === "result_error:error_max_budget_usd")
|
|
10621
|
-
return "The agent reached their spending limit. Check their budget settings
|
|
10678
|
+
return "The agent reached their spending limit. Check their budget settings. Reply in Cabane to try again.";
|
|
10622
10679
|
if (reason === "result_error:error_max_structured_output_retries")
|
|
10623
|
-
return "The agent
|
|
10680
|
+
return "The agent couldn't produce the response format they were asked for. Check that format. Reply in Cabane to try again.";
|
|
10624
10681
|
if (reason?.startsWith("result_error:"))
|
|
10625
|
-
return
|
|
10682
|
+
return `${harness} ended the turn with an error. Check the agent's transcript. Reply in Cabane to try again.`;
|
|
10626
10683
|
if (reason === "setup_failed")
|
|
10627
|
-
return "The turn
|
|
10684
|
+
return "The companion couldn't set up this turn, so it didn't run. Check the agent's setup on this device. Reply in Cabane to try again.";
|
|
10628
10685
|
if (reason === "unexpected_role")
|
|
10629
|
-
return "
|
|
10686
|
+
return "That message couldn't start a turn for the agent. Send a new message in Cabane.";
|
|
10630
10687
|
if (reason === "lease_lost" || reason?.startsWith("lease_refused:"))
|
|
10631
|
-
return "The turn
|
|
10688
|
+
return "The turn lost its connection to Cabane. Reply in Cabane to try again.";
|
|
10632
10689
|
const detail = humanText(reason?.replace(/^error:/, "") ?? "").slice(0, 400);
|
|
10633
10690
|
if (/\b(EACCES|EPERM)\b|permission denied/i.test(detail)) {
|
|
10634
10691
|
const readable = detail.replace(/\b(EACCES|EPERM)\b/g, "permission denied");
|
|
10635
|
-
return `The agent was denied access: ${readable}. Check
|
|
10692
|
+
return `The agent was denied access: ${readable}. Check the permissions on that file or command. Reply in Cabane to try again.`;
|
|
10636
10693
|
}
|
|
10637
10694
|
if (detail)
|
|
10638
|
-
return `The agent reported: ${detail}. Check the agent setup on this device
|
|
10639
|
-
return "The agent
|
|
10695
|
+
return `The agent reported an error: ${detail}. Check the agent's setup on this device. Reply in Cabane to try again.`;
|
|
10696
|
+
return "The agent couldn't finish this turn. Reply in Cabane to try again.";
|
|
10640
10697
|
}
|
|
10641
10698
|
|
|
10642
10699
|
// src/supervisor.ts
|
|
@@ -11823,6 +11880,7 @@ function runtimeIssuesFor(gates) {
|
|
|
11823
11880
|
}
|
|
11824
11881
|
|
|
11825
11882
|
// src/supervisor.ts
|
|
11883
|
+
init_harness_state();
|
|
11826
11884
|
init_harness_versions();
|
|
11827
11885
|
init_harness_usable();
|
|
11828
11886
|
|
|
@@ -13664,7 +13722,7 @@ var CompanionSupervisor = class {
|
|
|
13664
13722
|
attempt,
|
|
13665
13723
|
cap: MAX_RESUME_ATTEMPTS
|
|
13666
13724
|
},
|
|
13667
|
-
"
|
|
13725
|
+
"The turn was interrupted, and it couldn't pick back up after several attempts. Reply in Cabane to continue."
|
|
13668
13726
|
);
|
|
13669
13727
|
try {
|
|
13670
13728
|
await agent.api.setActiveRun(workspaceId, payload.conversationId, payload.agentId, {
|
|
@@ -15419,6 +15477,7 @@ import { readdirSync as readdirSync4, realpathSync as realpathSync6, existsSync
|
|
|
15419
15477
|
import { homedir as homedir10 } from "os";
|
|
15420
15478
|
import { dirname as dirname12, join as join28 } from "path";
|
|
15421
15479
|
init_config();
|
|
15480
|
+
init_harness_copy();
|
|
15422
15481
|
init_secrets();
|
|
15423
15482
|
init_term();
|
|
15424
15483
|
init_transcript_writer();
|
package/dist/runtime.js
CHANGED
|
@@ -1500,73 +1500,166 @@ var NEGATED_CAP = /not (your|a) usage limit/g;
|
|
|
1500
1500
|
var RATE_PATTERNS = [/rate[_ ]?limit/, /\b429\b/, /too many requests/];
|
|
1501
1501
|
var BARE_LIMIT = /\blimit (reached|exceeded)\b/;
|
|
1502
1502
|
|
|
1503
|
+
// src/harness-copy.ts
|
|
1504
|
+
import { homedir as homedir6 } from "os";
|
|
1505
|
+
import { join as join9, win32 } from "path";
|
|
1506
|
+
|
|
1507
|
+
// src/harness-state.ts
|
|
1508
|
+
function harnessEntry(runtime, state) {
|
|
1509
|
+
switch (state.state) {
|
|
1510
|
+
case "usable":
|
|
1511
|
+
return {
|
|
1512
|
+
runtime,
|
|
1513
|
+
state: "connected",
|
|
1514
|
+
reason: state.via,
|
|
1515
|
+
version: state.version,
|
|
1516
|
+
...state.url ? { url: state.url } : {}
|
|
1517
|
+
};
|
|
1518
|
+
case "signed_out":
|
|
1519
|
+
return { runtime, state: "needs_sign_in", reason: "signed_out", version: state.version };
|
|
1520
|
+
case "not_running":
|
|
1521
|
+
return { runtime, state: "problem", reason: "not_running", url: state.url };
|
|
1522
|
+
case "off":
|
|
1523
|
+
return { runtime, state: "off" };
|
|
1524
|
+
case "problem":
|
|
1525
|
+
return { runtime, state: "problem", reason: checkReason(state.check) };
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
function harnessEntriesFromDetection(detection) {
|
|
1529
|
+
return HARNESS_RUNTIMES.map((runtime) => harnessEntry(runtime, detection[runtime]));
|
|
1530
|
+
}
|
|
1531
|
+
function checkReason(check) {
|
|
1532
|
+
switch (check.status) {
|
|
1533
|
+
case "bundle_missing":
|
|
1534
|
+
return check.bundled;
|
|
1535
|
+
case "check_failed":
|
|
1536
|
+
return `check_failed:${check.reason}`;
|
|
1537
|
+
default:
|
|
1538
|
+
return check.status;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/harness-copy.ts
|
|
1543
|
+
var HARNESS_LABELS = {
|
|
1544
|
+
"claude-code": "Claude Code",
|
|
1545
|
+
codex: "Codex",
|
|
1546
|
+
opencode: "OpenCode"
|
|
1547
|
+
};
|
|
1548
|
+
function harnessLabel(runtime) {
|
|
1549
|
+
const known = HARNESS_RUNTIMES.find((candidate) => candidate === runtime);
|
|
1550
|
+
return known ? HARNESS_LABELS[known] : "The harness";
|
|
1551
|
+
}
|
|
1552
|
+
function failureText(runtime, reason) {
|
|
1553
|
+
const label = HARNESS_LABELS[runtime];
|
|
1554
|
+
switch (reason) {
|
|
1555
|
+
case "timeout":
|
|
1556
|
+
return `Couldn't check whether ${label} is signed in: the check didn't answer in time. Try again.`;
|
|
1557
|
+
case "spawn":
|
|
1558
|
+
return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
1559
|
+
case "config":
|
|
1560
|
+
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join9(homedir6(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
|
|
1561
|
+
case "unreadable":
|
|
1562
|
+
return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
function bundleMissingText(runtime) {
|
|
1566
|
+
const label = HARNESS_LABELS[runtime];
|
|
1567
|
+
return `This Cabane install is missing its bundled ${label}. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
1568
|
+
}
|
|
1569
|
+
function unusableReason(runtime, check) {
|
|
1570
|
+
if (check.status === "bundle_missing") return bundleMissingText(runtime);
|
|
1571
|
+
if (check.status === "check_failed") return failureText(runtime, check.reason);
|
|
1572
|
+
return null;
|
|
1573
|
+
}
|
|
1574
|
+
var SETUP_AGAIN = `Then ${cabane("status")} shows it signed in; nothing else to run.`;
|
|
1575
|
+
function spaced(sentence) {
|
|
1576
|
+
return sentence ? ` ${sentence}` : "";
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1503
1579
|
// src/log-copy.ts
|
|
1504
|
-
var RESTART = "
|
|
1580
|
+
var RESTART = "`cabane stop` and `cabane start`";
|
|
1505
1581
|
function turnFailureCopy(reason, runtime) {
|
|
1506
1582
|
const failure = decodeFailureReason(reason);
|
|
1583
|
+
const harness = harnessLabel(runtime);
|
|
1507
1584
|
if (failure?.kind === "auth_expired") {
|
|
1508
1585
|
if (runtime === "claude-code")
|
|
1509
|
-
return `Claude Code is signed out on this device. To sign back in, run \`claude auth login\`, ${RESTART}.
|
|
1586
|
+
return `Claude Code is signed out on this device. To sign back in, run \`claude auth login\`, ${RESTART}. Reply in Cabane to try again.`;
|
|
1510
1587
|
if (runtime === "codex")
|
|
1511
|
-
return `Codex is signed out on this device. To sign back in, run \`codex login\`, ${RESTART}.
|
|
1512
|
-
return
|
|
1588
|
+
return `Codex is signed out on this device. To sign back in, run \`codex login\`, ${RESTART}. Reply in Cabane to try again.`;
|
|
1589
|
+
return `${harness} is signed out on this device. Sign in to ${runtime === "opencode" ? "OpenCode" : "it"}, then run ${RESTART}. Reply in Cabane to try again.`;
|
|
1513
1590
|
}
|
|
1514
1591
|
if (failure?.kind === "usage_capped")
|
|
1515
|
-
return failure.resetsAt ? `The usage limit is reached
|
|
1592
|
+
return failure.resetsAt ? `The usage limit is reached and resets around ${new Date(failure.resetsAt).toLocaleTimeString([], { timeStyle: "short" })}. Reply in Cabane after reset to try again.` : "The usage limit is reached. Reply in Cabane later to try again.";
|
|
1516
1593
|
if (failure?.kind === "rate_limited")
|
|
1517
1594
|
return "The provider is limiting requests. Reply in Cabane to try again.";
|
|
1518
1595
|
if (failure?.kind === "server_error")
|
|
1519
1596
|
return "The provider had a temporary error. Reply in Cabane to try again.";
|
|
1520
|
-
if (reason?.startsWith("missing_secret:"))
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1597
|
+
if (reason?.startsWith("missing_secret:")) {
|
|
1598
|
+
const tokens = humanText(reason.slice(15)).split(",").map((name) => name.trim()).filter(Boolean);
|
|
1599
|
+
const more = tokens.reduce(
|
|
1600
|
+
(n, token) => /^\+\d+$/.test(token) ? n + Number(token.slice(1)) : n,
|
|
1601
|
+
0
|
|
1602
|
+
);
|
|
1603
|
+
const names = tokens.filter((token) => !/^\+\d+$/.test(token)).map((name) => `\`${name}\``);
|
|
1604
|
+
const count = names.length + more;
|
|
1605
|
+
const needs = names.length === 0 ? count > 1 ? `${count} secrets` : "a secret" : `${count > 1 ? "secrets" : "a secret"} called ${more > 0 ? `${names.join(", ")} and ${more} more` : names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names.at(-1)}` : names[0]}`;
|
|
1606
|
+
return `The agent's tools need ${needs}, which this device doesn't have. Add ${count > 1 ? "them" : "it"} to the secrets file, \`~/.cabane/secrets.json\`. Reply in Cabane to try again.`;
|
|
1607
|
+
}
|
|
1608
|
+
if (reason?.startsWith("runtime_incomplete:")) {
|
|
1609
|
+
const commands = repairCommands(void 0, (rest) => `cabane ${rest}`);
|
|
1610
|
+
const stopsFirst = commands.length > 1;
|
|
1611
|
+
if (!stopsFirst) commands.push("cabane stop");
|
|
1612
|
+
commands.push("cabane start");
|
|
1613
|
+
const list = commands.map((command) => `\`${command}\``);
|
|
1614
|
+
return `${harnessLabel(runtime ?? reason.slice("runtime_incomplete:".length))}'s installation on this device is incomplete. Run ${list.slice(0, -1).join(", ")} and ${list.at(-1)}. ${stopsFirst ? `${STOP_WARNING} ` : ""}Reply in Cabane to try again.`;
|
|
1615
|
+
}
|
|
1616
|
+
const unavailable = /^(codex|opencode)_unavailable:/.exec(reason ?? "")?.[1] ?? (reason?.startsWith("runtime_unavailable:") ? reason.slice("runtime_unavailable:".length).trim() : void 0);
|
|
1617
|
+
if (unavailable !== void 0)
|
|
1618
|
+
return `${harnessLabel(runtime ?? unavailable)} isn't connected on this device. Run \`cabane setup\`, or choose a different harness in the agent's settings in Cabane. Reply in Cabane to try again.`;
|
|
1526
1619
|
if (reason?.startsWith("model_unavailable:"))
|
|
1527
|
-
return "The
|
|
1620
|
+
return "The agent's model isn't available to the harness on this device. Choose a different model in the agent's settings in Cabane. Reply in Cabane to try again.";
|
|
1528
1621
|
if (reason?.startsWith("prepare_failed:"))
|
|
1529
|
-
return `
|
|
1622
|
+
return `This device couldn't get a folder ready for the agent's turn. It reported: "${humanText(reason.slice(15))}". Check the agent's prepare command in \`~/.cabane/config.json\`. Reply in Cabane to try again.`;
|
|
1530
1623
|
if (reason?.startsWith("fetch_failed:"))
|
|
1531
|
-
return `
|
|
1624
|
+
return `This device couldn't load the conversation (${humanText(reason.slice(13))}), so the turn didn't run. Reply in Cabane to try again.`;
|
|
1532
1625
|
if (reason === "timeout_idle")
|
|
1533
|
-
return "The agent stopped
|
|
1626
|
+
return "The agent went quiet for too long, so the turn was stopped. Reply in Cabane to try again.";
|
|
1534
1627
|
if (reason === "timeout_total")
|
|
1535
|
-
return "The turn ran
|
|
1628
|
+
return "The turn ran too long and was stopped. Reply in Cabane to continue.";
|
|
1536
1629
|
if (reason === "result_error:error_max_turns")
|
|
1537
1630
|
return "The agent reached their step limit. Reply in Cabane to continue.";
|
|
1538
|
-
if (reason === "
|
|
1539
|
-
return
|
|
1631
|
+
if (reason === "empty_result" || reason === "empty_result_unverified")
|
|
1632
|
+
return `${harness} finished the turn without a reply. Reply in Cabane to try again.`;
|
|
1540
1633
|
if (reason === "session_start_failed")
|
|
1541
|
-
return
|
|
1542
|
-
if (reason === "no_terminal")
|
|
1543
|
-
return
|
|
1634
|
+
return `${harness} closed before the turn could start, so the turn didn't run. Reply in Cabane to try again.`;
|
|
1635
|
+
if (reason === "no_terminal" || reason === "no_result")
|
|
1636
|
+
return `${harness} stopped before the turn finished. Reply in Cabane to try again.`;
|
|
1544
1637
|
if (reason === "lease_unconfirmed")
|
|
1545
|
-
return "
|
|
1638
|
+
return "Before a turn starts, this device checks with Cabane that it should run it. That check didn't go through, so nothing ran. Check this device's connection. Reply in Cabane to try again.";
|
|
1546
1639
|
if (reason === "workspace_tools_missing")
|
|
1547
|
-
return "The Cabane workspace tools
|
|
1640
|
+
return "The agent's Cabane workspace tools didn't load, so they couldn't reply. Reply in Cabane to try again.";
|
|
1548
1641
|
if (reason?.startsWith("seq_floor_unavailable:"))
|
|
1549
|
-
return "The interrupted
|
|
1642
|
+
return "The turn was interrupted, and it couldn't safely pick back up where it left off. Reply in Cabane to continue.";
|
|
1550
1643
|
if (reason === "result_error:error_max_budget_usd")
|
|
1551
|
-
return "The agent reached their spending limit. Check their budget settings
|
|
1644
|
+
return "The agent reached their spending limit. Check their budget settings. Reply in Cabane to try again.";
|
|
1552
1645
|
if (reason === "result_error:error_max_structured_output_retries")
|
|
1553
|
-
return "The agent
|
|
1646
|
+
return "The agent couldn't produce the response format they were asked for. Check that format. Reply in Cabane to try again.";
|
|
1554
1647
|
if (reason?.startsWith("result_error:"))
|
|
1555
|
-
return
|
|
1648
|
+
return `${harness} ended the turn with an error. Check the agent's transcript. Reply in Cabane to try again.`;
|
|
1556
1649
|
if (reason === "setup_failed")
|
|
1557
|
-
return "The turn
|
|
1650
|
+
return "The companion couldn't set up this turn, so it didn't run. Check the agent's setup on this device. Reply in Cabane to try again.";
|
|
1558
1651
|
if (reason === "unexpected_role")
|
|
1559
|
-
return "
|
|
1652
|
+
return "That message couldn't start a turn for the agent. Send a new message in Cabane.";
|
|
1560
1653
|
if (reason === "lease_lost" || reason?.startsWith("lease_refused:"))
|
|
1561
|
-
return "The turn
|
|
1654
|
+
return "The turn lost its connection to Cabane. Reply in Cabane to try again.";
|
|
1562
1655
|
const detail = humanText(reason?.replace(/^error:/, "") ?? "").slice(0, 400);
|
|
1563
1656
|
if (/\b(EACCES|EPERM)\b|permission denied/i.test(detail)) {
|
|
1564
1657
|
const readable = detail.replace(/\b(EACCES|EPERM)\b/g, "permission denied");
|
|
1565
|
-
return `The agent was denied access: ${readable}. Check
|
|
1658
|
+
return `The agent was denied access: ${readable}. Check the permissions on that file or command. Reply in Cabane to try again.`;
|
|
1566
1659
|
}
|
|
1567
1660
|
if (detail)
|
|
1568
|
-
return `The agent reported: ${detail}. Check the agent setup on this device
|
|
1569
|
-
return "The agent
|
|
1661
|
+
return `The agent reported an error: ${detail}. Check the agent's setup on this device. Reply in Cabane to try again.`;
|
|
1662
|
+
return "The agent couldn't finish this turn. Reply in Cabane to try again.";
|
|
1570
1663
|
}
|
|
1571
1664
|
|
|
1572
1665
|
// src/api-error-shape.ts
|
|
@@ -2304,17 +2397,17 @@ import {
|
|
|
2304
2397
|
rmSync as rmSync4,
|
|
2305
2398
|
writeFileSync as writeFileSync3
|
|
2306
2399
|
} from "fs";
|
|
2307
|
-
import { join as
|
|
2400
|
+
import { join as join10 } from "path";
|
|
2308
2401
|
import { z as z5 } from "zod";
|
|
2309
2402
|
var credentialMapSchema = z5.record(z5.string(), z5.string());
|
|
2310
2403
|
function credentialsDir() {
|
|
2311
|
-
return
|
|
2404
|
+
return join10(cabaneDir(), "credentials");
|
|
2312
2405
|
}
|
|
2313
2406
|
function legacyPath() {
|
|
2314
|
-
return
|
|
2407
|
+
return join10(cabaneDir(), "credentials.json");
|
|
2315
2408
|
}
|
|
2316
2409
|
function devicePath(deviceId) {
|
|
2317
|
-
return
|
|
2410
|
+
return join10(credentialsDir(), `${deviceId.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
|
|
2318
2411
|
}
|
|
2319
2412
|
function read(path) {
|
|
2320
2413
|
if (!existsSync6(path)) return null;
|
|
@@ -2383,13 +2476,13 @@ function pruneCredentials(deviceId, keepAgentIds) {
|
|
|
2383
2476
|
|
|
2384
2477
|
// src/dispatch-dedupe.ts
|
|
2385
2478
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync4, existsSync as existsSync7 } from "fs";
|
|
2386
|
-
import { join as
|
|
2479
|
+
import { join as join11 } from "path";
|
|
2387
2480
|
var MAX_IDS = 256;
|
|
2388
2481
|
function dir(log) {
|
|
2389
|
-
return
|
|
2482
|
+
return join11(cabaneDir(), log);
|
|
2390
2483
|
}
|
|
2391
2484
|
function pathFor(log, workspaceId) {
|
|
2392
|
-
return
|
|
2485
|
+
return join11(dir(log), encodeURIComponent(workspaceId));
|
|
2393
2486
|
}
|
|
2394
2487
|
function readIds(log, workspaceId) {
|
|
2395
2488
|
const path = pathFor(log, workspaceId);
|
|
@@ -2426,10 +2519,10 @@ function markCompleted(workspaceId, eventId) {
|
|
|
2426
2519
|
}
|
|
2427
2520
|
var MAX_RESUME_ATTEMPTS = 3;
|
|
2428
2521
|
function resumeDir() {
|
|
2429
|
-
return
|
|
2522
|
+
return join11(cabaneDir(), "resume-attempts");
|
|
2430
2523
|
}
|
|
2431
2524
|
function resumePathFor(workspaceId) {
|
|
2432
|
-
return
|
|
2525
|
+
return join11(resumeDir(), encodeURIComponent(workspaceId));
|
|
2433
2526
|
}
|
|
2434
2527
|
function readResumeCounts(workspaceId) {
|
|
2435
2528
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -2465,10 +2558,10 @@ function bumpResumeAttempt(workspaceId, eventId) {
|
|
|
2465
2558
|
return next;
|
|
2466
2559
|
}
|
|
2467
2560
|
function turnDir() {
|
|
2468
|
-
return
|
|
2561
|
+
return join11(cabaneDir(), "turns");
|
|
2469
2562
|
}
|
|
2470
2563
|
function turnPathFor(workspaceId) {
|
|
2471
|
-
return
|
|
2564
|
+
return join11(turnDir(), encodeURIComponent(workspaceId));
|
|
2472
2565
|
}
|
|
2473
2566
|
var TURN_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2474
2567
|
function readTurnIds(workspaceId) {
|
|
@@ -6053,7 +6146,7 @@ import { existsSync as existsSync11, mkdirSync as mkdirSync9 } from "fs";
|
|
|
6053
6146
|
|
|
6054
6147
|
// src/grants.ts
|
|
6055
6148
|
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
6056
|
-
import { join as
|
|
6149
|
+
import { join as join12 } from "path";
|
|
6057
6150
|
import { z as z14 } from "zod";
|
|
6058
6151
|
var VARIABLE_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
6059
6152
|
function allowedGrantVariable(name) {
|
|
@@ -6063,7 +6156,7 @@ function environmentSafe(value) {
|
|
|
6063
6156
|
return !value.includes("\0");
|
|
6064
6157
|
}
|
|
6065
6158
|
function grantsPath() {
|
|
6066
|
-
return
|
|
6159
|
+
return join12(cabaneDir(), "grants.json");
|
|
6067
6160
|
}
|
|
6068
6161
|
var grantsFileSchema = z14.record(z14.string(), z14.record(z14.string(), z14.string()));
|
|
6069
6162
|
function readGrants(keys, report) {
|
|
@@ -6112,7 +6205,7 @@ function loadGrantsFile(report) {
|
|
|
6112
6205
|
|
|
6113
6206
|
// src/work-dir.ts
|
|
6114
6207
|
import { statSync as statSync3 } from "fs";
|
|
6115
|
-
import { join as
|
|
6208
|
+
import { join as join13 } from "path";
|
|
6116
6209
|
|
|
6117
6210
|
// src/turn-control-tools.ts
|
|
6118
6211
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -6130,33 +6223,33 @@ var CANCEL_WAKE_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${CANCEL_WAKE_T
|
|
|
6130
6223
|
|
|
6131
6224
|
// src/codex-config.ts
|
|
6132
6225
|
import { readFile } from "fs/promises";
|
|
6133
|
-
import { homedir as
|
|
6134
|
-
import { join as
|
|
6226
|
+
import { homedir as homedir7 } from "os";
|
|
6227
|
+
import { join as join14 } from "path";
|
|
6135
6228
|
import { parse as parse2 } from "smol-toml";
|
|
6136
6229
|
|
|
6137
6230
|
// src/codex-images.ts
|
|
6138
6231
|
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
6139
6232
|
import { tmpdir } from "os";
|
|
6140
|
-
import { join as
|
|
6233
|
+
import { join as join15 } from "path";
|
|
6141
6234
|
|
|
6142
6235
|
// src/codex-compactions.ts
|
|
6143
6236
|
import { createReadStream } from "fs";
|
|
6144
6237
|
import { readdir } from "fs/promises";
|
|
6145
|
-
import { homedir as
|
|
6146
|
-
import { join as
|
|
6238
|
+
import { homedir as homedir8 } from "os";
|
|
6239
|
+
import { join as join16 } from "path";
|
|
6147
6240
|
import { createInterface as createInterface2 } from "readline";
|
|
6148
6241
|
|
|
6149
6242
|
// src/prepared.ts
|
|
6150
6243
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync9 } from "fs";
|
|
6151
|
-
import { join as
|
|
6244
|
+
import { join as join17 } from "path";
|
|
6152
6245
|
|
|
6153
6246
|
// src/secrets.ts
|
|
6154
6247
|
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
6155
|
-
import { join as
|
|
6248
|
+
import { join as join18 } from "path";
|
|
6156
6249
|
import { z as z16 } from "zod";
|
|
6157
6250
|
var PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
6158
6251
|
function secretsPath() {
|
|
6159
|
-
return
|
|
6252
|
+
return join18(cabaneDir(), "secrets.json");
|
|
6160
6253
|
}
|
|
6161
6254
|
var secretStoreSchema = z16.record(z16.string(), z16.string());
|
|
6162
6255
|
function loadSecretValues() {
|
|
@@ -6346,9 +6439,9 @@ function runBounded(path, args, env) {
|
|
|
6346
6439
|
|
|
6347
6440
|
// src/transcript-writer.ts
|
|
6348
6441
|
import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
|
|
6349
|
-
import { basename as basename3, dirname as dirname6, join as
|
|
6442
|
+
import { basename as basename3, dirname as dirname6, join as join19 } from "path";
|
|
6350
6443
|
function transcriptsDir() {
|
|
6351
|
-
return
|
|
6444
|
+
return join19(cabaneDir(), "transcripts");
|
|
6352
6445
|
}
|
|
6353
6446
|
|
|
6354
6447
|
// src/turn-execution.ts
|
|
@@ -6673,76 +6766,6 @@ function runtimeIssuesFor(gates) {
|
|
|
6673
6766
|
return issues;
|
|
6674
6767
|
}
|
|
6675
6768
|
|
|
6676
|
-
// src/harness-state.ts
|
|
6677
|
-
function harnessEntry(runtime, state) {
|
|
6678
|
-
switch (state.state) {
|
|
6679
|
-
case "usable":
|
|
6680
|
-
return {
|
|
6681
|
-
runtime,
|
|
6682
|
-
state: "connected",
|
|
6683
|
-
reason: state.via,
|
|
6684
|
-
version: state.version,
|
|
6685
|
-
...state.url ? { url: state.url } : {}
|
|
6686
|
-
};
|
|
6687
|
-
case "signed_out":
|
|
6688
|
-
return { runtime, state: "needs_sign_in", reason: "signed_out", version: state.version };
|
|
6689
|
-
case "not_running":
|
|
6690
|
-
return { runtime, state: "problem", reason: "not_running", url: state.url };
|
|
6691
|
-
case "off":
|
|
6692
|
-
return { runtime, state: "off" };
|
|
6693
|
-
case "problem":
|
|
6694
|
-
return { runtime, state: "problem", reason: checkReason(state.check) };
|
|
6695
|
-
}
|
|
6696
|
-
}
|
|
6697
|
-
function harnessEntriesFromDetection(detection) {
|
|
6698
|
-
return HARNESS_RUNTIMES.map((runtime) => harnessEntry(runtime, detection[runtime]));
|
|
6699
|
-
}
|
|
6700
|
-
function checkReason(check) {
|
|
6701
|
-
switch (check.status) {
|
|
6702
|
-
case "bundle_missing":
|
|
6703
|
-
return check.bundled;
|
|
6704
|
-
case "check_failed":
|
|
6705
|
-
return `check_failed:${check.reason}`;
|
|
6706
|
-
default:
|
|
6707
|
-
return check.status;
|
|
6708
|
-
}
|
|
6709
|
-
}
|
|
6710
|
-
|
|
6711
|
-
// src/harness-copy.ts
|
|
6712
|
-
import { homedir as homedir8 } from "os";
|
|
6713
|
-
import { join as join19, win32 } from "path";
|
|
6714
|
-
var HARNESS_LABELS = {
|
|
6715
|
-
"claude-code": "Claude Code",
|
|
6716
|
-
codex: "Codex",
|
|
6717
|
-
opencode: "OpenCode"
|
|
6718
|
-
};
|
|
6719
|
-
function failureText(runtime, reason) {
|
|
6720
|
-
const label = HARNESS_LABELS[runtime];
|
|
6721
|
-
switch (reason) {
|
|
6722
|
-
case "timeout":
|
|
6723
|
-
return `Couldn't check whether ${label} is signed in: the check didn't answer in time. Try again.`;
|
|
6724
|
-
case "spawn":
|
|
6725
|
-
return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
6726
|
-
case "config":
|
|
6727
|
-
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join19(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
|
|
6728
|
-
case "unreadable":
|
|
6729
|
-
return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
|
|
6730
|
-
}
|
|
6731
|
-
}
|
|
6732
|
-
function bundleMissingText(runtime) {
|
|
6733
|
-
const label = HARNESS_LABELS[runtime];
|
|
6734
|
-
return `This Cabane install is missing its bundled ${label}. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
6735
|
-
}
|
|
6736
|
-
function unusableReason(runtime, check) {
|
|
6737
|
-
if (check.status === "bundle_missing") return bundleMissingText(runtime);
|
|
6738
|
-
if (check.status === "check_failed") return failureText(runtime, check.reason);
|
|
6739
|
-
return null;
|
|
6740
|
-
}
|
|
6741
|
-
var SETUP_AGAIN = `Then ${cabane("status")} shows it signed in; nothing else to run.`;
|
|
6742
|
-
function spaced(sentence) {
|
|
6743
|
-
return sentence ? ` ${sentence}` : "";
|
|
6744
|
-
}
|
|
6745
|
-
|
|
6746
6769
|
// src/harness-detect.ts
|
|
6747
6770
|
var DEFAULT_OPENCODE_SERVER_URL = "http://127.0.0.1:4096";
|
|
6748
6771
|
var DEFAULT_OPENCODE_PROBE_MS = 1e3;
|
|
@@ -8893,7 +8916,7 @@ var CompanionSupervisor = class {
|
|
|
8893
8916
|
attempt,
|
|
8894
8917
|
cap: MAX_RESUME_ATTEMPTS
|
|
8895
8918
|
},
|
|
8896
|
-
"
|
|
8919
|
+
"The turn was interrupted, and it couldn't pick back up after several attempts. Reply in Cabane to continue."
|
|
8897
8920
|
);
|
|
8898
8921
|
try {
|
|
8899
8922
|
await agent.api.setActiveRun(workspaceId, payload.conversationId, payload.agentId, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.234",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Cabane Companion (headless): connect an agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"prepublishOnly": "pnpm build"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
41
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.319",
|
|
42
42
|
"@clack/prompts": "1.8.1",
|
|
43
|
-
"@openai/codex-sdk": "0.
|
|
43
|
+
"@openai/codex-sdk": "0.167.0",
|
|
44
44
|
"commander": "12.1.0",
|
|
45
45
|
"eventsource-parser": "3.0.8",
|
|
46
46
|
"graphql": "16.11.0",
|