@open-artifacts/bus 0.124.0 → 0.125.0

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/assets/oa-bus.ts CHANGED
@@ -11,7 +11,7 @@ import { StringDecoder } from "node:string_decoder";
11
11
  type Json = Record<string, unknown>;
12
12
 
13
13
  /** The IPC marker (CONTRACT.md): the package version, stamped by the CLI's bundler. */
14
- const CLIENT_VERSION = "0.124.0";
14
+ const CLIENT_VERSION = "0.125.0";
15
15
 
16
16
  type Frame = Json & { type: string; id: string };
17
17
  type Ctx = {
@@ -116,6 +116,16 @@ const refusals: Record<
116
116
  `200 messages are already waiting.`,
117
117
  "Send again once some are delivered or expire.",
118
118
  ],
119
+ // #1295: the cap sockets' inbox_full (pending count, bytes or the hub total).
120
+ mailbox_full: [
121
+ "Its mailbox is full.",
122
+ "Send again once it has read some, or message another agent.",
123
+ ],
124
+ // #1295: a name only held after a move; no one reads it.
125
+ reserved_name: [
126
+ "No one has that name now; it was left by an agent that moved.",
127
+ "Run the oa_bus list_agents tool to find who has it now.",
128
+ ],
119
129
  too_large: [
120
130
  "It is too large to send (the limit is 1 MB).",
121
131
  "Send a shorter message.",
@@ -416,6 +426,14 @@ function statusSaysText(
416
426
  ].join(" · ");
417
427
  }
418
428
 
429
+ /** "2026-11-05 10:00 UTC" from an ISO time; unreadable times are shown as sent. */
430
+ const awayTime = (iso: string): string => {
431
+ const t = Date.parse(iso);
432
+ return Number.isNaN(t)
433
+ ? iso
434
+ : `${new Date(t).toISOString().slice(0, 16).replace("T", " ")} UTC`;
435
+ };
436
+
419
437
  /** @oa/bus-ipc rosterLine: one list_agents line. */
420
438
  function rosterLine(
421
439
  a: StatusFields & {
@@ -424,11 +442,13 @@ function rosterLine(
424
442
  namespace: string;
425
443
  machine_label?: unknown;
426
444
  queued: boolean;
445
+ /** #1295: ISO time the agent went away; it is closed, its mailbox kept. */
446
+ away?: string | undefined;
427
447
  },
428
448
  now = Date.now(),
429
449
  ): string {
430
450
  const says = statusSaysText(a, now);
431
- return `- ${a.name} (${displayKind(a.kind)}, ${a.namespace}${typeof a.machine_label === "string" && a.machine_label ? `, ${a.machine_label}` : ""})${a.queued ? " (queued)" : ""}${says ? ` ${says}` : ""}`;
451
+ return `- ${a.name} (${displayKind(a.kind)}, ${a.namespace}${typeof a.machine_label === "string" && a.machine_label ? `, ${a.machine_label}` : ""})${a.away !== undefined ? (a.away ? ` (away since ${awayTime(a.away)})` : " (away)") : a.queued ? " (queued)" : ""}${says ? ` ${says}` : ""}`;
432
452
  }
433
453
 
434
454
  /** @oa/bus-ipc selfText: the agent entry, its status cleaned. */
@@ -838,6 +858,8 @@ function open(
838
858
  pid: process.pid,
839
859
  cwd: id.cwd,
840
860
  proposed_name: id.name,
861
+ // #1295: this asset decodes away rows and the new refusals.
862
+ caps: ["mailbox"],
841
863
  attest: start ? { proc_start: start } : {},
842
864
  }),
843
865
  );
@@ -1032,6 +1054,7 @@ export default function oaBus(pi: Pi): void {
1032
1054
  namespace: String(a.namespace),
1033
1055
  queued:
1034
1056
  (a.state ?? (a.live ? "live" : "queued")) === "queued",
1057
+ away: a.state === "away" ? String(a.since ?? "") : undefined,
1035
1058
  },
1036
1059
  now,
1037
1060
  ),
package/dist/index.js CHANGED
@@ -31389,7 +31389,17 @@ var init_caps = __esm({
31389
31389
  * to a socket whose hello listed it, and lists it in `hello_ok.caps`; the
31390
31390
  * client sends `to_id` only when it did.
31391
31391
  */
31392
- ids: "ids"
31392
+ ids: "ids",
31393
+ /**
31394
+ * Lasting mailboxes (#1295): the client decodes a send receipt's `queued`
31395
+ * and `since`, the `mailbox_full` and `reserved_name` refusals, and roster
31396
+ * rows with `state: "away"` and `since`. The hub sends those only to a
31397
+ * socket whose hello listed it, and lists it in `hello_ok.caps`; any other
31398
+ * socket sees today's shapes (`inbox_full`, `unknown_agent`, no away rows).
31399
+ * A client also lists it to have the hub hand an address over on `agent_up`
31400
+ * `replaces` (a /clear).
31401
+ */
31402
+ mailbox: "mailbox"
31393
31403
  };
31394
31404
  MAX_CAPS = 16;
31395
31405
  CapTokenSchema = external_exports.string().regex(/^[a-z][a-z0-9_-]{0,31}$/);
@@ -31819,6 +31829,33 @@ var init_names = __esm({
31819
31829
  }
31820
31830
  });
31821
31831
 
31832
+ // ../bus-protocol/src/status.ts
31833
+ var IsoTimeSchema, STATUS_STATES, StatusStateSchema, StatusLabelSchema, AgentStatusSchema;
31834
+ var init_status = __esm({
31835
+ "../bus-protocol/src/status.ts"() {
31836
+ "use strict";
31837
+ init_zod();
31838
+ init_limits();
31839
+ init_refs();
31840
+ IsoTimeSchema = external_exports.string().datetime({ offset: true });
31841
+ STATUS_STATES = [
31842
+ "working",
31843
+ "idle",
31844
+ "blocked",
31845
+ "waiting_on_owner"
31846
+ ];
31847
+ StatusStateSchema = external_exports.enum(STATUS_STATES);
31848
+ StatusLabelSchema = MachineLabelSchema.refine(
31849
+ (s) => [...s].length <= LIMITS.maxLabelChars,
31850
+ "status label too long"
31851
+ );
31852
+ AgentStatusSchema = external_exports.strictObject({
31853
+ state: StatusStateSchema,
31854
+ label: StatusLabelSchema.optional()
31855
+ });
31856
+ }
31857
+ });
31858
+
31822
31859
  // ../bus-protocol/src/receipts.ts
31823
31860
  var RECEIPT_STATES, ReceiptStateSchema, REFUSAL_REASONS, RefusalReasonSchema, ReceiptReasonSchema, MsgIdSchema, receiptReasonMatchesState, receiptShape, ReceiptSchema;
