@cabane/companion 0.6.106 → 0.6.107
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 +136 -45
- package/dist/pairing-config.js +9 -1
- package/dist/runtime.js +130 -39
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -93,7 +93,15 @@ var prepareHookSchema = z.object({
|
|
|
93
93
|
//
|
|
94
94
|
// Default OFF, and deliberately: an operator hook that provisions
|
|
95
95
|
// unconditionally must not start running on every turn because it upgraded.
|
|
96
|
-
validateCached: z.boolean().optional()
|
|
96
|
+
validateCached: z.boolean().optional(),
|
|
97
|
+
// Independent operator-owned reporter. Receives sanitized failure metadata
|
|
98
|
+
// on stdin; never re-enters this hook and cannot change the turn outcome.
|
|
99
|
+
failureReporter: z.object({
|
|
100
|
+
command: z.string().min(1),
|
|
101
|
+
args: z.array(z.string()).optional(),
|
|
102
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
103
|
+
timeoutMs: z.number().int().positive().max(3e4).optional()
|
|
104
|
+
}).strict().optional()
|
|
97
105
|
}).strict();
|
|
98
106
|
var DEFAULT_TIMEOUT_MS = 10 * 6e4;
|
|
99
107
|
var prepareResultSchema = z.object({
|
|
@@ -1769,8 +1777,8 @@ function requestEnrollmentCode(baseUrl, opts = {}) {
|
|
|
1769
1777
|
...opts.label ? { label: opts.label } : {}
|
|
1770
1778
|
});
|
|
1771
1779
|
}
|
|
1772
|
-
function deviceLabelFromHostname(
|
|
1773
|
-
const trimmed =
|
|
1780
|
+
function deviceLabelFromHostname(hostname5) {
|
|
1781
|
+
const trimmed = hostname5.trim().replace(/\.local$/i, "");
|
|
1774
1782
|
if (trimmed.length === 0) return void 0;
|
|
1775
1783
|
return trimmed.slice(0, 120);
|
|
1776
1784
|
}
|
|
@@ -2097,7 +2105,7 @@ async function logs(opts = {}) {
|
|
|
2097
2105
|
}
|
|
2098
2106
|
|
|
2099
2107
|
// src/commands/start.ts
|
|
2100
|
-
import { hostname as
|
|
2108
|
+
import { hostname as hostname4 } from "os";
|
|
2101
2109
|
|
|
2102
2110
|
// src/runtime.ts
|
|
2103
2111
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -2662,7 +2670,7 @@ async function warnAboutHarnessReadiness(cfg, deps = {}) {
|
|
|
2662
2670
|
}
|
|
2663
2671
|
|
|
2664
2672
|
// src/supervisor.ts
|
|
2665
|
-
import { hostname as
|
|
2673
|
+
import { hostname as hostname3 } from "os";
|
|
2666
2674
|
|
|
2667
2675
|
// packages/agent-runtime/src/failure.ts
|
|
2668
2676
|
import { z as z4 } from "zod";
|
|
@@ -8604,6 +8612,68 @@ function readOutboxFloor(read, turnId, log) {
|
|
|
8604
8612
|
}
|
|
8605
8613
|
}
|
|
8606
8614
|
|
|
8615
|
+
// src/prepare-failure.ts
|
|
8616
|
+
import { spawn as spawn4 } from "child_process";
|
|
8617
|
+
import { hostname as hostname2 } from "os";
|
|
8618
|
+
function prepareFailureCause(error) {
|
|
8619
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8620
|
+
if (/timed out/i.test(message)) return "prepare hook timed out";
|
|
8621
|
+
if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message))
|
|
8622
|
+
return "prepare hook module missing; check the configured command and its installation for this device";
|
|
8623
|
+
if (/not valid JSON|JSON must carry|produced no output/.test(message))
|
|
8624
|
+
return "prepare hook returned malformed output; expected a cwd path or JSON with a non-empty cwd";
|
|
8625
|
+
if (/ENOENT/.test(message)) return "prepare hook executable missing (ENOENT)";
|
|
8626
|
+
if (/EACCES/.test(message)) return "prepare hook executable is not accessible (EACCES)";
|
|
8627
|
+
const exit = message.match(/exited with code (\d+)/)?.[1];
|
|
8628
|
+
return exit ? `prepare hook exited with code ${exit}` : "prepare hook failed; inspect the device-local hook log";
|
|
8629
|
+
}
|
|
8630
|
+
async function runPrepareWithReporting(runner, hook, input, context, reportError) {
|
|
8631
|
+
try {
|
|
8632
|
+
return await runner(hook, input);
|
|
8633
|
+
} catch (error) {
|
|
8634
|
+
if (hook.failureReporter) {
|
|
8635
|
+
try {
|
|
8636
|
+
await reportFailure(hook.failureReporter, {
|
|
8637
|
+
workspaceId: input.workspaceId,
|
|
8638
|
+
conversationId: input.conversationId,
|
|
8639
|
+
agentId: input.agentId,
|
|
8640
|
+
agentUsername: input.agentUsername,
|
|
8641
|
+
turnId: context.turnId,
|
|
8642
|
+
deviceId: context.deviceId ?? hostname2(),
|
|
8643
|
+
cause: prepareFailureCause(error)
|
|
8644
|
+
});
|
|
8645
|
+
} catch {
|
|
8646
|
+
reportError();
|
|
8647
|
+
}
|
|
8648
|
+
}
|
|
8649
|
+
throw error;
|
|
8650
|
+
}
|
|
8651
|
+
}
|
|
8652
|
+
function reportFailure(config, body) {
|
|
8653
|
+
return new Promise((resolve2, reject) => {
|
|
8654
|
+
const child = spawn4(config.command, config.args ?? [], {
|
|
8655
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
8656
|
+
env: { ...process.env, ...config.env }
|
|
8657
|
+
});
|
|
8658
|
+
const timer = setTimeout(() => {
|
|
8659
|
+
child.kill("SIGKILL");
|
|
8660
|
+
reject(new Error("reporter timeout"));
|
|
8661
|
+
}, config.timeoutMs ?? 15e3);
|
|
8662
|
+
child.stdin.on("error", () => {
|
|
8663
|
+
});
|
|
8664
|
+
child.once("error", () => {
|
|
8665
|
+
clearTimeout(timer);
|
|
8666
|
+
reject(new Error("reporter failed to start"));
|
|
8667
|
+
});
|
|
8668
|
+
child.once("close", (code) => {
|
|
8669
|
+
clearTimeout(timer);
|
|
8670
|
+
if (code === 0) resolve2();
|
|
8671
|
+
else reject(new Error("reporter failed"));
|
|
8672
|
+
});
|
|
8673
|
+
child.stdin.end(JSON.stringify(body));
|
|
8674
|
+
});
|
|
8675
|
+
}
|
|
8676
|
+
|
|
8607
8677
|
// src/prepared.ts
|
|
8608
8678
|
import { mkdirSync as mkdirSync8, readFileSync as readFileSync6, rmSync as rmSync5, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
|
|
8609
8679
|
import { join as join12 } from "path";
|
|
@@ -8834,7 +8904,7 @@ function pruneOld(dir2, retain) {
|
|
|
8834
8904
|
}
|
|
8835
8905
|
|
|
8836
8906
|
// src/turn-containment.ts
|
|
8837
|
-
import { execFile, execFileSync, spawn as
|
|
8907
|
+
import { execFile, execFileSync, spawn as spawn5 } from "child_process";
|
|
8838
8908
|
import { readFileSync as readFileSync8 } from "fs";
|
|
8839
8909
|
import { promisify } from "util";
|
|
8840
8910
|
var execFileAsync = promisify(execFile);
|
|
@@ -8916,7 +8986,7 @@ function createTurnContainment(turnId, deps = {}) {
|
|
|
8916
8986
|
command: options.command
|
|
8917
8987
|
});
|
|
8918
8988
|
return asContained(
|
|
8919
|
-
|
|
8989
|
+
spawn5(
|
|
8920
8990
|
"systemd-run",
|
|
8921
8991
|
[
|
|
8922
8992
|
"--user",
|
|
@@ -8943,7 +9013,7 @@ function createTurnContainment(turnId, deps = {}) {
|
|
|
8943
9013
|
);
|
|
8944
9014
|
}
|
|
8945
9015
|
function spawnInGroup(options) {
|
|
8946
|
-
const child =
|
|
9016
|
+
const child = spawn5(options.command, options.args, {
|
|
8947
9017
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
8948
9018
|
env: options.env,
|
|
8949
9019
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -9645,18 +9715,28 @@ var TurnExecution = class {
|
|
|
9645
9715
|
if (prepareHook.validateCached) {
|
|
9646
9716
|
const runHook = this.opts.prepareHookRunner ?? runPrepareHook;
|
|
9647
9717
|
try {
|
|
9648
|
-
await
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9718
|
+
await runPrepareWithReporting(
|
|
9719
|
+
runHook,
|
|
9720
|
+
prepareHook,
|
|
9721
|
+
{
|
|
9722
|
+
workspaceId,
|
|
9723
|
+
conversationId: payload.conversationId,
|
|
9724
|
+
agentId: payload.agentId,
|
|
9725
|
+
agentUsername: this.opts.agentUsername,
|
|
9726
|
+
runtime: this.turnContext.runtime,
|
|
9727
|
+
hostAccess: this.turnContext.policy.hostFs,
|
|
9728
|
+
triggerEntryPaths: this.turnContext.conversation.triggerEntryPaths ?? [],
|
|
9729
|
+
title: this.turnContext.conversation.title,
|
|
9730
|
+
messageBody: this.turnContext.message.body,
|
|
9731
|
+
prepared: cached2
|
|
9732
|
+
},
|
|
9733
|
+
{ turnId, deviceId: this.opts.deviceId },
|
|
9734
|
+
() => {
|
|
9735
|
+
turnLog.error(
|
|
9736
|
+
"dispatcher: prepare failure reporter failed; original failure preserved"
|
|
9737
|
+
);
|
|
9738
|
+
}
|
|
9739
|
+
);
|
|
9660
9740
|
} catch (err) {
|
|
9661
9741
|
const reason = err instanceof Error ? err.message : String(err);
|
|
9662
9742
|
turnLog.debug({ err: reason }, "dispatcher: prepare hook rejected a prepared turn");
|
|
@@ -9692,24 +9772,34 @@ var TurnExecution = class {
|
|
|
9692
9772
|
preparingTimer.unref?.();
|
|
9693
9773
|
const runHook = this.opts.prepareHookRunner ?? runPrepareHook;
|
|
9694
9774
|
try {
|
|
9695
|
-
const result = await
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9775
|
+
const result = await runPrepareWithReporting(
|
|
9776
|
+
runHook,
|
|
9777
|
+
prepareHook,
|
|
9778
|
+
{
|
|
9779
|
+
workspaceId,
|
|
9780
|
+
conversationId: payload.conversationId,
|
|
9781
|
+
agentId: payload.agentId,
|
|
9782
|
+
agentUsername: this.opts.agentUsername,
|
|
9783
|
+
runtime: this.turnContext.runtime,
|
|
9784
|
+
hostAccess: this.turnContext.policy.hostFs,
|
|
9785
|
+
// CT317/CT319: the trigger message's referenced-entry paths — what the
|
|
9786
|
+
// tasker prepare hook keys its per-task env off. Defaults to `[]` for
|
|
9787
|
+
// an older API. The conversation anchor is gone (CT319).
|
|
9788
|
+
triggerEntryPaths: this.turnContext.conversation.triggerEntryPaths ?? [],
|
|
9789
|
+
title: this.turnContext.conversation.title,
|
|
9790
|
+
// CT943: the dispatching message's text — where an `env:` directive
|
|
9791
|
+
// rides. The server has always sent the trigger body on the turn
|
|
9792
|
+
// context (for the live feed); this is the first thing to read it as
|
|
9793
|
+
// an INPUT, so a dispatch can ask for its environment in words.
|
|
9794
|
+
messageBody: this.turnContext.message.body
|
|
9795
|
+
},
|
|
9796
|
+
{ turnId, deviceId: this.opts.deviceId },
|
|
9797
|
+
() => {
|
|
9798
|
+
turnLog.error(
|
|
9799
|
+
"dispatcher: prepare failure reporter failed; original failure preserved"
|
|
9800
|
+
);
|
|
9801
|
+
}
|
|
9802
|
+
);
|
|
9713
9803
|
clearTimeout(preparingTimer);
|
|
9714
9804
|
if (preparingStarted) reportPreparing("done");
|
|
9715
9805
|
writePrepared(workspaceId, payload.conversationId, payload.agentId, result);
|
|
@@ -10870,7 +10960,7 @@ var CompanionSupervisor = class {
|
|
|
10870
10960
|
async start() {
|
|
10871
10961
|
this.log.info(
|
|
10872
10962
|
{ protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
|
|
10873
|
-
`Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ??
|
|
10963
|
+
`Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname3()}`
|
|
10874
10964
|
);
|
|
10875
10965
|
const startupHarnessRefresh = this.refreshHarnessStatuses();
|
|
10876
10966
|
this.trackHeartbeat(startupHarnessRefresh);
|
|
@@ -10889,7 +10979,7 @@ var CompanionSupervisor = class {
|
|
|
10889
10979
|
token: this.config.deviceToken,
|
|
10890
10980
|
log: this.log,
|
|
10891
10981
|
lastEventId: null,
|
|
10892
|
-
onOpen: () => this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ??
|
|
10982
|
+
onOpen: () => this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ?? hostname3()}`),
|
|
10893
10983
|
onMessage: async (ev) => {
|
|
10894
10984
|
if (ev.event === "assignments_changed") {
|
|
10895
10985
|
if (this.refreshing && this.inFlightRefresh) await this.inFlightRefresh;
|
|
@@ -11305,6 +11395,7 @@ var CompanionSupervisor = class {
|
|
|
11305
11395
|
});
|
|
11306
11396
|
if (this.dispatcherFactory) return this.dispatcherFactory({ ...ctx, local });
|
|
11307
11397
|
return new Dispatcher({
|
|
11398
|
+
deviceId: this.deviceId ?? void 0,
|
|
11308
11399
|
api: ctx.api,
|
|
11309
11400
|
baseUrl: ctx.baseUrl,
|
|
11310
11401
|
workspaceId: ctx.workspaceId,
|
|
@@ -11952,9 +12043,9 @@ async function waitForBoundedHeartbeat(heartbeat) {
|
|
|
11952
12043
|
}
|
|
11953
12044
|
function defaultReexec() {
|
|
11954
12045
|
clearRuntimeState();
|
|
11955
|
-
void import("child_process").then(({ spawn:
|
|
12046
|
+
void import("child_process").then(({ spawn: spawn7 }) => {
|
|
11956
12047
|
try {
|
|
11957
|
-
const child =
|
|
12048
|
+
const child = spawn7(process.execPath, process.argv.slice(1), {
|
|
11958
12049
|
stdio: "inherit",
|
|
11959
12050
|
detached: false
|
|
11960
12051
|
});
|
|
@@ -12209,7 +12300,7 @@ async function closeSurfaces(control, dashboard) {
|
|
|
12209
12300
|
}
|
|
12210
12301
|
|
|
12211
12302
|
// src/commands/daemon.ts
|
|
12212
|
-
import { spawn as
|
|
12303
|
+
import { spawn as spawn6 } from "child_process";
|
|
12213
12304
|
import { closeSync as closeSync3, mkdirSync as mkdirSync12, openSync as openSync3 } from "fs";
|
|
12214
12305
|
|
|
12215
12306
|
// src/cli-entry.ts
|
|
@@ -12321,7 +12412,7 @@ function defaultSpawnDetached(args) {
|
|
|
12321
12412
|
mkdirSync12(cabaneDir(), { recursive: true });
|
|
12322
12413
|
const logFd = openSync3(companionLogPath(), "a");
|
|
12323
12414
|
try {
|
|
12324
|
-
return
|
|
12415
|
+
return spawn6(process.execPath, [cliPath, ...args], {
|
|
12325
12416
|
detached: true,
|
|
12326
12417
|
stdio: ["ignore", logFd, logFd],
|
|
12327
12418
|
env: { ...process.env, CABANE_COMPANION_DAEMON: "1" }
|
|
@@ -12489,7 +12580,7 @@ function prePairFoundPhrase(harness) {
|
|
|
12489
12580
|
}
|
|
12490
12581
|
async function pairHere(opts, interactive) {
|
|
12491
12582
|
const baseUrl = resolvePairBaseUrl(opts.server);
|
|
12492
|
-
const label = deviceLabelFromHostname(
|
|
12583
|
+
const label = deviceLabelFromHostname(hostname4());
|
|
12493
12584
|
const aborter = new AbortController();
|
|
12494
12585
|
const onSigint = () => aborter.abort();
|
|
12495
12586
|
process.on("SIGINT", onSigint);
|
package/dist/pairing-config.js
CHANGED
|
@@ -76,7 +76,15 @@ var prepareHookSchema = z.object({
|
|
|
76
76
|
//
|
|
77
77
|
// Default OFF, and deliberately: an operator hook that provisions
|
|
78
78
|
// unconditionally must not start running on every turn because it upgraded.
|
|
79
|
-
validateCached: z.boolean().optional()
|
|
79
|
+
validateCached: z.boolean().optional(),
|
|
80
|
+
// Independent operator-owned reporter. Receives sanitized failure metadata
|
|
81
|
+
// on stdin; never re-enters this hook and cannot change the turn outcome.
|
|
82
|
+
failureReporter: z.object({
|
|
83
|
+
command: z.string().min(1),
|
|
84
|
+
args: z.array(z.string()).optional(),
|
|
85
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
86
|
+
timeoutMs: z.number().int().positive().max(3e4).optional()
|
|
87
|
+
}).strict().optional()
|
|
80
88
|
}).strict();
|
|
81
89
|
var DEFAULT_TIMEOUT_MS = 10 * 6e4;
|
|
82
90
|
var prepareResultSchema = z.object({
|
package/dist/runtime.js
CHANGED
|
@@ -85,7 +85,15 @@ var prepareHookSchema = z.object({
|
|
|
85
85
|
//
|
|
86
86
|
// Default OFF, and deliberately: an operator hook that provisions
|
|
87
87
|
// unconditionally must not start running on every turn because it upgraded.
|
|
88
|
-
validateCached: z.boolean().optional()
|
|
88
|
+
validateCached: z.boolean().optional(),
|
|
89
|
+
// Independent operator-owned reporter. Receives sanitized failure metadata
|
|
90
|
+
// on stdin; never re-enters this hook and cannot change the turn outcome.
|
|
91
|
+
failureReporter: z.object({
|
|
92
|
+
command: z.string().min(1),
|
|
93
|
+
args: z.array(z.string()).optional(),
|
|
94
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
95
|
+
timeoutMs: z.number().int().positive().max(3e4).optional()
|
|
96
|
+
}).strict().optional()
|
|
89
97
|
}).strict();
|
|
90
98
|
var DEFAULT_TIMEOUT_MS = 10 * 6e4;
|
|
91
99
|
var prepareResultSchema = z.object({
|
|
@@ -1996,7 +2004,7 @@ async function verifyRuntime(state, requestImpl = controlRequest) {
|
|
|
1996
2004
|
}
|
|
1997
2005
|
|
|
1998
2006
|
// src/supervisor.ts
|
|
1999
|
-
import { hostname } from "os";
|
|
2007
|
+
import { hostname as hostname2 } from "os";
|
|
2000
2008
|
|
|
2001
2009
|
// packages/agent-runtime/src/failure.ts
|
|
2002
2010
|
import { z as z3 } from "zod";
|
|
@@ -8027,6 +8035,68 @@ function readOutboxFloor(read, turnId, log) {
|
|
|
8027
8035
|
}
|
|
8028
8036
|
}
|
|
8029
8037
|
|
|
8038
|
+
// src/prepare-failure.ts
|
|
8039
|
+
import { spawn as spawn4 } from "child_process";
|
|
8040
|
+
import { hostname } from "os";
|
|
8041
|
+
function prepareFailureCause(error) {
|
|
8042
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8043
|
+
if (/timed out/i.test(message)) return "prepare hook timed out";
|
|
8044
|
+
if (/MODULE_NOT_FOUND|Cannot find (?:module|package)/.test(message))
|
|
8045
|
+
return "prepare hook module missing; check the configured command and its installation for this device";
|
|
8046
|
+
if (/not valid JSON|JSON must carry|produced no output/.test(message))
|
|
8047
|
+
return "prepare hook returned malformed output; expected a cwd path or JSON with a non-empty cwd";
|
|
8048
|
+
if (/ENOENT/.test(message)) return "prepare hook executable missing (ENOENT)";
|
|
8049
|
+
if (/EACCES/.test(message)) return "prepare hook executable is not accessible (EACCES)";
|
|
8050
|
+
const exit = message.match(/exited with code (\d+)/)?.[1];
|
|
8051
|
+
return exit ? `prepare hook exited with code ${exit}` : "prepare hook failed; inspect the device-local hook log";
|
|
8052
|
+
}
|
|
8053
|
+
async function runPrepareWithReporting(runner, hook, input, context, reportError) {
|
|
8054
|
+
try {
|
|
8055
|
+
return await runner(hook, input);
|
|
8056
|
+
} catch (error) {
|
|
8057
|
+
if (hook.failureReporter) {
|
|
8058
|
+
try {
|
|
8059
|
+
await reportFailure(hook.failureReporter, {
|
|
8060
|
+
workspaceId: input.workspaceId,
|
|
8061
|
+
conversationId: input.conversationId,
|
|
8062
|
+
agentId: input.agentId,
|
|
8063
|
+
agentUsername: input.agentUsername,
|
|
8064
|
+
turnId: context.turnId,
|
|
8065
|
+
deviceId: context.deviceId ?? hostname(),
|
|
8066
|
+
cause: prepareFailureCause(error)
|
|
8067
|
+
});
|
|
8068
|
+
} catch {
|
|
8069
|
+
reportError();
|
|
8070
|
+
}
|
|
8071
|
+
}
|
|
8072
|
+
throw error;
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
function reportFailure(config, body) {
|
|
8076
|
+
return new Promise((resolve2, reject) => {
|
|
8077
|
+
const child = spawn4(config.command, config.args ?? [], {
|
|
8078
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
8079
|
+
env: { ...process.env, ...config.env }
|
|
8080
|
+
});
|
|
8081
|
+
const timer = setTimeout(() => {
|
|
8082
|
+
child.kill("SIGKILL");
|
|
8083
|
+
reject(new Error("reporter timeout"));
|
|
8084
|
+
}, config.timeoutMs ?? 15e3);
|
|
8085
|
+
child.stdin.on("error", () => {
|
|
8086
|
+
});
|
|
8087
|
+
child.once("error", () => {
|
|
8088
|
+
clearTimeout(timer);
|
|
8089
|
+
reject(new Error("reporter failed to start"));
|
|
8090
|
+
});
|
|
8091
|
+
child.once("close", (code) => {
|
|
8092
|
+
clearTimeout(timer);
|
|
8093
|
+
if (code === 0) resolve2();
|
|
8094
|
+
else reject(new Error("reporter failed"));
|
|
8095
|
+
});
|
|
8096
|
+
child.stdin.end(JSON.stringify(body));
|
|
8097
|
+
});
|
|
8098
|
+
}
|
|
8099
|
+
|
|
8030
8100
|
// src/prepared.ts
|
|
8031
8101
|
import { mkdirSync as mkdirSync8, readFileSync as readFileSync6, rmSync as rmSync5, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
|
|
8032
8102
|
import { join as join12 } from "path";
|
|
@@ -8257,7 +8327,7 @@ function pruneOld(dir2, retain) {
|
|
|
8257
8327
|
}
|
|
8258
8328
|
|
|
8259
8329
|
// src/turn-containment.ts
|
|
8260
|
-
import { execFile, execFileSync, spawn as
|
|
8330
|
+
import { execFile, execFileSync, spawn as spawn5 } from "child_process";
|
|
8261
8331
|
import { readFileSync as readFileSync8 } from "fs";
|
|
8262
8332
|
import { promisify } from "util";
|
|
8263
8333
|
var execFileAsync = promisify(execFile);
|
|
@@ -8339,7 +8409,7 @@ function createTurnContainment(turnId, deps = {}) {
|
|
|
8339
8409
|
command: options.command
|
|
8340
8410
|
});
|
|
8341
8411
|
return asContained(
|
|
8342
|
-
|
|
8412
|
+
spawn5(
|
|
8343
8413
|
"systemd-run",
|
|
8344
8414
|
[
|
|
8345
8415
|
"--user",
|
|
@@ -8366,7 +8436,7 @@ function createTurnContainment(turnId, deps = {}) {
|
|
|
8366
8436
|
);
|
|
8367
8437
|
}
|
|
8368
8438
|
function spawnInGroup(options) {
|
|
8369
|
-
const child =
|
|
8439
|
+
const child = spawn5(options.command, options.args, {
|
|
8370
8440
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
8371
8441
|
env: options.env,
|
|
8372
8442
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -9068,18 +9138,28 @@ var TurnExecution = class {
|
|
|
9068
9138
|
if (prepareHook.validateCached) {
|
|
9069
9139
|
const runHook = this.opts.prepareHookRunner ?? runPrepareHook;
|
|
9070
9140
|
try {
|
|
9071
|
-
await
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9141
|
+
await runPrepareWithReporting(
|
|
9142
|
+
runHook,
|
|
9143
|
+
prepareHook,
|
|
9144
|
+
{
|
|
9145
|
+
workspaceId,
|
|
9146
|
+
conversationId: payload.conversationId,
|
|
9147
|
+
agentId: payload.agentId,
|
|
9148
|
+
agentUsername: this.opts.agentUsername,
|
|
9149
|
+
runtime: this.turnContext.runtime,
|
|
9150
|
+
hostAccess: this.turnContext.policy.hostFs,
|
|
9151
|
+
triggerEntryPaths: this.turnContext.conversation.triggerEntryPaths ?? [],
|
|
9152
|
+
title: this.turnContext.conversation.title,
|
|
9153
|
+
messageBody: this.turnContext.message.body,
|
|
9154
|
+
prepared: cached2
|
|
9155
|
+
},
|
|
9156
|
+
{ turnId, deviceId: this.opts.deviceId },
|
|
9157
|
+
() => {
|
|
9158
|
+
turnLog.error(
|
|
9159
|
+
"dispatcher: prepare failure reporter failed; original failure preserved"
|
|
9160
|
+
);
|
|
9161
|
+
}
|
|
9162
|
+
);
|
|
9083
9163
|
} catch (err) {
|
|
9084
9164
|
const reason = err instanceof Error ? err.message : String(err);
|
|
9085
9165
|
turnLog.debug({ err: reason }, "dispatcher: prepare hook rejected a prepared turn");
|
|
@@ -9115,24 +9195,34 @@ var TurnExecution = class {
|
|
|
9115
9195
|
preparingTimer.unref?.();
|
|
9116
9196
|
const runHook = this.opts.prepareHookRunner ?? runPrepareHook;
|
|
9117
9197
|
try {
|
|
9118
|
-
const result = await
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9198
|
+
const result = await runPrepareWithReporting(
|
|
9199
|
+
runHook,
|
|
9200
|
+
prepareHook,
|
|
9201
|
+
{
|
|
9202
|
+
workspaceId,
|
|
9203
|
+
conversationId: payload.conversationId,
|
|
9204
|
+
agentId: payload.agentId,
|
|
9205
|
+
agentUsername: this.opts.agentUsername,
|
|
9206
|
+
runtime: this.turnContext.runtime,
|
|
9207
|
+
hostAccess: this.turnContext.policy.hostFs,
|
|
9208
|
+
// CT317/CT319: the trigger message's referenced-entry paths — what the
|
|
9209
|
+
// tasker prepare hook keys its per-task env off. Defaults to `[]` for
|
|
9210
|
+
// an older API. The conversation anchor is gone (CT319).
|
|
9211
|
+
triggerEntryPaths: this.turnContext.conversation.triggerEntryPaths ?? [],
|
|
9212
|
+
title: this.turnContext.conversation.title,
|
|
9213
|
+
// CT943: the dispatching message's text — where an `env:` directive
|
|
9214
|
+
// rides. The server has always sent the trigger body on the turn
|
|
9215
|
+
// context (for the live feed); this is the first thing to read it as
|
|
9216
|
+
// an INPUT, so a dispatch can ask for its environment in words.
|
|
9217
|
+
messageBody: this.turnContext.message.body
|
|
9218
|
+
},
|
|
9219
|
+
{ turnId, deviceId: this.opts.deviceId },
|
|
9220
|
+
() => {
|
|
9221
|
+
turnLog.error(
|
|
9222
|
+
"dispatcher: prepare failure reporter failed; original failure preserved"
|
|
9223
|
+
);
|
|
9224
|
+
}
|
|
9225
|
+
);
|
|
9136
9226
|
clearTimeout(preparingTimer);
|
|
9137
9227
|
if (preparingStarted) reportPreparing("done");
|
|
9138
9228
|
writePrepared(workspaceId, payload.conversationId, payload.agentId, result);
|
|
@@ -10293,7 +10383,7 @@ var CompanionSupervisor = class {
|
|
|
10293
10383
|
async start() {
|
|
10294
10384
|
this.log.info(
|
|
10295
10385
|
{ protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
|
|
10296
|
-
`Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ??
|
|
10386
|
+
`Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname2()}`
|
|
10297
10387
|
);
|
|
10298
10388
|
const startupHarnessRefresh = this.refreshHarnessStatuses();
|
|
10299
10389
|
this.trackHeartbeat(startupHarnessRefresh);
|
|
@@ -10312,7 +10402,7 @@ var CompanionSupervisor = class {
|
|
|
10312
10402
|
token: this.config.deviceToken,
|
|
10313
10403
|
log: this.log,
|
|
10314
10404
|
lastEventId: null,
|
|
10315
|
-
onOpen: () => this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ??
|
|
10405
|
+
onOpen: () => this.log.info(`Connected to Cabane as ${this.hub.statusJson().device_label ?? hostname2()}`),
|
|
10316
10406
|
onMessage: async (ev) => {
|
|
10317
10407
|
if (ev.event === "assignments_changed") {
|
|
10318
10408
|
if (this.refreshing && this.inFlightRefresh) await this.inFlightRefresh;
|
|
@@ -10728,6 +10818,7 @@ var CompanionSupervisor = class {
|
|
|
10728
10818
|
});
|
|
10729
10819
|
if (this.dispatcherFactory) return this.dispatcherFactory({ ...ctx, local });
|
|
10730
10820
|
return new Dispatcher({
|
|
10821
|
+
deviceId: this.deviceId ?? void 0,
|
|
10731
10822
|
api: ctx.api,
|
|
10732
10823
|
baseUrl: ctx.baseUrl,
|
|
10733
10824
|
workspaceId: ctx.workspaceId,
|
|
@@ -11375,9 +11466,9 @@ async function waitForBoundedHeartbeat(heartbeat) {
|
|
|
11375
11466
|
}
|
|
11376
11467
|
function defaultReexec() {
|
|
11377
11468
|
clearRuntimeState();
|
|
11378
|
-
void import("child_process").then(({ spawn:
|
|
11469
|
+
void import("child_process").then(({ spawn: spawn6 }) => {
|
|
11379
11470
|
try {
|
|
11380
|
-
const child =
|
|
11471
|
+
const child = spawn6(process.execPath, process.argv.slice(1), {
|
|
11381
11472
|
stdio: "inherit",
|
|
11382
11473
|
detached: false
|
|
11383
11474
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.107",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Cabane Companion (headless): connect a coding 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",
|