@cabane/companion 0.6.237 → 0.6.239
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 +2 -0
- package/dist/cli.js +78 -253
- package/dist/launch.js +41 -10
- package/dist/postinstall.js +41 -10
- package/package.json +1 -3
- package/dist/pairing-config.js +0 -591
- package/dist/runtime.js +0 -9663
package/dist/cli.js
CHANGED
|
@@ -279,7 +279,7 @@ var init_errors = __esm({
|
|
|
279
279
|
});
|
|
280
280
|
|
|
281
281
|
// src/pairing.ts
|
|
282
|
-
function isAllowedBaseUrl(raw) {
|
|
282
|
+
function isAllowedBaseUrl(raw, env = process.env) {
|
|
283
283
|
let url;
|
|
284
284
|
try {
|
|
285
285
|
url = new URL(raw);
|
|
@@ -289,13 +289,57 @@ function isAllowedBaseUrl(raw) {
|
|
|
289
289
|
if (url.protocol === "https:") return true;
|
|
290
290
|
if (url.protocol === "http:") {
|
|
291
291
|
const host = url.hostname.toLowerCase();
|
|
292
|
-
|
|
292
|
+
if (host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1" || host === "::1" || host === "[::1]")
|
|
293
|
+
return true;
|
|
294
|
+
const allowed = plainHttpHost(env);
|
|
295
|
+
return allowed !== null && host === allowed;
|
|
293
296
|
}
|
|
294
297
|
return false;
|
|
295
298
|
}
|
|
299
|
+
function plainHttpHost(env = process.env) {
|
|
300
|
+
const raw = env[ALLOW_PLAIN_HTTP_HOST_ENV]?.trim().toLowerCase();
|
|
301
|
+
if (!raw) return null;
|
|
302
|
+
if (isSingleLabel(raw) || isPrivateIpv4(raw)) return raw;
|
|
303
|
+
const bare = raw.startsWith("[") && raw.endsWith("]") ? raw.slice(1, -1) : raw;
|
|
304
|
+
return privateIpv6(bare);
|
|
305
|
+
}
|
|
306
|
+
function isSingleLabel(host) {
|
|
307
|
+
return /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(host) && !/^\d+$/.test(host);
|
|
308
|
+
}
|
|
309
|
+
function isPrivateIpv4(host) {
|
|
310
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
311
|
+
if (!m) return false;
|
|
312
|
+
const [a, b, c, d] = m.slice(1).map(Number);
|
|
313
|
+
if ([a, b, c, d].some((n) => n > 255)) return false;
|
|
314
|
+
return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
|
|
315
|
+
}
|
|
316
|
+
function privateIpv6(host) {
|
|
317
|
+
if (!host.includes(":")) return null;
|
|
318
|
+
let bracketed;
|
|
319
|
+
try {
|
|
320
|
+
bracketed = new URL(`http://[${host}]/`).hostname;
|
|
321
|
+
} catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
const first = Number.parseInt(bracketed.slice(1).split(":")[0] || "0", 16);
|
|
325
|
+
return (first & 65024) === 64512 ? bracketed : null;
|
|
326
|
+
}
|
|
327
|
+
function addressToKeep(pairedOver, cabaneNamed, env = process.env) {
|
|
328
|
+
const allowed = plainHttpHost(env);
|
|
329
|
+
if (allowed === null) return cabaneNamed;
|
|
330
|
+
let url;
|
|
331
|
+
try {
|
|
332
|
+
url = new URL(pairedOver);
|
|
333
|
+
} catch {
|
|
334
|
+
return cabaneNamed;
|
|
335
|
+
}
|
|
336
|
+
return url.protocol === "http:" && url.hostname.toLowerCase() === allowed ? pairedOver.replace(/\/+$/, "") : cabaneNamed;
|
|
337
|
+
}
|
|
338
|
+
var ALLOW_PLAIN_HTTP_HOST_ENV;
|
|
296
339
|
var init_pairing = __esm({
|
|
297
340
|
"src/pairing.ts"() {
|
|
298
341
|
"use strict";
|
|
342
|
+
ALLOW_PLAIN_HTTP_HOST_ENV = "CABANE_ALLOW_PLAIN_HTTP_HOST";
|
|
299
343
|
}
|
|
300
344
|
});
|
|
301
345
|
|
|
@@ -712,10 +756,10 @@ var init_config = __esm({
|
|
|
712
756
|
}).strict();
|
|
713
757
|
agentsConfigSchema = z2.record(z2.string(), localAgentConfigSchema);
|
|
714
758
|
companionConfigSchema = z2.object({
|
|
715
|
-
// https-enforced (loopback
|
|
716
|
-
// device token and prompt on a plaintext channel.
|
|
717
|
-
baseUrl: z2.string().url().refine(isAllowedBaseUrl, {
|
|
718
|
-
message:
|
|
759
|
+
// https-enforced (loopback, and the one private host a person names, exempt), so a
|
|
760
|
+
// hand-edited config can't put the device token and prompt on a plaintext channel.
|
|
761
|
+
baseUrl: z2.string().url().refine((url) => isAllowedBaseUrl(url), {
|
|
762
|
+
message: `baseUrl must be https (plain http only to this machine, or to the private host ${ALLOW_PLAIN_HTTP_HOST_ENV} names)`
|
|
719
763
|
}),
|
|
720
764
|
// The `cabdev_` device token plaintext — the companion's one durable credential,
|
|
721
765
|
// presented as `Authorization: Bearer <token>` on the device-facing pull +
|
|
@@ -724,7 +768,7 @@ var init_config = __esm({
|
|
|
724
768
|
// the rest of the config; `pair` always writes one.
|
|
725
769
|
deviceToken: z2.string().optional(),
|
|
726
770
|
// Identity hints, learned from the device flow and refreshed on the first
|
|
727
|
-
// assignments pull. Cosmetic — used for `status
|
|
771
|
+
// assignments pull. Cosmetic — used for `status` display only.
|
|
728
772
|
deviceId: z2.string().optional(),
|
|
729
773
|
deviceLabel: z2.string().optional(),
|
|
730
774
|
// Optional per-agent machine-local overrides (cwd / prepareHook), keyed by
|
|
@@ -736,9 +780,8 @@ var init_config = __esm({
|
|
|
736
780
|
// silent roster: an agent nobody listed gets no hook and no error, and starts
|
|
737
781
|
// in the operator's `$HOME`.
|
|
738
782
|
prepareHook: prepareHookSchema.optional(),
|
|
739
|
-
//
|
|
740
|
-
//
|
|
741
|
-
// startup. `logLevel` is pino's level, live-editable.
|
|
783
|
+
// Nothing reads either dashboard setting any more; both stay parseable so an
|
|
784
|
+
// older config file still loads. `logLevel` is pino's level, live-editable.
|
|
742
785
|
dashboardPort: z2.number().int().min(1).max(65535).optional(),
|
|
743
786
|
autoOpen: z2.boolean().optional(),
|
|
744
787
|
logLevel: z2.enum(["warn", "info", "debug"]).optional(),
|
|
@@ -7434,10 +7477,6 @@ var init_turn_committer = __esm({
|
|
|
7434
7477
|
get lastMessageSeq() {
|
|
7435
7478
|
return this.pump.lastMessageSeq;
|
|
7436
7479
|
}
|
|
7437
|
-
// The turn's final reply text, or null if none landed.
|
|
7438
|
-
get replyBody() {
|
|
7439
|
-
return this.pump.replyBody;
|
|
7440
|
-
}
|
|
7441
7480
|
get finalSource() {
|
|
7442
7481
|
return this.pump.finalSource;
|
|
7443
7482
|
}
|
|
@@ -7815,7 +7854,6 @@ var init_turn_execution = __esm({
|
|
|
7815
7854
|
);
|
|
7816
7855
|
}
|
|
7817
7856
|
const durationMs = Date.now() - startedAt;
|
|
7818
|
-
this.supervisor.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
|
|
7819
7857
|
return { ok: false, durationMs, reason, runtime: this.turnContext?.runtime };
|
|
7820
7858
|
}
|
|
7821
7859
|
// The admitted turn holds the conversation's lease, so the clear must NAME the
|
|
@@ -7854,7 +7892,6 @@ var init_turn_execution = __esm({
|
|
|
7854
7892
|
}
|
|
7855
7893
|
}
|
|
7856
7894
|
const durationMs = Date.now() - startedAt;
|
|
7857
|
-
this.supervisor.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
|
|
7858
7895
|
return { ok: false, durationMs, reason, runtime: this.turnContext?.runtime };
|
|
7859
7896
|
}
|
|
7860
7897
|
async fetchContext() {
|
|
@@ -7963,11 +8000,7 @@ var init_turn_execution = __esm({
|
|
|
7963
8000
|
turnLog.debug({ role: message2.role }, "dispatcher: trigger role not dispatchable \u2014 skipping");
|
|
7964
8001
|
throw this.concluded("unexpected_role", UNEXPECTED_ROLE_REASON);
|
|
7965
8002
|
}
|
|
7966
|
-
this.supervisor.notifyStart({
|
|
7967
|
-
id: this.dispatchId,
|
|
7968
|
-
conversationId: payload.conversationId,
|
|
7969
|
-
message: message2.body
|
|
7970
|
-
});
|
|
8003
|
+
this.supervisor.notifyStart({ id: this.dispatchId, conversationId: payload.conversationId });
|
|
7971
8004
|
}
|
|
7972
8005
|
async resolveSecrets() {
|
|
7973
8006
|
const { turnLog } = this;
|
|
@@ -8736,7 +8769,6 @@ var init_turn_execution = __esm({
|
|
|
8736
8769
|
const o = this.outcome;
|
|
8737
8770
|
const { startedAt, turnLog } = this;
|
|
8738
8771
|
const durationMs = Date.now() - startedAt;
|
|
8739
|
-
const finalReplyBody = this.committer.replyBody;
|
|
8740
8772
|
if (this.transcript) {
|
|
8741
8773
|
this.transcript.close({
|
|
8742
8774
|
ok: o.okResult,
|
|
@@ -8749,21 +8781,9 @@ var init_turn_execution = __esm({
|
|
|
8749
8781
|
}
|
|
8750
8782
|
if (o.okResult) {
|
|
8751
8783
|
turnLog.debug({ durationMs }, "dispatcher: turn end (ok)");
|
|
8752
|
-
this.supervisor.notifyEnd({
|
|
8753
|
-
id: this.dispatchId,
|
|
8754
|
-
ok: true,
|
|
8755
|
-
durationMs,
|
|
8756
|
-
...finalReplyBody ? { reply: finalReplyBody.trim() } : {}
|
|
8757
|
-
});
|
|
8758
8784
|
return { ok: true, durationMs };
|
|
8759
8785
|
}
|
|
8760
8786
|
turnLog.debug({ durationMs, reason: o.resultReason }, "dispatcher: turn end (not ok)");
|
|
8761
|
-
this.supervisor.notifyEnd({
|
|
8762
|
-
id: this.dispatchId,
|
|
8763
|
-
ok: false,
|
|
8764
|
-
durationMs,
|
|
8765
|
-
...o.resultReason ? { reason: o.resultReason } : {}
|
|
8766
|
-
});
|
|
8767
8787
|
return {
|
|
8768
8788
|
ok: false,
|
|
8769
8789
|
durationMs,
|
|
@@ -9269,8 +9289,7 @@ async function runTurn(env, deps) {
|
|
|
9269
9289
|
emit({ cabaneRunner: "session_write_notice" });
|
|
9270
9290
|
return true;
|
|
9271
9291
|
},
|
|
9272
|
-
notifyStart: (info) => emit({ cabaneRunner: "start", ...info })
|
|
9273
|
-
notifyEnd: (info) => emit({ cabaneRunner: "end", ...info })
|
|
9292
|
+
notifyStart: (info) => emit({ cabaneRunner: "start", ...info })
|
|
9274
9293
|
};
|
|
9275
9294
|
let outbox;
|
|
9276
9295
|
const opts = runnerTurnOptions(input, log2, emit, {
|
|
@@ -10312,6 +10331,9 @@ function openLink(link) {
|
|
|
10312
10331
|
});
|
|
10313
10332
|
}
|
|
10314
10333
|
|
|
10334
|
+
// src/commands/setup.ts
|
|
10335
|
+
init_pairing();
|
|
10336
|
+
|
|
10315
10337
|
// src/commands/start.ts
|
|
10316
10338
|
init_cabane_command();
|
|
10317
10339
|
init_config();
|
|
@@ -10322,13 +10344,12 @@ init_config();
|
|
|
10322
10344
|
init_pairing();
|
|
10323
10345
|
init_errors();
|
|
10324
10346
|
init_install_kind();
|
|
10325
|
-
init_errors();
|
|
10326
10347
|
function resolvePairBaseUrl(server) {
|
|
10327
10348
|
const raw = (server ?? process.env.CABANE_BASE_URL ?? "").trim();
|
|
10328
10349
|
if (!raw) throw new NoPairAddressError();
|
|
10329
10350
|
if (!isAllowedBaseUrl(raw)) {
|
|
10330
10351
|
throw new CompanionError(
|
|
10331
|
-
`invalid server URL "${raw}": must be an https URL (
|
|
10352
|
+
`invalid server URL "${raw}": must be an https URL (plain http only to this machine, or to the private host ${ALLOW_PLAIN_HTTP_HOST_ENV} names). Pass it as \`${cabane("setup --server https://your-cabane")}\`.`
|
|
10332
10353
|
);
|
|
10333
10354
|
}
|
|
10334
10355
|
return raw;
|
|
@@ -10371,7 +10392,6 @@ function isDevicePaired() {
|
|
|
10371
10392
|
init_config();
|
|
10372
10393
|
init_cabane_command();
|
|
10373
10394
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10374
|
-
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
10375
10395
|
|
|
10376
10396
|
// src/prereqs.ts
|
|
10377
10397
|
init_config();
|
|
@@ -10388,71 +10408,37 @@ function warnAboutHarnessReadiness(cfg, deps = {}) {
|
|
|
10388
10408
|
}
|
|
10389
10409
|
|
|
10390
10410
|
// src/state.ts
|
|
10391
|
-
import { EventEmitter } from "events";
|
|
10392
|
-
function dispatchToWire(r) {
|
|
10393
|
-
return {
|
|
10394
|
-
id: r.id,
|
|
10395
|
-
timestamp: r.timestamp,
|
|
10396
|
-
workspace_slug: r.workspaceSlug,
|
|
10397
|
-
...r.agentUsername ? { agent_username: r.agentUsername } : {},
|
|
10398
|
-
message: r.message,
|
|
10399
|
-
status: r.status,
|
|
10400
|
-
...r.durationMs !== void 0 ? { duration_ms: r.durationMs } : {},
|
|
10401
|
-
...r.error ? { error: r.error } : {},
|
|
10402
|
-
...r.reply ? { reply: r.reply } : {}
|
|
10403
|
-
};
|
|
10404
|
-
}
|
|
10405
|
-
var MAX_DISPATCHES = 50;
|
|
10406
10411
|
function today() {
|
|
10407
10412
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10408
10413
|
}
|
|
10409
10414
|
var CompanionStateHub = class {
|
|
10410
10415
|
constructor(opts) {
|
|
10411
10416
|
this.opts = opts;
|
|
10412
|
-
this.emitter.setMaxListeners(0);
|
|
10413
10417
|
}
|
|
10414
10418
|
opts;
|
|
10415
|
-
emitter = new EventEmitter();
|
|
10416
10419
|
workspaces = /* @__PURE__ */ new Map();
|
|
10417
|
-
dispatches = [];
|
|
10418
|
-
dashboardUrl = null;
|
|
10419
10420
|
deviceId = null;
|
|
10420
10421
|
deviceLabel = null;
|
|
10421
10422
|
deviceError = null;
|
|
10422
10423
|
// The latest harness snapshot the supervisor probed, or null before the
|
|
10423
10424
|
// first probe (see CompanionStatusJson.harnesses).
|
|
10424
10425
|
harnesses = null;
|
|
10425
|
-
// ---- subscription (SSE) ----
|
|
10426
|
-
on(listener) {
|
|
10427
|
-
this.emitter.on("event", listener);
|
|
10428
|
-
return () => this.emitter.off("event", listener);
|
|
10429
|
-
}
|
|
10430
|
-
emit(type, data) {
|
|
10431
|
-
this.emitter.emit("event", { type, data });
|
|
10432
|
-
}
|
|
10433
10426
|
// ---- device ----
|
|
10434
10427
|
setDevice(info) {
|
|
10435
10428
|
if (info.deviceId !== void 0) this.deviceId = info.deviceId;
|
|
10436
10429
|
if (info.deviceLabel !== void 0) this.deviceLabel = info.deviceLabel;
|
|
10437
|
-
this.emitStatus();
|
|
10438
10430
|
}
|
|
10439
10431
|
setDeviceError(message2) {
|
|
10440
|
-
if (this.deviceError === message2) return;
|
|
10441
10432
|
this.deviceError = message2;
|
|
10442
|
-
this.emitStatus();
|
|
10443
10433
|
}
|
|
10444
10434
|
clearDeviceError() {
|
|
10445
|
-
if (this.deviceError === null) return;
|
|
10446
10435
|
this.deviceError = null;
|
|
10447
|
-
this.emitStatus();
|
|
10448
10436
|
}
|
|
10449
10437
|
// ---- harnesses ----
|
|
10450
|
-
// Replace the harness snapshot
|
|
10451
|
-
// the Companion's Harnesses surface live). The supervisor calls this on each
|
|
10438
|
+
// Replace the harness snapshot. The supervisor calls this on each
|
|
10452
10439
|
// heartbeat-cadence probe and on an on-demand recheck.
|
|
10453
10440
|
setHarnesses(harnesses) {
|
|
10454
10441
|
this.harnesses = harnesses;
|
|
10455
|
-
this.emitStatus();
|
|
10456
10442
|
}
|
|
10457
10443
|
// ---- workspace lifecycle ----
|
|
10458
10444
|
registerWorkspace(ws) {
|
|
@@ -10473,29 +10459,21 @@ var CompanionStateHub = class {
|
|
|
10473
10459
|
countDate: today(),
|
|
10474
10460
|
agents: /* @__PURE__ */ new Map()
|
|
10475
10461
|
});
|
|
10476
|
-
this.emitStatus();
|
|
10477
10462
|
}
|
|
10478
10463
|
removeWorkspace(workspaceId) {
|
|
10479
|
-
const ws = this.workspaces.get(workspaceId);
|
|
10480
|
-
if (!ws) return;
|
|
10481
10464
|
this.workspaces.delete(workspaceId);
|
|
10482
|
-
this.emit("workspace:disconnected", { slug: ws.slug });
|
|
10483
|
-
this.emitStatus();
|
|
10484
10465
|
}
|
|
10485
10466
|
setConnected(workspaceId, connected) {
|
|
10486
10467
|
const ws = this.workspaces.get(workspaceId);
|
|
10487
10468
|
if (!ws || ws.connected === connected) return;
|
|
10488
10469
|
ws.connected = connected;
|
|
10489
10470
|
if (connected) ws.authFailed = false;
|
|
10490
|
-
this.emitStatus();
|
|
10491
10471
|
}
|
|
10492
10472
|
setAuthFailed(workspaceId) {
|
|
10493
10473
|
const ws = this.workspaces.get(workspaceId);
|
|
10494
10474
|
if (!ws) return;
|
|
10495
|
-
if (ws.authFailed && !ws.connected) return;
|
|
10496
10475
|
ws.authFailed = true;
|
|
10497
10476
|
ws.connected = false;
|
|
10498
|
-
this.emitStatus();
|
|
10499
10477
|
}
|
|
10500
10478
|
recordEvent(workspaceId) {
|
|
10501
10479
|
const ws = this.workspaces.get(workspaceId);
|
|
@@ -10508,22 +10486,15 @@ var CompanionStateHub = class {
|
|
|
10508
10486
|
const ws = this.workspaces.get(workspaceId);
|
|
10509
10487
|
if (!ws) return;
|
|
10510
10488
|
ws.agents.set(agent.agentId, agent);
|
|
10511
|
-
this.emitStatus();
|
|
10512
10489
|
}
|
|
10513
10490
|
removeAgent(workspaceId, agentId) {
|
|
10514
|
-
|
|
10515
|
-
if (!ws) return;
|
|
10516
|
-
if (ws.agents.delete(agentId)) this.emitStatus();
|
|
10491
|
+
this.workspaces.get(workspaceId)?.agents.delete(agentId);
|
|
10517
10492
|
}
|
|
10518
|
-
// ---- dispatch
|
|
10519
|
-
// A DispatchObserver bound to one
|
|
10520
|
-
//
|
|
10521
|
-
observerFor(workspaceId
|
|
10522
|
-
|
|
10523
|
-
return {
|
|
10524
|
-
onStart: (info) => this.dispatchStarted(workspaceId, slug, username(), info),
|
|
10525
|
-
onEnd: (info) => this.dispatchEnded(slug, username(), info)
|
|
10526
|
-
};
|
|
10493
|
+
// ---- dispatch count ----
|
|
10494
|
+
// A DispatchObserver bound to one workspace, handed to each of its agents'
|
|
10495
|
+
// Dispatchers by the supervisor: a turn counts toward the day when it starts.
|
|
10496
|
+
observerFor(workspaceId) {
|
|
10497
|
+
return { onStart: () => this.bumpCount(workspaceId) };
|
|
10527
10498
|
}
|
|
10528
10499
|
bumpCount(workspaceId) {
|
|
10529
10500
|
const ws = this.workspaces.get(workspaceId);
|
|
@@ -10535,52 +10506,6 @@ var CompanionStateHub = class {
|
|
|
10535
10506
|
}
|
|
10536
10507
|
ws.dispatchCountToday += 1;
|
|
10537
10508
|
}
|
|
10538
|
-
dispatchStarted(workspaceId, slug, agentUsername, info) {
|
|
10539
|
-
this.bumpCount(workspaceId);
|
|
10540
|
-
const record = {
|
|
10541
|
-
id: info.id,
|
|
10542
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10543
|
-
workspaceSlug: slug,
|
|
10544
|
-
...agentUsername ? { agentUsername } : {},
|
|
10545
|
-
message: info.message,
|
|
10546
|
-
status: "running"
|
|
10547
|
-
};
|
|
10548
|
-
this.upsertDispatch(record);
|
|
10549
|
-
this.emit("dispatch:started", dispatchToWire(record));
|
|
10550
|
-
this.emitStatus();
|
|
10551
|
-
}
|
|
10552
|
-
dispatchEnded(slug, agentUsername, info) {
|
|
10553
|
-
const existing = this.dispatches.find((d) => d.id === info.id);
|
|
10554
|
-
const record = {
|
|
10555
|
-
id: info.id,
|
|
10556
|
-
timestamp: existing?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10557
|
-
workspaceSlug: existing?.workspaceSlug ?? slug,
|
|
10558
|
-
...existing?.agentUsername ?? agentUsername ? { agentUsername: existing?.agentUsername ?? agentUsername } : {},
|
|
10559
|
-
message: existing?.message ?? "",
|
|
10560
|
-
status: info.ok ? "replied" : "error",
|
|
10561
|
-
durationMs: info.durationMs,
|
|
10562
|
-
...info.reason ? { error: info.reason } : {},
|
|
10563
|
-
...info.reply ? { reply: info.reply } : {}
|
|
10564
|
-
};
|
|
10565
|
-
this.upsertDispatch(record);
|
|
10566
|
-
this.emit(info.ok ? "dispatch:completed" : "dispatch:error", dispatchToWire(record));
|
|
10567
|
-
}
|
|
10568
|
-
upsertDispatch(record) {
|
|
10569
|
-
const idx = this.dispatches.findIndex((d) => d.id === record.id);
|
|
10570
|
-
if (idx >= 0) {
|
|
10571
|
-
this.dispatches[idx] = record;
|
|
10572
|
-
return;
|
|
10573
|
-
}
|
|
10574
|
-
this.dispatches.unshift(record);
|
|
10575
|
-
if (this.dispatches.length > MAX_DISPATCHES) this.dispatches.length = MAX_DISPATCHES;
|
|
10576
|
-
}
|
|
10577
|
-
// ---- snapshots for the HTTP routes ----
|
|
10578
|
-
setDashboardUrl(url) {
|
|
10579
|
-
this.dashboardUrl = url;
|
|
10580
|
-
}
|
|
10581
|
-
recentDispatches(limit = 20) {
|
|
10582
|
-
return this.dispatches.slice(0, Math.max(0, limit));
|
|
10583
|
-
}
|
|
10584
10509
|
statusJson() {
|
|
10585
10510
|
const list = [...this.workspaces.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
10586
10511
|
const lastOverall = list.reduce((acc, ws) => {
|
|
@@ -10610,15 +10535,11 @@ var CompanionStateHub = class {
|
|
|
10610
10535
|
}))
|
|
10611
10536
|
})),
|
|
10612
10537
|
last_event_at_overall: lastOverall,
|
|
10613
|
-
dashboard_url: this.dashboardUrl,
|
|
10614
10538
|
companion_version: this.opts.companionVersion,
|
|
10615
10539
|
instance_id: this.opts.instanceId ?? null,
|
|
10616
10540
|
harnesses: this.harnesses
|
|
10617
10541
|
};
|
|
10618
10542
|
}
|
|
10619
|
-
emitStatus() {
|
|
10620
|
-
this.emit("status:changed", this.statusJson());
|
|
10621
|
-
}
|
|
10622
10543
|
};
|
|
10623
10544
|
|
|
10624
10545
|
// src/supervisor.ts
|
|
@@ -11387,7 +11308,6 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
11387
11308
|
duties.releaseAbort(turnId, controller);
|
|
11388
11309
|
const durationMs2 = Date.now() - startedAt;
|
|
11389
11310
|
await settleUnstarted(opts, payload, turnId, minted.errorReason, log2);
|
|
11390
|
-
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs: durationMs2, reason: minted.reason });
|
|
11391
11311
|
return { ok: false, durationMs: durationMs2, reason: minted.reason };
|
|
11392
11312
|
}
|
|
11393
11313
|
let result;
|
|
@@ -11418,7 +11338,6 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
11418
11338
|
reason: "provider_failed"
|
|
11419
11339
|
});
|
|
11420
11340
|
const durationMs2 = Date.now() - startedAt;
|
|
11421
|
-
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs: durationMs2, reason: reason2 });
|
|
11422
11341
|
return { ok: false, durationMs: durationMs2, reason: reason2 };
|
|
11423
11342
|
}
|
|
11424
11343
|
try {
|
|
@@ -11482,17 +11401,8 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
11482
11401
|
}
|
|
11483
11402
|
return;
|
|
11484
11403
|
case "start":
|
|
11485
|
-
duties.notifyStart({
|
|
11486
|
-
id: event.id,
|
|
11487
|
-
conversationId: event.conversationId,
|
|
11488
|
-
message: event.message
|
|
11489
|
-
});
|
|
11490
|
-
return;
|
|
11491
|
-
case "end": {
|
|
11492
|
-
const { cabaneRunner: _kind, ...info } = event;
|
|
11493
|
-
duties.notifyEnd(info);
|
|
11404
|
+
duties.notifyStart({ id: event.id, conversationId: event.conversationId });
|
|
11494
11405
|
return;
|
|
11495
|
-
}
|
|
11496
11406
|
case "session_write_notice":
|
|
11497
11407
|
duties.markSessionWriteNotice();
|
|
11498
11408
|
return;
|
|
@@ -11596,7 +11506,6 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
11596
11506
|
if (thrown !== void 0) throw new Error(thrown);
|
|
11597
11507
|
if (stopped) log2.info({ turnId, code }, "runner: stopped before it could report");
|
|
11598
11508
|
else log2.warn({ turnId, code }, "runner: exited without reporting a result");
|
|
11599
|
-
duties.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
|
|
11600
11509
|
return { ok: false, durationMs, reason };
|
|
11601
11510
|
}
|
|
11602
11511
|
|
|
@@ -11623,12 +11532,6 @@ var Dispatcher = class {
|
|
|
11623
11532
|
} catch {
|
|
11624
11533
|
}
|
|
11625
11534
|
}
|
|
11626
|
-
notifyEnd(info) {
|
|
11627
|
-
try {
|
|
11628
|
-
this.opts.observer?.onEnd(info);
|
|
11629
|
-
} catch {
|
|
11630
|
-
}
|
|
11631
|
-
}
|
|
11632
11535
|
// The supervisor supplies a stable turnId on both fresh and resumed dispatch.
|
|
11633
11536
|
// resumed is separate: only replay seeds sequence/speech recovery from the
|
|
11634
11537
|
// prior span. A direct caller omitting turnId gets a fresh id.
|
|
@@ -11656,8 +11559,7 @@ var Dispatcher = class {
|
|
|
11656
11559
|
markSessionWriteNotice: () => {
|
|
11657
11560
|
this.sessionWriteNotified.add(key);
|
|
11658
11561
|
},
|
|
11659
|
-
notifyStart: (info) => this.notifyStart(info)
|
|
11660
|
-
notifyEnd: (info) => this.notifyEnd(info)
|
|
11562
|
+
notifyStart: (info) => this.notifyStart(info)
|
|
11661
11563
|
},
|
|
11662
11564
|
payload,
|
|
11663
11565
|
{
|
|
@@ -12756,7 +12658,6 @@ var CompanionSupervisor = class {
|
|
|
12756
12658
|
runHarnessCheck;
|
|
12757
12659
|
probeOpencode;
|
|
12758
12660
|
exitFn;
|
|
12759
|
-
reexecFn;
|
|
12760
12661
|
dispatcherFactory;
|
|
12761
12662
|
runnerCommand;
|
|
12762
12663
|
runnerEnv;
|
|
@@ -12767,8 +12668,8 @@ var CompanionSupervisor = class {
|
|
|
12767
12668
|
host = hostReport();
|
|
12768
12669
|
deviceApi = null;
|
|
12769
12670
|
deviceSub = null;
|
|
12770
|
-
// Whether the device stream is open, which is what every workspace's link
|
|
12771
|
-
// the
|
|
12671
|
+
// Whether the device stream is open, which is what every workspace's link in
|
|
12672
|
+
// the status snapshot reports.
|
|
12772
12673
|
deviceConnected = false;
|
|
12773
12674
|
// Set when the device stream ended on a 403: the device was deactivated, not
|
|
12774
12675
|
// unpaired. The next accepted heartbeat proves it was reactivated, and
|
|
@@ -12829,7 +12730,6 @@ var CompanionSupervisor = class {
|
|
|
12829
12730
|
this.probeOpencode = opts.probeOpencode;
|
|
12830
12731
|
this.harnessVersions = opts.harnessVersions ?? emptyHarnessVersions();
|
|
12831
12732
|
this.exitFn = opts.exit ?? ((code) => process.exit(code));
|
|
12832
|
-
this.reexecFn = opts.reexec ?? defaultReexec;
|
|
12833
12733
|
this.restartForUpdate = opts.restartForUpdate ?? (async () => {
|
|
12834
12734
|
await this.shutdown();
|
|
12835
12735
|
this.exitFn(0);
|
|
@@ -13463,7 +13363,7 @@ var CompanionSupervisor = class {
|
|
|
13463
13363
|
locks: this.runnerLocks,
|
|
13464
13364
|
gzipBodies: this.gzipBodies
|
|
13465
13365
|
},
|
|
13466
|
-
observer: this.hub.observerFor(ctx2.workspaceId
|
|
13366
|
+
observer: this.hub.observerFor(ctx2.workspaceId),
|
|
13467
13367
|
aborts: ctx2.aborts
|
|
13468
13368
|
});
|
|
13469
13369
|
}
|
|
@@ -13917,36 +13817,9 @@ var CompanionSupervisor = class {
|
|
|
13917
13817
|
};
|
|
13918
13818
|
return { kick, cancel: cancel2 };
|
|
13919
13819
|
}
|
|
13920
|
-
// ---- dashboard-facing operations ----
|
|
13921
|
-
// Re-read `~/.cabane/config.json` (the operator may have edited the `agents`
|
|
13922
|
-
// overrides or dashboard prefs), apply the log level live, and re-pull
|
|
13923
|
-
// assignments so any structural change is reflected. A device-token change
|
|
13924
|
-
// means re-pairing, which `pair` + a restart handle.
|
|
13925
|
-
reloadConfig() {
|
|
13926
|
-
const next = loadConfig();
|
|
13927
|
-
if (!next) return;
|
|
13928
|
-
this.config = next;
|
|
13929
|
-
configureLogger(this.log, next);
|
|
13930
|
-
this.rebuildDispatchers();
|
|
13931
|
-
void this.refreshAssignments();
|
|
13932
|
-
}
|
|
13933
|
-
// The dashboard "refresh assignments" control — an immediate pull.
|
|
13934
|
-
refresh() {
|
|
13935
|
-
void this.refreshAssignments();
|
|
13936
|
-
}
|
|
13937
|
-
applySettings(patch) {
|
|
13938
|
-
const next = {
|
|
13939
|
-
...this.config,
|
|
13940
|
-
...patch.logLevel ? { logLevel: patch.logLevel } : {},
|
|
13941
|
-
...patch.autoOpen !== void 0 ? { autoOpen: patch.autoOpen } : {}
|
|
13942
|
-
};
|
|
13943
|
-
this.config = next;
|
|
13944
|
-
saveConfig(next);
|
|
13945
|
-
configureLogger(this.log, next);
|
|
13946
|
-
}
|
|
13947
13820
|
// ---- harnesses ----
|
|
13948
13821
|
// Look at every agent, in this process: the one whose environment turns run
|
|
13949
|
-
// in. Cached for the heartbeat and the
|
|
13822
|
+
// in. Cached for the heartbeat and the status snapshot; coalesced, so a re-detect
|
|
13950
13823
|
// during the timer's own run waits for that one. Never throws.
|
|
13951
13824
|
async detect() {
|
|
13952
13825
|
if (this.detecting) return this.detecting;
|
|
@@ -13974,8 +13847,8 @@ var CompanionSupervisor = class {
|
|
|
13974
13847
|
});
|
|
13975
13848
|
return this.detecting;
|
|
13976
13849
|
}
|
|
13977
|
-
// A re-detect asked for over the control socket (`setup`, `connect`)
|
|
13978
|
-
//
|
|
13850
|
+
// A re-detect asked for over the control socket (`setup`, `connect`): read
|
|
13851
|
+
// the config file again first — this process owns it, so an
|
|
13979
13852
|
// edit made on disk (the allowlist, the OpenCode address) is adopted here
|
|
13980
13853
|
// rather than overwritten by its next save — then detect now, and beat at
|
|
13981
13854
|
// once so Cabane shows it promptly.
|
|
@@ -14111,9 +13984,6 @@ var CompanionSupervisor = class {
|
|
|
14111
13984
|
await this.shutdown();
|
|
14112
13985
|
this.exitFn(0);
|
|
14113
13986
|
}
|
|
14114
|
-
requestRestart() {
|
|
14115
|
-
this.reexecFn();
|
|
14116
|
-
}
|
|
14117
13987
|
};
|
|
14118
13988
|
async function waitForBoundedHeartbeat(heartbeat) {
|
|
14119
13989
|
let timer = null;
|
|
@@ -14127,21 +13997,6 @@ async function waitForBoundedHeartbeat(heartbeat) {
|
|
|
14127
13997
|
if (timer) clearTimeout(timer);
|
|
14128
13998
|
}
|
|
14129
13999
|
}
|
|
14130
|
-
function defaultReexec() {
|
|
14131
|
-
clearRuntimeState();
|
|
14132
|
-
void import("child_process").then(({ spawn: spawn8 }) => {
|
|
14133
|
-
try {
|
|
14134
|
-
const child = spawn8(process.execPath, process.argv.slice(1), {
|
|
14135
|
-
stdio: "inherit",
|
|
14136
|
-
detached: false
|
|
14137
|
-
});
|
|
14138
|
-
child.on("spawn", () => process.exit(0));
|
|
14139
|
-
child.on("error", () => {
|
|
14140
|
-
});
|
|
14141
|
-
} catch {
|
|
14142
|
-
}
|
|
14143
|
-
});
|
|
14144
|
-
}
|
|
14145
14000
|
|
|
14146
14001
|
// src/safety.ts
|
|
14147
14002
|
var RECOVERABLE_CODES = /* @__PURE__ */ new Set([
|
|
@@ -14313,15 +14168,6 @@ function strayCompanionLines(strays, retry) {
|
|
|
14313
14168
|
}
|
|
14314
14169
|
|
|
14315
14170
|
// src/runtime.ts
|
|
14316
|
-
init_errors();
|
|
14317
|
-
function embeddedRunner(host) {
|
|
14318
|
-
if (!host.electron) return null;
|
|
14319
|
-
const cli = fileURLToPath5(new URL("./cli.js", host.moduleUrl));
|
|
14320
|
-
return {
|
|
14321
|
-
runnerCommand: [host.execPath, cli, "run-turn"],
|
|
14322
|
-
runnerEnv: { ...host.env, ELECTRON_RUN_AS_NODE: "1" }
|
|
14323
|
-
};
|
|
14324
|
-
}
|
|
14325
14171
|
async function createCompanionRuntime(opts = {}) {
|
|
14326
14172
|
const log2 = getLogger();
|
|
14327
14173
|
installProcessSafetyNet(log2);
|
|
@@ -14361,7 +14207,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
14361
14207
|
const clearRuntimeOnExit = () => clearRuntimeStateIfOurs(instanceId);
|
|
14362
14208
|
process.on("exit", clearRuntimeOnExit);
|
|
14363
14209
|
const hub = new CompanionStateHub({
|
|
14364
|
-
//
|
|
14210
|
+
// `status` names the Cabane it is paired with.
|
|
14365
14211
|
baseUrl: cfg.baseUrl,
|
|
14366
14212
|
companionVersion: COMPANION_VERSION,
|
|
14367
14213
|
// Surfaced on `/api/status` so `stop`/`status` can confirm the
|
|
@@ -14372,7 +14218,6 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
14372
14218
|
};
|
|
14373
14219
|
let supervisor;
|
|
14374
14220
|
let control = null;
|
|
14375
|
-
let dashboard = null;
|
|
14376
14221
|
let stopped = false;
|
|
14377
14222
|
const stop2 = async () => {
|
|
14378
14223
|
if (stopped) return;
|
|
@@ -14381,8 +14226,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
14381
14226
|
process.removeListener("exit", clearRuntimeOnExit);
|
|
14382
14227
|
await Promise.allSettled([
|
|
14383
14228
|
...supervisor ? [supervisor.shutdown()] : [],
|
|
14384
|
-
...control ? [control.close()] : []
|
|
14385
|
-
...dashboard ? [dashboard.close()] : []
|
|
14229
|
+
...control ? [control.close()] : []
|
|
14386
14230
|
]);
|
|
14387
14231
|
};
|
|
14388
14232
|
try {
|
|
@@ -14391,12 +14235,6 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
14391
14235
|
log: log2,
|
|
14392
14236
|
hub,
|
|
14393
14237
|
...opts.checkHarness ? { checkHarness: opts.checkHarness } : {},
|
|
14394
|
-
...embeddedRunner({
|
|
14395
|
-
electron: Boolean(process.versions.electron),
|
|
14396
|
-
execPath: process.execPath,
|
|
14397
|
-
moduleUrl: import.meta.url,
|
|
14398
|
-
env: process.env
|
|
14399
|
-
}) ?? {},
|
|
14400
14238
|
restartForUpdate: () => restartForUpdate()
|
|
14401
14239
|
});
|
|
14402
14240
|
const active2 = supervisor;
|
|
@@ -14411,21 +14249,10 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
14411
14249
|
update: (auto) => active2.update(auto),
|
|
14412
14250
|
stop: () => void stop2().then(() => active2.requestStop())
|
|
14413
14251
|
});
|
|
14414
|
-
if (opts.startDashboard) {
|
|
14415
|
-
const preferredPort = cfg.dashboardPort;
|
|
14416
|
-
dashboard = await opts.startDashboard({
|
|
14417
|
-
supervisor,
|
|
14418
|
-
hub,
|
|
14419
|
-
logPath: companionLogPath(),
|
|
14420
|
-
...preferredPort !== void 0 ? { port: preferredPort } : {}
|
|
14421
|
-
});
|
|
14422
|
-
hub.setDashboardUrl(dashboard.url);
|
|
14423
|
-
}
|
|
14424
14252
|
if (stopped) throw new Error("The companion was stopped during startup.");
|
|
14425
14253
|
writeRuntimeState({
|
|
14426
14254
|
pid: process.pid,
|
|
14427
14255
|
socket: control.path,
|
|
14428
|
-
...dashboard ? { url: dashboard.url, port: dashboard.port } : {},
|
|
14429
14256
|
startedAt,
|
|
14430
14257
|
// The daemon launcher sets this env on the detached child, so the
|
|
14431
14258
|
// marker records whether this companion is backgrounded (foreground start
|
|
@@ -14440,8 +14267,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
14440
14267
|
process.removeListener("exit", clearRuntimeOnExit);
|
|
14441
14268
|
await Promise.allSettled([
|
|
14442
14269
|
...supervisor ? [supervisor.shutdown()] : [],
|
|
14443
|
-
...control ? [control.close()] : []
|
|
14444
|
-
...dashboard ? [dashboard.close()] : []
|
|
14270
|
+
...control ? [control.close()] : []
|
|
14445
14271
|
]);
|
|
14446
14272
|
recordCrash({
|
|
14447
14273
|
reason: err instanceof Error ? err.message : String(err),
|
|
@@ -14465,7 +14291,6 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
14465
14291
|
return {
|
|
14466
14292
|
ok: true,
|
|
14467
14293
|
runtime: {
|
|
14468
|
-
...dashboard ? { url: dashboard.url, port: dashboard.port } : {},
|
|
14469
14294
|
socketPath: control.path,
|
|
14470
14295
|
config: cfg,
|
|
14471
14296
|
stop: stop2,
|
|
@@ -15346,7 +15171,7 @@ async function setupScript(opts, deps) {
|
|
|
15346
15171
|
onCode: () => rememberPairAddress(baseUrl)
|
|
15347
15172
|
});
|
|
15348
15173
|
if (!device) return;
|
|
15349
|
-
writePairedConfig(device);
|
|
15174
|
+
writePairedConfig({ ...device, baseUrl: addressToKeep(baseUrl, device.baseUrl) });
|
|
15350
15175
|
justPaired = true;
|
|
15351
15176
|
}
|
|
15352
15177
|
const detection = await start(
|