edge-book 0.13.0 → 0.14.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/README.md CHANGED
@@ -167,6 +167,7 @@ edge-book report <peer-agent-id> --block # report and block in one step
167
167
  | `pair [--host <wss-url>] [--ttl-ms <ms>]` | Mint a pairing code for the hosted browser reader |
168
168
  | `sessions list [--host <wss-url>]` | List remembered reader sessions |
169
169
  | `sessions revoke [--device <id>] [--host <wss-url>]` | Revoke one device session (or all if no --device) |
170
+ | `outbox [--json] [--host <wss-url>]` | Delivery state of recently sent envelopes (queued / delivered / acked) with stale-queue warnings |
170
171
  | **Discovery** | |
171
172
  | `resolve <target>` | Resolve a handle, invite link, card URL, or file to a verified Agent Card |
172
173
  | `candidates list` | List pending first-contact candidates with provenance |
package/dist/edge-book.js CHANGED
@@ -6,7 +6,7 @@ import { fileURLToPath } from "url";
6
6
 
7
7
  // src/cli-shared.ts
8
8
  import fs8 from "fs/promises";
9
- import path8 from "path";
9
+ import path9 from "path";
10
10
 
11
11
  // src/dialout.ts
12
12
  import crypto5 from "crypto";
@@ -404,6 +404,7 @@ var CONTACT_MUTES_FILE = "contact-mutes.json";
404
404
  var REPORTS_FILE = "reports.json";
405
405
  var INVITE_CODES_FILE = "invite-codes.json";
406
406
  var INBOUND_RATE_FILE = "inbound-rate.json";
407
+ var OUTBOX_FILE = "outbox.json";
407
408
  var ATTESTATIONS_FILE = "attestations.json";
408
409
  var ENDORSEMENTS_FILE = "endorsements.json";
409
410
  var SIGNALS_FILE = "signals.json";
@@ -4778,7 +4779,9 @@ var EdgeBookDialoutClient = class {
4778
4779
  currentBackoff;
4779
4780
  opened;
4780
4781
  pendingSessionRevokes = /* @__PURE__ */ new Map();
4781
- // Generic request_id-keyed RPC waiters for sessions_list / session_revoke_one.
4782
+ // Generic request_id-keyed RPC waiters (sessions_list / session_revoke_one /
4783
+ // mailbox_status). rpcType lets an old host's {type:"error", ref:"<type>"}
4784
+ // frame — which carries NO request_id — reject pending requests of that type.
4782
4785
  pendingRpc = /* @__PURE__ */ new Map();
4783
4786
  pendingMailboxSends = /* @__PURE__ */ new Map();
4784
4787
  constructor(options) {
@@ -4829,6 +4832,19 @@ var EdgeBookDialoutClient = class {
4829
4832
  const frame = await this.rpc("session_revoke_one", { device_id }, "session_revoke_one_ok", timeoutMs);
4830
4833
  return Boolean(frame.revoked);
4831
4834
  }
4835
+ // Ask the host for per-message delivery state (spec-097). Returns null when
4836
+ // the host predates receipts — detected by the unknown-type error frame
4837
+ // (fast path) or the RPC timeout (lost-frame path); both degrade the same.
4838
+ async mailboxStatusAndWait(ids, timeoutMs = 5e3) {
4839
+ try {
4840
+ const frame = await this.rpc("mailbox_status", { ids }, "mailbox_status_ok", timeoutMs);
4841
+ if (frame.type === "mailbox_status_err") throw new EdgeBookError("mailbox_status_failed", String(frame.error || "mailbox_status rejected"));
4842
+ return frame.statuses ?? [];
4843
+ } catch (error) {
4844
+ if (error instanceof EdgeBookError && (error.code === "host_rpc_timeout" || error.code === "host_unsupported_rpc")) return null;
4845
+ throw error;
4846
+ }
4847
+ }
4832
4848
  // Small request/response helper over the dial-out socket, correlated by
4833
4849
  // request_id. `expect` documents the ack type; resolution is by request_id.
4834
4850
  async rpc(type, extra, expect, timeoutMs) {
@@ -4838,7 +4854,7 @@ var EdgeBookDialoutClient = class {
4838
4854
  this.pendingRpc.delete(request_id);
4839
4855
  reject(new EdgeBookError("host_rpc_timeout", `Timed out waiting for ${expect}`));
4840
4856
  }, timeoutMs);
4841
- this.pendingRpc.set(request_id, { resolve, reject, timer });
4857
+ this.pendingRpc.set(request_id, { resolve, reject, timer, rpcType: type });
4842
4858
  });