31824
31861
  var init_receipts = __esm({
@@ -31826,6 +31863,7 @@ var init_receipts = __esm({
31826
31863
  "use strict";
31827
31864
  init_zod();
31828
31865
  init_names();
31866
+ init_status();
31829
31867
  RECEIPT_STATES = [
31830
31868
  "pending",
31831
31869
  "delivered",
@@ -31848,7 +31886,19 @@ var init_receipts = __esm({
31848
31886
  */
31849
31887
  "role_vacant",
31850
31888
  /** A reply's `to_id` no longer holds the name: the agent it answers is gone. */
31851
- "agent_gone"
31889
+ "agent_gone",
31890
+ /**
31891
+ * #1295: the address's mailbox is full (pending count or bytes). Sent only
31892
+ * to a socket whose hello listed `BUS_CAPS.mailbox`; any other gets
31893
+ * `inbox_full`.
31894
+ */
31895
+ "mailbox_full",
31896
+ /**
31897
+ * #1295: the name is only a `held` reservation of a moved or flipped agent;
31898
+ * it has no mailbox. Sent only to a `BUS_CAPS.mailbox` socket; any other
31899
+ * gets `unknown_agent`.
31900
+ */
31901
+ "reserved_name"
31852
31902
  ];
31853
31903
  RefusalReasonSchema = external_exports.enum(REFUSAL_REASONS);
31854
31904
  ReceiptReasonSchema = external_exports.union([
@@ -31867,39 +31917,20 @@ var init_receipts = __esm({
31867
31917
  * through a role address (#1296, the role's holder), to a socket whose
31868
31918
  * hello listed `BUS_CAPS.roles`. An ordinary send never carries it.
31869
31919
  */
31870
- to_name: BusNameSchema.optional()
31920
+ to_name: BusNameSchema.optional(),
31921
+ /**
31922
+ * #1295, on a `pending` send receipt only, to a socket whose hello listed
31923
+ * `BUS_CAPS.mailbox`: the mail was stored, not pushed. `away`: the
31924
+ * recipient is closed and its mailbox waits for it. `offline`: it is open
31925
+ * but its machine is offline. `since` is when that began.
31926
+ */
31927
+ queued: external_exports.enum(["away", "offline"]).optional(),
31928
+ since: IsoTimeSchema.optional()
31871
31929
  };
31872
31930
  ReceiptSchema = external_exports.strictObject(receiptShape).refine(receiptReasonMatchesState, "reason does not match state");
31873
31931
  }
31874
31932
  });
31875
31933
 
31876
- // ../bus-protocol/src/status.ts
31877
- var IsoTimeSchema, STATUS_STATES, StatusStateSchema, StatusLabelSchema, AgentStatusSchema;
31878
- var init_status = __esm({
31879
- "../bus-protocol/src/status.ts"() {
31880
- "use strict";
31881
- init_zod();
31882
- init_limits();
31883
- init_refs();
31884
- IsoTimeSchema = external_exports.string().datetime({ offset: true });
31885
- STATUS_STATES = [
31886
- "working",
31887
- "idle",
31888
- "blocked",
31889
- "waiting_on_owner"
31890
- ];
31891
- StatusStateSchema = external_exports.enum(STATUS_STATES);
31892
- StatusLabelSchema = MachineLabelSchema.refine(
31893
- (s) => [...s].length <= LIMITS.maxLabelChars,
31894
- "status label too long"
31895
- );
31896
- AgentStatusSchema = external_exports.strictObject({
31897
- state: StatusStateSchema,
31898
- label: StatusLabelSchema.optional()
31899
- });
31900
- }
31901
- });
31902
-
31903
31934
  // ../bus-protocol/src/rename.ts
31904
31935
  var NAME_KINDS, NameKindSchema, FormerNameSchema, FormerNamesSchema;
31905
31936
  var init_rename = __esm({
@@ -32063,7 +32094,17 @@ var init_protocol2 = __esm({
32063
32094
  * decoder older than this field refuses the unknown key, so a client sends
32064
32095
  * it only to hubs that accept it.
32065
32096
  */
32066
- name_kind: NameKindSchema.optional()
32097
+ name_kind: NameKindSchema.optional(),
32098
+ /**
32099
+ * #1295: the agent_ref this one takes over from, after a /clear: the same
32100
+ * live process now reports a new session. The hub honours it only for a
32101
+ * socket whose hello listed `BUS_CAPS.mailbox`, when the old ref is an
32102
+ * agent of the same machine; the new ref then inherits the old address
32103
+ * (name, former names) and its pending mail, instead of `<name>-2`. A hub
32104
+ * decoder older than this field refuses the key, so a client sends it only
32105
+ * after `hello_ok.caps` listed `mailbox`.
32106
+ */
32107
+ replaces: AgentRefSchema.optional()
32067
32108
  });
32068
32109
  AgentDown = external_exports.strictObject({
32069
32110
  ...base,
@@ -32169,9 +32210,16 @@ var init_protocol2 = __esm({
32169
32210
  global: external_exports.boolean(),
32170
32211
  /**
32171
32212
  * `live`: machine connected. `queued`: machine disconnected but the session
32172
- * was last reported open (mail waits up to 1 h). Closed agents are omitted.
32213
+ * was last reported open (mail waits). `away` (#1295, cap-gated): closed,
32214
+ * its mailbox kept for 7 d. Other closed agents are omitted.
32173
32215
  */
32174
- state: external_exports.enum(["live", "queued"]),
32216
+ state: external_exports.enum(["live", "queued", "away"]),
32217
+ /**
32218
+ * When an `away` agent closed (#1295). Sent, with `state: "away"`, only to
32219
+ * a socket whose hello listed `BUS_CAPS.mailbox`: a decoder older than
32220
+ * either refuses the row, and with it the whole roster.
32221
+ */
32222
+ since: IsoTimeSchema.optional(),
32175
32223
  /** The agent's machine label. */
32176
32224
  machine_label: MachineLabelSchema.optional(),
32177
32225
  /** Self-reported ("agent says"); cleared on close. */
@@ -32344,7 +32392,7 @@ var init_src = __esm({
32344
32392
  });
32345
32393
 
32346
32394
  // ../bus-ipc/src/frames.ts
32347
- var V, MAX_BODY_BYTES, MAX_LINE_BYTES, INLINE_CAP_BYTES, KIND, KIND_TOKEN, AGENT_REF, base2, late, isWellFormed2, WELL_FORMED, CLIENT_VERSION, clientVersion, Hello2, SPAWNED_SOURCES, optIn, Send2, List2, Self, Fetch2, Ack2, SetStatus, StatusRequest, AdapterFrame, AgentInfo, Welcome, Receipt, Roster2, Agent, StatusSet, Body, Deliver2, Notice2, ErrorFrame2, StatusReply, DaemonFrame;
32395
+ var V, MAX_BODY_BYTES, MAX_LINE_BYTES, INLINE_CAP_BYTES, KIND, KIND_TOKEN, AGENT_REF, base2, late, isWellFormed2, WELL_FORMED, CLIENT_VERSION, clientVersion, ADAPTER_CAPS, Hello2, SPAWNED_SOURCES, optIn, Send2, List2, Self, Fetch2, Ack2, SetStatus, StatusRequest, AdapterFrame, ConfigFile, AgentInfo, Welcome, Receipt, Roster2, Agent, StatusSet, Body, Deliver2, Notice2, ErrorFrame2, StatusReply, DaemonFrame;
32348
32396
  var init_frames = __esm({
32349
32397
  "../bus-ipc/src/frames.ts"() {
32350
32398
  "use strict";
@@ -32361,10 +32409,11 @@ var init_frames = __esm({
32361
32409
  late = { late: external_exports.array(external_exports.string().min(1)).optional() };
32362
32410
  isWellFormed2 = (s) => s.isWellFormed();
32363
32411
  WELL_FORMED = { message: "text is not well-formed unicode" };
32364
- CLIENT_VERSION = true ? "0.124.0" : "0.0.0";
32412
+ CLIENT_VERSION = true ? "0.125.0" : "0.0.0";
32365
32413
  clientVersion = {
32366
32414
  client_version: external_exports.string().min(1).max(64).optional()
32367
32415
  };
32416
+ ADAPTER_CAPS = { mailbox: "mailbox" };
32368
32417
  Hello2 = external_exports.object({
32369
32418
  ...base2,
32370
32419
  ...clientVersion,
@@ -32379,6 +32428,8 @@ var init_frames = __esm({
32379
32428
  */
32380
32429
  cwd: external_exports.string().min(1).max(4096).optional(),
32381
32430
  proposed_name: external_exports.string().min(1).max(64).refine(isWellFormed2, WELL_FORMED).optional(),
32431
+ /** What this adapter can decode (ADAPTER_CAPS); absent means none. */
32432
+ caps: external_exports.array(external_exports.string().max(32)).max(16).optional(),
32382
32433
  /** What the harness told the adapter, re-checked by the daemon. */
32383
32434
  attest: external_exports.object({
32384
32435
  session_id: external_exports.string().optional(),
@@ -32474,21 +32525,34 @@ var init_frames = __esm({
32474
32525
  Ack2,
32475
32526
  SetStatus
32476
32527
  ]);
32528
+ ConfigFile = external_exports.object({
32529
+ file: external_exports.string(),
32530
+ state: external_exports.enum(["refused", "noted"]),
32531
+ reason: external_exports.string(),
32532
+ fix: external_exports.string()
32533
+ });
32477
32534
  AgentInfo = external_exports.object({
32478
32535
  name: external_exports.string(),
32479
32536
  namespace: external_exports.string(),
32480
32537
  kind: KIND_TOKEN,
32481
32538
  /** `state === "live"`; kept for older adapters. */
32482
32539
  live: external_exports.boolean(),
32483
- /** Roster entries only: `queued` means the agent's machine is disconnected. */
32484
- state: external_exports.enum(["live", "queued"]).optional(),
32540
+ /**
32541
+ * Roster entries only: `queued` means the agent's machine is disconnected;
32542
+ * `away` (#1295) means it is closed and its mailbox is kept, with `since`.
32543
+ */
32544
+ state: external_exports.enum(["live", "queued", "away"]).optional(),
32545
+ /** When an `away` agent closed. */
32546
+ since: IsoTimeSchema.optional(),
32485
32547
  global: external_exports.boolean(),
32486
32548
  /** The agent's machine (B13); absent when the hub is older. */
32487
32549
  machine_label: MachineLabelSchema.optional(),
32488
32550
  /** The agent's own status claim, from the hub. */
32489
32551
  status: StatusStateSchema.optional(),
32490
32552
  status_label: StatusLabelSchema.optional(),
32491
- status_at: IsoTimeSchema.optional()
32553
+ status_at: IsoTimeSchema.optional(),
32554
+ /** `self` with `row` only: config files refused or noted. Older adapters ignore it. */
32555
+ config_files: external_exports.array(ConfigFile).optional()
32492
32556
  });
32493
32557
  Welcome = external_exports.object({
32494
32558
  ...base2,
@@ -32599,6 +32663,8 @@ var init_frames = __esm({
32599
32663
  )
32600
32664
  })
32601
32665
  ),
32666
+ /** Config files refused or noted (#1527). Absent from older daemons, and when there are none. */
32667
+ config_files: external_exports.array(ConfigFile).optional(),
32602
32668
  /** Messages sent from this machine the hub delivered after the 5 s window, within the last hour. */
32603
32669
  late: external_exports.array(
32604
32670
  external_exports.object({
@@ -32984,7 +33050,7 @@ function statusSaysText(row, now = Date.now()) {
32984
33050
  }
32985
33051
  function rosterLine(a, now = Date.now()) {
32986
33052
  const says = statusSaysText(a, now);
32987
- return `- ${a.name} (${displayKind(a.kind)}, ${a.namespace}${typeof a.machine_label === "string" && a.machine_label ? `, ${a.machine_label}` : ""})${a.queued ? " (queued)" : ""}${says ? ` ${says}` : ""}`;
33053
+ return `- ${a.name} (${displayKind(a.kind)}, ${a.namespace}${typeof a.machine_label === "string" && a.machine_label ? `, ${a.machine_label}` : ""})${a.away !== void 0 ? a.away ? ` (away since ${awayTime(a.away)})` : " (away)" : a.queued ? " (queued)" : ""}${says ? ` ${says}` : ""}`;
32988
33054
  }
32989
33055
  function selfText(agent) {
32990
33056
  if (!agent) return JSON.stringify(agent, null, 2);
@@ -32998,7 +33064,7 @@ function selfText(agent) {
32998
33064
  };
32999
33065
  return JSON.stringify(shown2, null, 2);
33000
33066
  }
33001
- var MAX_COLS, RETAIN, MB, refusals, quoted, TERMINAL_CONTROLS, CUT_OFF, stripTerminalControls, KIND_CHARS, displayKind, neutralizeBody, deliveredText, oversizedText, INVISIBLES, EXCERPT_CHARS, STATUS_COPY, BAD_LABEL_CHARS2, cleanStatusLabel, statusLabelTooLong, statusSetText, earlierStatusText, STATUS_STALE_MS, agoText, displayLabel, replyGoneText;
33067
+ var MAX_COLS, RETAIN, MB, refusals, quoted, TERMINAL_CONTROLS, CUT_OFF, stripTerminalControls, KIND_CHARS, displayKind, neutralizeBody, deliveredText, oversizedText, INVISIBLES, EXCERPT_CHARS, STATUS_COPY, BAD_LABEL_CHARS2, cleanStatusLabel, statusLabelTooLong, statusSetText, earlierStatusText, STATUS_STALE_MS, agoText, displayLabel, awayTime, replyGoneText;
33002
33068
  var init_copy = __esm({
33003
33069
  "../bus-ipc/src/copy.ts"() {
33004
33070
  "use strict";
@@ -33019,6 +33085,16 @@ var init_copy = __esm({
33019
33085
  `${MAILBOX_PENDING_CAP} messages are already waiting.`,
33020
33086
  "Send again once some are delivered or expire."
33021
33087
  ],
33088
+ // #1295: the cap sockets' inbox_full (pending count, bytes or the hub total).
33089
+ mailbox_full: [
33090
+ "Its mailbox is full.",
33091
+ "Send again once it has read some, or message another agent."
33092
+ ],
33093
+ // #1295: a name only held after a move; no one reads it.
33094
+ reserved_name: [
33095
+ "No one has that name now; it was left by an agent that moved.",
33096
+ "Run the oa_bus list_agents tool to find who has it now."
33097
+ ],
33022
33098
  too_large: [
33023
33099
  `It is too large to send (the limit is ${MB}).`,
33024
33100
  "Send a shorter message."
@@ -33099,6 +33175,10 @@ ${neutralizeBody(m.body)}`;
33099
33175
  return `${Math.round(ms / 864e5)} d ago`;
33100
33176
  };
33101
33177
  displayLabel = (label) => [...cleanStatusLabel(stripTerminalControls(label).replace(INVISIBLES, ""))].slice(0, LIMITS.maxLabelChars).join("").replaceAll('"', "'");
33178
+ awayTime = (iso) => {
33179
+ const t = Date.parse(iso);
33180
+ return Number.isNaN(t) ? iso : `${new Date(t).toISOString().slice(0, 16).replace("T", " ")} UTC`;
33181
+ };
33102
33182
  replyGoneText = (name) => `[oa-bus] Information: your reply to ${name} was not sent. The agent that wrote to you as ${name} is gone, or wrote over an hour ago, and another agent may hold that name now. Use the oa_bus list_agents and send_message tools to reach the agent you mean.`;
33103
33183
  }
33104
33184
  });
@@ -33441,7 +33521,16 @@ async function run(name, args, request) {
33441
33521
  if (r.type !== "roster") return failed(r);
33442
33522
  const now = Date.now();
33443
33523
  return text(
33444
- r.agents.length ? r.agents.map((a) => rosterLine({ ...a, queued: queued(a) }, now)).join("\n") : "No agents in your namespace."
33524
+ r.agents.length ? r.agents.map(
33525
+ (a) => rosterLine(
33526
+ {
33527
+ ...a,
33528
+ queued: queued(a),
33529
+ away: a.state === "away" ? a.since ?? "" : void 0
33530
+ },
33531
+ now
33532
+ )
33533
+ ).join("\n") : "No agents in your namespace."
33445
33534
  );
33446
33535
  }
33447
33536
  if (name === "send_message") {
@@ -33669,6 +33758,7 @@ function codexHello(call, pid, ppid) {
33669
33758
  pid,
33670
33759
  ...call.cwd ? { cwd: call.cwd } : {},
33671
33760
  proposed_name: "codex",
33761
+ caps: [ADAPTER_CAPS.mailbox],
33672
33762
  codex: {
33673
33763
  thread_id: call.threadId,
33674
33764
  call_id: call.callId,
@@ -33911,6 +34001,7 @@ function sessionHello(session, pid, cwd) {
33911
34001
  pid,
33912
34002
  cwd,
33913
34003
  proposed_name: fitName(session.name, 64) || void 0,
34004
+ caps: [ADAPTER_CAPS.mailbox],
33914
34005
  attest: {
33915
34006
  session_id: session.sessionId,
33916
34007
  socket_path: session.socketPath ?? void 0
@@ -33989,8 +34080,8 @@ function shortSocketDir(home2) {
33989
34080
  );
33990
34081
  }
33991
34082
  function daemonSocketPath(home2) {
33992
- const plain2 = join2(canonicalHome(home2), "daemon.sock");
33993
- return fits(plain2) ? plain2 : join2(shortSocketDir(home2), "daemon.sock");
34083
+ const plain3 = join2(canonicalHome(home2), "daemon.sock");
34084
+ return fits(plain3) ? plain3 : join2(shortSocketDir(home2), "daemon.sock");
33994
34085
  }
33995
34086
  function claudeReturnDir(home2) {
33996
34087
  const dir = join2(canonicalHome(home2), "claude-return");
@@ -37803,6 +37894,7 @@ async function handleToolCall(input2) {
37803
37894
  pid: process.pid,
37804
37895
  cwd: session.directory,
37805
37896
  proposed_name: nameOf(session.title),
37897
+ caps: [ADAPTER_CAPS.mailbox],
37806
37898
  attest: { session_id: sessionId }
37807
37899
  });
37808
37900
  let client;
@@ -38088,6 +38180,33 @@ var init_codex_main = __esm({
38088
38180
  }
38089
38181
  });
38090
38182
 
38183
+ // ../shared/src/harness-skills.ts
38184
+ var harnessSkills = {
38185
+ claude: {
38186
+ skillPath: "~/.claude/skills/open-artifacts/SKILL.md",
38187
+ busToolName: (tool) => `mcp__oa_bus__${tool}`
38188
+ },
38189
+ // CODEX_TOOL_NAMESPACE = "mcp__oa_bus" (packages/bus-daemon/src/codex-attest.ts).
38190
+ codex: {
38191
+ skillPath: "~/.codex/AGENTS.md",
38192
+ busToolName: (tool) => `mcp__oa_bus__${tool}`,
38193
+ agentsMd: true
38194
+ },
38195
+ opencode: {
38196
+ skillPath: "~/.config/opencode/skills/open-artifacts/SKILL.md",
38197
+ busToolName: (tool) => `oa_bus_${tool}`
38198
+ },
38199
+ pi: {
38200
+ skillPath: "~/.agents/skills/open-artifacts/SKILL.md"
38201
+ },
38202
+ omp: {
38203
+ skillPath: "~/.agents/skills/open-artifacts/SKILL.md"
38204
+ }
38205
+ };
38206
+
38207
+ // src/harness-table.ts
38208
+ var harnessNames = Object.keys(harnessSkills);
38209
+
38091
38210
  // src/main.ts
38092
38211
  import { resolve as resolve7 } from "node:path";
38093
38212
 
@@ -38097,7 +38216,7 @@ import { join as join15 } from "node:path";
38097
38216
 
38098
38217
  // ../bus-daemon/src/index.ts
38099
38218
  init_src3();
38100
- import { lstatSync as lstatSync6, writeFileSync as writeFileSync4 } from "node:fs";
38219
+ import { lstatSync as lstatSync6, realpathSync as realpathSync6, writeFileSync as writeFileSync4 } from "node:fs";
38101
38220
  import { basename as basename5, isAbsolute as isAbsolute5, join as join11 } from "node:path";
38102
38221
 
38103
38222
  // ../bus-adapter-opencode/src/version.ts
@@ -38949,7 +39068,7 @@ function sessionFiles(dir) {
38949
39068
  return out;
38950
39069
  }
38951
39070
  var alive = (s, deps) => deps.isRunning(s.pid) && deps.procStart(s.pid, s.stamp) === s.procStart;
38952
- var strip = ({ procStart: _, stamp: __, ...s }) => s;
39071
+ var strip = ({ stamp: _, ...s }) => s;
38953
39072
  function liveClaudeSessions(deps) {
38954
39073
  return sessionFiles(deps.dir).filter((s) => alive(s, deps)).map(strip);
38955
39074
  }
@@ -39081,6 +39200,314 @@ function defaultOpenSessions() {
39081
39200
  });
39082
39201
  }
39083
39202
 
39203
+ // ../bus-daemon/src/config-issues.ts
39204
+ init_src();
39205
+ import { realpathSync as realpathSync4 } from "node:fs";
39206
+ import { dirname as dirname4, join as join8 } from "node:path";
39207
+
39208
+ // ../bus-daemon/src/global.ts
39209
+ import { randomBytes } from "node:crypto";
39210
+ import {
39211
+ chmodSync as chmodSync3,
39212
+ closeSync as closeSync3,
39213
+ lstatSync as lstatSync4,
39214
+ mkdirSync as mkdirSync4,
39215
+ openSync as openSync3,
39216
+ realpathSync as realpathSync3,
39217
+ renameSync as renameSync2,
39218
+ statSync as statSync6,
39219
+ unlinkSync,
39220
+ writeSync
39221
+ } from "node:fs";
39222
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join7, sep } from "node:path";
39223
+
39224
+ // ../bus-daemon/src/own-file.ts
39225
+ import {
39226
+ closeSync as closeSync2,
39227
+ constants,
39228
+ fstatSync as fstatSync2,
39229
+ openSync as openSync2,
39230
+ readFileSync as readFileSync6
39231
+ } from "node:fs";
39232
+ import { userInfo } from "node:os";
39233
+ function isPrivateGroup(gid, user, read = () => readFileSync6("/etc/group", "utf8"), readPasswd = () => readFileSync6("/etc/passwd", "utf8")) {
39234
+ let text3;
39235
+ let passwd;
39236
+ try {
39237
+ text3 = read();
39238
+ passwd = readPasswd();
39239
+ } catch {
39240
+ return false;
39241
+ }
39242
+ for (const line of passwd.split("\n")) {
39243
+ const f = line.split(":");
39244
+ if (f[3] === String(gid) && f[0] !== user) return false;
39245
+ }
39246
+ for (const line of text3.split("\n")) {
39247
+ const [name, , id, members2] = line.split(":");
39248
+ if (id === String(gid))
39249
+ return name === user && (members2 ?? "").trim() === "";
39250
+ }
39251
+ return false;
39252
+ }
39253
+ var ownGroup = () => ({
39254
+ gid: process.getgid?.(),
39255
+ isPrivate: (gid) => {
39256
+ if (process.platform !== "linux") return false;
39257
+ try {
39258
+ return isPrivateGroup(gid, userInfo().username);
39259
+ } catch {
39260
+ return false;
39261
+ }
39262
+ }
39263
+ });
39264
+ function readOwnFile(file2, uid = process.getuid?.(), opts = {}) {
39265
+ let fd;
39266
+ try {
39267
+ fd = openSync2(
39268
+ file2,
39269
+ constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0)
39270
+ );
39271
+ } catch (e) {
39272
+ const code2 = e.code;
39273
+ if (code2 === "ENOENT" || code2 === "ENOTDIR") return { state: "none" };
39274
+ return {
39275
+ state: "unsafe",
39276
+ why: code2 === "ELOOP" ? "is a symlink" : `cannot be read (${code2})`
39277
+ };
39278
+ }
39279
+ try {
39280
+ const st = fstatSync2(fd);
39281
+ if (!st.isFile()) return { state: "unsafe", why: "is not a regular file" };
39282
+ if (uid !== void 0 && st.uid !== uid)
39283
+ return { state: "unsafe", why: "belongs to another user" };
39284
+ if (opts.allowGroupWrite) {
39285
+ if (st.mode & 2)
39286
+ return { state: "unsafe", why: "is writable by everyone" };
39287
+ let note;
39288
+ if (st.mode & 16) {
39289
+ const g = opts.group ?? ownGroup();
39290
+ if (st.gid !== g.gid || !g.isPrivate(st.gid))
39291
+ return {
39292
+ state: "unsafe",
39293
+ why: "is writable by a group others share"
39294
+ };
39295
+ note = "is writable by its group";
39296
+ }
39297
+ return {
39298
+ state: "ok",
39299
+ text: readFileSync6(fd, "utf8"),
39300
+ ...note ? { note } : {}
39301
+ };
39302
+ }
39303
+ if (st.mode & 18)
39304
+ return { state: "unsafe", why: "is writable by others" };
39305
+ return { state: "ok", text: readFileSync6(fd, "utf8") };
39306
+ } catch (e) {
39307
+ return {
39308
+ state: "unsafe",
39309
+ why: `cannot be read (${e.code ?? String(e)})`
39310
+ };
39311
+ } finally {
39312
+ closeSync2(fd);
39313
+ }
39314
+ }
39315
+
39316
+ // ../bus-daemon/src/global.ts
39317
+ var globalFile = (h = home()) => join7(h, "global.json");
39318
+ var canonicalPath = (path) => realpathSync3.native(path);
39319
+ function readGlobalDirs(file2 = globalFile()) {
39320
+ const own2 = readOwnFile(file2);
39321
+ if (own2.state === "none") return { dirs: [] };
39322
+ if (own2.state === "unsafe") return { dirs: [], error: own2.why };
39323
+ try {
39324
+ const raw = JSON.parse(own2.text);
39325
+ const dirs = raw?.dirs;
39326
+ if (!Array.isArray(raw) && Array.isArray(dirs) && dirs.every((d) => typeof d === "string" && isAbsolute3(d)))
39327
+ return { dirs };
39328
+ } catch {
39329
+ }
39330
+ return { dirs: [], error: "is not valid JSON" };
39331
+ }
39332
+ var idOf = (path, follow = true) => {
39333
+ try {
39334
+ const st = follow ? statSync6(path) : lstatSync4(path);
39335
+ return st.isDirectory() ? `${st.dev}:${st.ino}` : void 0;
39336
+ } catch {
39337
+ return void 0;
39338
+ }
39339
+ };
39340
+ var loose = (p) => p.normalize("NFC").toLowerCase();
39341
+ function isGlobalDir(dir, dirs, onStale) {
39342
+ let real2;
39343
+ try {
39344
+ real2 = realpathSync3(dir);
39345
+ } catch {
39346
+ return false;
39347
+ }
39348
+ const near = loose(real2);
39349
+ const ids = /* @__PURE__ */ new Set();
39350
+ for (const entry of dirs) {
39351
+ const e = loose(entry);
39352
+ const prefix = e.endsWith(sep) ? e : `${e}${sep}`;
39353
+ if (near !== e && !near.startsWith(prefix)) continue;
39354
+ let still;
39355
+ try {
39356
+ still = realpathSync3.native(entry);
39357
+ } catch {
39358
+ }
39359
+ if (still !== entry) {
39360
+ if (still !== void 0) onStale?.(entry);
39361
+ continue;
39362
+ }
39363
+ const id = idOf(entry, false);
39364
+ if (id !== void 0) ids.add(id);
39365
+ }
39366
+ if (ids.size === 0) return false;
39367
+ for (let d = real2; ; d = dirname3(d)) {
39368
+ const id = idOf(d);
39369
+ if (id !== void 0 && ids.has(id)) return true;
39370
+ if (dirname3(d) === d) return false;
39371
+ }
39372
+ }
39373
+ function sameDir(entry, dir) {
39374
+ try {
39375
+ if (realpathSync3.native(entry) !== entry) return false;
39376
+ } catch {
39377
+ return false;
39378
+ }
39379
+ const id = idOf(entry, false);
39380
+ return id !== void 0 && id === idOf(dir);
39381
+ }
39382
+ function writeGlobalDirs(dirs, h = home()) {
39383
+ mkdirSync4(h, { recursive: true, mode: 448 });
39384
+ chmodSync3(h, 448);
39385
+ const tmp = join7(h, `.global.json.${randomBytes(8).toString("hex")}.tmp`);
39386
+ const fd = openSync3(tmp, "wx", 384);
39387
+ try {
39388
+ writeSync(fd, `${JSON.stringify({ dirs }, null, 2)}
39389
+ `);
39390
+ } catch (e) {
39391
+ closeSync3(fd);
39392
+ unlinkSync(tmp);
39393
+ throw e;
39394
+ }
39395
+ closeSync3(fd);
39396
+ try {
39397
+ chmodSync3(tmp, 384);
39398
+ renameSync2(tmp, globalFile(h));
39399
+ } catch (e) {
39400
+ unlinkSync(tmp);
39401
+ throw e;
39402
+ }
39403
+ }
39404
+
39405
+ // ../bus-daemon/src/config-issues.ts
39406
+ var shq = (s) => `'${s.replace(/'/g, "'\\''")}'`;
39407
+ function fixFor(file2, why) {
39408
+ const q = shq(file2);
39409
+ if (why === "is writable by everyone") return `run chmod o-w ${q}`;
39410
+ if (why === "is writable by others") return `run chmod go-w ${q}`;
39411
+ if (why === "is writable by a group others share")
39412
+ return `run chmod g-w ${q}`;
39413
+ if (why === "belongs to another user")
39414
+ return `remove it (rm ${q}) or take it over (chown "$USER" ${q})`;
39415
+ if (why === "is a symlink" || why === "is not a regular file")
39416
+ return `remove it (rm ${q}), then save a regular file there`;
39417
+ if (why.startsWith("cannot be read")) return `run chmod u+r ${q}`;
39418
+ return `fix or remove ${q}`;
39419
+ }
39420
+ function configIssue(file2, own2, valid2 = true) {
39421
+ if (own2.state === "unsafe")
39422
+ return {
39423
+ file: file2,
39424
+ state: "refused",
39425
+ reason: own2.why,
39426
+ fix: fixFor(file2, own2.why)
39427
+ };
39428
+ if (own2.state === "ok" && !valid2)
39429
+ return {
39430
+ file: file2,
39431
+ state: "refused",
39432
+ reason: "is not a valid .oa-bus.json",
39433
+ fix: `edit ${shq(file2)} to valid JSON, or remove it`
39434
+ };
39435
+ if (own2.state === "ok" && own2.note)
39436
+ return {
39437
+ file: file2,
39438
+ state: "noted",
39439
+ reason: own2.note,
39440
+ fix: `optional: run chmod g-w ${shq(file2)}`
39441
+ };
39442
+ return void 0;
39443
+ }
39444
+ function isValidConfig(text3) {
39445
+ try {
39446
+ return BusConfigSchema.safeParse(JSON.parse(text3)).success;
39447
+ } catch {
39448
+ return false;
39449
+ }
39450
+ }
39451
+ function inspectConfig(file2, uid, group) {
39452
+ const own2 = readOwnFile(file2, uid, {
39453
+ allowGroupWrite: true,
39454
+ ...group ? { group } : {}
39455
+ });
39456
+ return configIssue(file2, own2, own2.state !== "ok" || isValidConfig(own2.text));
39457
+ }
39458
+ function inspectGlobal(file2 = globalFile()) {
39459
+ const { error: error62 } = readGlobalDirs(file2);
39460
+ if (error62 === void 0) return void 0;
39461
+ return {
39462
+ file: file2,
39463
+ state: "refused",
39464
+ reason: error62,
39465
+ fix: error62 === "is not valid JSON" ? `remove it (rm ${shq(file2)}), then run oa-bus global add <dir> again` : fixFor(file2, error62)
39466
+ };
39467
+ }
39468
+ var ConfigIssues = class {
39469
+ #files = /* @__PURE__ */ new Map();
39470
+ /** Records the result of reading `file` (undefined: it is fine now). */
39471
+ set(file2, uid, issue2, group) {
39472
+ if (issue2) this.#files.set(file2, { uid, group });
39473
+ else this.#files.delete(file2);
39474
+ }
39475
+ /** Every tracked file, re-read now; `within` keeps only those at these paths. */
39476
+ list(within) {
39477
+ const out = [];
39478
+ for (const [file2, { uid, group }] of [...this.#files]) {
39479
+ if (within && !within.has(file2)) continue;
39480
+ const issue2 = inspectConfig(file2, uid, group);
39481
+ if (issue2) out.push(issue2);
39482
+ else this.#files.delete(file2);
39483
+ }
39484
+ return out;
39485
+ }
39486
+ };
39487
+ function scanConfigFiles(cwd, opts = {}) {
39488
+ const uid = opts.uid ?? process.getuid?.();
39489
+ const out = [];
39490
+ let real2 = cwd;
39491
+ try {
39492
+ real2 = realpathSync4(cwd);
39493
+ } catch {
39494
+ }
39495
+ for (const file2 of configPaths(real2)) {
39496
+ const issue2 = inspectConfig(file2, uid, opts.group);
39497
+ if (issue2) out.push(issue2);
39498
+ }
39499
+ const g = inspectGlobal(opts.globalFile);
39500
+ if (g) out.push(g);
39501
+ return out;
39502
+ }
39503
+ function configPaths(cwd) {
39504
+ const out = [];
39505
+ for (let dir = cwd; ; dir = dirname4(dir)) {
39506
+ out.push(join8(dir, CONFIG_FILE));
39507
+ if (dirname4(dir) === dir) return out;
39508
+ }
39509
+ }
39510
+
39084
39511
  // ../bus-daemon/src/deliver.ts
39085
39512
  init_src2();
39086
39513
  init_src();
@@ -39510,7 +39937,9 @@ var HubConnection = class {
39510
39937
  machine_label: this.opts.label,
39511
39938
  client_version: this.opts.version,
39512
39939
  // Reply ids: the hub then names each sender's agent (deliver.from_id).
39513
- caps: [BUS_CAPS.ids]
39940
+ // Mailboxes: the hub then sends queued/since, away rows and the new
39941
+ // refusals, and honours agent_up.replaces (#1295).
39942
+ caps: [BUS_CAPS.ids, BUS_CAPS.mailbox]
39514
39943
  });
39515
39944
  this.#guard("sessions", () => {
39516
39945
  this.send({
@@ -39592,156 +40021,8 @@ function defaultBackoff(attempt) {
39592
40021
  // ../bus-daemon/src/namespace.ts
39593
40022
  init_src();
39594
40023
  import { execFileSync as execFileSync4 } from "node:child_process";
39595
- import { realpathSync as realpathSync4 } from "node:fs";
39596
- import { basename as basename4, dirname as dirname4, join as join8, resolve as resolve2 } from "node:path";
39597
-
39598
- // ../bus-daemon/src/global.ts
39599
- import { randomBytes } from "node:crypto";
39600
- import {
39601
- chmodSync as chmodSync3,
39602
- closeSync as closeSync3,
39603
- lstatSync as lstatSync4,
39604
- mkdirSync as mkdirSync4,
39605
- openSync as openSync3,
39606
- realpathSync as realpathSync3,
39607
- renameSync as renameSync2,
39608
- statSync as statSync6,
39609
- unlinkSync,
39610
- writeSync
39611
- } from "node:fs";
39612
- import { dirname as dirname3, isAbsolute as isAbsolute3, join as join7, sep } from "node:path";
39613
-
39614
- // ../bus-daemon/src/own-file.ts
39615
- import {
39616
- closeSync as closeSync2,
39617
- constants,
39618
- fstatSync as fstatSync2,
39619
- openSync as openSync2,
39620
- readFileSync as readFileSync6
39621
- } from "node:fs";
39622
- function readOwnFile(file2, uid = process.getuid?.()) {
39623
- let fd;
39624
- try {
39625
- fd = openSync2(
39626
- file2,
39627
- constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0)
39628
- );
39629
- } catch (e) {
39630
- const code2 = e.code;
39631
- if (code2 === "ENOENT" || code2 === "ENOTDIR") return { state: "none" };
39632
- return {
39633
- state: "unsafe",
39634
- why: code2 === "ELOOP" ? "is a symlink" : `cannot be read (${code2})`
39635
- };
39636
- }
39637
- try {
39638
- const st = fstatSync2(fd);
39639
- if (!st.isFile()) return { state: "unsafe", why: "is not a regular file" };
39640
- if (uid !== void 0 && st.uid !== uid)
39641
- return { state: "unsafe", why: "belongs to another user" };
39642
- if (st.mode & 18)
39643
- return { state: "unsafe", why: "is writable by others" };
39644
- return { state: "ok", text: readFileSync6(fd, "utf8") };
39645
- } catch (e) {
39646
- return {
39647
- state: "unsafe",
39648
- why: `cannot be read (${e.code ?? String(e)})`
39649
- };
39650
- } finally {
39651
- closeSync2(fd);
39652
- }
39653
- }
39654
-
39655
- // ../bus-daemon/src/global.ts
39656
- var globalFile = (h = home()) => join7(h, "global.json");
39657
- var canonicalPath = (path) => realpathSync3.native(path);
39658
- function readGlobalDirs(file2 = globalFile()) {
39659
- const own2 = readOwnFile(file2);
39660
- if (own2.state === "none") return { dirs: [] };
39661
- if (own2.state === "unsafe") return { dirs: [], error: own2.why };
39662
- try {
39663
- const raw = JSON.parse(own2.text);
39664
- const dirs = raw?.dirs;
39665
- if (!Array.isArray(raw) && Array.isArray(dirs) && dirs.every((d) => typeof d === "string" && isAbsolute3(d)))
39666
- return { dirs };
39667
- } catch {
39668
- }
39669
- return { dirs: [], error: "is not valid JSON" };
39670
- }
39671
- var idOf = (path, follow = true) => {
39672
- try {
39673
- const st = follow ? statSync6(path) : lstatSync4(path);
39674
- return st.isDirectory() ? `${st.dev}:${st.ino}` : void 0;
39675
- } catch {
39676
- return void 0;
39677
- }
39678
- };
39679
- var loose = (p) => p.normalize("NFC").toLowerCase();
39680
- function isGlobalDir(dir, dirs, onStale) {
39681
- let real2;
39682
- try {
39683
- real2 = realpathSync3(dir);
39684
- } catch {
39685
- return false;
39686
- }
39687
- const near = loose(real2);
39688
- const ids = /* @__PURE__ */ new Set();
39689
- for (const entry of dirs) {
39690
- const e = loose(entry);
39691
- const prefix = e.endsWith(sep) ? e : `${e}${sep}`;
39692
- if (near !== e && !near.startsWith(prefix)) continue;
39693
- let still;
39694
- try {
39695
- still = realpathSync3.native(entry);
39696
- } catch {
39697
- }
39698
- if (still !== entry) {
39699
- if (still !== void 0) onStale?.(entry);
39700
- continue;
39701
- }
39702
- const id = idOf(entry, false);
39703
- if (id !== void 0) ids.add(id);
39704
- }
39705
- if (ids.size === 0) return false;
39706
- for (let d = real2; ; d = dirname3(d)) {
39707
- const id = idOf(d);
39708
- if (id !== void 0 && ids.has(id)) return true;
39709
- if (dirname3(d) === d) return false;
39710
- }
39711
- }
39712
- function sameDir(entry, dir) {
39713
- try {
39714
- if (realpathSync3.native(entry) !== entry) return false;
39715
- } catch {
39716
- return false;
39717
- }
39718
- const id = idOf(entry, false);
39719
- return id !== void 0 && id === idOf(dir);
39720
- }
39721
- function writeGlobalDirs(dirs, h = home()) {
39722
- mkdirSync4(h, { recursive: true, mode: 448 });
39723
- chmodSync3(h, 448);
39724
- const tmp = join7(h, `.global.json.${randomBytes(8).toString("hex")}.tmp`);
39725
- const fd = openSync3(tmp, "wx", 384);
39726
- try {
39727
- writeSync(fd, `${JSON.stringify({ dirs }, null, 2)}
39728
- `);
39729
- } catch (e) {
39730
- closeSync3(fd);
39731
- unlinkSync(tmp);
39732
- throw e;
39733
- }
39734
- closeSync3(fd);
39735
- try {
39736
- chmodSync3(tmp, 384);
39737
- renameSync2(tmp, globalFile(h));
39738
- } catch (e) {
39739
- unlinkSync(tmp);
39740
- throw e;
39741
- }
39742
- }
39743
-
39744
- // ../bus-daemon/src/namespace.ts
40024
+ import { realpathSync as realpathSync5 } from "node:fs";
40025
+ import { basename as basename4, resolve as resolve2 } from "node:path";
39745
40026
  var warned = /* @__PURE__ */ new Set();
39746
40027
  var once = (deps, key2, msg) => {
39747
40028
  if (warned.has(key2)) return;
@@ -39752,7 +40033,7 @@ function resolveNamespace(start, deps = {}) {
39752
40033
  let cwd;
39753
40034
  let missing2 = false;
39754
40035
  try {
39755
- cwd = realpathSync4(start);
40036
+ cwd = realpathSync5(start);
39756
40037
  } catch (e) {
39757
40038
  if (!deps.allowMissing) throw e;
39758
40039
  cwd = resolve2(start);
@@ -39811,12 +40092,16 @@ function readConfigs(cwd, deps) {
39811
40092
  const uid = deps.uid ?? process.getuid?.();
39812
40093
  const configs = [];
39813
40094
  const asked = [];
39814
- for (let dir = cwd; ; dir = dirname4(dir)) {
39815
- const file2 = join8(dir, CONFIG_FILE);
39816
- const own2 = readOwnFile(file2, uid);
40095
+ for (const file2 of configPaths(cwd)) {
40096
+ const own2 = readOwnFile(file2, uid, {
40097
+ allowGroupWrite: true,
40098
+ ...deps.group ? { group: deps.group } : {}
40099
+ });
39817
40100
  if (own2.state === "unsafe")
39818
40101
  once(deps, file2, `ignoring ${file2}: it ${own2.why}`);
40102
+ let valid2 = true;
39819
40103
  if (own2.state === "ok") {
40104
+ if (own2.note) once(deps, `note:${file2}`, `using ${file2}: it ${own2.note}`);
39820
40105
  let parsed;
39821
40106
  try {
39822
40107
  parsed = BusConfigSchema.safeParse(JSON.parse(own2.text));
@@ -39825,15 +40110,18 @@ function readConfigs(cwd, deps) {
39825
40110
  if (parsed?.success) {
39826
40111
  configs.push(parsed.data);
39827
40112
  if (parsed.data.global === true) asked.push(file2);
39828
- } else deps.log?.(`ignoring invalid ${file2}`);
40113
+ } else {
40114
+ valid2 = false;
40115
+ deps.log?.(`ignoring invalid ${file2}`);
40116
+ }
39829
40117
  }
39830
- if (dirname4(dir) === dir) break;
40118
+ deps.issues?.set(file2, uid, configIssue(file2, own2, valid2), deps.group);
39831
40119
  }
39832
40120
  return { cfg: mergeConfigs(configs), asked };
39833
40121
  }
39834
40122
  function nearestConfig(dir, deps = {}) {
39835
40123
  try {
39836
- return readConfigs(realpathSync4(dir), deps).cfg;
40124
+ return readConfigs(realpathSync5(dir), deps).cfg;
39837
40125
  } catch {
39838
40126
  return {};
39839
40127
  }
@@ -40058,6 +40346,7 @@ var Resends = class {
40058
40346
  };
40059
40347
 
40060
40348
  // ../bus-daemon/src/routes.ts
40349
+ init_src2();
40061
40350
  init_src();
40062
40351
 
40063
40352
  // ../bus-daemon/src/status.ts
@@ -40278,6 +40567,27 @@ var statusOf = (row) => Object.fromEntries(
40278
40567
  (k) => row?.[k] === void 0 ? [] : [[k, row[k]]]
40279
40568
  )
40280
40569
  );
40570
+ var OLD_REASON = {
40571
+ mailbox_full: "inbox_full",
40572
+ reserved_name: "unknown_agent"
40573
+ };
40574
+ function forOldAdapter(f) {
40575
+ if (f.type === "roster")
40576
+ return {
40577
+ ...f,
40578
+ agents: f.agents.filter((a) => a.state !== "away").map(({ since: _since, ...a }) => a)
40579
+ };
40580
+ if (f.type === "receipt") {
40581
+ const {
40582
+ queued: _q,
40583
+ since: _s,
40584
+ ...r
40585
+ } = f;
40586
+ const reason = r.reason !== void 0 && r.reason in OLD_REASON ? OLD_REASON[r.reason] : r.reason;
40587
+ return { ...r, ...reason === void 0 ? {} : { reason } };
40588
+ }
40589
+ return f;
40590
+ }
40281
40591
  function routeLocal(frame, hello, reply, ctx) {
40282
40592
  const agent = ctx.agents.byRef(hello.agent_ref);
40283
40593
  if (!agent)
@@ -40285,6 +40595,7 @@ function routeLocal(frame, hello, reply, ctx) {
40285
40595
  const ref = agent.ref;
40286
40596
  switch (frame.type) {
40287
40597
  case "self": {
40598
+ const files = frame.row ? ctx.configFiles?.(agent.cwd) ?? [] : [];
40288
40599
  const self = {
40289
40600
  type: "agent",
40290
40601
  agent: {
@@ -40292,7 +40603,8 @@ function routeLocal(frame, hello, reply, ctx) {
40292
40603
  namespace: agent.namespace,
40293
40604
  kind: agent.kind,
40294
40605
  live: agent.live,
40295
- global: agent.global
40606
+ global: agent.global,
40607
+ ...files.length > 0 ? { config_files: files } : {}
40296
40608
  }
40297
40609
  };
40298
40610
  const own2 = frame.row ? ctx.ownRow?.(ref) : void 0;
@@ -40354,7 +40666,8 @@ function routeLocal(frame, hello, reply, ctx) {
40354
40666
  code: "bad_frame",
40355
40667
  message: "the frame is not valid on the hub wire"
40356
40668
  };
40357
- const id = ctx.pending.open(frame.id, reply);
40669
+ const gated = hello.caps?.includes(ADAPTER_CAPS.mailbox) ? reply : (f) => reply(forOldAdapter(f));
40670
+ const id = ctx.pending.open(frame.id, gated);
40358
40671
  if (ctx.hub.send(build(id))) return void 0;
40359
40672
  ctx.pending.cancel(id);
40360
40673
  return {
@@ -41809,7 +42122,9 @@ async function runDaemon(deps = {}) {
41809
42122
  kind: kind2,
41810
42123
  proposed_name: fitName(rec.proposedName, 64),
41811
42124
  namespace: rec.namespace,
41812
- global: rec.global
42125
+ global: rec.global,
42126
+ // A /clear: the new ref takes over the old address and its mail (#1295).
42127
+ ...rec.replaces !== void 0 && hubMailbox ? { replaces: rec.replaces } : {}
41813
42128
  });
41814
42129
  };
41815
42130
  const markLimited = (ref, message) => {
@@ -41829,7 +42144,20 @@ async function runDaemon(deps = {}) {
41829
42144
  limited.delete(ref);
41830
42145
  caps.delete(ref);
41831
42146
  };
41832
- const resolveNs = (cwd, allowMissing) => (deps.resolveNamespace ?? ((dir) => resolveNamespace(dir, { log, allowMissing })))(cwd);
42147
+ const configIssues = new ConfigIssues();
42148
+ const configFiles = (cwd) => {
42149
+ const g = inspectGlobal();
42150
+ let within;
42151
+ if (cwd !== void 0) {
42152
+ try {
42153
+ within = new Set(configPaths(realpathSync6(cwd)));
42154
+ } catch {
42155
+ within = new Set(configPaths(cwd));
42156
+ }
42157
+ }
42158
+ return [...configIssues.list(within), ...g ? [g] : []];
42159
+ };
42160
+ const resolveNs = (cwd, allowMissing) => (deps.resolveNamespace ?? ((dir) => resolveNamespace(dir, { log, allowMissing, issues: configIssues })))(cwd);
41833
42161
  const cl = deps.claude ?? {};
41834
42162
  const listenPath = deps.socketPath ?? socketPath();
41835
42163
  const returnDir = cl.socketsDir ?? claudeReturnDir2();
@@ -42025,6 +42353,14 @@ async function runDaemon(deps = {}) {
42025
42353
  if (agent) void claude.notify(agent, replyGoneText(peerName));
42026
42354
  }
42027
42355
  });
42356
+ const clearedFrom = (ref, pid, procStart, liveRefs) => agents.all().filter(
42357
+ (a) => a.kind === "claude-code" && a.pid === pid && procStart !== void 0 && a.procStart === procStart && a.ref !== ref && !liveRefs.has(a.ref) && !takenOver.has(a.ref)
42358
+ ).sort(
42359
+ (a, b) => Number(b.live) - Number(a.live) || (seenAt.get(b.ref) ?? 0) - (seenAt.get(a.ref) ?? 0)
42360
+ )[0];
42361
+ const takenOver = /* @__PURE__ */ new Set();
42362
+ const seenAt = /* @__PURE__ */ new Map();
42363
+ let seenSeq = 0;
42028
42364
  const syncClaude = () => {
42029
42365
  const live = liveClaudeSessions(claudeSessions);
42030
42366
  const liveRefs = new Set(live.map((s) => s.ref));
@@ -42032,8 +42368,13 @@ async function runDaemon(deps = {}) {
42032
42368
  for (const s of live) {
42033
42369
  const prev = agents.byRef(s.ref);
42034
42370
  if (prev && prev.kind !== "claude-code") continue;
42371
+ if (prev && prev.procStart === void 0 && prev.pid === s.pid)
42372
+ prev.procStart = s.procStart;
42035
42373
  if (prev?.live) {
42036
- if (!livePid(s.ref, prev.pid)) prev.pid = s.pid;
42374
+ if (!livePid(s.ref, prev.pid)) {
42375
+ prev.pid = s.pid;
42376
+ prev.procStart = s.procStart;
42377
+ }
42037
42378
  continue;
42038
42379
  }
42039
42380
  if (prev && prev.pid === s.pid) continue;
@@ -42045,15 +42386,20 @@ async function runDaemon(deps = {}) {
42045
42386
  continue;
42046
42387
  }
42047
42388
  if (!WireNamespaceSchema.safeParse(ns.namespace).success) continue;
42389
+ seenAt.set(s.ref, ++seenSeq);
42390
+ const old = prev ? void 0 : clearedFrom(s.ref, s.pid, s.procStart, liveRefs);
42391
+ if (old) takenOver.add(old.ref);
42048
42392
  announceUp(
42049
42393
  agents.up({
42394
+ ...old ? { replaces: old.ref } : {},
42050
42395
  ref: s.ref,
42051
42396
  kind: "claude-code",
42052
- proposedName: s.name,
42053
- busName: prev?.busName ?? s.name,
42397
+ proposedName: old?.busName ?? s.name,
42398
+ busName: prev?.busName ?? old?.busName ?? s.name,
42054
42399
  namespace: ns.namespace,
42055
42400
  global: ns.global,
42056
42401
  pid: s.pid,
42402
+ procStart: s.procStart,
42057
42403
  cwd: s.cwd,
42058
42404
  live: true
42059
42405
  })
@@ -42105,6 +42451,7 @@ async function runDaemon(deps = {}) {
42105
42451
  let hubEver = false;
42106
42452
  let hubDroppedAt;
42107
42453
  let hubIds = false;
42454
+ let hubMailbox = false;
42108
42455
  let hubVersion;
42109
42456
  const hub = new HubConnection({
42110
42457
  url: `${cfg.hub.replace(/^http/, "ws").replace(/\/+$/, "")}${BUS_ROUTES.connect}`,
@@ -42123,7 +42470,10 @@ async function runDaemon(deps = {}) {
42123
42470
  upIds.delete(frame.id);
42124
42471
  clearLimited(frame.agent_ref);
42125
42472
  const agent = agents.byRef(frame.agent_ref);
42126
- if (agent) agent.busName = frame.bus_name;
42473
+ if (agent) {
42474
+ agent.busName = frame.bus_name;
42475
+ delete agent.replaces;
42476
+ }
42127
42477
  resends.up(frame.agent_ref);
42128
42478
  return;
42129
42479
  }
@@ -42190,6 +42540,7 @@ async function runDaemon(deps = {}) {
42190
42540
  case "hello_ok":
42191
42541
  hubVersion = frame.server_version;
42192
42542
  hubIds = frame.caps?.includes(BUS_CAPS.ids) ?? false;
42543
+ hubMailbox = frame.caps?.includes(BUS_CAPS.mailbox) ?? false;
42193
42544
  outbox.flush();
42194
42545
  return;
42195
42546
  }
@@ -42235,6 +42586,7 @@ async function runDaemon(deps = {}) {
42235
42586
  upIds.clear();
42236
42587
  hubVersion = void 0;
42237
42588
  hubIds = false;
42589
+ hubMailbox = false;
42238
42590
  statusRelay.dropped();
42239
42591
  receipts.release();
42240
42592
  resends.dropped();
@@ -42315,11 +42667,11 @@ async function runDaemon(deps = {}) {
42315
42667
  const local = live.filter((a) => a.namespace === namespace);
42316
42668
  const mine = new Map(local.map((a) => [a.busName, a.kind]));
42317
42669
  const roster2 = local[0] && hub.open ? await hubRoster(local[0].ref) : void 0;
42318
- const rows = roster2 ? roster2.map((a) => ({
42670
+ const rows = roster2 ? roster2.filter((a) => a.state !== "away").map((a) => ({
42319
42671
  name: a.name,
42320
42672
  kind: a.kind,
42321
42673
  machine: a.machine_label ?? (mine.get(a.name) === a.kind ? cfg.label : ""),
42322
- state: a.state ?? "live",
42674
+ state: a.state === "away" ? "queued" : a.state ?? "live",
42323
42675
  ...a.status === void 0 ? {} : { status: a.status },
42324
42676
  ...a.status_label === void 0 ? {} : { status_label: a.status_label },
42325
42677
  ...a.status_at === void 0 ? {} : { status_at: a.status_at }
@@ -42332,10 +42684,12 @@ async function runDaemon(deps = {}) {
42332
42684
  return { namespace, agents: rows };
42333
42685
  })
42334
42686
  );
42687
+ const config_files = configFiles();
42335
42688
  return {
42336
42689
  pid: process.pid,
42337
42690
  version: version2,
42338
42691
  label: cfg.label,
42692
+ ...config_files.length > 0 ? { config_files } : {},
42339
42693
  hub: hub.open ? "connected" : "connecting",
42340
42694
  ...hub.open || !hub.lastError ? {} : { hub_error: hub.lastError },
42341
42695
  hub_ever: hubEver,
@@ -42418,14 +42772,28 @@ async function runDaemon(deps = {}) {
42418
42772
  };
42419
42773
  attested(pid);
42420
42774
  const proposedName = hello.proposed_name ?? hello.agent_ref;
42775
+ const files = hello.kind === "claude-code" ? liveClaudeSessions(claudeSessions) : [];
42776
+ const mine = files.find(
42777
+ (x) => x.ref === hello.agent_ref && x.pid === pid
42778
+ );
42779
+ const old = mine && !prev ? clearedFrom(
42780
+ hello.agent_ref,
42781
+ pid,
42782
+ mine.procStart,
42783
+ new Set(files.map((x) => x.ref))
42784
+ ) : void 0;
42785
+ seenAt.set(hello.agent_ref, ++seenSeq);
42786
+ if (old) takenOver.add(old.ref);
42421
42787
  const rec = agents.up({
42788
+ ...old ? { replaces: old.ref } : {},
42422
42789
  ref: hello.agent_ref,
42423
42790
  kind: hello.kind,
42424
- proposedName,
42425
- busName: prev?.busName ?? proposedName,
42791
+ proposedName: old?.busName ?? proposedName,
42792
+ busName: prev?.busName ?? old?.busName ?? proposedName,
42426
42793
  namespace: ns.namespace,
42427
42794
  global: ns.global,
42428
42795
  pid,
42796
+ ...mine ? { procStart: mine.procStart } : {},
42429
42797
  cwd,
42430
42798
  live: true
42431
42799
  });
@@ -42462,6 +42830,7 @@ async function runDaemon(deps = {}) {
42462
42830
  pending,
42463
42831
  limited: (ref) => limited.has(ref) ? caps.get(ref) ?? "machine" : void 0,
42464
42832
  version: version2,
42833
+ configFiles,
42465
42834
  // The self tool's status: the agent's own row on its roster.
42466
42835
  ownRow: (ref) => {
42467
42836
  const me = agents.byRef(ref);
@@ -42605,7 +42974,7 @@ async function runDaemon(deps = {}) {
42605
42974
  init_src3();
42606
42975
  import { execFileSync as execFileSync5, spawn as spawn2 } from "node:child_process";
42607
42976
  import { closeSync as closeSync4, openSync as openSync4, readFileSync as readFileSync13 } from "node:fs";
42608
- import { homedir as homedir3, userInfo } from "node:os";
42977
+ import { homedir as homedir3, userInfo as userInfo2 } from "node:os";
42609
42978
  init_src2();
42610
42979
 
42611
42980
  // src/copy.ts
@@ -43116,6 +43485,14 @@ function copy(style) {
43116
43485
  next(p("muted", clip(`It ${why}.`, 3))),
43117
43486
  next(`Remove it, then run ${BUS_CMD} global add <dir> again.`)
43118
43487
  ],
43488
+ /** A refused or noted config file (#1527): the reason, then the exact fix. Paths arrive already shortened. */
43489
+ configFile: (f) => [
43490
+ warn(
43491
+ f.state === "refused" ? `${f.file} ${f.reason}, so it is ignored.` : `${f.file} ${f.reason}; it is still used.`
43492
+ ),
43493
+ next(`Fix: ${f.fix}`),
43494
+ ...f.restart ? [next("Restart the session to pick up a fix.")] : []
43495
+ ],
43119
43496
  globalAsked: () => [
43120
43497
  warn("This repo's .oa-bus.json asks for global, which is ignored here."),
43121
43498
  next(`Allow it with ${BUS_CMD} global add .`)
@@ -43321,7 +43698,7 @@ import {
43321
43698
  existsSync as existsSync3,
43322
43699
  lstatSync as lstatSync8,
43323
43700
  mkdirSync as mkdirSync7,
43324
- realpathSync as realpathSync5,
43701
+ realpathSync as realpathSync7,
43325
43702
  renameSync as renameSync7,
43326
43703
  rmSync as rmSync4,
43327
43704
  writeFileSync as writeFileSync6
@@ -44535,7 +44912,7 @@ function writeReplacing(path, text3) {
44535
44912
  var real = (path) => {
44536
44913
  const abs = resolve4(path);
44537
44914
  try {
44538
- return realpathSync5(abs);
44915
+ return realpathSync7(abs);
44539
44916
  } catch {
44540
44917
  const parent = dirname7(abs);
44541
44918
  return parent === abs ? abs : join13(real(parent), basename6(abs));
@@ -44768,7 +45145,7 @@ var noService = {
44768
45145
  remove: async () => ({}),
44769
45146
  cli: () => ["oa-bus"]
44770
45147
  };
44771
- var VERSION = true ? "0.124.0" : JSON.parse(
45148
+ var VERSION = true ? "0.125.0" : JSON.parse(
44772
45149
  readFileSync13(new URL("../package.json", import.meta.url), "utf8")
44773
45150
  ).version;
44774
45151
  var which = (cmd) => {
@@ -44814,7 +45191,7 @@ var spawnDaemon = (cmd, args, opts) => {
44814
45191
  };
44815
45192
  var userName = () => {
44816
45193
  try {
44817
- return userInfo().username;
45194
+ return userInfo2().username;
44818
45195
  } catch {
44819
45196
  return "";
44820
45197
  }
@@ -44909,6 +45286,17 @@ var sayErr = (ctx, ...lines) => {
44909
45286
  };
44910
45287
  var tildeReason = (ctx, harness, reason) => harness === "codex" && reason.startsWith(`${ctx.userHome}/`) ? `~${reason.slice(ctx.userHome.length)}` : reason;
44911
45288
  var tilde = (ctx, path) => path === ctx.userHome ? "~" : path.startsWith(`${ctx.userHome}/`) ? `~${path.slice(ctx.userHome.length)}` : path;
45289
+ var plain2 = (text3) => stripTerminalControls(text3).replace(/[\r\n\t]/g, " ");
45290
+ var configFileLines = (ctx, files) => files.flatMap(
45291
+ (f) => ctx.copy.configFile({
45292
+ state: f.state,
45293
+ reason: plain2(f.reason),
45294
+ file: plain2(tilde(ctx, f.file)),
45295
+ fix: plain2(f.fix),
45296
+ // The namespace is resolved when a session starts.
45297
+ restart: f.file.endsWith("/.oa-bus.json")
45298
+ })
45299
+ );
44912
45300
 
44913
45301
  // src/machine.ts
44914
45302
  import { readFileSync as readFileSync14, statSync as statSync8 } from "node:fs";
@@ -47399,6 +47787,7 @@ async function status(ctx) {
47399
47787
  async () => (await health).version
47400
47788
  );
47401
47789
  if (update) say(ctx, c.updateAvailable(update, st.version));
47790
+ if (st.config_files?.length) say(ctx, configFileLines(ctx, st.config_files));
47402
47791
  if (st.hub !== "connected") {
47403
47792
  sayErr(ctx, c.hubNotReachable(host, st.hub_error, "status"));
47404
47793
  return 1;
@@ -47590,19 +47979,26 @@ async function doctor(ctx) {
47590
47979
  const allow = globalFile(ctx.home);
47591
47980
  const list = readGlobalDirs(allow);
47592
47981
  if (list.error) warn(c.globalUnusable(tilde(ctx, allow), list.error));
47982
+ const files = scanConfigFiles(ctx.cwd, { globalFile: allow }).filter(
47983
+ (f) => f.file !== allow
47984
+ );
47985
+ if (files.length > 0) {
47986
+ n.warnings += files.length;
47987
+ say(ctx, configFileLines(ctx, files));
47988
+ }
47593
47989
  if (asksForGlobal(ctx, list.dirs)) warn(c.globalAsked());
47594
47990
  say(ctx, "", c.summary(n.failed, n.warnings, n.passed));
47595
47991
  return n.failed > 0 ? 1 : 0;
47596
47992
  }
47597
47993
 
47598
47994
  // src/commands/global.ts
47599
- import { realpathSync as realpathSync6, statSync as statSync10 } from "node:fs";
47995
+ import { realpathSync as realpathSync8, statSync as statSync10 } from "node:fs";
47600
47996
  import { resolve as resolve6 } from "node:path";
47601
47997
  function shown(ctx, path) {
47602
47998
  const t = tilde(ctx, path);
47603
47999
  if (t !== path) return t;
47604
48000
  try {
47605
- return tilde({ userHome: realpathSync6(ctx.userHome) }, path);
48001
+ return tilde({ userHome: realpathSync8(ctx.userHome) }, path);
47606
48002
  } catch {
47607
48003
  return path;
47608
48004
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@open-artifacts/bus",
3
3
  "type": "module",
4
- "version": "0.124.0",
4
+ "version": "0.125.0",
5
5
  "description": "The Open Artifacts bus: your AI agents reach each other across machines.",
6
6
  "license": "MIT",
7
7
  "repository": {