@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/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
- return host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1" || host === "::1" || host === "[::1]";
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 exempt), so a hand-edited config can't put the
716
- // device token and prompt on a plaintext channel.
717
- baseUrl: z2.string().url().refine(isAllowedBaseUrl, {
718
- message: "baseUrl must be https (loopback http is allowed for local dev only)"
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`/dashboard display only.
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
- // The CLI ignores both dashboard settings. Desktop uses `dashboardPort`;
740
- // `autoOpen` remains parseable for older config files but no longer controls
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 (loopback http is allowed for local dev). Pass it as \`${cabane("setup --server https://your-cabane")}\`.`
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 and push a status change (so the SSE feed flips
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
- const ws = this.workspaces.get(workspaceId);
10515
- if (!ws) return;
10516
- if (ws.agents.delete(agentId)) this.emitStatus();
10491
+ this.workspaces.get(workspaceId)?.agents.delete(agentId);
10517
10492
  }
10518
- // ---- dispatch feed ----
10519
- // A DispatchObserver bound to one (workspace, agent), handed to that agent's
10520
- // Dispatcher by the supervisor.
10521
- observerFor(workspaceId, agentId, slug) {
10522
- const username = () => this.workspaces.get(workspaceId)?.agents.get(agentId)?.username ?? "";
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 on
12771
- // the dashboard reports.
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, ctx2.agentId, ctx2.workspaceSlug),
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 dashboard; coalesced, so a re-detect
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`) or the
13978
- // dashboard: read the config file again first — this process owns it, so an
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
- // The dashboard's connection line shows it.
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(