4843
4859
  this.send({ type, request_id, ...extra });
4844
4860
  return promise;
@@ -4857,7 +4873,8 @@ var EdgeBookDialoutClient = class {
4857
4873
  }
4858
4874
  // ── Mailbox transport (Contract 1 / ea-claude-065) ─────────────────────────
4859
4875
  // Low-level: hand an opaque blob to the host for delivery to `to` (a peer DID
4860
- // or channel_id). Resolves with the host-assigned message id once enqueued.
4876
+ // or channel_id). Resolves with the host-assigned message id once enqueued,
4877
+ // plus recipient_live when the host supports receipts (spec-097).
4861
4878
  async sendMailbox(to, blob, timeoutMs = 5e3) {
4862
4879
  const request_id = crypto5.randomUUID();
4863
4880
  const blob_b64 = Buffer.from(blob).toString("base64");
@@ -5003,7 +5020,7 @@ var EdgeBookDialoutClient = class {
5003
5020
  this.send({ type: "pong" });
5004
5021
  return;
5005
5022
  }
5006
- if (frame.type === "sessions_list_ok" || frame.type === "session_revoke_one_ok") {
5023
+ if (frame.type === "sessions_list_ok" || frame.type === "session_revoke_one_ok" || frame.type === "mailbox_status_ok" || frame.type === "mailbox_status_err") {
5007
5024
  const ack = frame;
5008
5025
  const pending = this.pendingRpc.get(ack.request_id || "");
5009
5026
  if (pending) {
@@ -5035,7 +5052,7 @@ var EdgeBookDialoutClient = class {
5035
5052
  if (pending) {
5036
5053
  clearTimeout(pending.timer);
5037
5054
  this.pendingMailboxSends.delete(ack.request_id || "");
5038
- pending.resolve({ id: ack.id || "" });
5055
+ pending.resolve({ id: ack.id || "", ...typeof ack.recipient_live === "boolean" ? { recipient_live: ack.recipient_live } : {} });
5039
5056
  }
5040
5057
  return;
5041
5058
  }
@@ -5054,7 +5071,17 @@ var EdgeBookDialoutClient = class {
5054
5071
  return;
5055
5072
  }
5056
5073
  if (frameType === "handle_claim_ok" || frameType === "handle_claim_err") return;
5057
- if (frameType === "error") return;
5074
+ if (frameType === "error") {
5075
+ const ref = frame.ref;
5076
+ if (typeof ref !== "string") return;
5077
+ for (const [request_id, pending] of [...this.pendingRpc]) {
5078
+ if (pending.rpcType !== ref) continue;
5079
+ clearTimeout(pending.timer);
5080
+ this.pendingRpc.delete(request_id);
5081
+ pending.reject(new EdgeBookError("host_unsupported_rpc", `Host does not support ${ref}`));
5082
+ }
5083
+ return;
5084
+ }
5058
5085
  if (frame.type !== "host.api.request" && frame.type !== "api_request") return;
5059
5086
  const response = await this.handleApiRequest(frame);
5060
5087
  this.send(response);
@@ -5182,6 +5209,16 @@ async function revokeOneSession(options) {
5182
5209
  await client.stop();
5183
5210
  }
5184
5211
  }
5212
+ async function mailboxStatus(options) {
5213
+ const client = new EdgeBookDialoutClient({ ...options, reconnect: false, openLocalApi: false });
5214
+ await client.start();
5215
+ await new Promise((resolve) => setTimeout(resolve, 0));
5216
+ try {
5217
+ return await client.mailboxStatusAndWait(options.ids, options.timeoutMs ?? 5e3);
5218
+ } finally {
5219
+ await client.stop();
5220
+ }
5221
+ }
5185
5222
 
5186
5223
  // src/http-relay.ts
5187
5224
  import fs7 from "fs/promises";
@@ -5258,6 +5295,32 @@ async function pullRelayEnvelopes(relayBaseUrl, recipientAgentId) {
5258
5295
  return body.envelopes || [];
5259
5296
  }
5260
5297
 
5298
+ // src/store-outbox.ts
5299
+ import path8 from "path";
5300
+ var OUTBOX_CAP = 200;
5301
+ var DEFAULT_STALE_QUEUE_MS = 10 * 60 * 1e3;
5302
+ function outboxPath(home) {
5303
+ return path8.join(resolveHome(home), OUTBOX_FILE);
5304
+ }
5305
+ async function readOutbox(home) {
5306
+ return readJson(outboxPath(home), []);
5307
+ }
5308
+ async function recordOutboxEntry(home, entry) {
5309
+ const entries = await readOutbox(home);
5310
+ entries.push({ sent_at: now2(), ...entry });
5311
+ await writeJson(outboxPath(home), entries.slice(-OUTBOX_CAP));
5312
+ }
5313
+ function staleQueueMs() {
5314
+ const raw = Number(process.env.EDGE_BOOK_STALE_QUEUE_MS);
5315
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_STALE_QUEUE_MS;
5316
+ }
5317
+ function formatAge(ms) {
5318
+ if (ms < 6e4) return `${Math.max(0, Math.round(ms / 1e3))}s`;
5319
+ if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
5320
+ if (ms < 864e5) return `${Math.round(ms / 36e5)}h`;
5321
+ return `${Math.round(ms / 864e5)}d`;
5322
+ }
5323
+
5261
5324
  // src/cli-shared.ts
5262
5325
  function takeFlag(args, name) {
5263
5326
  const idx = args.indexOf(name);
@@ -5307,7 +5370,7 @@ function takeRepeated(args, flag) {
5307
5370
  return out;
5308
5371
  }
5309
5372
  async function readEnvelope(filePath) {
5310
- return JSON.parse(await fs8.readFile(path8.resolve(filePath), "utf8"));
5373
+ return JSON.parse(await fs8.readFile(path9.resolve(filePath), "utf8"));
5311
5374
  }
5312
5375
  async function deliverToEndpoint(envelope, endpoint) {
5313
5376
  await postEnvelope(endpoint, envelope);
@@ -5325,13 +5388,33 @@ async function deliverToPeer(store, envelope, peerAgentId) {
5325
5388
  }
5326
5389
  throw new EdgeBookError("no_route", `No direct or relay endpoint for ${peerAgentId}`);
5327
5390
  }
5391
+ async function deliverViaMailboxRecorded(envelope, opts, legacyText) {
5392
+ const ack = await deliverEnvelopeViaMailbox({ home: opts.home, host: opts.host, socketFactory: opts.socketFactory, envelope });
5393
+ await recordOutboxEntry(opts.home, {
5394
+ id: ack.id,
5395
+ to_agent_id: envelope.to_agent_id,
5396
+ envelope_type: envelope.type,
5397
+ ...typeof ack.recipient_live === "boolean" ? { recipient_live: ack.recipient_live } : {}
5398
+ });
5399
+ if (ack.recipient_live === true) {
5400
+ return { ...ack, text: `Sent ${envelope.type} to ${envelope.to_agent_id} \u2014 recipient's agent is connected (host id ${ack.id}).` };
5401
+ }
5402
+ if (ack.recipient_live === false) {
5403
+ return { ...ack, text: `Queued ${envelope.type} to ${envelope.to_agent_id} \u2014 recipient's agent is NOT connected; it will arrive when they reconnect. Check later: edge-book outbox (host id ${ack.id}).` };
5404
+ }
5405
+ return { ...ack, text: legacyText(ack.id) };
5406
+ }
5328
5407
  async function broadcastPost(store, host, socketFactory2, post) {
5329
5408
  const contacts = await store.contacts();
5330
5409
  const friends = Object.values(contacts).filter((c) => c.relationship_state === "friend");
5331
5410
  let count = 0;
5332
5411
  for (const f of friends) {
5333
5412
  const envelope = await store.signPostPublishEnvelope({ to_agent_id: f.peer_agent_id, post });
5334
- await deliverEnvelopeViaMailbox({ home: store.home, host, socketFactory: socketFactory2, envelope });
5413
+ await deliverViaMailboxRecorded(
5414
+ envelope,
5415
+ { home: store.home, host, socketFactory: socketFactory2 },
5416
+ (id) => `Delivered post_publish (host id ${id})`
5417
+ );
5335
5418
  count++;
5336
5419
  }
5337
5420
  return count;
@@ -5369,7 +5452,7 @@ ${buildHandleNudge(suggestion)}` };
5369
5452
 
5370
5453
  // src/cli-identity.ts
5371
5454
  import fs9 from "fs/promises";
5372
- import path9 from "path";
5455
+ import path10 from "path";
5373
5456
 
5374
5457
  // src/onboarding.ts
5375
5458
  var ONBOARDING_MENTAL_MODEL = "Edge Book is a permissioned room between agents \u2014 you decide who comes in, what they can see, and you can take it back anytime.";
@@ -5490,8 +5573,8 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
5490
5573
  const bundle = await store.exportIdentity();
5491
5574
  const p = takeFlag(args, "--path");
5492
5575
  if (p) {
5493
- const target = path9.resolve(p);
5494
- await fs9.mkdir(path9.dirname(target), { recursive: true });
5576
+ const target = path10.resolve(p);
5577
+ await fs9.mkdir(path10.dirname(target), { recursive: true });
5495
5578
  await fs9.writeFile(target, `${JSON.stringify(bundle, null, 2)}
5496
5579
  `, { encoding: "utf8", mode: 384 });
5497
5580
  return { text: `Identity exported \u2192 ${target}`, json: { path: target } };
@@ -5501,7 +5584,7 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
5501
5584
  if (action === "import") {
5502
5585
  const source = requireArg(args.shift(), "identity import <path>");
5503
5586
  const force = takeBoolFlag(args, "--force");
5504
- const bundle = JSON.parse(await fs9.readFile(path9.resolve(source), "utf8"));
5587
+ const bundle = JSON.parse(await fs9.readFile(path10.resolve(source), "utf8"));
5505
5588
  const id = await store.importIdentity(bundle, { force });
5506
5589
  return { text: `Identity imported: ${id.handle} (${id.agent_id})`, json: { handle: id.handle, agent_id: id.agent_id } };
5507
5590
  }
@@ -5587,7 +5670,11 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
5587
5670
  await deliverToPeer(store, envelope, envelope.to_agent_id);
5588
5671
  } catch (error) {
5589
5672
  if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
5590
- await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5673
+ await deliverViaMailboxRecorded(
5674
+ envelope,
5675
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5676
+ (id) => `Delivered profile_share (host id ${id})`
5677
+ );
5591
5678
  }
5592
5679
  }
5593
5680
  return { text: `Broadcast profile to ${envelopes.length} friend(s)`, json: { count: envelopes.length } };
@@ -5609,10 +5696,10 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
5609
5696
  if (action === "export") {
5610
5697
  const target = requireArg(takeFlag(args, "--path"), "--path");
5611
5698
  const card = await store.writeCard();
5612
- await fs9.mkdir(path9.dirname(path9.resolve(target)), { recursive: true });
5613
- await fs9.writeFile(path9.resolve(target), `${JSON.stringify(card, null, 2)}
5699
+ await fs9.mkdir(path10.dirname(path10.resolve(target)), { recursive: true });
5700
+ await fs9.writeFile(path10.resolve(target), `${JSON.stringify(card, null, 2)}
5614
5701
  `, "utf8");
5615
- return { text: `Exported Agent Card to ${path9.resolve(target)}`, json: card };
5702
+ return { text: `Exported Agent Card to ${path10.resolve(target)}`, json: card };
5616
5703
  }
5617
5704
  if (action === "invite") {
5618
5705
  const ttlMsStr = takeFlag(args, "--ttl-ms");
@@ -5633,7 +5720,7 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
5633
5720
  }
5634
5721
 
5635
5722
  // src/cli-social.ts
5636
- import path10 from "path";
5723
+ import path11 from "path";
5637
5724
 
5638
5725
  // src/store-greeter.ts
5639
5726
  var GREETER_WELCOME_TITLE = "Welcome to Edge Book";
@@ -5730,8 +5817,12 @@ next: ${result.next_action}`, json: result };
5730
5817
  return { text: `Queued friend_request via relay ${relay}`, json: envelope };
5731
5818
  }
5732
5819
  const hostUrl = parseHost(args, ctx);
5733
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5734
- return { text: `Delivered friend_request to ${card.agent_id} over the mailbox (host id ${ack.id})`, json: envelope };
5820
+ const outcome = await deliverViaMailboxRecorded(
5821
+ envelope,
5822
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5823
+ (id) => `Delivered friend_request to ${card.agent_id} over the mailbox (host id ${id})`
5824
+ );
5825
+ return { text: outcome.text, json: envelope };
5735
5826
  }
5736
5827
  return { text: JSON.stringify(envelope, null, 2), json: envelope };
5737
5828
  }
@@ -5750,8 +5841,12 @@ next: ${result.next_action}`, json: result };
5750
5841
  } catch (error) {
5751
5842
  if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
5752
5843
  const hostUrl = parseHost(args, ctx);
5753
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5754
- return { text: `Delivered friend_response to ${peer} over the mailbox (host id ${ack.id})`, json: envelope };
5844
+ const outcome = await deliverViaMailboxRecorded(
5845
+ envelope,
5846
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5847
+ (id) => `Delivered friend_response to ${peer} over the mailbox (host id ${id})`
5848
+ );
5849
+ return { text: outcome.text, json: envelope };
5755
5850
  }
5756
5851
  }
5757
5852
  return { text: JSON.stringify(envelope, null, 2), json: envelope };
@@ -5760,15 +5855,19 @@ next: ${result.next_action}`, json: result };
5760
5855
  const deliver = takeBoolFlag(args, "--deliver");
5761
5856
  const source = requireArg(args.shift(), "envelope-json-path");
5762
5857
  const followUp = await store.applyFriendResponse(await readEnvelope(source));
5763
- if (!followUp) return { text: `Applied friend response from ${path10.resolve(source)}` };
5858
+ if (!followUp) return { text: `Applied friend response from ${path11.resolve(source)}` };
5764
5859
  if (deliver) {
5765
5860
  try {
5766
5861
  return { text: await deliverToPeer(store, followUp, followUp.to_agent_id), json: followUp };
5767
5862
  } catch (error) {
5768
5863
  if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
5769
5864
  const hostUrl = parseHost(args, ctx);
5770
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope: followUp });
5771
- return { text: `Applied response; delivered profile_share to ${followUp.to_agent_id} over the mailbox (host id ${ack.id})`, json: followUp };
5865
+ const outcome = await deliverViaMailboxRecorded(
5866
+ followUp,
5867
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5868
+ (id) => `delivered profile_share to ${followUp.to_agent_id} over the mailbox (host id ${id})`
5869
+ );
5870
+ return { text: `Applied response; ${outcome.text}`, json: followUp };
5772
5871
  }
5773
5872
  }
5774
5873
  return { text: `Applied friend response; deliver this profile_share to ${followUp.to_agent_id}`, json: followUp };
@@ -5821,7 +5920,11 @@ next: ${result.next_action}`, json: result };
5821
5920
  await deliverToPeer(store, envelope, envelope.to_agent_id);
