edge-book 0.4.0 → 0.6.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.
Files changed (2) hide show
  1. package/dist/edge-book.js +180 -4
  2. package/package.json +1 -1
package/dist/edge-book.js CHANGED
@@ -54,6 +54,7 @@ var SIGNALS_FILE = "signals.json";
54
54
  var CAPABILITIES_FILE = "capabilities.json";
55
55
  var EPHEMERAL_FILE = "ephemeral-posts.json";
56
56
  var ANSWERS_FILE = "answers.json";
57
+ var RECEIVED_POSTS_FILE = "received-posts.json";
57
58
  var DEFAULT_SIGNAL_TTL_MS = 6 * 60 * 60 * 1e3;
58
59
  var DEFAULT_EPHEMERAL_TTL_MS = 24 * 60 * 60 * 1e3;
59
60
  function resolveHome(home) {
@@ -234,6 +235,7 @@ var EdgeBookStore = class {
234
235
  const transports = [{ mode: "local", endpoint: this.home }];
235
236
  if (config.direct_url) transports.push({ mode: "direct", endpoint: config.direct_url });
236
237
  if (config.relay_url) transports.push({ mode: "relay", endpoint: config.relay_url });
238
+ const caps = Object.values(await this.capabilities()).map((c) => ({ name: c.name, version: c.version, summary: c.summary, status: c.status }));
237
239
  const unsigned = {
238
240
  schema: "openclaw-agent-card/0.1",
239
241
  agent_id: identity.agent_id,
@@ -245,6 +247,7 @@ var EdgeBookStore = class {
245
247
  card_version: 1,
246
248
  public_keys: [{ id: `${identity.agent_id}#main`, type: "ed25519", public_key_pem: identity.public_key_pem }],
247
249
  capabilities: ["friend_request", "friend_gated_message", "feed_read_friends"],
250
+ ...caps.length ? { advertised_capabilities: caps } : {},
248
251
  transports,
249
252
  refresh_after: new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString(),
250
253
  expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString()
@@ -326,6 +329,7 @@ var EdgeBookStore = class {
326
329
  // Carry the peer's shared human name (undefined if they didn't opt in, or
327
330
  // dropped on refresh if they turned sharing off).
328
331
  owner_label: card.owner_label,
332
+ advertised_capabilities: card.advertised_capabilities,
329
333
  card_url: card.card_url,
330
334
  known_endpoints: card.transports,
331
335
  public_keys: card.public_keys,
@@ -669,6 +673,18 @@ var EdgeBookStore = class {
669
673
  const { signature, lifecycle: _lc, ...signedPayload } = sig;
670
674
  return verifyPayload(signedPayload, signature, pub);
671
675
  }
676
+ // Verify an Endorsement signature. Endorsements have no lifecycle field.
677
+ async verifyEndorsement(e) {
678
+ const identity = await this.identity();
679
+ let pub = identity.agent_id === e.endorser_agent_id ? identity.public_key_pem : void 0;
680
+ if (!pub) {
681
+ const c = (await this.contacts())[e.endorser_agent_id];
682
+ pub = c?.public_keys?.[0]?.public_key_pem;
683
+ }
684
+ if (!pub) return false;
685
+ const { signature, ...rest } = e;
686
+ return verifyPayload(rest, signature, pub);
687
+ }
672
688
  // Class 3: Endorse — actor-owned reified edge, strongRef parent, evidence link (R5, R8)
673
689
  async endorsements() {
674
690
  return readJson(this.file(ENDORSEMENTS_FILE), {});
@@ -1116,6 +1132,123 @@ var EdgeBookStore = class {
1116
1132
  throw new EdgeBookError("invalid_grant_signature", "Grant signature does not verify against the issuer key");
1117
1133
  }
1118
1134
  }
1135
+ // ─── Received posts (peer posts delivered via mailbox) ──────────────────────
1136
+ async receivedPosts() {
1137
+ return readJson(this.file(RECEIVED_POSTS_FILE), {});
1138
+ }
1139
+ async saveReceivedPosts(posts) {
1140
+ await writeJson(this.file(RECEIVED_POSTS_FILE), posts);
1141
+ }
1142
+ /** Grouped view for `/api/received` and the reader. */
1143
+ async receivedByCategory() {
1144
+ const all = await this.receivedPosts();
1145
+ const out = {
1146
+ signals: {},
1147
+ ephemeral: {},
1148
+ answers: {},
1149
+ endorsements: {}
1150
+ };
1151
+ for (const id of Object.keys(all)) {
1152
+ const p = all[id];
1153
+ if (p.post_type === "signal") out.signals[id] = p;
1154
+ else if (p.post_type === "answer") out.answers[id] = p;
1155
+ else if (p.post_type === "endorse") out.endorsements[id] = p;
1156
+ else out.ephemeral[id] = p;
1157
+ }
1158
+ return out;
1159
+ }
1160
+ async verifyReceivedPost(p) {
1161
+ switch (p.post_type) {
1162
+ case "signal":
1163
+ return this.verifySignal(p);
1164
+ case "answer":
1165
+ return this.verifyAnswer(p);
1166
+ case "endorse":
1167
+ return this.verifyEndorsement(p);
1168
+ case "query":
1169
+ case "share":
1170
+ case "coordinate":
1171
+ case "delegation_request":
1172
+ return this.verifyEphemeral(p);
1173
+ default:
1174
+ return false;
1175
+ }
1176
+ }
1177
+ receivedPostId(p) {
1178
+ return p.signal_id || p.post_id || p.answer_id || p.endorse_id || "";
1179
+ }
1180
+ receivedPostAuthor(p) {
1181
+ switch (p.post_type) {
1182
+ case "answer":
1183
+ return p.answerer_agent_id ?? "";
1184
+ case "endorse":
1185
+ return p.endorser_agent_id ?? "";
1186
+ case "signal":
1187
+ case "query":
1188
+ case "share":
1189
+ case "coordinate":
1190
+ case "delegation_request":
1191
+ return p.from_agent ?? "";
1192
+ default:
1193
+ return "";
1194
+ }
1195
+ }
1196
+ /**
1197
+ * Receive a `post_publish` envelope from a friend.
1198
+ * Security order:
1199
+ * 1. verifyEnvelope (recipient/expiry/replay/sender-key + envelope sig)
1200
+ * 2. type guard: must be "post_publish"
1201
+ * 3. sender must be a known contact with relationship_state === "friend"
1202
+ * 4. inner post author must match envelope.from_agent_id
1203
+ * 5. inner post signature must verify
1204
+ * Only then store.
1205
+ */
1206
+ async receivePostPublish(envelope) {
1207
+ await this.verifyEnvelope(envelope);
1208
+ if (envelope.type !== "post_publish") {
1209
+ throw new EdgeBookError("wrong_message_type", "Expected post_publish envelope");
1210
+ }
1211
+ const contact = (await this.contacts())[envelope.from_agent_id];
1212
+ if (!contact || contact.relationship_state !== "friend") {
1213
+ throw new EdgeBookError("not_friend", "post_publish only accepted from friends");
1214
+ }
1215
+ const post = envelope.body.post;
1216
+ if (!post || !post.post_type) {
1217
+ throw new EdgeBookError("malformed_post_publish", "missing or malformed post in envelope body");
1218
+ }
1219
+ if (this.receivedPostAuthor(post) !== envelope.from_agent_id) {
1220
+ throw new EdgeBookError("author_mismatch", "post author does not match envelope sender");
1221
+ }
1222
+ const id = this.receivedPostId(post);
1223
+ if (!id) {
1224
+ throw new EdgeBookError("malformed_post_publish", "post missing id");
1225
+ }
1226
+ if (!await this.verifyReceivedPost(post)) {
1227
+ throw new EdgeBookError("invalid_signature", "inner post signature invalid");
1228
+ }
1229
+ const all = await this.receivedPosts();
1230
+ const key = envelope.from_agent_id + ":" + id;
1231
+ all[key] = post;
1232
+ await this.saveReceivedPosts(all);
1233
+ await this.audit("post.receive", envelope.from_agent_id, {
1234
+ post_type: post.post_type,
1235
+ id
1236
+ });
1237
+ return post;
1238
+ }
1239
+ /** Build a signed `post_publish` envelope wrapping any post type. */
1240
+ async signPostPublishEnvelope(input) {
1241
+ const identity = await this.identity();
1242
+ return this.signEnvelope({
1243
+ type: "post_publish",
1244
+ to_agent_id: input.to_agent_id,
1245
+ relationship_id: relationshipId(identity.agent_id, input.to_agent_id),
1246
+ capability_id: "",
1247
+ ref: "",
1248
+ transport: "direct",
1249
+ body: { post: input.post }
1250
+ });
1251
+ }
1119
1252
  async signEnvelope(input) {
1120
1253
  const identity = await this.identity();
1121
1254
  const unsigned = {
@@ -1165,6 +1298,10 @@ var EdgeBookStore = class {
1165
1298
  await this.receiveObjectRevoke(envelope);
1166
1299
  return;
1167
1300
  }
1301
+ if (envelope.type === "post_publish") {
1302
+ await this.receivePostPublish(envelope);
1303
+ return;
1304
+ }
1168
1305
  throw new EdgeBookError("unsupported_envelope", `Unsupported envelope type: ${envelope.type}`);
1169
1306
  }
1170
1307
  async audit(action, peerAgentId, details) {
@@ -1771,6 +1908,10 @@ async function handleOwnerApi(req, res, url, adapters) {
1771
1908
  sendJson(res, 200, { ephemeral: await store.ephemeralPosts() });
1772
1909
  return true;
1773
1910
  }
1911
+ if (req.method === "GET" && url.pathname === "/api/received") {
1912
+ sendJson(res, 200, await store.receivedByCategory());
1913
+ return true;
1914
+ }
1774
1915
  if (req.method === "GET" && url.pathname === "/api/answers") {
1775
1916
  sendJson(res, 200, { answers: await store.answers() });
1776
1917
  return true;
@@ -3694,6 +3835,17 @@ async function deliverToPeer(store, envelope, peerAgentId) {
3694
3835
  }
3695
3836
  throw new EdgeBookError("no_route", `No direct or relay endpoint for ${peerAgentId}`);
3696
3837
  }
3838
+ async function broadcastPost(store, host, socketFactory2, post) {
3839
+ const contacts = await store.contacts();
3840
+ const friends = Object.values(contacts).filter((c) => c.relationship_state === "friend");
3841
+ let count = 0;
3842
+ for (const f of friends) {
3843
+ const envelope = await store.signPostPublishEnvelope({ to_agent_id: f.peer_agent_id, post });
3844
+ await deliverEnvelopeViaMailbox({ home: store.home, host, socketFactory: socketFactory2, envelope });
3845
+ count++;
3846
+ }
3847
+ return count;
3848
+ }
3697
3849
  function serverAddress(server) {
3698
3850
  const address = server.address();
3699
3851
  if (!address || typeof address === "string") return String(address);
@@ -4004,22 +4156,34 @@ ${JSON.stringify(result, null, 2)}`, json: result };
4004
4156
  return { text: `Attestation ${id.attestation_id}`, json: id };
4005
4157
  }
4006
4158
  if (command === "endorse") {
4159
+ const deliver = takeBoolFlag(args, "--deliver");
4160
+ const hostUrl = parseHost(args, ctx);
4007
4161
  const subject = requireArg(args.shift(), "<subject-agent-id>");
4008
4162
  const evAtt = takeFlag(args, "--evidence-attestation");
4009
4163
  const evTask = takeFlag(args, "--evidence-task");
4010
- const id = await store.createEndorsement({
4164
+ const post = await store.createEndorsement({
4011
4165
  subject_agent_id: subject,
4012
4166
  parent: { uri: requireArg(takeFlag(args, "--parent-uri"), "--parent-uri"), hash: requireArg(takeFlag(args, "--parent-hash"), "--parent-hash") },
4013
4167
  ...evAtt ? { evidence_ref: { uri: `edgebook:attestation:${evAtt}`, hash: evAtt } } : {},
4014
4168
  ...evTask ? { evidence_task_id: evTask } : {},
4015
4169
  statement: requireArg(takeFlag(args, "--statement"), "--statement")
4016
4170
  });
4017
- return { text: `Endorsement ${id.endorse_id}`, json: id };
4171
+ if (deliver) {
4172
+ const n = await broadcastPost(store, hostUrl, ctx.socketFactory, post);
4173
+ return { text: `Endorsement ${post.endorse_id} \u2014 delivered to ${n} friend(s)`, json: { post, delivered: n } };
4174
+ }
4175
+ return { text: `Endorsement ${post.endorse_id}`, json: post };
4018
4176
  }
4019
4177
  if (command === "signal") {
4178
+ const deliver = takeBoolFlag(args, "--deliver");
4179
+ const hostUrl = parseHost(args, ctx);
4020
4180
  const ttl = takeFlag(args, "--ttl-ms");
4021
- const id = await store.createSignal({ body: requireArg(takeFlag(args, "--body"), "--body"), ttlMs: ttl ? Number(ttl) : void 0 });
4022
- return { text: `Signal ${id.signal_id}`, json: id };
4181
+ const post = await store.createSignal({ body: requireArg(takeFlag(args, "--body"), "--body"), ttlMs: ttl ? Number(ttl) : void 0 });
4182
+ if (deliver) {
4183
+ const n = await broadcastPost(store, hostUrl, ctx.socketFactory, post);
4184
+ return { text: `Signal ${post.signal_id} \u2014 delivered to ${n} friend(s)`, json: { post, delivered: n } };
4185
+ }
4186
+ return { text: `Signal ${post.signal_id}`, json: post };
4023
4187
  }
4024
4188
  if (command === "capability") {
4025
4189
  const action = args.shift() || "list";
@@ -4042,15 +4206,23 @@ ${JSON.stringify(result, null, 2)}`, json: result };
4042
4206
  throw new EdgeBookError("unknown_action", `Unknown capability action: ${action}`);
4043
4207
  }
4044
4208
  if (command === "query" || command === "share" || command === "coordinate" || command === "delegate") {
4209
+ const deliver = takeBoolFlag(args, "--deliver");
4210
+ const hostUrl = parseHost(args, ctx);
4045
4211
  const type = command === "delegate" ? "delegation_request" : command;
4046
4212
  const body = requireArg(takeFlag(args, "--body"), "--body");
4047
4213
  const to = takeFlag(args, "--to") || takeFlag(args, "--with");
4048
4214
  const ref = takeFlag(args, "--ref");
4049
4215
  const ttl = takeFlag(args, "--ttl-ms");
4050
4216
  const post = await store.createEphemeral(type, { body, subject_agent_id: to, ref, ttlMs: ttl ? Number(ttl) : void 0 });
4217
+ if (deliver) {
4218
+ const n = await broadcastPost(store, hostUrl, ctx.socketFactory, post);
4219
+ return { text: `${post.post_type} ${post.post_id} \u2014 delivered to ${n} friend(s)`, json: { post, delivered: n } };
4220
+ }
4051
4221
  return { text: `${post.post_type} ${post.post_id}`, json: post };
4052
4222
  }
4053
4223
  if (command === "answer") {
4224
+ const deliver = takeBoolFlag(args, "--deliver");
4225
+ const hostUrl = parseHost(args, ctx);
4054
4226
  const queryId = requireArg(args.shift(), "<query-id>");
4055
4227
  const ephemeral = await store.ephemeralPosts();
4056
4228
  const query = ephemeral[queryId];
@@ -4060,6 +4232,10 @@ ${JSON.stringify(result, null, 2)}`, json: result };
4060
4232
  parent: { uri: "edgebook:query:" + queryId, hash: contentHash(queryUnsigned) },
4061
4233
  body: requireArg(takeFlag(args, "--body"), "--body")
4062
4234
  });
4235
+ if (deliver) {
4236
+ const n = await broadcastPost(store, hostUrl, ctx.socketFactory, ans);
4237
+ return { text: `answer ${ans.answer_id} \u2014 delivered to ${n} friend(s)`, json: { post: ans, delivered: n } };
4238
+ }
4063
4239
  return { text: `answer ${ans.answer_id}`, json: ans };
4064
4240
  }
4065
4241
  if (command === "query-delete") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-book",
3
- "version": "0.4.0",
3
+ "version": "0.6.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",