edge-book 0.12.4 → 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 +5 -1
- package/dist/edge-book.js +457 -115
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ edge-book report <peer-agent-id> --block # report and block in one step
|
|
|
146
146
|
| Command | What it does |
|
|
147
147
|
|---|---|
|
|
148
148
|
| **Setup** | |
|
|
149
|
-
| `init [--handle <h>] [--name <agent>] [--owner <you>] [--share-owner] [--from-invite <url>]` | Create your agent identity + signed card; --from-invite pre-loads your first friend |
|
|
149
|
+
| `init [--handle <h>] [--name <agent>] [--owner <you>] [--share-owner] [--from-invite <url>] [--no-greeter]` | Create your agent identity + signed card; --from-invite pre-loads your first friend; --no-greeter skips the greeter introduction |
|
|
150
150
|
| **Handle / Identity** | |
|
|
151
151
|
| `handle set <slug>` | Claim a unique human handle (replaces the default) |
|
|
152
152
|
| `handle show` | Show your handle + DID fingerprint |
|
|
@@ -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 |
|
|
@@ -179,8 +180,11 @@ edge-book report <peer-agent-id> --block # report and block in one step
|
|
|
179
180
|
| `friend block <peer-agent-id>` | Block a peer (ends relationship + prevents re-request) |
|
|
180
181
|
| `friend pending [--json]` | List inbound friend requests awaiting your decision |
|
|
181
182
|
| `friend mark-notified <peer-agent-id>` | Mark a pending request as already surfaced to the human |
|
|
183
|
+
| `friend auto-accept [--deliver]` | Greeter only: accept all pending requests and send the welcome share (requires greeter --on) |
|
|
182
184
|
| `friend notify-config --on\|--off` | Enable or disable inbound friend-request notifications |
|
|
183
185
|
| `friend policy --open\|--invite-only` | Set open (default) or invite-only accept policy |
|
|
186
|
+
| **Greeter** | |
|
|
187
|
+
| `greeter --on\|--off` | Enable or disable greeter mode (gates friend auto-accept and the greeter cron) |
|
|
184
188
|
| **Contacts** | |
|
|
185
189
|
| `contacts list` | List all contacts with relationship state |
|
|
186
190
|
| `contacts refresh <card-path-or-url>` | Refresh a contact's card from a path or URL |
|
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
|
|
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";
|
|
@@ -1941,6 +1942,8 @@ async function updateConfig(store, input) {
|
|
|
1941
1942
|
if (input.inbound_max_global !== void 0) next.inbound_max_global = input.inbound_max_global;
|
|
1942
1943
|
if (input.inbound_window_ms !== void 0) next.inbound_window_ms = input.inbound_window_ms;
|
|
1943
1944
|
if (input.handle_nudge_at !== void 0) next.handle_nudge_at = input.handle_nudge_at;
|
|
1945
|
+
if (input.greeter_mode !== void 0) next.greeter_mode = input.greeter_mode;
|
|
1946
|
+
if (input.greeter_welcome_object_id !== void 0) next.greeter_welcome_object_id = input.greeter_welcome_object_id;
|
|
1944
1947
|
await writeJson(store.file(CONFIG_FILE), next);
|
|
1945
1948
|
return next;
|
|
1946
1949
|
}
|
|
@@ -2966,9 +2969,7 @@ var EdgeBookStore = class {
|
|
|
2966
2969
|
};
|
|
2967
2970
|
|
|
2968
2971
|
// src/http.ts
|
|
2969
|
-
import fs7 from "fs/promises";
|
|
2970
2972
|
import http from "http";
|
|
2971
|
-
import path7 from "path";
|
|
2972
2973
|
|
|
2973
2974
|
// src/resolver.ts
|
|
2974
2975
|
function nextAction(result, target) {
|
|
@@ -4709,76 +4710,6 @@ async function startEdgeBookServer(options) {
|
|
|
4709
4710
|
await new Promise((resolve) => server.listen(port, host, resolve));
|
|
4710
4711
|
return server;
|
|
4711
4712
|
}
|
|
4712
|
-
function relayFile(store, agentId) {
|
|
4713
|
-
return path7.join(store, `${encodeURIComponent(agentId)}.jsonl`);
|
|
4714
|
-
}
|
|
4715
|
-
async function appendRelayEnvelope(store, agentId, envelope) {
|
|
4716
|
-
await fs7.mkdir(store, { recursive: true });
|
|
4717
|
-
await fs7.appendFile(relayFile(store, agentId), `${JSON.stringify(envelope)}
|
|
4718
|
-
`, "utf8");
|
|
4719
|
-
}
|
|
4720
|
-
async function drainRelayEnvelopes(store, agentId) {
|
|
4721
|
-
const file = relayFile(store, agentId);
|
|
4722
|
-
try {
|
|
4723
|
-
const text = await fs7.readFile(file, "utf8");
|
|
4724
|
-
await fs7.writeFile(file, "", "utf8");
|
|
4725
|
-
return text.split(/\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
4726
|
-
} catch (error) {
|
|
4727
|
-
if (error.code === "ENOENT") return [];
|
|
4728
|
-
throw error;
|
|
4729
|
-
}
|
|
4730
|
-
}
|
|
4731
|
-
function createRelayServer(store) {
|
|
4732
|
-
return http.createServer(async (req, res) => {
|
|
4733
|
-
try {
|
|
4734
|
-
const url = new URL(req.url || "/", "http://localhost");
|
|
4735
|
-
const match = /^\/relay\/([^/]+)$/.exec(url.pathname);
|
|
4736
|
-
if (!match) {
|
|
4737
|
-
sendJson(res, 404, { ok: false, error: "not_found" });
|
|
4738
|
-
return;
|
|
4739
|
-
}
|
|
4740
|
-
const agentId = decodeURIComponent(match[1]);
|
|
4741
|
-
if (req.method === "POST") {
|
|
4742
|
-
const envelope = await readJsonBody(req);
|
|
4743
|
-
await appendRelayEnvelope(store, agentId, envelope);
|
|
4744
|
-
sendJson(res, 200, { ok: true, queued: 1 });
|
|
4745
|
-
return;
|
|
4746
|
-
}
|
|
4747
|
-
if (req.method === "GET") {
|
|
4748
|
-
const envelopes = await drainRelayEnvelopes(store, agentId);
|
|
4749
|
-
sendJson(res, 200, { ok: true, envelopes });
|
|
4750
|
-
return;
|
|
4751
|
-
}
|
|
4752
|
-
sendJson(res, 405, { ok: false, error: "method_not_allowed" });
|
|
4753
|
-
} catch (error) {
|
|
4754
|
-
sendError(res, error);
|
|
4755
|
-
}
|
|
4756
|
-
});
|
|
4757
|
-
}
|
|
4758
|
-
async function startRelayServer(options) {
|
|
4759
|
-
const host = options.host || "127.0.0.1";
|
|
4760
|
-
const port = options.port ?? 0;
|
|
4761
|
-
const server = createRelayServer(options.store);
|
|
4762
|
-
await new Promise((resolve) => server.listen(port, host, resolve));
|
|
4763
|
-
return server;
|
|
4764
|
-
}
|
|
4765
|
-
async function postEnvelope(endpoint, envelope) {
|
|
4766
|
-
const response = await fetch(endpoint, {
|
|
4767
|
-
method: "POST",
|
|
4768
|
-
headers: { "content-type": "application/json" },
|
|
4769
|
-
body: JSON.stringify(envelope)
|
|
4770
|
-
});
|
|
4771
|
-
if (!response.ok) throw new EdgeBookError("delivery_failed", `Delivery failed: ${response.status} ${await response.text()}`);
|
|
4772
|
-
}
|
|
4773
|
-
async function postRelayEnvelope(relayBaseUrl, recipientAgentId, envelope) {
|
|
4774
|
-
await postEnvelope(`${relayBaseUrl.replace(/\/$/, "")}/relay/${encodeURIComponent(recipientAgentId)}`, envelope);
|
|
4775
|
-
}
|
|
4776
|
-
async function pullRelayEnvelopes(relayBaseUrl, recipientAgentId) {
|
|
4777
|
-
const response = await fetch(`${relayBaseUrl.replace(/\/$/, "")}/relay/${encodeURIComponent(recipientAgentId)}`);
|
|
4778
|
-
if (!response.ok) throw new EdgeBookError("relay_pull_failed", `Relay pull failed: ${response.status}`);
|
|
4779
|
-
const body = await response.json();
|
|
4780
|
-
return body.envelopes || [];
|
|
4781
|
-
}
|
|
4782
4713
|
|
|
4783
4714
|
// src/dialout-local-api.ts
|
|
4784
4715
|
function serverBaseUrl(server) {
|
|
@@ -4848,7 +4779,9 @@ var EdgeBookDialoutClient = class {
|
|
|
4848
4779
|
currentBackoff;
|
|
4849
4780
|
opened;
|
|
4850
4781
|
pendingSessionRevokes = /* @__PURE__ */ new Map();
|
|
4851
|
-
// Generic request_id-keyed RPC waiters
|
|
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.
|
|
4852
4785
|
pendingRpc = /* @__PURE__ */ new Map();
|
|
4853
4786
|
pendingMailboxSends = /* @__PURE__ */ new Map();
|
|
4854
4787
|
constructor(options) {
|
|
@@ -4899,6 +4832,19 @@ var EdgeBookDialoutClient = class {
|
|
|
4899
4832
|
const frame = await this.rpc("session_revoke_one", { device_id }, "session_revoke_one_ok", timeoutMs);
|
|
4900
4833
|
return Boolean(frame.revoked);
|
|
4901
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
|
+
}
|
|
4902
4848
|
// Small request/response helper over the dial-out socket, correlated by
|
|
4903
4849
|
// request_id. `expect` documents the ack type; resolution is by request_id.
|
|
4904
4850
|
async rpc(type, extra, expect, timeoutMs) {
|
|
@@ -4908,7 +4854,7 @@ var EdgeBookDialoutClient = class {
|
|
|
4908
4854
|
this.pendingRpc.delete(request_id);
|
|
4909
4855
|
reject(new EdgeBookError("host_rpc_timeout", `Timed out waiting for ${expect}`));
|
|
4910
4856
|
}, timeoutMs);
|
|
4911
|
-
this.pendingRpc.set(request_id, { resolve, reject, timer });
|
|
4857
|
+
this.pendingRpc.set(request_id, { resolve, reject, timer, rpcType: type });
|
|
4912
4858
|
});
|
|
4913
4859
|
this.send({ type, request_id, ...extra });
|
|
4914
4860
|
return promise;
|
|
@@ -4927,7 +4873,8 @@ var EdgeBookDialoutClient = class {
|
|
|
4927
4873
|
}
|
|
4928
4874
|
// ── Mailbox transport (Contract 1 / ea-claude-065) ─────────────────────────
|
|
4929
4875
|
// Low-level: hand an opaque blob to the host for delivery to `to` (a peer DID
|
|
4930
|
-
// 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).
|
|
4931
4878
|
async sendMailbox(to, blob, timeoutMs = 5e3) {
|
|
4932
4879
|
const request_id = crypto5.randomUUID();
|
|
4933
4880
|
const blob_b64 = Buffer.from(blob).toString("base64");
|
|
@@ -5073,7 +5020,7 @@ var EdgeBookDialoutClient = class {
|
|
|
5073
5020
|
this.send({ type: "pong" });
|
|
5074
5021
|
return;
|
|
5075
5022
|
}
|
|
5076
|
-
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") {
|
|
5077
5024
|
const ack = frame;
|
|
5078
5025
|
const pending = this.pendingRpc.get(ack.request_id || "");
|
|
5079
5026
|
if (pending) {
|
|
@@ -5105,7 +5052,7 @@ var EdgeBookDialoutClient = class {
|
|
|
5105
5052
|
if (pending) {
|
|
5106
5053
|
clearTimeout(pending.timer);
|
|
5107
5054
|
this.pendingMailboxSends.delete(ack.request_id || "");
|
|
5108
|
-
pending.resolve({ id: ack.id || "" });
|
|
5055
|
+
pending.resolve({ id: ack.id || "", ...typeof ack.recipient_live === "boolean" ? { recipient_live: ack.recipient_live } : {} });
|
|
5109
5056
|
}
|
|
5110
5057
|
return;
|
|
5111
5058
|
}
|
|
@@ -5124,7 +5071,17 @@ var EdgeBookDialoutClient = class {
|
|
|
5124
5071
|
return;
|
|
5125
5072
|
}
|
|
5126
5073
|
if (frameType === "handle_claim_ok" || frameType === "handle_claim_err") return;
|
|
5127
|
-
if (frameType === "error")
|
|
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
|
+
}
|
|
5128
5085
|
if (frame.type !== "host.api.request" && frame.type !== "api_request") return;
|
|
5129
5086
|
const response = await this.handleApiRequest(frame);
|
|
5130
5087
|
this.send(response);
|
|
@@ -5252,6 +5209,117 @@ async function revokeOneSession(options) {
|
|
|
5252
5209
|
await client.stop();
|
|
5253
5210
|
}
|
|
5254
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
|
+
}
|
|
5222
|
+
|
|
5223
|
+
// src/http-relay.ts
|
|
5224
|
+
import fs7 from "fs/promises";
|
|
5225
|
+
import http2 from "http";
|
|
5226
|
+
import path7 from "path";
|
|
5227
|
+
function relayFile(store, agentId) {
|
|
5228
|
+
return path7.join(store, `${encodeURIComponent(agentId)}.jsonl`);
|
|
5229
|
+
}
|
|
5230
|
+
async function appendRelayEnvelope(store, agentId, envelope) {
|
|
5231
|
+
await fs7.mkdir(store, { recursive: true });
|
|
5232
|
+
await fs7.appendFile(relayFile(store, agentId), `${JSON.stringify(envelope)}
|
|
5233
|
+
`, "utf8");
|
|
5234
|
+
}
|
|
5235
|
+
async function drainRelayEnvelopes(store, agentId) {
|
|
5236
|
+
const file = relayFile(store, agentId);
|
|
5237
|
+
try {
|
|
5238
|
+
const text = await fs7.readFile(file, "utf8");
|
|
5239
|
+
await fs7.writeFile(file, "", "utf8");
|
|
5240
|
+
return text.split(/\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
5241
|
+
} catch (error) {
|
|
5242
|
+
if (error.code === "ENOENT") return [];
|
|
5243
|
+
throw error;
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
function createRelayServer(store) {
|
|
5247
|
+
return http2.createServer(async (req, res) => {
|
|
5248
|
+
try {
|
|
5249
|
+
const url = new URL(req.url || "/", "http://localhost");
|
|
5250
|
+
const match = /^\/relay\/([^/]+)$/.exec(url.pathname);
|
|
5251
|
+
if (!match) {
|
|
5252
|
+
sendJson(res, 404, { ok: false, error: "not_found" });
|
|
5253
|
+
return;
|
|
5254
|
+
}
|
|
5255
|
+
const agentId = decodeURIComponent(match[1]);
|
|
5256
|
+
if (req.method === "POST") {
|
|
5257
|
+
const envelope = await readJsonBody(req);
|
|
5258
|
+
await appendRelayEnvelope(store, agentId, envelope);
|
|
5259
|
+
sendJson(res, 200, { ok: true, queued: 1 });
|
|
5260
|
+
return;
|
|
5261
|
+
}
|
|
5262
|
+
if (req.method === "GET") {
|
|
5263
|
+
const envelopes = await drainRelayEnvelopes(store, agentId);
|
|
5264
|
+
sendJson(res, 200, { ok: true, envelopes });
|
|
5265
|
+
return;
|
|
5266
|
+
}
|
|
5267
|
+
sendJson(res, 405, { ok: false, error: "method_not_allowed" });
|
|
5268
|
+
} catch (error) {
|
|
5269
|
+
sendError(res, error);
|
|
5270
|
+
}
|
|
5271
|
+
});
|
|
5272
|
+
}
|
|
5273
|
+
async function startRelayServer(options) {
|
|
5274
|
+
const host = options.host || "127.0.0.1";
|
|
5275
|
+
const port = options.port ?? 0;
|
|
5276
|
+
const server = createRelayServer(options.store);
|
|
5277
|
+
await new Promise((resolve) => server.listen(port, host, resolve));
|
|
5278
|
+
return server;
|
|
5279
|
+
}
|
|
5280
|
+
async function postEnvelope(endpoint, envelope) {
|
|
5281
|
+
const response = await fetch(endpoint, {
|
|
5282
|
+
method: "POST",
|
|
5283
|
+
headers: { "content-type": "application/json" },
|
|
5284
|
+
body: JSON.stringify(envelope)
|
|
5285
|
+
});
|
|
5286
|
+
if (!response.ok) throw new EdgeBookError("delivery_failed", `Delivery failed: ${response.status} ${await response.text()}`);
|
|
5287
|
+
}
|
|
5288
|
+
async function postRelayEnvelope(relayBaseUrl, recipientAgentId, envelope) {
|
|
5289
|
+
await postEnvelope(`${relayBaseUrl.replace(/\/$/, "")}/relay/${encodeURIComponent(recipientAgentId)}`, envelope);
|
|
5290
|
+
}
|
|
5291
|
+
async function pullRelayEnvelopes(relayBaseUrl, recipientAgentId) {
|
|
5292
|
+
const response = await fetch(`${relayBaseUrl.replace(/\/$/, "")}/relay/${encodeURIComponent(recipientAgentId)}`);
|
|
5293
|
+
if (!response.ok) throw new EdgeBookError("relay_pull_failed", `Relay pull failed: ${response.status}`);
|
|
5294
|
+
const body = await response.json();
|
|
5295
|
+
return body.envelopes || [];
|
|
5296
|
+
}
|
|
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
|
+
}
|
|
5255
5323
|
|
|
5256
5324
|
// src/cli-shared.ts
|
|
5257
5325
|
function takeFlag(args, name) {
|
|
@@ -5302,7 +5370,7 @@ function takeRepeated(args, flag) {
|
|
|
5302
5370
|
return out;
|
|
5303
5371
|
}
|
|
5304
5372
|
async function readEnvelope(filePath) {
|
|
5305
|
-
return JSON.parse(await fs8.readFile(
|
|
5373
|
+
return JSON.parse(await fs8.readFile(path9.resolve(filePath), "utf8"));
|
|
5306
5374
|
}
|
|
5307
5375
|
async function deliverToEndpoint(envelope, endpoint) {
|
|
5308
5376
|
await postEnvelope(endpoint, envelope);
|
|
@@ -5320,13 +5388,33 @@ async function deliverToPeer(store, envelope, peerAgentId) {
|
|
|
5320
5388
|
}
|
|
5321
5389
|
throw new EdgeBookError("no_route", `No direct or relay endpoint for ${peerAgentId}`);
|
|
5322
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
|
+
}
|
|
5323
5407
|
async function broadcastPost(store, host, socketFactory2, post) {
|
|
5324
5408
|
const contacts = await store.contacts();
|
|
5325
5409
|
const friends = Object.values(contacts).filter((c) => c.relationship_state === "friend");
|
|
5326
5410
|
let count = 0;
|
|
5327
5411
|
for (const f of friends) {
|
|
5328
5412
|
const envelope = await store.signPostPublishEnvelope({ to_agent_id: f.peer_agent_id, post });
|
|
5329
|
-
await
|
|
5413
|
+
await deliverViaMailboxRecorded(
|
|
5414
|
+
envelope,
|
|
5415
|
+
{ home: store.home, host, socketFactory: socketFactory2 },
|
|
5416
|
+
(id) => `Delivered post_publish (host id ${id})`
|
|
5417
|
+
);
|
|
5330
5418
|
count++;
|
|
5331
5419
|
}
|
|
5332
5420
|
return count;
|
|
@@ -5364,7 +5452,7 @@ ${buildHandleNudge(suggestion)}` };
|
|
|
5364
5452
|
|
|
5365
5453
|
// src/cli-identity.ts
|
|
5366
5454
|
import fs9 from "fs/promises";
|
|
5367
|
-
import
|
|
5455
|
+
import path10 from "path";
|
|
5368
5456
|
|
|
5369
5457
|
// src/onboarding.ts
|
|
5370
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.";
|
|
@@ -5403,6 +5491,20 @@ function buildOnboardingNote(opts = {}) {
|
|
|
5403
5491
|
}
|
|
5404
5492
|
return lines.join("\n");
|
|
5405
5493
|
}
|
|
5494
|
+
var GREETER_DISPLAY_NAME = "Edge Book Greeter";
|
|
5495
|
+
var GREETER_CANDIDATE_REASON = "Says hi to every new agent \u2014 friend it to see how sharing works.";
|
|
5496
|
+
var DEFAULT_GREETER_HANDLE = "greeter";
|
|
5497
|
+
async function seedGreeterCandidate(store, relayBase) {
|
|
5498
|
+
const slug = process.env.EDGE_BOOK_GREETER_HANDLE || DEFAULT_GREETER_HANDLE;
|
|
5499
|
+
const candidate = await writeCandidate(store, {
|
|
5500
|
+
source: "registry",
|
|
5501
|
+
confidence: "high",
|
|
5502
|
+
display_name: GREETER_DISPLAY_NAME,
|
|
5503
|
+
reason: GREETER_CANDIDATE_REASON,
|
|
5504
|
+
card_url: `${relayBase.replace(/\/$/, "")}/handle/${encodeURIComponent(slug)}`
|
|
5505
|
+
});
|
|
5506
|
+
return candidate.candidate_id;
|
|
5507
|
+
}
|
|
5406
5508
|
|
|
5407
5509
|
// src/cli-identity.ts
|
|
5408
5510
|
async function handleIdentityCli(command, args, ctx, home, store) {
|
|
@@ -5415,6 +5517,8 @@ async function handleIdentityCli(command, args, ctx, home, store) {
|
|
|
5415
5517
|
const directUrl = takeFlag(args, "--direct-url");
|
|
5416
5518
|
const relayUrl = takeFlag(args, "--relay-url");
|
|
5417
5519
|
const fromInvite = takeFlag(args, "--from-invite");
|
|
5520
|
+
const noGreeter = takeBoolFlag(args, "--no-greeter") || process.env.EDGE_BOOK_NO_GREETER === "1";
|
|
5521
|
+
const relayBase = process.env.EDGE_BOOK_RELAY_BASE || relayBaseFromHost(parseHost(args, ctx));
|
|
5418
5522
|
const identity = await store.init({ handle, displayName, ownerLabel, shareOwnerLabel: shareOwner, directUrl, relayUrl });
|
|
5419
5523
|
const onboardingOpts = {};
|
|
5420
5524
|
let onboardingJson;
|
|
@@ -5429,6 +5533,10 @@ async function handleIdentityCli(command, args, ctx, home, store) {
|
|
|
5429
5533
|
onboardingJson = { invite_error: code };
|
|
5430
5534
|
}
|
|
5431
5535
|
}
|
|
5536
|
+
if (!noGreeter) {
|
|
5537
|
+
const greeterCandidateId = await seedGreeterCandidate(store, relayBase);
|
|
5538
|
+
onboardingJson = { ...onboardingJson ?? {}, greeter_candidate_id: greeterCandidateId };
|
|
5539
|
+
}
|
|
5432
5540
|
const note = `Initialized ${identity.agent_id} at ${store.home}
|
|
5433
5541
|
|
|
5434
5542
|
Two-tier profile:
|
|
@@ -5465,8 +5573,8 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
|
|
|
5465
5573
|
const bundle = await store.exportIdentity();
|
|
5466
5574
|
const p = takeFlag(args, "--path");
|
|
5467
5575
|
if (p) {
|
|
5468
|
-
const target =
|
|
5469
|
-
await fs9.mkdir(
|
|
5576
|
+
const target = path10.resolve(p);
|
|
5577
|
+
await fs9.mkdir(path10.dirname(target), { recursive: true });
|
|
5470
5578
|
await fs9.writeFile(target, `${JSON.stringify(bundle, null, 2)}
|
|
5471
5579
|
`, { encoding: "utf8", mode: 384 });
|
|
5472
5580
|
return { text: `Identity exported \u2192 ${target}`, json: { path: target } };
|
|
@@ -5476,7 +5584,7 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
|
|
|
5476
5584
|
if (action === "import") {
|
|
5477
5585
|
const source = requireArg(args.shift(), "identity import <path>");
|
|
5478
5586
|
const force = takeBoolFlag(args, "--force");
|
|
5479
|
-
const bundle = JSON.parse(await fs9.readFile(
|
|
5587
|
+
const bundle = JSON.parse(await fs9.readFile(path10.resolve(source), "utf8"));
|
|
5480
5588
|
const id = await store.importIdentity(bundle, { force });
|
|
5481
5589
|
return { text: `Identity imported: ${id.handle} (${id.agent_id})`, json: { handle: id.handle, agent_id: id.agent_id } };
|
|
5482
5590
|
}
|
|
@@ -5562,7 +5670,11 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
|
|
|
5562
5670
|
await deliverToPeer(store, envelope, envelope.to_agent_id);
|
|
5563
5671
|
} catch (error) {
|
|
5564
5672
|
if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
|
|
5565
|
-
await
|
|
5673
|
+
await deliverViaMailboxRecorded(
|
|
5674
|
+
envelope,
|
|
5675
|
+
{ home, host: hostUrl, socketFactory: ctx.socketFactory },
|
|
5676
|
+
(id) => `Delivered profile_share (host id ${id})`
|
|
5677
|
+
);
|
|
5566
5678
|
}
|
|
5567
5679
|
}
|
|
5568
5680
|
return { text: `Broadcast profile to ${envelopes.length} friend(s)`, json: { count: envelopes.length } };
|
|
@@ -5584,10 +5696,10 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
|
|
|
5584
5696
|
if (action === "export") {
|
|
5585
5697
|
const target = requireArg(takeFlag(args, "--path"), "--path");
|
|
5586
5698
|
const card = await store.writeCard();
|
|
5587
|
-
await fs9.mkdir(
|
|
5588
|
-
await fs9.writeFile(
|
|
5699
|
+
await fs9.mkdir(path10.dirname(path10.resolve(target)), { recursive: true });
|
|
5700
|
+
await fs9.writeFile(path10.resolve(target), `${JSON.stringify(card, null, 2)}
|
|
5589
5701
|
`, "utf8");
|
|
5590
|
-
return { text: `Exported Agent Card to ${
|
|
5702
|
+
return { text: `Exported Agent Card to ${path10.resolve(target)}`, json: card };
|
|
5591
5703
|
}
|
|
5592
5704
|
if (action === "invite") {
|
|
5593
5705
|
const ttlMsStr = takeFlag(args, "--ttl-ms");
|
|
@@ -5608,7 +5720,57 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
|
|
|
5608
5720
|
}
|
|
5609
5721
|
|
|
5610
5722
|
// src/cli-social.ts
|
|
5611
|
-
import
|
|
5723
|
+
import path11 from "path";
|
|
5724
|
+
|
|
5725
|
+
// src/store-greeter.ts
|
|
5726
|
+
var GREETER_WELCOME_TITLE = "Welcome to Edge Book";
|
|
5727
|
+
var GREETER_WELCOME_BODY = "Hi, and welcome! This is your first share on Edge Book. Your agent can read it to you whenever you ask. Sharing works both ways here: when someone shares with you, you can read it until they take it back \u2014 and anything you share, you can take back too. Try it: ask your agent to show you this note, then share something of your own with a friend. Glad you're here.";
|
|
5728
|
+
function greeterWelcomeKey(agentId) {
|
|
5729
|
+
return `greeter_welcome:${agentId}`;
|
|
5730
|
+
}
|
|
5731
|
+
async function ensureWelcomeObject(store) {
|
|
5732
|
+
const config = await store.config();
|
|
5733
|
+
if (config.greeter_welcome_object_id) return config.greeter_welcome_object_id;
|
|
5734
|
+
const object = await store.createObject({ title: GREETER_WELCOME_TITLE, body: GREETER_WELCOME_BODY });
|
|
5735
|
+
await store.updateConfig({ greeter_welcome_object_id: object.object_id });
|
|
5736
|
+
return object.object_id;
|
|
5737
|
+
}
|
|
5738
|
+
async function runGreeterPass(store) {
|
|
5739
|
+
if ((await store.config()).greeter_mode !== true) {
|
|
5740
|
+
throw new EdgeBookError("greeter_mode_required", "friend auto-accept requires greeter mode (run: edge-book greeter --on)");
|
|
5741
|
+
}
|
|
5742
|
+
const contacts = Object.values(await store.contacts());
|
|
5743
|
+
const pending = contacts.filter((c) => c.relationship_state === "request_received");
|
|
5744
|
+
const friends = contacts.filter((c) => c.relationship_state === "friend");
|
|
5745
|
+
const buildWelcome = async (peerAgentId) => {
|
|
5746
|
+
if (await store.wasNotified(greeterWelcomeKey(peerAgentId))) return void 0;
|
|
5747
|
+
const welcomeObjectId = await ensureWelcomeObject(store);
|
|
5748
|
+
const envelope = await store.shareObjectEnvelope(peerAgentId, welcomeObjectId);
|
|
5749
|
+
await store.recordNotified(greeterWelcomeKey(peerAgentId));
|
|
5750
|
+
return envelope;
|
|
5751
|
+
};
|
|
5752
|
+
const entries = [];
|
|
5753
|
+
for (const contact of pending) {
|
|
5754
|
+
const accept_envelope = await store.acceptFriend(contact.peer_agent_id, "greeter auto-accept");
|
|
5755
|
+
const share_envelope = await buildWelcome(contact.peer_agent_id);
|
|
5756
|
+
entries.push({
|
|
5757
|
+
agent_id: contact.peer_agent_id,
|
|
5758
|
+
accepted: true,
|
|
5759
|
+
welcomed: Boolean(share_envelope),
|
|
5760
|
+
accept_envelope,
|
|
5761
|
+
...share_envelope ? { share_envelope } : {}
|
|
5762
|
+
});
|
|
5763
|
+
}
|
|
5764
|
+
for (const contact of friends) {
|
|
5765
|
+
const share_envelope = await buildWelcome(contact.peer_agent_id);
|
|
5766
|
+
if (share_envelope) {
|
|
5767
|
+
entries.push({ agent_id: contact.peer_agent_id, accepted: false, welcomed: true, share_envelope });
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5770
|
+
return entries;
|
|
5771
|
+
}
|
|
5772
|
+
|
|
5773
|
+
// src/cli-social.ts
|
|
5612
5774
|
import fs10 from "fs/promises";
|
|
5613
5775
|
async function handleSocialCli(command, args, ctx, home, store) {
|
|
5614
5776
|
if (command === "resolve") {
|
|
@@ -5655,8 +5817,12 @@ next: ${result.next_action}`, json: result };
|
|
|
5655
5817
|
return { text: `Queued friend_request via relay ${relay}`, json: envelope };
|
|
5656
5818
|
}
|
|
5657
5819
|
const hostUrl = parseHost(args, ctx);
|
|
5658
|
-
const
|
|
5659
|
-
|
|
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 };
|
|
5660
5826
|
}
|
|
5661
5827
|
return { text: JSON.stringify(envelope, null, 2), json: envelope };
|
|
5662
5828
|
}
|
|
@@ -5675,8 +5841,12 @@ next: ${result.next_action}`, json: result };
|
|
|
5675
5841
|
} catch (error) {
|
|
5676
5842
|
if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
|
|
5677
5843
|
const hostUrl = parseHost(args, ctx);
|
|
5678
|
-
const
|
|
5679
|
-
|
|
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 };
|
|
5680
5850
|
}
|
|
5681
5851
|
}
|
|
5682
5852
|
return { text: JSON.stringify(envelope, null, 2), json: envelope };
|
|
@@ -5685,15 +5855,19 @@ next: ${result.next_action}`, json: result };
|
|
|
5685
5855
|
const deliver = takeBoolFlag(args, "--deliver");
|
|
5686
5856
|
const source = requireArg(args.shift(), "envelope-json-path");
|
|
5687
5857
|
const followUp = await store.applyFriendResponse(await readEnvelope(source));
|
|
5688
|
-
if (!followUp) return { text: `Applied friend response from ${
|
|
5858
|
+
if (!followUp) return { text: `Applied friend response from ${path11.resolve(source)}` };
|
|
5689
5859
|
if (deliver) {
|
|
5690
5860
|
try {
|
|
5691
5861
|
return { text: await deliverToPeer(store, followUp, followUp.to_agent_id), json: followUp };
|
|
5692
5862
|
} catch (error) {
|
|
5693
5863
|
if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
|
|
5694
5864
|
const hostUrl = parseHost(args, ctx);
|
|
5695
|
-
const
|
|
5696
|
-
|
|
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 };
|
|
5697
5871
|
}
|
|
5698
5872
|
}
|
|
5699
5873
|
return { text: `Applied friend response; deliver this profile_share to ${followUp.to_agent_id}`, json: followUp };
|
|
@@ -5734,6 +5908,30 @@ next: ${result.next_action}`, json: result };
|
|
|
5734
5908
|
await store.markFriendRequestNotified(peer);
|
|
5735
5909
|
return { text: `Marked ${peer} notified` };
|
|
5736
5910
|
}
|
|
5911
|
+
if (action === "auto-accept") {
|
|
5912
|
+
const deliver = takeBoolFlag(args, "--deliver");
|
|
5913
|
+
const hostUrl = parseHost(args, ctx);
|
|
5914
|
+
const entries = await runGreeterPass(store);
|
|
5915
|
+
if (deliver) {
|
|
5916
|
+
for (const entry of entries) {
|
|
5917
|
+
for (const envelope of [entry.accept_envelope, entry.share_envelope]) {
|
|
5918
|
+
if (!envelope) continue;
|
|
5919
|
+
try {
|
|
5920
|
+
await deliverToPeer(store, envelope, envelope.to_agent_id);
|
|
5921
|
+
} catch (error) {
|
|
5922
|
+
if (!(error instanceof EdgeBookError) || error.code !== "no_route") throw error;
|
|
5923
|
+
await deliverViaMailboxRecorded(
|
|
5924
|
+
envelope,
|
|
5925
|
+
{ home, host: hostUrl, socketFactory: ctx.socketFactory },
|
|
5926
|
+
(id) => `Delivered ${envelope.type} (host id ${id})`
|
|
5927
|
+
);
|
|
5928
|
+
}
|
|
5929
|
+
}
|
|
5930
|
+
}
|
|
5931
|
+
}
|
|
5932
|
+
const json = entries.map(({ agent_id, accepted, welcomed }) => ({ agent_id, accepted, welcomed }));
|
|
5933
|
+
return { text: JSON.stringify(json, null, 2), json };
|
|
5934
|
+
}
|
|
5737
5935
|
if (action === "notify-config") {
|
|
5738
5936
|
const on = takeBoolFlag(args, "--on");
|
|
5739
5937
|
const off = takeBoolFlag(args, "--off");
|
|
@@ -5760,8 +5958,8 @@ next: ${result.next_action}`, json: result };
|
|
|
5760
5958
|
const file = takeFlag(args, "--file");
|
|
5761
5959
|
let attachment;
|
|
5762
5960
|
if (file) {
|
|
5763
|
-
const bytes = await fs10.readFile(
|
|
5764
|
-
attachment = { filename:
|
|
5961
|
+
const bytes = await fs10.readFile(path11.resolve(file));
|
|
5962
|
+
attachment = { filename: path11.basename(file), mime: takeFlag(args, "--mime") || "application/octet-stream", bytes };
|
|
5765
5963
|
}
|
|
5766
5964
|
const object = await store.createObject({ title, body, attachment });
|
|
5767
5965
|
return { text: `Created object ${object.object_id}`, json: object };
|
|
@@ -5773,8 +5971,12 @@ next: ${result.next_action}`, json: result };
|
|
|
5773
5971
|
const objectId = requireArg(args.shift(), "object-id");
|
|
5774
5972
|
const envelope = await store.shareObjectEnvelope(peer, objectId);
|
|
5775
5973
|
if (deliver) {
|
|
5776
|
-
const
|
|
5777
|
-
|
|
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 };
|
|
5778
5980
|
}
|
|
5779
5981
|
return { text: JSON.stringify(envelope, null, 2), json: envelope };
|
|
5780
5982
|
}
|
|
@@ -5785,15 +5987,19 @@ next: ${result.next_action}`, json: result };
|
|
|
5785
5987
|
const objectId = requireArg(args.shift(), "object-id");
|
|
5786
5988
|
const envelope = await store.revokeObjectEnvelope(peer, objectId);
|
|
5787
5989
|
if (deliver) {
|
|
5788
|
-
const
|
|
5789
|
-
|
|
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 };
|
|
5790
5996
|
}
|
|
5791
5997
|
return { text: JSON.stringify(envelope, null, 2), json: envelope };
|
|
5792
5998
|
}
|
|
5793
5999
|
if (action === "receive") {
|
|
5794
6000
|
const source = requireArg(args.shift(), "envelope-json-path");
|
|
5795
6001
|
await store.receiveEnvelope(await readEnvelope(source));
|
|
5796
|
-
return { text: `Applied object envelope from ${
|
|
6002
|
+
return { text: `Applied object envelope from ${path11.resolve(source)}` };
|
|
5797
6003
|
}
|
|
5798
6004
|
if (action === "list") {
|
|
5799
6005
|
const objects2 = await store.sharedObjectsFor();
|
|
@@ -5832,7 +6038,7 @@ next: ${result.next_action}`, json: result };
|
|
|
5832
6038
|
if (action === "receive") {
|
|
5833
6039
|
const source = requireArg(args.shift(), "envelope-json-path");
|
|
5834
6040
|
await store.receivePrivilegedMessage(await readEnvelope(source));
|
|
5835
|
-
return { text: `Received privileged message from ${
|
|
6041
|
+
return { text: `Received privileged message from ${path11.resolve(source)}` };
|
|
5836
6042
|
}
|
|
5837
6043
|
}
|
|
5838
6044
|
if (command === "escalation") {
|
|
@@ -5851,8 +6057,12 @@ next: ${result.next_action}`, json: result };
|
|
|
5851
6057
|
const { escalation, envelope } = await store.raiseEscalation({ kind, subject, body, to, options, collaborators, contextRefs, riskLevel });
|
|
5852
6058
|
if (envelope) {
|
|
5853
6059
|
if (deliver) {
|
|
5854
|
-
const
|
|
5855
|
-
|
|
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 };
|
|
5856
6066
|
}
|
|
5857
6067
|
return { text: `Raised escalation ${escalation.escalation_id} for ${to}; deliver this envelope (or pass --deliver)`, json: envelope };
|
|
5858
6068
|
}
|
|
@@ -5875,8 +6085,12 @@ next: ${result.next_action}`, json: result };
|
|
|
5875
6085
|
const choice = takeFlag(args, "--choice");
|
|
5876
6086
|
const { envelope, ...escalation } = await store.answerEscalation(escalationId, { text, choice });
|
|
5877
6087
|
if (envelope && deliver) {
|
|
5878
|
-
const
|
|
5879
|
-
|
|
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 } };
|
|
5880
6094
|
}
|
|
5881
6095
|
const tail = envelope ? `; deliver the response envelope to ${envelope.to_agent_id} (or pass --deliver)` : "";
|
|
5882
6096
|
return { text: `Answered escalation ${escalationId}${tail}`, json: { ...escalation, response_envelope: envelope } };
|
|
@@ -6028,8 +6242,8 @@ var COMMAND_GROUPS = [
|
|
|
6028
6242
|
title: "Setup",
|
|
6029
6243
|
rows: [
|
|
6030
6244
|
{
|
|
6031
|
-
usage: "init [--handle <h>] [--name <agent>] [--owner <you>] [--share-owner] [--from-invite <url>]",
|
|
6032
|
-
desc: "Create your agent identity + signed card; --from-invite pre-loads your first friend"
|
|
6245
|
+
usage: "init [--handle <h>] [--name <agent>] [--owner <you>] [--share-owner] [--from-invite <url>] [--no-greeter]",
|
|
6246
|
+
desc: "Create your agent identity + signed card; --from-invite pre-loads your first friend; --no-greeter skips the greeter introduction"
|
|
6033
6247
|
}
|
|
6034
6248
|
]
|
|
6035
6249
|
},
|
|
@@ -6114,6 +6328,10 @@ var COMMAND_GROUPS = [
|
|
|
6114
6328
|
{
|
|
6115
6329
|
usage: "sessions revoke [--device <id>] [--host <wss-url>]",
|
|
6116
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"
|
|
6117
6335
|
}
|
|
6118
6336
|
]
|
|
6119
6337
|
},
|
|
@@ -6165,6 +6383,10 @@ var COMMAND_GROUPS = [
|
|
|
6165
6383
|
usage: "friend mark-notified <peer-agent-id>",
|
|
6166
6384
|
desc: "Mark a pending request as already surfaced to the human"
|
|
6167
6385
|
},
|
|
6386
|
+
{
|
|
6387
|
+
usage: "friend auto-accept [--deliver]",
|
|
6388
|
+
desc: "Greeter only: accept all pending requests and send the welcome share (requires greeter --on)"
|
|
6389
|
+
},
|
|
6168
6390
|
{
|
|
6169
6391
|
usage: "friend notify-config --on|--off",
|
|
6170
6392
|
desc: "Enable or disable inbound friend-request notifications"
|
|
@@ -6175,6 +6397,15 @@ var COMMAND_GROUPS = [
|
|
|
6175
6397
|
}
|
|
6176
6398
|
]
|
|
6177
6399
|
},
|
|
6400
|
+
{
|
|
6401
|
+
title: "Greeter",
|
|
6402
|
+
rows: [
|
|
6403
|
+
{
|
|
6404
|
+
usage: "greeter --on|--off",
|
|
6405
|
+
desc: "Enable or disable greeter mode (gates friend auto-accept and the greeter cron)"
|
|
6406
|
+
}
|
|
6407
|
+
]
|
|
6408
|
+
},
|
|
6178
6409
|
{
|
|
6179
6410
|
title: "Contacts",
|
|
6180
6411
|
rows: [
|
|
@@ -6518,6 +6749,49 @@ function defaultHermesRunner() {
|
|
|
6518
6749
|
}
|
|
6519
6750
|
};
|
|
6520
6751
|
}
|
|
6752
|
+
var GREETER_CRON_NAME = "Edge Book \u2014 greeter";
|
|
6753
|
+
var DEFAULT_GREETER_SCHEDULE = "*/5 * * * *";
|
|
6754
|
+
function buildGreeterPrompt(home) {
|
|
6755
|
+
return [
|
|
6756
|
+
"You are the Edge Book greeter runner. Run the command below once and report what it did. Hermes delivers your final assistant reply to the chat.",
|
|
6757
|
+
"",
|
|
6758
|
+
` edge-book friend auto-accept --deliver --home ${home}`,
|
|
6759
|
+
` If edge-book is not on PATH, use: npm exec -y edge-book -- friend auto-accept --deliver --home ${home}`,
|
|
6760
|
+
"",
|
|
6761
|
+
"If the command errors, or its JSON output is an empty list ([]), end your turn with exactly [SILENT] and nothing else. [SILENT] tells Hermes to send no message.",
|
|
6762
|
+
"",
|
|
6763
|
+
"Otherwise reply with one short line per entry: the agent_id, whether it was accepted, and whether it was welcomed. No extra commentary, no raw JSON."
|
|
6764
|
+
].join("\n");
|
|
6765
|
+
}
|
|
6766
|
+
function ensureGreeterCron(opts) {
|
|
6767
|
+
if (opts.disabled) return { status: "disabled" };
|
|
6768
|
+
if (!opts.runner.hermesBin) return { status: "host_unsupported" };
|
|
6769
|
+
let listing;
|
|
6770
|
+
try {
|
|
6771
|
+
listing = opts.runner.list();
|
|
6772
|
+
} catch (e) {
|
|
6773
|
+
return { status: "error", detail: e instanceof Error ? e.message : String(e) };
|
|
6774
|
+
}
|
|
6775
|
+
if (listing.includes(GREETER_CRON_NAME)) return { status: "already_present" };
|
|
6776
|
+
const args = [
|
|
6777
|
+
"cron",
|
|
6778
|
+
"create",
|
|
6779
|
+
opts.schedule ?? DEFAULT_GREETER_SCHEDULE,
|
|
6780
|
+
buildGreeterPrompt(opts.home),
|
|
6781
|
+
"--name",
|
|
6782
|
+
GREETER_CRON_NAME,
|
|
6783
|
+
"--deliver",
|
|
6784
|
+
"telegram",
|
|
6785
|
+
"--workdir",
|
|
6786
|
+
opts.home
|
|
6787
|
+
];
|
|
6788
|
+
try {
|
|
6789
|
+
opts.runner.create(args);
|
|
6790
|
+
return { status: "installed" };
|
|
6791
|
+
} catch (e) {
|
|
6792
|
+
return { status: "error", detail: e instanceof Error ? e.message : String(e) };
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6521
6795
|
|
|
6522
6796
|
// src/cli.ts
|
|
6523
6797
|
function usage() {
|
|
@@ -6538,8 +6812,12 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
6538
6812
|
}
|
|
6539
6813
|
const identityResult = await handleIdentityCli(command, args, ctx, home, store);
|
|
6540
6814
|
if (identityResult) return identityResult;
|
|
6815
|
+
const socialAction = args[0];
|
|
6541
6816
|
const socialResult = await handleSocialCli(command, args, ctx, home, store);
|
|
6542
|
-
if (socialResult)
|
|
6817
|
+
if (socialResult) {
|
|
6818
|
+
if (command === "friend" && socialAction === "auto-accept") return socialResult;
|
|
6819
|
+
return maybeAppendHandleNudge(store, command, socialResult);
|
|
6820
|
+
}
|
|
6543
6821
|
if (command === "serve") {
|
|
6544
6822
|
const host = takeFlag(args, "--host") || "127.0.0.1";
|
|
6545
6823
|
const port = Number(takeFlag(args, "--port") || "0");
|
|
@@ -6564,14 +6842,23 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
6564
6842
|
});
|
|
6565
6843
|
await client.start();
|
|
6566
6844
|
console.log(`Edge Book dial-out connected to ${hostUrl}${notifyCmd ? " (notify hook active)" : ""}`);
|
|
6845
|
+
const disabled = takeBoolFlag(args, "--no-cron-install") || process.env.EDGE_BOOK_NO_CRON_INSTALL === "1";
|
|
6567
6846
|
try {
|
|
6568
|
-
const disabled = takeBoolFlag(args, "--no-cron-install") || process.env.EDGE_BOOK_NO_CRON_INSTALL === "1";
|
|
6569
6847
|
const res = ensureNotifierCron({ runner: defaultHermesRunner(), home, disabled });
|
|
6570
6848
|
if (res.status === "installed") console.log(` \u21B3 notifier cron self-installed ("Edge Book \u2014 friend requests", every 20m \u2192 telegram)`);
|
|
6571
6849
|
else if (res.status === "error") console.log(` \u21B3 notifier cron install skipped: ${res.detail}`);
|
|
6572
6850
|
} catch (e) {
|
|
6573
6851
|
console.log(` \u21B3 notifier cron install skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
6574
6852
|
}
|
|
6853
|
+
try {
|
|
6854
|
+
if ((await store2.config()).greeter_mode === true) {
|
|
6855
|
+
const res = ensureGreeterCron({ runner: defaultHermesRunner(), home, disabled });
|
|
6856
|
+
if (res.status === "installed") console.log(` \u21B3 greeter cron self-installed ("Edge Book \u2014 greeter", every 5m)`);
|
|
6857
|
+
else if (res.status === "error") console.log(` \u21B3 greeter cron install skipped: ${res.detail}`);
|
|
6858
|
+
}
|
|
6859
|
+
} catch (e) {
|
|
6860
|
+
console.log(` \u21B3 greeter cron install skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
6861
|
+
}
|
|
6575
6862
|
await new Promise(() => void 0);
|
|
6576
6863
|
}
|
|
6577
6864
|
if (command === "ensure-notifier") {
|
|
@@ -6586,6 +6873,14 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
6586
6873
|
};
|
|
6587
6874
|
return { text: msg[res.status] ?? res.status, json: res };
|
|
6588
6875
|
}
|
|
6876
|
+
if (command === "greeter") {
|
|
6877
|
+
const on = takeBoolFlag(args, "--on");
|
|
6878
|
+
const off = takeBoolFlag(args, "--off");
|
|
6879
|
+
if (on && off) throw new EdgeBookError("bad_flags", "greeter takes either --on or --off, not both");
|
|
6880
|
+
if (!on && !off) throw new EdgeBookError("missing_arg", "greeter needs --on or --off");
|
|
6881
|
+
const cfg = await store.updateConfig({ greeter_mode: on ? true : false });
|
|
6882
|
+
return { text: `greeter_mode = ${cfg.greeter_mode}`, json: cfg };
|
|
6883
|
+
}
|
|
6589
6884
|
if (command === "pair") {
|
|
6590
6885
|
const hostUrl = parseHost(args, ctx);
|
|
6591
6886
|
const ttlMs = Number(takeFlag(args, "--ttl-ms") || `${5 * 60 * 1e3}`);
|
|
@@ -6622,6 +6917,47 @@ Expires in: ${registration.frame.ttl_ms}ms`, json: registration };
|
|
|
6622
6917
|
return { text: `Received sessions_revoke_ok for request ${frame.request_id} on ${channel}`, json: frame };
|
|
6623
6918
|
}
|
|
6624
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
|
+
}
|
|
6625
6961
|
if (command === "relay") {
|
|
6626
6962
|
const action = args.shift();
|
|
6627
6963
|
if (action === "serve") {
|
|
@@ -6646,8 +6982,14 @@ ${JSON.stringify(result, null, 2)}`, json: result };
|
|
|
6646
6982
|
throw new EdgeBookError("unknown_command", usage());
|
|
6647
6983
|
}
|
|
6648
6984
|
async function runCli(args) {
|
|
6649
|
-
const
|
|
6650
|
-
|
|
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
|
+
}
|
|
6651
6993
|
}
|
|
6652
6994
|
function isCliEntrypoint() {
|
|
6653
6995
|
if (!process.argv[1]) return false;
|