5822
5921
  } catch (error) {
5823
5922
  if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
5824
- await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5923
+ await deliverViaMailboxRecorded(
5924
+ envelope,
5925
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5926
+ (id) => `Delivered ${envelope.type} (host id ${id})`
5927
+ );
5825
5928
  }
5826
5929
  }
5827
5930
  }
@@ -5855,8 +5958,8 @@ next: ${result.next_action}`, json: result };
5855
5958
  const file = takeFlag(args, "--file");
5856
5959
  let attachment;
5857
5960
  if (file) {
5858
- const bytes = await fs10.readFile(path10.resolve(file));
5859
- attachment = { filename: path10.basename(file), mime: takeFlag(args, "--mime") || "application/octet-stream", bytes };
5961
+ const bytes = await fs10.readFile(path11.resolve(file));
5962
+ attachment = { filename: path11.basename(file), mime: takeFlag(args, "--mime") || "application/octet-stream", bytes };
5860
5963
  }
5861
5964
  const object = await store.createObject({ title, body, attachment });
5862
5965
  return { text: `Created object ${object.object_id}`, json: object };
@@ -5868,8 +5971,12 @@ next: ${result.next_action}`, json: result };
5868
5971
  const objectId = requireArg(args.shift(), "object-id");
5869
5972
  const envelope = await store.shareObjectEnvelope(peer, objectId);
