@cabane/companion 0.6.202 → 0.6.204
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/README.md +25 -1
- package/dist/cli.js +1454 -1324
- package/dist/launch.js +5 -23
- package/dist/pairing-config.js +29 -45
- package/dist/runtime.js +356 -139
- package/package.json +1 -1
package/dist/runtime.js
CHANGED
|
@@ -167,25 +167,7 @@ var prepareHookSchema = z.object({
|
|
|
167
167
|
// Wall-clock cap for the hook. Provisioning is slow (minutes), so the
|
|
168
168
|
// default is generous; a hook that hangs past this is killed and the turn
|
|
169
169
|
// fails with a clear timeout message rather than pinning the companion.
|
|
170
|
-
timeoutMs: z.number().int().positive().optional()
|
|
171
|
-
// Re-invoke the hook when a prepared result already exists, in VALIDATE
|
|
172
|
-
// mode: the cached result is handed back as `prepared` and stdout is
|
|
173
|
-
// ignored, so the cwd cannot move. Its only power is to exit non-zero and
|
|
174
|
-
// fail the turn. The hook resolves once per (conversation, agent), so a
|
|
175
|
-
// later message asking for a different environment is otherwise silently
|
|
176
|
-
// ignored; this lets a hook refuse it out loud.
|
|
177
|
-
//
|
|
178
|
-
// Default OFF: a hook that provisions unconditionally must not start
|
|
179
|
-
// running on every turn because the companion upgraded.
|
|
180
|
-
validateCached: z.boolean().optional(),
|
|
181
|
-
// Independent operator-owned reporter. Receives sanitized failure metadata
|
|
182
|
-
// on stdin; never re-enters this hook and cannot change the turn outcome.
|
|
183
|
-
failureReporter: z.object({
|
|
184
|
-
command: z.string().min(1),
|
|
185
|
-
args: z.array(z.string()).optional(),
|
|
186
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
187
|
-
timeoutMs: z.number().int().positive().max(3e4).optional()
|
|
188
|
-
}).strict().optional()
|
|
170
|
+
timeoutMs: z.number().int().positive().optional()
|
|
189
171
|
}).strict();
|
|
190
172
|
var DEFAULT_TIMEOUT_MS = 10 * 6e4;
|
|
191
173
|
var prepareResultSchema = z.object({
|
|
@@ -260,8 +242,7 @@ var companionConfigSchema = z2.object({
|
|
|
260
242
|
// The DEVICE-LEVEL prepare hook, for every agent assigned here unless its own
|
|
261
243
|
// `agents` row names one. Per-agent hooks alone make the config a second
|
|
262
244
|
// silent roster: an agent nobody listed gets no hook and no error, and starts
|
|
263
|
-
// in the operator's `$HOME
|
|
264
|
-
// 2026-08-17).
|
|
245
|
+
// in the operator's `$HOME`.
|
|
265
246
|
prepareHook: prepareHookSchema.optional(),
|
|
266
247
|
// `dashboardPort` and `autoOpen` are IGNORED by the CLI (no port, no browser)
|
|
267
248
|
// but stay in the schema so an existing file still parses; the Electron shell
|
|
@@ -298,9 +279,10 @@ var companionConfigSchema = z2.object({
|
|
|
298
279
|
root: z2.string().min(1).refine(isRootPath, { message: "must be an absolute path, or start with ~/" }).optional(),
|
|
299
280
|
// An argv prefix the operator puts in front of every turn's runner: the companion starts
|
|
300
281
|
// `<prefix…> <its own run-turn command…>` instead of the runner bare. What the prefix does
|
|
301
|
-
// is the operator's (a
|
|
282
|
+
// is the operator's (a container or VM per turn, say); the companion knows nothing of it
|
|
302
283
|
// beyond handing the runner over, with the turn's inputs in its environment and the
|
|
303
|
-
// credential and secrets on its stdin (`runner/launch.ts`, `
|
|
284
|
+
// credential and secrets on its stdin (`runner/launch.ts`, `wrapped`). The README lists
|
|
285
|
+
// what a wrapper can rely on.
|
|
304
286
|
runner: z2.object({
|
|
305
287
|
command: z2.array(z2.string().min(1)).min(1)
|
|
306
288
|
}).strict().optional()
|
|
@@ -312,33 +294,38 @@ function workRoot(cfg) {
|
|
|
312
294
|
if (root.startsWith("~/")) return join4(homedir5(), root.slice(2));
|
|
313
295
|
return root;
|
|
314
296
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
297
|
+
var RETIRED_ROW_KEYS = ["sandbox"];
|
|
298
|
+
var RETIRED_HOOK_KEYS = ["validateCached", "failureReporter"];
|
|
299
|
+
function isObject(value) {
|
|
300
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
301
|
+
}
|
|
302
|
+
function splitRetired(raw) {
|
|
318
303
|
const keys = [];
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
304
|
+
const without = (obj, names, path) => {
|
|
305
|
+
const out = {};
|
|
306
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
307
|
+
if (names.includes(k)) keys.push(`${path}${k}`);
|
|
308
|
+
else out[k] = v;
|
|
324
309
|
}
|
|
310
|
+
return out;
|
|
311
|
+
};
|
|
312
|
+
const hook = (row, path) => isObject(row.prepareHook) ? { ...row, prepareHook: without(row.prepareHook, RETIRED_HOOK_KEYS, `${path}prepareHook.`) } : row;
|
|
313
|
+
const kept = hook(without(raw, ["runners"], ""), "");
|
|
314
|
+
if (isObject(kept.agents)) {
|
|
315
|
+
const agents = {};
|
|
316
|
+
for (const [key, row] of Object.entries(kept.agents)) {
|
|
317
|
+
const path = `agents.${key}.`;
|
|
318
|
+
agents[key] = isObject(row) ? hook(without(row, RETIRED_ROW_KEYS, path), path) : row;
|
|
319
|
+
}
|
|
320
|
+
kept.agents = agents;
|
|
325
321
|
}
|
|
326
|
-
return keys;
|
|
322
|
+
return { keys, kept };
|
|
323
|
+
}
|
|
324
|
+
function retiredConfigKeys(raw) {
|
|
325
|
+
return isObject(raw) ? splitRetired(raw).keys : [];
|
|
327
326
|
}
|
|
328
327
|
function withoutRetiredKeys(raw) {
|
|
329
|
-
|
|
330
|
-
const { runners: _runners, ...rest } = raw;
|
|
331
|
-
if (!rest.agents || typeof rest.agents !== "object") return rest;
|
|
332
|
-
const agents = {};
|
|
333
|
-
for (const [key, row] of Object.entries(rest.agents)) {
|
|
334
|
-
if (row && typeof row === "object" && "sandbox" in row) {
|
|
335
|
-
const { sandbox: _sandbox, ...kept } = row;
|
|
336
|
-
agents[key] = kept;
|
|
337
|
-
} else {
|
|
338
|
-
agents[key] = row;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return { ...rest, agents };
|
|
328
|
+
return isObject(raw) ? splitRetired(raw).kept : raw;
|
|
342
329
|
}
|
|
343
330
|
function retiredConfigKeysOnDisk() {
|
|
344
331
|
try {
|
|
@@ -1598,7 +1585,7 @@ var emptyHeartbeatStatus = () => ({
|
|
|
1598
1585
|
});
|
|
1599
1586
|
|
|
1600
1587
|
// src/supervisor.ts
|
|
1601
|
-
import { hostname
|
|
1588
|
+
import { hostname } from "os";
|
|
1602
1589
|
|
|
1603
1590
|
// packages/agent-runtime/src/failure.ts
|
|
1604
1591
|
import { z as z4 } from "zod";
|
|
@@ -1950,7 +1937,7 @@ var CabaneApi = class {
|
|
|
1950
1937
|
// text use (turnId, seq) within each kind for nonzero seq; tool activity
|
|
1951
1938
|
// uses (turnId, toolUseId). Legacy zero/missing seq bypasses row deduplication.
|
|
1952
1939
|
// Server fences and the outbox's discard/eviction bounds still apply.
|
|
1953
|
-
async durableCommit(kind, path, body, turnId, seq, signal) {
|
|
1940
|
+
async durableCommit(kind, path, body, turnId, seq, signal, trailPath) {
|
|
1954
1941
|
try {
|
|
1955
1942
|
await this.request("POST", path, body, {
|
|
1956
1943
|
retry: true,
|
|
@@ -1961,7 +1948,16 @@ var CabaneApi = class {
|
|
|
1961
1948
|
if (!outbox) throw err;
|
|
1962
1949
|
if (signal?.aborted || isAbortError(err)) throw err;
|
|
1963
1950
|
if (!isRetryable(err)) throw err;
|
|
1964
|
-
outbox.persist({
|
|
1951
|
+
outbox.persist({
|
|
1952
|
+
enqueuedAt: Date.now(),
|
|
1953
|
+
turnId,
|
|
1954
|
+
seq,
|
|
1955
|
+
method: "POST",
|
|
1956
|
+
path,
|
|
1957
|
+
body,
|
|
1958
|
+
kind,
|
|
1959
|
+
...trailPath ? { trailPath } : {}
|
|
1960
|
+
});
|
|
1965
1961
|
this.opts.log?.debug(
|
|
1966
1962
|
{ kind, turnId, seq, err: err instanceof Error ? err.message : String(err) },
|
|
1967
1963
|
"companion: commit queued to outbox after transient failure (will drain when the API returns)"
|
|
@@ -1986,6 +1982,29 @@ var CabaneApi = class {
|
|
|
1986
1982
|
progressed = true;
|
|
1987
1983
|
} catch (err) {
|
|
1988
1984
|
if (err instanceof ApiError && err.status >= 400 && err.status < 500) {
|
|
1985
|
+
if (entry.kind === "message" && entry.trailPath && isAlreadyPublished(err)) {
|
|
1986
|
+
try {
|
|
1987
|
+
await this.request("POST", entry.trailPath, trailOf(entry.body), {
|
|
1988
|
+
retry: true
|
|
1989
|
+
});
|
|
1990
|
+
} catch (trailErr) {
|
|
1991
|
+
if (!(trailErr instanceof ApiError && trailErr.status >= 400 && trailErr.status < 500)) {
|
|
1992
|
+
break;
|
|
1993
|
+
}
|
|
1994
|
+
this.opts.log?.warn(
|
|
1995
|
+
{
|
|
1996
|
+
kind: entry.kind,
|
|
1997
|
+
turnId: entry.turnId,
|
|
1998
|
+
seq: entry.seq,
|
|
1999
|
+
...apiErrorLogFields(trailErr)
|
|
2000
|
+
},
|
|
2001
|
+
`Cabane rejected a postscript; it could not be filed. ${mayNeedUpdate()}`
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
outbox.remove(entry.turnId, entry.seq);
|
|
2005
|
+
progressed = true;
|
|
2006
|
+
continue;
|
|
2007
|
+
}
|
|
1989
2008
|
if (entry.kind === "active-run" && isTurnEndedRefusal(err)) {
|
|
1990
2009
|
this.opts.log?.debug(
|
|
1991
2010
|
{ kind: entry.kind, turnId: entry.turnId, ...apiErrorLogFields(err) },
|
|
@@ -2061,6 +2080,19 @@ var CabaneApi = class {
|
|
|
2061
2080
|
(e) => e.turnId === turnId && e.kind === "message" && e.body?.kind !== "runtime_notice"
|
|
2062
2081
|
);
|
|
2063
2082
|
}
|
|
2083
|
+
// The `seq` of this turn's SELECTED ANSWER still waiting in the outbox — the
|
|
2084
|
+
// `declared` commit the server's `answerArmed` cannot see — or null when none
|
|
2085
|
+
// is queued. A resumed run that re-armed over it would let a newer text
|
|
2086
|
+
// consume the arm first; with the selection queued, the resumed run starts
|
|
2087
|
+
// declared, its texts are trail, and the drain delivers the selection.
|
|
2088
|
+
outboxDeclaredSeqForTurn(turnId) {
|
|
2089
|
+
const outbox = this.opts.outbox;
|
|
2090
|
+
if (!outbox) return null;
|
|
2091
|
+
const queued = outbox.list().find(
|
|
2092
|
+
(e) => e.turnId === turnId && e.kind === "message" && e.body?.declared === true
|
|
2093
|
+
);
|
|
2094
|
+
return queued ? queued.seq : null;
|
|
2095
|
+
}
|
|
2064
2096
|
// ADOPT a turn context's issuance as the execution source of its work, once,
|
|
2065
2097
|
// before preparation emits or queues anything, so every row can name a source
|
|
2066
2098
|
// the server durably recorded and will never reap. Adoption is idempotent, so
|
|
@@ -2234,15 +2266,36 @@ var CabaneApi = class {
|
|
|
2234
2266
|
// seq also orders speech against the turn's tool/thinking/text trail.
|
|
2235
2267
|
// Transport/5xx failures get bounded retries and optional outbox delivery;
|
|
2236
2268
|
// terminal 4xx and cancellation stop delivery rather than proving no commit.
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2269
|
+
//
|
|
2270
|
+
// A turn's answer can be out before a later speech commit arrives — the
|
|
2271
|
+
// declaration published it, or another host's selection drained first — and
|
|
2272
|
+
// the server refuses the commit `409 already_published`. The text is a
|
|
2273
|
+
// postscript then: with `trail` (the committing agent, which names the text
|
|
2274
|
+
// route), it is filed as trail at the same seq, live here and from the outbox
|
|
2275
|
+
// on a drain. Without `trail` (a runtime notice) the refusal is terminal.
|
|
2276
|
+
async postTurnMessage(workspaceId, conversationId, body, signal, trail) {
|
|
2277
|
+
const trailPath = trail ? `/api/workspaces/${workspaceId}/conversations/${conversationId}/participants/agents/${trail.agentId}/text` : void 0;
|
|
2278
|
+
try {
|
|
2279
|
+
await this.durableCommit(
|
|
2280
|
+
"message",
|
|
2281
|
+
`/api/workspaces/${workspaceId}/conversations/${conversationId}/messages`,
|
|
2282
|
+
body,
|
|
2283
|
+
body.turnId,
|
|
2284
|
+
body.seq ?? 0,
|
|
2285
|
+
signal,
|
|
2286
|
+
trailPath
|
|
2287
|
+
);
|
|
2288
|
+
} catch (err) {
|
|
2289
|
+
if (!trailPath || !isAlreadyPublished(err)) throw err;
|
|
2290
|
+
await this.durableCommit(
|
|
2291
|
+
"text",
|
|
2292
|
+
trailPath,
|
|
2293
|
+
trailOf(body),
|
|
2294
|
+
body.turnId,
|
|
2295
|
+
body.seq ?? 0,
|
|
2296
|
+
signal
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2246
2299
|
}
|
|
2247
2300
|
// Report a tool transition (start/done/error). The server checks span
|
|
2248
2301
|
// ownership, upserts by (turnId, toolUseId), and publishes agent_activity SSE.
|
|
@@ -2288,6 +2341,18 @@ var CabaneApi = class {
|
|
|
2288
2341
|
);
|
|
2289
2342
|
}
|
|
2290
2343
|
};
|
|
2344
|
+
function isAlreadyPublished(err) {
|
|
2345
|
+
return err instanceof ApiError && err.status === 409 && apiErrorCode(err) === "already_published";
|
|
2346
|
+
}
|
|
2347
|
+
function trailOf(body) {
|
|
2348
|
+
const b = body;
|
|
2349
|
+
return {
|
|
2350
|
+
turnId: b.turnId,
|
|
2351
|
+
text: b.body,
|
|
2352
|
+
...b.seq !== void 0 ? { seq: b.seq } : {},
|
|
2353
|
+
...b.executionSourceId !== void 0 ? { executionSourceId: b.executionSourceId } : {}
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2291
2356
|
function isRetryable(err) {
|
|
2292
2357
|
if (err instanceof ApiError) return err.status >= 500;
|
|
2293
2358
|
if (isAbortError(err)) return false;
|
|
@@ -2778,6 +2843,8 @@ var turnOutcomes = ["success", "failure", "cancelled", "skipped"];
|
|
|
2778
2843
|
var turnSessionModes = ["fresh", "resumed", "degraded"];
|
|
2779
2844
|
var turnRuntimeResultKinds = ["success", "error", "no_terminal"];
|
|
2780
2845
|
var turnFinalSources = [
|
|
2846
|
+
// The agent declared its answer with the `answer` turn act (CT1973).
|
|
2847
|
+
"declared",
|
|
2781
2848
|
"runtime_text",
|
|
2782
2849
|
"progress_promotion",
|
|
2783
2850
|
"host_fallback",
|
|
@@ -3063,11 +3130,6 @@ function truncate(s, max) {
|
|
|
3063
3130
|
return `${s.slice(0, max)}\u2026[+${s.length - max} chars]`;
|
|
3064
3131
|
}
|
|
3065
3132
|
|
|
3066
|
-
// packages/agent-runtime/src/claude-code/index.ts
|
|
3067
|
-
import {
|
|
3068
|
-
query as defaultQuery
|
|
3069
|
-
} from "@anthropic-ai/claude-agent-sdk";
|
|
3070
|
-
|
|
3071
3133
|
// packages/agent-runtime/src/turn-control/descriptions.ts
|
|
3072
3134
|
var MAX_ASK_QUESTION_BYTES = 400;
|
|
3073
3135
|
var MAX_ASK_HEADLINE_BYTES = 120;
|
|
@@ -3078,7 +3140,9 @@ var MAX_ASK_OPTIONS = 4;
|
|
|
3078
3140
|
var MAX_WAKE_NOTE_BYTES = 2e3;
|
|
3079
3141
|
var MAX_WITHDRAW_REASON_BYTES = 400;
|
|
3080
3142
|
var MAX_SKIP_REASON_BYTES = 500;
|
|
3143
|
+
var TURN_CONTROL_MCP_SERVER = "cabane_companion";
|
|
3081
3144
|
var TURN_CONTROL_TOOLS = [
|
|
3145
|
+
"answer",
|
|
3082
3146
|
"ask",
|
|
3083
3147
|
"cancel_wake",
|
|
3084
3148
|
"skip_turn",
|
|
@@ -3086,6 +3150,7 @@ var TURN_CONTROL_TOOLS = [
|
|
|
3086
3150
|
"withdraw_ask"
|
|
3087
3151
|
];
|
|
3088
3152
|
var TURN_CONTROL_TOOL_DESCRIPTIONS = {
|
|
3153
|
+
answer: `Call this just before you write your answer: the text you write next is what the conversation keeps as your message. Call it once.`,
|
|
3089
3154
|
ask: `Ask a person a structured question you need answered to continue, then end your turn; don't wait for the reply. \`targetUserId\` is a person on your roster, by their \`@handle\`; to bring in an agent, name them with \`@handle\` in your reply.
|
|
3090
3155
|
- Form: one question (\`headline\`, optional \`question\` body, \`options\`) or \`questions\`, 1\u2013${MAX_ASK_ITEMS} \`{ headline, body?, options? }\`; never both. Decisions that arise together go in ONE call, in the order you want them answered. A second \`ask\` is a separate item, never an edit; raise one only for a genuinely new question.
|
|
3091
3156
|
- \`headline\`: one sentence ending in \`?\`. Body (\`question\` or each item's \`body\`): a sentence or two of framing, not a report; inline Markdown only, no lists or headings. \`options\`: ${MIN_ASK_OPTIONS}\u2013${MAX_ASK_OPTIONS} one-click answers (always for a yes/no go-ahead), each a distinct course of action you can carry out without asking back.
|
|
@@ -3100,6 +3165,7 @@ var TURN_CONTROL_TOOL_DESCRIPTIONS = {
|
|
|
3100
3165
|
withdraw_ask: `Withdraw an open ask \u2014 yours or anyone's in this conversation \u2014 because the question is moot, was wrong, or has been overtaken. \`reason\` is required and is shown to the person and, if it wasn't your ask, to the agent who asked it. If there is a current question to put in its place, don't call this \u2014 call \`ask\` with \`replaces\` so the two land as one and the person never sees neither or both. Never withdraw to hurry an answer; to act on a deadline, arm a \`wake_me\` and decide then.`
|
|
3101
3166
|
};
|
|
3102
3167
|
var TURN_CONTROL_FIELD_DESCRIPTIONS = {
|
|
3168
|
+
answer: {},
|
|
3103
3169
|
ask: {
|
|
3104
3170
|
targetUserId: "The person to ask: their `@handle` from your turn context's roster (preferred), or their user id.",
|
|
3105
3171
|
question: `SINGLE-question form: a short body \u2014 one or two sentences of framing the headline can't hold. NOT a report (up to ${MAX_ASK_QUESTION_BYTES} characters, inline markdown only). Provide EITHER this or \`questions\`, not both. Put the crisp one-sentence question in \`headline\`.`,
|
|
@@ -3128,8 +3194,14 @@ var TURN_CONTROL_FIELD_DESCRIPTIONS = {
|
|
|
3128
3194
|
}
|
|
3129
3195
|
};
|
|
3130
3196
|
|
|
3197
|
+
// packages/agent-runtime/src/claude-code/index.ts
|
|
3198
|
+
import {
|
|
3199
|
+
query as defaultQuery
|
|
3200
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
3201
|
+
|
|
3131
3202
|
// packages/agent-runtime/src/turn-control/prompt-guidance.ts
|
|
3132
3203
|
var MEANINGS = {
|
|
3204
|
+
answer: "declare the text you write next as your answer",
|
|
3133
3205
|
ask: "put a structured question to a person",
|
|
3134
3206
|
cancel_wake: "cancel your own armed wake in this conversation",
|
|
3135
3207
|
skip_turn: "end without posting a reply",
|
|
@@ -4527,6 +4599,15 @@ function deepestError(node) {
|
|
|
4527
4599
|
}
|
|
4528
4600
|
|
|
4529
4601
|
// packages/agent-runtime/src/opencode/loop.ts
|
|
4602
|
+
function opencodeMcpServerOf(tool2, servers) {
|
|
4603
|
+
let best = null;
|
|
4604
|
+
for (const server of servers ?? []) {
|
|
4605
|
+
if (tool2.length > server.length + 1 && tool2.startsWith(`${server}_`)) {
|
|
4606
|
+
if (best === null || server.length > best.length) best = server;
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
return best;
|
|
4610
|
+
}
|
|
4530
4611
|
async function* decodeOpencodeStream(events, ctx) {
|
|
4531
4612
|
let held = null;
|
|
4532
4613
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -4553,12 +4634,14 @@ async function* decodeOpencodeStream(events, ctx) {
|
|
|
4553
4634
|
if (sealed) yield sealed;
|
|
4554
4635
|
const key = part.callID ?? part.id;
|
|
4555
4636
|
const input = part.state.input;
|
|
4637
|
+
const server = opencodeMcpServerOf(part.tool, ctx.mcpServers);
|
|
4638
|
+
const origin = server ? { mcpServer: server } : {};
|
|
4556
4639
|
if (!startedTools.has(key)) {
|
|
4557
4640
|
const name = prettyToolName(part.tool);
|
|
4558
4641
|
const summary = summarizeToolInput(input);
|
|
4559
4642
|
pending.set(key, { name, summary, input });
|
|
4560
4643
|
startedTools.add(key);
|
|
4561
|
-
yield { type: "tool", id: key, name, phase: "start", summary, input };
|
|
4644
|
+
yield { type: "tool", id: key, name, phase: "start", summary, input, ...origin };
|
|
4562
4645
|
}
|
|
4563
4646
|
const entry = pending.get(key);
|
|
4564
4647
|
if (entry && hasToolInput(input)) {
|
|
@@ -4578,7 +4661,8 @@ async function* decodeOpencodeStream(events, ctx) {
|
|
|
4578
4661
|
phase,
|
|
4579
4662
|
summary: started?.summary ?? "",
|
|
4580
4663
|
input: started?.input ?? input,
|
|
4581
|
-
result
|
|
4664
|
+
result,
|
|
4665
|
+
...origin
|
|
4582
4666
|
};
|
|
4583
4667
|
}
|
|
4584
4668
|
} else {
|
|
@@ -4662,7 +4746,6 @@ function parseOpencodeModel(model) {
|
|
|
4662
4746
|
|
|
4663
4747
|
// packages/agent-runtime/src/opencode/run-spec.ts
|
|
4664
4748
|
var CABANE_MCP_SERVER2 = "cabane";
|
|
4665
|
-
var TURN_CONTROL_MCP_SERVER = "cabane_companion";
|
|
4666
4749
|
var ACTIVE_CONVERSATION_HEADER2 = "x-cabane-active-conversation";
|
|
4667
4750
|
function buildRunSpec(req, resumeSessionId) {
|
|
4668
4751
|
const { config } = req;
|
|
@@ -5056,7 +5139,10 @@ function createOpencodeAdapter(deps = {}) {
|
|
|
5056
5139
|
}
|
|
5057
5140
|
yield* decodeOpencodeStream(result.events, {
|
|
5058
5141
|
signal,
|
|
5059
|
-
sessionId: result.sessionId
|
|
5142
|
+
sessionId: result.sessionId,
|
|
5143
|
+
// The keys the turn's servers were installed under: what the decoder
|
|
5144
|
+
// splits a raw `<server>_<tool>` id on.
|
|
5145
|
+
mcpServers: Object.keys(spec.mcp)
|
|
5060
5146
|
});
|
|
5061
5147
|
}
|
|
5062
5148
|
};
|
|
@@ -5192,7 +5278,15 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5192
5278
|
expected: [
|
|
5193
5279
|
sessionEvent2(NEW_SESSION_ID),
|
|
5194
5280
|
// start: fires off the first (empty) snapshot — empty input + summary, by design.
|
|
5195
|
-
{
|
|
5281
|
+
{
|
|
5282
|
+
type: "tool",
|
|
5283
|
+
id: "call_1",
|
|
5284
|
+
name: "cabane_list",
|
|
5285
|
+
phase: "start",
|
|
5286
|
+
summary: "",
|
|
5287
|
+
input: {},
|
|
5288
|
+
mcpServer: "cabane"
|
|
5289
|
+
},
|
|
5196
5290
|
// done: carries the full args captured from the later snapshot, and the
|
|
5197
5291
|
// summary recomputed from them — NOT the first-seen `{}`.
|
|
5198
5292
|
{
|
|
@@ -5202,8 +5296,60 @@ var OPENCODE_CONFORMANCE_FIXTURES = [
|
|
|
5202
5296
|
phase: "done",
|
|
5203
5297
|
summary: "/product/tasks",
|
|
5204
5298
|
input: { path: "/product/tasks" },
|
|
5205
|
-
result: "CT301.md"
|
|
5299
|
+
result: "CT301.md",
|
|
5300
|
+
mcpServer: "cabane"
|
|
5301
|
+
},
|
|
5302
|
+
{ type: "result", ok: true }
|
|
5303
|
+
]
|
|
5304
|
+
},
|
|
5305
|
+
{
|
|
5306
|
+
// The turn-control server's `answer` (CT1973). opencode names an MCP tool `<server>_<tool>`,
|
|
5307
|
+
// so the installed keys split the raw id: `cabane_companion_answer` is the turn-control
|
|
5308
|
+
// server's, never the workspace server's `companion_answer`. The pump binds on that.
|
|
5309
|
+
name: "turn-control answer is read as the turn-control server\u2019s, not cabane\u2019s",
|
|
5310
|
+
request: makeRequest2({
|
|
5311
|
+
cabane: {
|
|
5312
|
+
mcpUrl: "https://cabane.test/api/mcp",
|
|
5313
|
+
turnControlUrl: "https://cabane.test/api/turn-control",
|
|
5314
|
+
bearer: "tok",
|
|
5315
|
+
activeConversationId: "conv"
|
|
5316
|
+
}
|
|
5317
|
+
}),
|
|
5318
|
+
nativeStream: [
|
|
5319
|
+
textPart("p1", "On it."),
|
|
5320
|
+
toolPart("t1", "cabane_companion_answer", { status: "running", input: {} }, "call_1"),
|
|
5321
|
+
toolPart(
|
|
5322
|
+
"t1",
|
|
5323
|
+
"cabane_companion_answer",
|
|
5324
|
+
{ status: "completed", input: {}, output: '{"ok":true,"kind":"answer"}' },
|
|
5325
|
+
"call_1"
|
|
5326
|
+
),
|
|
5327
|
+
textPart("p2", "The answer."),
|
|
5328
|
+
idle()
|
|
5329
|
+
],
|
|
5330
|
+
expected: [
|
|
5331
|
+
sessionEvent2(NEW_SESSION_ID),
|
|
5332
|
+
{ type: "text", body: "On it.", terminal: false },
|
|
5333
|
+
{
|
|
5334
|
+
type: "tool",
|
|
5335
|
+
id: "call_1",
|
|
5336
|
+
name: "cabane_companion_answer",
|
|
5337
|
+
phase: "start",
|
|
5338
|
+
summary: "",
|
|
5339
|
+
input: {},
|
|
5340
|
+
mcpServer: "cabane_companion"
|
|
5341
|
+
},
|
|
5342
|
+
{
|
|
5343
|
+
type: "tool",
|
|
5344
|
+
id: "call_1",
|
|
5345
|
+
name: "cabane_companion_answer",
|
|
5346
|
+
phase: "done",
|
|
5347
|
+
summary: "",
|
|
5348
|
+
input: {},
|
|
5349
|
+
result: '{"ok":true,"kind":"answer"}',
|
|
5350
|
+
mcpServer: "cabane_companion"
|
|
5206
5351
|
},
|
|
5352
|
+
{ type: "text", body: "The answer.", terminal: true },
|
|
5207
5353
|
{ type: "result", ok: true }
|
|
5208
5354
|
]
|
|
5209
5355
|
},
|
|
@@ -5561,6 +5707,7 @@ function readToolItem(item, eventType) {
|
|
|
5561
5707
|
name: server && server !== "cabane" ? `${server}.${tool2}` : tool2,
|
|
5562
5708
|
input: item.arguments,
|
|
5563
5709
|
status: toStatus(item.status),
|
|
5710
|
+
...server ? { server } : {},
|
|
5564
5711
|
...item.status === "completed" ? { result } : item.status === "failed" ? { result: hasMcpReceipt(result) ? result : readMcpError(item.error) } : {}
|
|
5565
5712
|
};
|
|
5566
5713
|
}
|
|
@@ -5789,11 +5936,20 @@ async function* decodeCodexStream(events, ctx) {
|
|
|
5789
5936
|
if (!tool2) continue;
|
|
5790
5937
|
yield* flushInterim();
|
|
5791
5938
|
const name = prettyToolName(tool2.name);
|
|
5939
|
+
const origin = tool2.server ? { mcpServer: tool2.server } : {};
|
|
5792
5940
|
if (!startedTools.has(tool2.id)) {
|
|
5793
5941
|
const summary = summarizeToolInput(tool2.input);
|
|
5794
5942
|
pending.set(tool2.id, { name, summary, input: tool2.input });
|
|
5795
5943
|
startedTools.add(tool2.id);
|
|
5796
|
-
yield {
|
|
5944
|
+
yield {
|
|
5945
|
+
type: "tool",
|
|
5946
|
+
id: tool2.id,
|
|
5947
|
+
name,
|
|
5948
|
+
phase: "start",
|
|
5949
|
+
summary,
|
|
5950
|
+
input: tool2.input,
|
|
5951
|
+
...origin
|
|
5952
|
+
};
|
|
5797
5953
|
}
|
|
5798
5954
|
const entry = pending.get(tool2.id);
|
|
5799
5955
|
if (entry && hasToolInput2(tool2.input)) {
|
|
@@ -5811,7 +5967,8 @@ async function* decodeCodexStream(events, ctx) {
|
|
|
5811
5967
|
phase: tool2.status === "completed" ? "done" : "error",
|
|
5812
5968
|
summary: started?.summary ?? "",
|
|
5813
5969
|
input: started?.input ?? tool2.input,
|
|
5814
|
-
result: tool2.result
|
|
5970
|
+
result: tool2.result,
|
|
5971
|
+
...origin
|
|
5815
5972
|
};
|
|
5816
5973
|
}
|
|
5817
5974
|
continue;
|
|
@@ -5902,7 +6059,6 @@ function parseCodexModel(model) {
|
|
|
5902
6059
|
|
|
5903
6060
|
// packages/agent-runtime/src/codex/run-spec.ts
|
|
5904
6061
|
var CABANE_MCP_SERVER3 = "cabane";
|
|
5905
|
-
var TURN_CONTROL_MCP_SERVER2 = "cabane_companion";
|
|
5906
6062
|
var ACTIVE_CONVERSATION_HEADER3 = "x-cabane-active-conversation";
|
|
5907
6063
|
var ENV_ENVELOPE_KEYS = [
|
|
5908
6064
|
"CABANE_CONVERSATION_ID",
|
|
@@ -6031,7 +6187,7 @@ function buildConfig(req) {
|
|
|
6031
6187
|
default_tools_approval_mode: "approve"
|
|
6032
6188
|
};
|
|
6033
6189
|
if (req.cabane.turnControlUrl) {
|
|
6034
|
-
mcp_servers[
|
|
6190
|
+
mcp_servers[TURN_CONTROL_MCP_SERVER] = {
|
|
6035
6191
|
url: req.cabane.turnControlUrl,
|
|
6036
6192
|
http_headers: {
|
|
6037
6193
|
Authorization: `Bearer ${req.cabane.bearer}`,
|
|
@@ -6209,7 +6365,7 @@ function createCodexAdapter(deps = {}) {
|
|
|
6209
6365
|
if (signal.aborted) return;
|
|
6210
6366
|
if (compactions === null) {
|
|
6211
6367
|
deps.onWarn?.(
|
|
6212
|
-
"codex adapter: the thread's compaction count could not be read \u2014 re-sending the standing instructions
|
|
6368
|
+
"codex adapter: the thread's compaction count could not be read \u2014 re-sending the standing instructions",
|
|
6213
6369
|
{ threadId: decision.resume }
|
|
6214
6370
|
);
|
|
6215
6371
|
} else if (decision.compactions === compactions) {
|
|
@@ -6219,7 +6375,7 @@ function createCodexAdapter(deps = {}) {
|
|
|
6219
6375
|
};
|
|
6220
6376
|
} else if (decision.compactions !== null && compactions > decision.compactions) {
|
|
6221
6377
|
deps.onWarn?.(
|
|
6222
|
-
"codex adapter: the thread compacted since the last turn \u2014 re-sending the standing instructions
|
|
6378
|
+
"codex adapter: the thread compacted since the last turn \u2014 re-sending the standing instructions",
|
|
6223
6379
|
{
|
|
6224
6380
|
threadId: decision.resume,
|
|
6225
6381
|
compactions,
|
|
@@ -6481,6 +6637,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6481
6637
|
type: "tool",
|
|
6482
6638
|
id: "t1",
|
|
6483
6639
|
name: "read",
|
|
6640
|
+
mcpServer: "cabane",
|
|
6484
6641
|
phase: "start",
|
|
6485
6642
|
summary: "sleep.md",
|
|
6486
6643
|
input: { path: "sleep.md" }
|
|
@@ -6489,6 +6646,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6489
6646
|
type: "tool",
|
|
6490
6647
|
id: "t1",
|
|
6491
6648
|
name: "read",
|
|
6649
|
+
mcpServer: "cabane",
|
|
6492
6650
|
phase: "done",
|
|
6493
6651
|
summary: "sleep.md",
|
|
6494
6652
|
input: { path: "sleep.md" },
|
|
@@ -6534,6 +6692,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6534
6692
|
type: "tool",
|
|
6535
6693
|
id: "query-success",
|
|
6536
6694
|
name: "query",
|
|
6695
|
+
mcpServer: "cabane",
|
|
6537
6696
|
phase: "start",
|
|
6538
6697
|
summary: "",
|
|
6539
6698
|
input: { op: "files.read", args: { path: "/ABOUT.md" } }
|
|
@@ -6542,6 +6701,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6542
6701
|
type: "tool",
|
|
6543
6702
|
id: "query-success",
|
|
6544
6703
|
name: "query",
|
|
6704
|
+
mcpServer: "cabane",
|
|
6545
6705
|
phase: "done",
|
|
6546
6706
|
summary: "",
|
|
6547
6707
|
input: { op: "files.read", args: { path: "/ABOUT.md" } },
|
|
@@ -6588,6 +6748,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6588
6748
|
type: "tool",
|
|
6589
6749
|
id: "query-failed",
|
|
6590
6750
|
name: "query",
|
|
6751
|
+
mcpServer: "cabane",
|
|
6591
6752
|
phase: "start",
|
|
6592
6753
|
summary: "",
|
|
6593
6754
|
input: { op: "files.read", args: { path: "/ABOUT.md" } }
|
|
@@ -6596,6 +6757,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6596
6757
|
type: "tool",
|
|
6597
6758
|
id: "query-failed",
|
|
6598
6759
|
name: "query",
|
|
6760
|
+
mcpServer: "cabane",
|
|
6599
6761
|
phase: "error",
|
|
6600
6762
|
summary: "",
|
|
6601
6763
|
input: { op: "files.read", args: { path: "/ABOUT.md" } },
|
|
@@ -6605,6 +6767,54 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6605
6767
|
{ type: "result", ok: true }
|
|
6606
6768
|
]
|
|
6607
6769
|
},
|
|
6770
|
+
{
|
|
6771
|
+
// The turn-control server's `answer` (CT1973): the tool event carries the server Codex names
|
|
6772
|
+
// beside the tool, which is what the pump binds the declaration to. A non-cabane server keeps
|
|
6773
|
+
// its prefix on the card as before.
|
|
6774
|
+
name: "turn-control answer carries its server",
|
|
6775
|
+
request: makeRequest3(),
|
|
6776
|
+
nativeStream: [
|
|
6777
|
+
threadStarted(NEW_THREAD_ID),
|
|
6778
|
+
agentMessage("m1", "On it."),
|
|
6779
|
+
toolFrame("item.completed", {
|
|
6780
|
+
id: "answer-1",
|
|
6781
|
+
type: "mcp_tool_call",
|
|
6782
|
+
server: "cabane_companion",
|
|
6783
|
+
tool: "answer",
|
|
6784
|
+
arguments: {},
|
|
6785
|
+
status: "completed",
|
|
6786
|
+
result: { content: [{ type: "text", text: '{"ok":true,"kind":"answer"}' }] }
|
|
6787
|
+
}),
|
|
6788
|
+
agentMessage("m2", "The answer."),
|
|
6789
|
+
turnCompleted()
|
|
6790
|
+
],
|
|
6791
|
+
expected: [
|
|
6792
|
+
startedSession(NEW_THREAD_ID),
|
|
6793
|
+
{ type: "text", body: "On it.", terminal: false },
|
|
6794
|
+
{
|
|
6795
|
+
type: "tool",
|
|
6796
|
+
id: "answer-1",
|
|
6797
|
+
name: "cabane_companion.answer",
|
|
6798
|
+
mcpServer: "cabane_companion",
|
|
6799
|
+
phase: "start",
|
|
6800
|
+
summary: "",
|
|
6801
|
+
input: {}
|
|
6802
|
+
},
|
|
6803
|
+
{
|
|
6804
|
+
type: "tool",
|
|
6805
|
+
id: "answer-1",
|
|
6806
|
+
name: "cabane_companion.answer",
|
|
6807
|
+
mcpServer: "cabane_companion",
|
|
6808
|
+
phase: "done",
|
|
6809
|
+
summary: "",
|
|
6810
|
+
input: {},
|
|
6811
|
+
result: [{ type: "text", text: '{"ok":true,"kind":"answer"}' }]
|
|
6812
|
+
},
|
|
6813
|
+
{ type: "text", body: "The answer.", terminal: true },
|
|
6814
|
+
closingSession(NEW_THREAD_ID),
|
|
6815
|
+
{ type: "result", ok: true }
|
|
6816
|
+
]
|
|
6817
|
+
},
|
|
6608
6818
|
{
|
|
6609
6819
|
// A transport/tool failure with no MCP result payload still uses the real adapter error message,
|
|
6610
6820
|
// never `undefined`.
|
|
@@ -6630,6 +6840,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6630
6840
|
type: "tool",
|
|
6631
6841
|
id: "mcp-failed",
|
|
6632
6842
|
name: "other.lookup",
|
|
6843
|
+
mcpServer: "other",
|
|
6633
6844
|
phase: "start",
|
|
6634
6845
|
summary: "receipt",
|
|
6635
6846
|
input: { q: "receipt" }
|
|
@@ -6638,6 +6849,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6638
6849
|
type: "tool",
|
|
6639
6850
|
id: "mcp-failed",
|
|
6640
6851
|
name: "other.lookup",
|
|
6852
|
+
mcpServer: "other",
|
|
6641
6853
|
phase: "error",
|
|
6642
6854
|
summary: "receipt",
|
|
6643
6855
|
input: { q: "receipt" },
|
|
@@ -6674,6 +6886,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6674
6886
|
type: "tool",
|
|
6675
6887
|
id: "mcp-image",
|
|
6676
6888
|
name: "other.render",
|
|
6889
|
+
mcpServer: "other",
|
|
6677
6890
|
phase: "start",
|
|
6678
6891
|
summary: "",
|
|
6679
6892
|
input: {}
|
|
@@ -6682,6 +6895,7 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6682
6895
|
type: "tool",
|
|
6683
6896
|
id: "mcp-image",
|
|
6684
6897
|
name: "other.render",
|
|
6898
|
+
mcpServer: "other",
|
|
6685
6899
|
phase: "done",
|
|
6686
6900
|
summary: "",
|
|
6687
6901
|
input: {},
|
|
@@ -6729,11 +6943,20 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6729
6943
|
],
|
|
6730
6944
|
expected: [
|
|
6731
6945
|
startedSession(NEW_THREAD_ID),
|
|
6732
|
-
{ type: "tool", id: "t1", name: "list", phase: "start", summary: "", input: {} },
|
|
6733
6946
|
{
|
|
6734
6947
|
type: "tool",
|
|
6735
6948
|
id: "t1",
|
|
6736
6949
|
name: "list",
|
|
6950
|
+
mcpServer: "cabane",
|
|
6951
|
+
phase: "start",
|
|
6952
|
+
summary: "",
|
|
6953
|
+
input: {}
|
|
6954
|
+
},
|
|
6955
|
+
{
|
|
6956
|
+
type: "tool",
|
|
6957
|
+
id: "t1",
|
|
6958
|
+
name: "list",
|
|
6959
|
+
mcpServer: "cabane",
|
|
6737
6960
|
phase: "done",
|
|
6738
6961
|
summary: "/product/tasks",
|
|
6739
6962
|
input: { path: "/product/tasks" },
|
|
@@ -7303,8 +7526,6 @@ var ConnectorHealthStore = class {
|
|
|
7303
7526
|
|
|
7304
7527
|
// src/dispatcher.ts
|
|
7305
7528
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
7306
|
-
import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
|
|
7307
|
-
import { join as join21 } from "path";
|
|
7308
7529
|
|
|
7309
7530
|
// src/runner/launch.ts
|
|
7310
7531
|
import { createInterface } from "readline";
|
|
@@ -7554,7 +7775,7 @@ function selfRunnerCommand() {
|
|
|
7554
7775
|
function wrappedRunnerCommand(prefix, own) {
|
|
7555
7776
|
return [...prefix ?? [], ...own];
|
|
7556
7777
|
}
|
|
7557
|
-
var
|
|
7778
|
+
var WRAPPED_ENV_DROPPED = [
|
|
7558
7779
|
"ANTHROPIC_API_KEY",
|
|
7559
7780
|
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
7560
7781
|
"CODEX_API_KEY",
|
|
@@ -7563,11 +7784,11 @@ var BOXED_ENV_DROPPED = [
|
|
|
7563
7784
|
"DBUS_SESSION_BUS_ADDRESS",
|
|
7564
7785
|
"XDG_RUNTIME_DIR"
|
|
7565
7786
|
];
|
|
7566
|
-
function runnerExecEnv(base,
|
|
7787
|
+
function runnerExecEnv(base, wrapped) {
|
|
7567
7788
|
const env = {};
|
|
7568
7789
|
for (const [k, v] of Object.entries(base)) {
|
|
7569
7790
|
if (v === void 0) continue;
|
|
7570
|
-
if (
|
|
7791
|
+
if (wrapped && WRAPPED_ENV_DROPPED.includes(k)) continue;
|
|
7571
7792
|
env[k] = v;
|
|
7572
7793
|
}
|
|
7573
7794
|
return env;
|
|
@@ -7789,13 +8010,13 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7789
8010
|
return { ok: false, durationMs: durationMs2, reason: reason2 };
|
|
7790
8011
|
}
|
|
7791
8012
|
try {
|
|
7792
|
-
const
|
|
7793
|
-
const env = runnerExecEnv(launch.env ?? process.env,
|
|
8013
|
+
const wrapped = launch.wrapped === true;
|
|
8014
|
+
const env = runnerExecEnv(launch.env ?? process.env, wrapped);
|
|
7794
8015
|
const grants = minted.deviceGrants.length > 0 ? launch.grants?.(minted.deviceGrants) ?? {} : {};
|
|
7795
8016
|
const input = {
|
|
7796
8017
|
baseUrl: opts.baseUrl,
|
|
7797
8018
|
credential: minted.credential,
|
|
7798
|
-
...
|
|
8019
|
+
...wrapped ? { secrets: launch.secrets?.() ?? {} } : {},
|
|
7799
8020
|
...Object.keys(grants).length > 0 ? { grants } : {},
|
|
7800
8021
|
dispatch: payload,
|
|
7801
8022
|
turnId,
|
|
@@ -7869,7 +8090,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7869
8090
|
locks?.release(lockOwner(event.id));
|
|
7870
8091
|
return;
|
|
7871
8092
|
case "process_group":
|
|
7872
|
-
if (!
|
|
8093
|
+
if (!wrapped) groups.push(event.pgid);
|
|
7873
8094
|
return;
|
|
7874
8095
|
case "remote_session":
|
|
7875
8096
|
remote.push({ serverUrl: event.serverUrl, sessionId: event.sessionId });
|
|
@@ -7944,7 +8165,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7944
8165
|
|
|
7945
8166
|
// src/turn-execution.ts
|
|
7946
8167
|
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
7947
|
-
import { mkdirSync as mkdirSync9 } from "fs";
|
|
8168
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
|
|
7948
8169
|
|
|
7949
8170
|
// src/grants.ts
|
|
7950
8171
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
@@ -7998,7 +8219,7 @@ function loadGrantsFile(report) {
|
|
|
7998
8219
|
const result = grantsFileSchema.safeParse(parsed);
|
|
7999
8220
|
if (!result.success) {
|
|
8000
8221
|
report.unreadable(
|
|
8001
|
-
`${path} must map a secret's variable name to an object of "VARIABLE": "value" pairs (e.g. { "
|
|
8222
|
+
`${path} must map a secret's variable name to an object of "VARIABLE": "value" pairs (e.g. { "HAS_STAGING_DB": { "DATABASE_URL": "postgres://\u2026" } }).`
|
|
8002
8223
|
);
|
|
8003
8224
|
return {};
|
|
8004
8225
|
}
|
|
@@ -8012,7 +8233,7 @@ import { join as join14 } from "path";
|
|
|
8012
8233
|
// src/turn-control-tools.ts
|
|
8013
8234
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8014
8235
|
import { z as z16 } from "zod";
|
|
8015
|
-
var COMPANION_LOCAL_MCP_SERVER =
|
|
8236
|
+
var COMPANION_LOCAL_MCP_SERVER = TURN_CONTROL_MCP_SERVER;
|
|
8016
8237
|
var COMPANION_LOCAL_TOOL_GLOB = `mcp__${COMPANION_LOCAL_MCP_SERVER}__*`;
|
|
8017
8238
|
var ASK_TOOL = "ask";
|
|
8018
8239
|
var ASK_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${ASK_TOOL}`;
|
|
@@ -8041,10 +8262,6 @@ import { homedir as homedir7 } from "os";
|
|
|
8041
8262
|
import { join as join17 } from "path";
|
|
8042
8263
|
import { createInterface as createInterface2 } from "readline";
|
|
8043
8264
|
|
|
8044
|
-
// src/prepare-failure.ts
|
|
8045
|
-
import { spawn as spawn3 } from "child_process";
|
|
8046
|
-
import { hostname } from "os";
|
|
8047
|
-
|
|
8048
8265
|
// src/prepared.ts
|
|
8049
8266
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "fs";
|
|
8050
8267
|
import { join as join18 } from "path";
|
|
@@ -8120,7 +8337,7 @@ function requiredSecretNames(mcpServers) {
|
|
|
8120
8337
|
}
|
|
8121
8338
|
|
|
8122
8339
|
// src/harness-usable.ts
|
|
8123
|
-
import { spawn as
|
|
8340
|
+
import { spawn as spawn3 } from "child_process";
|
|
8124
8341
|
|
|
8125
8342
|
// src/harness-versions.ts
|
|
8126
8343
|
var EMPTY = { claudeCode: null, opencode: null, codex: null };
|
|
@@ -8223,7 +8440,7 @@ function runBounded(path, args, env) {
|
|
|
8223
8440
|
};
|
|
8224
8441
|
let child;
|
|
8225
8442
|
try {
|
|
8226
|
-
child =
|
|
8443
|
+
child = spawn3(path, args, { stdio: ["ignore", "pipe", "pipe"], env });
|
|
8227
8444
|
} catch {
|
|
8228
8445
|
resolve({ code: null, output: "", error: "spawn" });
|
|
8229
8446
|
return;
|
|
@@ -8390,7 +8607,7 @@ function hostReport() {
|
|
|
8390
8607
|
}
|
|
8391
8608
|
|
|
8392
8609
|
// src/providers/local-process.ts
|
|
8393
|
-
import { spawn as
|
|
8610
|
+
import { spawn as spawn4 } from "child_process";
|
|
8394
8611
|
import { constants as constants2 } from "os";
|
|
8395
8612
|
var DEFAULT_KILL_GRACE_MS = 3e4;
|
|
8396
8613
|
var DEFAULT_REAP_GRACE_MS = 3e3;
|
|
@@ -8401,7 +8618,7 @@ function exitCode(code, signal) {
|
|
|
8401
8618
|
}
|
|
8402
8619
|
function createLocalProcessProvider(opts = {}) {
|
|
8403
8620
|
const killGraceMs = opts.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
8404
|
-
const spawnFn = opts.spawnFn ??
|
|
8621
|
+
const spawnFn = opts.spawnFn ?? spawn4;
|
|
8405
8622
|
const reapGraceMs = opts.reapGraceMs ?? DEFAULT_REAP_GRACE_MS;
|
|
8406
8623
|
const killFn = opts.killFn ?? ((pid, signal) => process.kill(pid, signal));
|
|
8407
8624
|
const groupAlive = (pgid) => {
|
|
@@ -8605,7 +8822,7 @@ function checkReason(check) {
|
|
|
8605
8822
|
|
|
8606
8823
|
// src/harness-copy.ts
|
|
8607
8824
|
import { homedir as homedir8 } from "os";
|
|
8608
|
-
import { join as
|
|
8825
|
+
import { join as join21 } from "path";
|
|
8609
8826
|
var HARNESS_LABELS = {
|
|
8610
8827
|
"claude-code": "Claude Code",
|
|
8611
8828
|
codex: "Codex",
|
|
@@ -8619,7 +8836,7 @@ function failureText(runtime, reason) {
|
|
|
8619
8836
|
case "spawn":
|
|
8620
8837
|
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())}`;
|
|
8621
8838
|
case "config":
|
|
8622
|
-
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(
|
|
8839
|
+
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join21(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
|
|
8623
8840
|
case "unreadable":
|
|
8624
8841
|
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.`;
|
|
8625
8842
|
}
|
|
@@ -8878,13 +9095,13 @@ var DispatchClaims = class {
|
|
|
8878
9095
|
import {
|
|
8879
9096
|
existsSync as existsSync14,
|
|
8880
9097
|
mkdirSync as mkdirSync10,
|
|
8881
|
-
readdirSync as
|
|
9098
|
+
readdirSync as readdirSync2,
|
|
8882
9099
|
readFileSync as readFileSync11,
|
|
8883
9100
|
renameSync as renameSync5,
|
|
8884
9101
|
rmSync as rmSync7,
|
|
8885
9102
|
writeFileSync as writeFileSync6
|
|
8886
9103
|
} from "fs";
|
|
8887
|
-
import { join as
|
|
9104
|
+
import { join as join22 } from "path";
|
|
8888
9105
|
var MAX_ENTRIES = 2e3;
|
|
8889
9106
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
8890
9107
|
var Outbox = class {
|
|
@@ -8897,10 +9114,10 @@ var Outbox = class {
|
|
|
8897
9114
|
// Resolved lazily (per call) so tests that swap HOME between
|
|
8898
9115
|
// cases route writes at the right tmpdir.
|
|
8899
9116
|
dir() {
|
|
8900
|
-
return
|
|
9117
|
+
return join22(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
|
|
8901
9118
|
}
|
|
8902
9119
|
fileFor(turnId, seq) {
|
|
8903
|
-
return
|
|
9120
|
+
return join22(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
|
|
8904
9121
|
}
|
|
8905
9122
|
// Persist a commit for later draining. Atomic (temp file + rename) so a
|
|
8906
9123
|
// concurrent `list()` never reads a half-written entry, then enforces the
|
|
@@ -8935,14 +9152,14 @@ var Outbox = class {
|
|
|
8935
9152
|
if (!existsSync14(dir2)) return [];
|
|
8936
9153
|
let names;
|
|
8937
9154
|
try {
|
|
8938
|
-
names =
|
|
9155
|
+
names = readdirSync2(dir2);
|
|
8939
9156
|
} catch {
|
|
8940
9157
|
return [];
|
|
8941
9158
|
}
|
|
8942
9159
|
const entries = [];
|
|
8943
9160
|
for (const name of names) {
|
|
8944
9161
|
if (!name.endsWith(".json")) continue;
|
|
8945
|
-
const full =
|
|
9162
|
+
const full = join22(dir2, name);
|
|
8946
9163
|
try {
|
|
8947
9164
|
const parsed = JSON.parse(readFileSync11(full, "utf8"));
|
|
8948
9165
|
if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
|
|
@@ -8979,7 +9196,7 @@ var Outbox = class {
|
|
|
8979
9196
|
maxSeqForTurn(turnId) {
|
|
8980
9197
|
const dir2 = this.dir();
|
|
8981
9198
|
if (!existsSync14(dir2)) return 0;
|
|
8982
|
-
const names =
|
|
9199
|
+
const names = readdirSync2(dir2);
|
|
8983
9200
|
const prefix = `${encodeURIComponent(turnId)}__`;
|
|
8984
9201
|
let max = 0;
|
|
8985
9202
|
for (const name of names) {
|
|
@@ -9000,7 +9217,7 @@ var Outbox = class {
|
|
|
9000
9217
|
const dir2 = this.dir();
|
|
9001
9218
|
if (!existsSync14(dir2)) return 0;
|
|
9002
9219
|
try {
|
|
9003
|
-
return
|
|
9220
|
+
return readdirSync2(dir2).filter((n) => n.endsWith(".json")).length;
|
|
9004
9221
|
} catch {
|
|
9005
9222
|
return 0;
|
|
9006
9223
|
}
|
|
@@ -9164,9 +9381,9 @@ var SseSubscriber = class {
|
|
|
9164
9381
|
};
|
|
9165
9382
|
|
|
9166
9383
|
// src/updater.ts
|
|
9167
|
-
import { spawn as
|
|
9384
|
+
import { spawn as spawn5 } from "child_process";
|
|
9168
9385
|
import { copyFileSync as copyFileSync2, existsSync as existsSync16, mkdirSync as mkdirSync14, renameSync as renameSync9, rmSync as rmSync10 } from "fs";
|
|
9169
|
-
import { dirname as dirname11, join as
|
|
9386
|
+
import { dirname as dirname11, join as join26 } from "path";
|
|
9170
9387
|
|
|
9171
9388
|
// src/boot-rule.ts
|
|
9172
9389
|
import {
|
|
@@ -9178,7 +9395,7 @@ import {
|
|
|
9178
9395
|
symlinkSync,
|
|
9179
9396
|
writeFileSync as writeFileSync7
|
|
9180
9397
|
} from "fs";
|
|
9181
|
-
import { dirname as dirname8, join as
|
|
9398
|
+
import { dirname as dirname8, join as join23 } from "path";
|
|
9182
9399
|
var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
|
|
9183
9400
|
var MAX_STAGE_ATTEMPTS = 3;
|
|
9184
9401
|
function superseded(pending, ...running) {
|
|
@@ -9205,32 +9422,32 @@ import {
|
|
|
9205
9422
|
lstatSync,
|
|
9206
9423
|
mkdirSync as mkdirSync12,
|
|
9207
9424
|
readFileSync as readFileSync13,
|
|
9208
|
-
readdirSync as
|
|
9425
|
+
readdirSync as readdirSync3,
|
|
9209
9426
|
realpathSync as realpathSync5,
|
|
9210
9427
|
renameSync as renameSync7,
|
|
9211
9428
|
rmSync as rmSync9,
|
|
9212
9429
|
symlinkSync as symlinkSync2
|
|
9213
9430
|
} from "fs";
|
|
9214
|
-
import { dirname as dirname9, join as
|
|
9431
|
+
import { dirname as dirname9, join as join24 } from "path";
|
|
9215
9432
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
9216
9433
|
function versionsDir() {
|
|
9217
|
-
return
|
|
9434
|
+
return join24(cabaneDir(), "versions");
|
|
9218
9435
|
}
|
|
9219
9436
|
function currentLink() {
|
|
9220
|
-
return
|
|
9437
|
+
return join24(cabaneDir(), "current");
|
|
9221
9438
|
}
|
|
9222
9439
|
function bootGuardPath() {
|
|
9223
|
-
return
|
|
9440
|
+
return join24(cabaneDir(), "boot-guard.mjs");
|
|
9224
9441
|
}
|
|
9225
9442
|
function versionPackageRoot(version) {
|
|
9226
|
-
return
|
|
9443
|
+
return join24(versionsDir(), version, "node_modules", "@cabane", "companion");
|
|
9227
9444
|
}
|
|
9228
9445
|
function ownPackageRoot(moduleUrl = import.meta.url) {
|
|
9229
9446
|
return dirname9(dirname9(fileURLToPath5(moduleUrl)));
|
|
9230
9447
|
}
|
|
9231
9448
|
function packageVersion(root) {
|
|
9232
9449
|
try {
|
|
9233
|
-
const pkg2 = JSON.parse(readFileSync13(
|
|
9450
|
+
const pkg2 = JSON.parse(readFileSync13(join24(root, "package.json"), "utf8"));
|
|
9234
9451
|
return typeof pkg2.version === "string" ? pkg2.version : null;
|
|
9235
9452
|
} catch {
|
|
9236
9453
|
return null;
|
|
@@ -9239,7 +9456,7 @@ function packageVersion(root) {
|
|
|
9239
9456
|
function currentRoot() {
|
|
9240
9457
|
try {
|
|
9241
9458
|
const root = realpathSync5(currentLink());
|
|
9242
|
-
return existsSync15(
|
|
9459
|
+
return existsSync15(join24(root, "dist", "cli.js")) ? root : null;
|
|
9243
9460
|
} catch {
|
|
9244
9461
|
return null;
|
|
9245
9462
|
}
|
|
@@ -9260,9 +9477,9 @@ function pruneVersions(keep) {
|
|
|
9260
9477
|
const dir2 = versionsDir();
|
|
9261
9478
|
if (!existsSync15(dir2)) return [];
|
|
9262
9479
|
const removed = [];
|
|
9263
|
-
for (const name of
|
|
9480
|
+
for (const name of readdirSync3(dir2)) {
|
|
9264
9481
|
if (keep.includes(name)) continue;
|
|
9265
|
-
rmSync9(
|
|
9482
|
+
rmSync9(join24(dir2, name), { recursive: true, force: true });
|
|
9266
9483
|
removed.push(name);
|
|
9267
9484
|
}
|
|
9268
9485
|
return removed;
|
|
@@ -9281,7 +9498,7 @@ function compareVersions(a, b) {
|
|
|
9281
9498
|
|
|
9282
9499
|
// src/update-state.ts
|
|
9283
9500
|
import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, renameSync as renameSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
9284
|
-
import { dirname as dirname10, join as
|
|
9501
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
9285
9502
|
import { z as z18 } from "zod";
|
|
9286
9503
|
var resultSchema = z18.object({
|
|
9287
9504
|
kind: z18.enum(["updated", "rolled_back", "failed"]),
|
|
@@ -9305,7 +9522,7 @@ var stateSchema = z18.object({
|
|
|
9305
9522
|
failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
|
|
9306
9523
|
});
|
|
9307
9524
|
function updateStatePath(dir2 = cabaneDir()) {
|
|
9308
|
-
return
|
|
9525
|
+
return join25(dir2, "update.json");
|
|
9309
9526
|
}
|
|
9310
9527
|
function readUpdateState(path = updateStatePath()) {
|
|
9311
9528
|
try {
|
|
@@ -9354,7 +9571,7 @@ function rollBack(pending, reason, now = /* @__PURE__ */ new Date()) {
|
|
|
9354
9571
|
return updateUpdateState((state) => rolledBackState(state, pending, reason, now));
|
|
9355
9572
|
}
|
|
9356
9573
|
function writeBootGuard(root = ownPackageRoot()) {
|
|
9357
|
-
const built =
|
|
9574
|
+
const built = join26(root, "dist", "boot-guard.js");
|
|
9358
9575
|
if (!existsSync16(built)) return false;
|
|
9359
9576
|
const target = bootGuardPath();
|
|
9360
9577
|
mkdirSync14(dirname11(target), { recursive: true });
|
|
@@ -9371,7 +9588,7 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve) => {
|
|
|
9371
9588
|
let stderr = "";
|
|
9372
9589
|
let child;
|
|
9373
9590
|
try {
|
|
9374
|
-
child =
|
|
9591
|
+
child = spawn5(command, [...args], {
|
|
9375
9592
|
stdio: ["ignore", "pipe", "pipe"],
|
|
9376
9593
|
env: opts.env ?? process.env
|
|
9377
9594
|
});
|
|
@@ -9394,17 +9611,17 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve) => {
|
|
|
9394
9611
|
async function stageVersion(version, harnesses, deps = {}) {
|
|
9395
9612
|
const run = deps.run ?? runProcess;
|
|
9396
9613
|
const node = deps.execPath ?? process.execPath;
|
|
9397
|
-
const target =
|
|
9614
|
+
const target = join26(versionsDir(), version);
|
|
9398
9615
|
const placed = versionPackageRoot(version);
|
|
9399
9616
|
if (existsSync16(placed)) {
|
|
9400
9617
|
if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
|
|
9401
9618
|
return { ok: true, root: placed };
|
|
9402
9619
|
}
|
|
9403
|
-
const npm =
|
|
9620
|
+
const npm = join26(dirname11(node), "npm");
|
|
9404
9621
|
if (!existsSync16(npm))
|
|
9405
9622
|
return { ok: false, reason: `there is no npm beside Node at ${dirname11(node)}` };
|
|
9406
9623
|
mkdirSync14(versionsDir(), { recursive: true });
|
|
9407
|
-
const staging =
|
|
9624
|
+
const staging = join26(versionsDir(), `.${version}.staging-${process.pid}`);
|
|
9408
9625
|
rmSync10(staging, { recursive: true, force: true });
|
|
9409
9626
|
const installed = await run(
|
|
9410
9627
|
npm,
|
|
@@ -9433,7 +9650,7 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
9433
9650
|
return { ok: false, reason: `version ${version} isn't published yet`, unpublished: true };
|
|
9434
9651
|
return { ok: false, reason: `npm couldn't install it: ${lastLine(installed.stderr)}` };
|
|
9435
9652
|
}
|
|
9436
|
-
const root =
|
|
9653
|
+
const root = join26(staging, "node_modules", "@cabane", "companion");
|
|
9437
9654
|
const verdict = await verifyInstall(root, version, harnesses, { run, execPath: node });
|
|
9438
9655
|
if (verdict) {
|
|
9439
9656
|
rmSync10(staging, { recursive: true, force: true });
|
|
@@ -9445,7 +9662,7 @@ async function stageVersion(version, harnesses, deps = {}) {
|
|
|
9445
9662
|
}
|
|
9446
9663
|
async function verifyInstall(root, version, harnesses, deps) {
|
|
9447
9664
|
if (packageVersion(root) !== version) return `the installed package isn't version ${version}`;
|
|
9448
|
-
const answered = await deps.run(deps.execPath, [
|
|
9665
|
+
const answered = await deps.run(deps.execPath, [join26(root, "dist", "bin.js"), "--version"], {
|
|
9449
9666
|
timeoutMs: 6e4,
|
|
9450
9667
|
env: { ...process.env, CABANE_LAUNCH_SELF: "1" }
|
|
9451
9668
|
});
|
|
@@ -9453,7 +9670,7 @@ async function verifyInstall(root, version, harnesses, deps) {
|
|
|
9453
9670
|
return `it doesn't start: ${lastLine(answered.stderr) || "no version printed"}`;
|
|
9454
9671
|
}
|
|
9455
9672
|
if (harnesses.length === 0) return null;
|
|
9456
|
-
const probed = await deps.run(deps.execPath, [
|
|
9673
|
+
const probed = await deps.run(deps.execPath, [join26(root, "dist", "postinstall.js"), "--json"], {
|
|
9457
9674
|
timeoutMs: 6e4
|
|
9458
9675
|
});
|
|
9459
9676
|
let resolutions = {};
|
|
@@ -9870,13 +10087,13 @@ var CompanionSupervisor = class {
|
|
|
9870
10087
|
if (this.stopped) return;
|
|
9871
10088
|
this.log.info(
|
|
9872
10089
|
{ protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
|
|
9873
|
-
`Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ??
|
|
10090
|
+
`Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname()}`
|
|
9874
10091
|
);
|
|
9875
10092
|
const retired = retiredConfigKeysOnDisk();
|
|
9876
10093
|
if (retired.length > 0) {
|
|
9877
10094
|
this.log.warn(
|
|
9878
10095
|
{ keys: retired },
|
|
9879
|
-
`config.json: ${retired.join(", ")} ${retired.length === 1 ? "is" : "are"} no longer read and can be removed
|
|
10096
|
+
`config.json: ${retired.join(", ")} ${retired.length === 1 ? "is" : "are"} no longer read and can be removed.`
|
|
9880
10097
|
);
|
|
9881
10098
|
}
|
|
9882
10099
|
if (this.config.deviceToken) {
|
|
@@ -9916,7 +10133,7 @@ var CompanionSupervisor = class {
|
|
|
9916
10133
|
log: this.log,
|
|
9917
10134
|
lastEventId: null,
|
|
9918
10135
|
onOpen: () => {
|
|
9919
|
-
this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ??
|
|
10136
|
+
this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ?? hostname()}`);
|
|
9920
10137
|
this.deviceConnected = true;
|
|
9921
10138
|
for (const wr of this.workspaces.values()) {
|
|
9922
10139
|
this.hub.setConnected(wr.workspaceId, true);
|
|
@@ -10462,9 +10679,9 @@ var CompanionSupervisor = class {
|
|
|
10462
10679
|
this.runnerCommand ?? selfRunnerCommand()
|
|
10463
10680
|
),
|
|
10464
10681
|
...this.config.runner?.command ? {
|
|
10465
|
-
|
|
10682
|
+
wrapped: true,
|
|
10466
10683
|
secrets: () => loadSecretValuesTolerant(
|
|
10467
|
-
(m) => this.log.warn({ err: m }, "Could not read the secrets file for a
|
|
10684
|
+
(m) => this.log.warn({ err: m }, "Could not read the secrets file for a wrapped turn.")
|
|
10468
10685
|
)
|
|
10469
10686
|
} : {},
|
|
10470
10687
|
// Read per dispatch, for the secret variables its mint named and no others.
|
|
@@ -11143,9 +11360,9 @@ async function waitForBoundedHeartbeat(heartbeat) {
|
|
|
11143
11360
|
}
|
|
11144
11361
|
function defaultReexec() {
|
|
11145
11362
|
clearRuntimeState();
|
|
11146
|
-
void import("child_process").then(({ spawn:
|
|
11363
|
+
void import("child_process").then(({ spawn: spawn6 }) => {
|
|
11147
11364
|
try {
|
|
11148
|
-
const child =
|
|
11365
|
+
const child = spawn6(process.execPath, process.argv.slice(1), {
|
|
11149
11366
|
stdio: "inherit",
|
|
11150
11367
|
detached: false
|
|
11151
11368
|
});
|
|
@@ -11216,9 +11433,9 @@ function handleUncaught(log, err, origin) {
|
|
|
11216
11433
|
|
|
11217
11434
|
// src/crash-marker.ts
|
|
11218
11435
|
import { existsSync as existsSync17, mkdirSync as mkdirSync15, readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
11219
|
-
import { join as
|
|
11436
|
+
import { join as join27 } from "path";
|
|
11220
11437
|
function crashMarkerPath() {
|
|
11221
|
-
return
|
|
11438
|
+
return join27(cabaneDir(), "last-error.json");
|
|
11222
11439
|
}
|
|
11223
11440
|
function recordCrash(rec) {
|
|
11224
11441
|
try {
|
|
@@ -11238,7 +11455,7 @@ function clearCrash() {
|
|
|
11238
11455
|
// src/stray-companion.ts
|
|
11239
11456
|
import { spawnSync } from "child_process";
|
|
11240
11457
|
import { readFileSync as readFileSync16 } from "fs";
|
|
11241
|
-
import { join as
|
|
11458
|
+
import { join as join28 } from "path";
|
|
11242
11459
|
var COMPANION_ENTRY = /@cabane\/companion\/|cabane-companion|(^|[\s/])cabane\s|\.cabane\/(versions|current|boot-guard)/;
|
|
11243
11460
|
var START = /\sstart(\s|$)/;
|
|
11244
11461
|
function companionStarts(psOutput) {
|
|
@@ -11255,7 +11472,7 @@ function companionStarts(psOutput) {
|
|
|
11255
11472
|
}
|
|
11256
11473
|
function stateDirOf(env) {
|
|
11257
11474
|
const home = env.CABANE_COMPANION_HOME || env.HOME;
|
|
11258
|
-
return home ?
|
|
11475
|
+
return home ? join28(home, ".cabane") : null;
|
|
11259
11476
|
}
|
|
11260
11477
|
function envFromPsE(output, command) {
|
|
11261
11478
|
const line = output.replace(/\n$/, "");
|