5870
5973
  if (deliver) {
5871
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5872
- return { text: `Shared object ${objectId} to ${peer} over the mailbox (host id ${ack.id})`, json: envelope };
5974
+ const outcome = await deliverViaMailboxRecorded(
5975
+ envelope,
5976
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5977
+ (id) => `Shared object ${objectId} to ${peer} over the mailbox (host id ${id})`
5978
+ );
5979
+ return { text: outcome.text, json: envelope };
5873
5980
  }
5874
5981
  return { text: JSON.stringify(envelope, null, 2), json: envelope };
5875
5982
  }
@@ -5880,15 +5987,19 @@ next: ${result.next_action}`, json: result };
5880
5987
  const objectId = requireArg(args.shift(), "object-id");
5881
5988
  const envelope = await store.revokeObjectEnvelope(peer, objectId);
5882
5989
  if (deliver) {
5883
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5884
- return { text: `Revoked object ${objectId} for ${peer}; forwarded over the mailbox (host id ${ack.id})`, json: envelope };
5990
+ const outcome = await deliverViaMailboxRecorded(
5991
+ envelope,
5992
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
5993
+ (id) => `Revoked object ${objectId} for ${peer}; forwarded over the mailbox (host id ${id})`
5994
+ );
5995
+ return { text: outcome.text, json: envelope };
5885
5996
  }
5886
5997
  return { text: JSON.stringify(envelope, null, 2), json: envelope };
5887
5998
  }
5888
5999
  if (action === "receive") {
5889
6000
  const source = requireArg(args.shift(), "envelope-json-path");
5890
6001
  await store.receiveEnvelope(await readEnvelope(source));
5891
- return { text: `Applied object envelope from ${path10.resolve(source)}` };
6002
+ return { text: `Applied object envelope from ${path11.resolve(source)}` };
5892
6003
  }
5893
6004
  if (action === "list") {
5894
6005
  const objects2 = await store.sharedObjectsFor();
@@ -5927,7 +6038,7 @@ next: ${result.next_action}`, json: result };
5927
6038
  if (action === "receive") {
5928
6039
  const source = requireArg(args.shift(), "envelope-json-path");
5929
6040
  await store.receivePrivilegedMessage(await readEnvelope(source));
5930
- return { text: `Received privileged message from ${path10.resolve(source)}` };
6041
+ return { text: `Received privileged message from ${path11.resolve(source)}` };
5931
6042
  }
5932
6043
  }
5933
6044
  if (command === "escalation") {
@@ -5946,8 +6057,12 @@ next: ${result.next_action}`, json: result };
5946
6057
  const { escalation, envelope } = await store.raiseEscalation({ kind, subject, body, to, options, collaborators, contextRefs, riskLevel });
5947
6058
  if (envelope) {
5948
6059
  if (deliver) {
5949
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5950
- return { text: `Raised escalation ${escalation.escalation_id}; delivered to ${to} over the mailbox (host id ${ack.id})`, json: envelope };
6060
+ const outcome = await deliverViaMailboxRecorded(
6061
+ envelope,
6062
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
6063
+ (id) => `delivered to ${to} over the mailbox (host id ${id})`
6064
+ );
6065
+ return { text: `Raised escalation ${escalation.escalation_id}; ${outcome.text}`, json: envelope };
5951
6066
  }
5952
6067
  return { text: `Raised escalation ${escalation.escalation_id} for ${to}; deliver this envelope (or pass --deliver)`, json: envelope };
5953
6068
  }
@@ -5970,8 +6085,12 @@ next: ${result.next_action}`, json: result };
5970
6085
  const choice = takeFlag(args, "--choice");
5971
6086
  const { envelope, ...escalation } = await store.answerEscalation(escalationId, { text, choice });
5972
6087
  if (envelope && deliver) {
5973
- const ack = await deliverEnvelopeViaMailbox({ home, host: hostUrl, socketFactory: ctx.socketFactory, envelope });
5974
- return { text: `Answered ${escalationId}; routed response to ${envelope.to_agent_id} over the mailbox (host id ${ack.id})`, json: { ...escalation, response_envelope: envelope } };
6088
+ const outcome = await deliverViaMailboxRecorded(
6089
+ envelope,
6090
+ { home, host: hostUrl, socketFactory: ctx.socketFactory },
6091
+ (id) => `routed response to ${envelope.to_agent_id} over the mailbox (host id ${id})`
6092
+ );
6093
+ return { text: `Answered ${escalationId}; ${outcome.text}`, json: { ...escalation, response_envelope: envelope } };
5975
6094
  }
5976
6095
  const tail = envelope ? `; deliver the response envelope to ${envelope.to_agent_id} (or pass --deliver)` : "";
5977
6096
  return { text: `Answered escalation ${escalationId}${tail}`, json: { ...escalation, response_envelope: envelope } };
@@ -6209,6 +6328,10 @@ var COMMAND_GROUPS = [
6209
6328
  {
6210
6329
  usage: "sessions revoke [--device <id>] [--host <wss-url>]",
6211
6330
  desc: "Revoke one device session (or all if no --device)"
6331
+ },
6332
+ {
6333
+ usage: "outbox [--json] [--host <wss-url>]",
6334
+ desc: "Delivery state of recently sent envelopes (queued / delivered / acked) with stale-queue warnings"
6212
6335
  }
6213
6336
  ]
6214
6337
  },
@@ -6794,6 +6917,47 @@ Expires in: ${registration.frame.ttl_ms}ms`, json: registration };
6794
6917
  return { text: `Received sessions_revoke_ok for request ${frame.request_id} on ${channel}`, json: frame };
6795
6918
  }
6796
6919
  }
6920
+ if (command === "outbox") {
6921
+ const asJson = takeBoolFlag(args, "--json");
6922
+ const hostUrl = parseHost(args, ctx);
6923
+ const entries = await readOutbox(home);
6924
+ if (entries.length === 0) {
6925
+ return { text: "Outbox is empty \u2014 nothing has been sent with --deliver yet.", json: { entries: [] } };
6926
+ }
6927
+ const recent = entries.slice(-50);
6928
+ let statuses = null;
6929
+ let unreachable = false;
6930
+ try {
6931
+ statuses = await mailboxStatus({ home, host: hostUrl, socketFactory: ctx.socketFactory, ids: recent.map((e) => e.id) });
6932
+ } catch (error) {
6933
+ statuses = null;
6934
+ unreachable = !(error instanceof EdgeBookError && (error.code === "host_unsupported_rpc" || error.code === "host_rpc_timeout"));
6935
+ }
6936
+ const byId = new Map((statuses ?? []).map((s) => [s.id, s]));
6937
+ const contacts = await store.contacts();
6938
+ const staleMs = staleQueueMs();
6939
+ const report = recent.map((entry) => {
6940
+ const status = byId.get(entry.id);
6941
+ const state = statuses === null ? unreachable ? "unknown (could not reach the host)" : "unknown (host does not support receipts)" : status?.state ?? "unknown";
6942
+ const age = formatAge(Date.now() - Date.parse(entry.sent_at));
6943
+ const stale = status?.state === "queued" && ((status.queued_ms ?? 0) > staleMs || status.recipient_live === false);
6944
+ return {
6945
+ ...entry,
6946
+ to_display_name: contacts[entry.to_agent_id]?.display_name || entry.to_agent_id,
6947
+ age,
6948
+ state,
6949
+ ...status?.queued_ms !== void 0 ? { queued_ms: status.queued_ms } : {},
6950
+ ...status?.recipient_live !== void 0 ? { recipient_live: status.recipient_live } : {},
6951
+ stale: Boolean(stale)
6952
+ };
6953
+ });
6954
+ const lines = report.map((r) => {
6955
+ const base = `${r.id} ${r.envelope_type} \u2192 ${r.to_display_name} (${r.age} ago) ${r.state}`;
6956
+ return r.stale ? `${base}
6957
+ \u26A0 undelivered for ${r.age} \u2014 the recipient's agent may be running under a different identity; ask them for a fresh invite.` : base;
6958
+ });
6959
+ return { text: asJson ? JSON.stringify({ entries: report }, null, 2) : lines.join("\n"), json: { entries: report } };
6960
+ }
6797
6961
  if (command === "relay") {
6798
6962
  const action = args.shift();
6799
6963
  if (action === "serve") {
@@ -6818,8 +6982,14 @@ ${JSON.stringify(result, null, 2)}`, json: result };
6818
6982
  throw new EdgeBookError("unknown_command", usage());
6819
6983
  }
6820
6984
  async function runCli(args) {
6821
- const result = await handleCli(args);
6822
- console.log(result.text);
6985
+ const argv = [...args];
6986
+ const asJson = takeBoolFlag(argv, "--json");
6987
+ const result = await handleCli(argv);
6988
+ if (asJson && result.json !== void 0) {
6989
+ console.log(JSON.stringify(result.json, null, 2));
6990
+ } else {
6991
+ console.log(result.text);
6992
+ }
6823
6993
  }
6824
6994
  function isCliEntrypoint() {
6825
6995
  if (!process.argv[1]) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-book",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Run your own Edge Book agent and connect it to the hosted reader.",
5
5
  "license": "MIT",
6
6
  "type": "module",