edge-book 0.6.0 → 0.7.1

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 +161 -3
  2. package/package.json +4 -2
package/dist/edge-book.js CHANGED
@@ -485,6 +485,7 @@ var EdgeBookStore = class {
485
485
  const contacts = await this.contacts();
486
486
  const contact = contacts[peerAgentId];
487
487
  if (!contact) throw new EdgeBookError("unknown_contact", `Unknown contact: ${peerAgentId}`);
488
+ if (contact.relationship_state === "blocked") throw new EdgeBookError("blocked", `Peer ${peerAgentId} is blocked`);
488
489
  if (contact.relationship_state !== "friend") {
489
490
  throw new EdgeBookError("not_friend", `Cannot send friend-gated message to relationship_state=${contact.relationship_state}`);
490
491
  }
@@ -965,9 +966,13 @@ var EdgeBookStore = class {
965
966
  const object = await this.getObject(objectId);
966
967
  if (object && object.from_agent === subjectAgentId) return true;
967
968
  const grants = await this.grants();
968
- return Object.values(grants).some(
969
+ const candidates = Object.values(grants).filter(
969
970
  (grant) => grant.object_id === objectId && grant.subject_agent_id === subjectAgentId && grant.scopes.includes("object.read") && grant.status === "active" && (!grant.expires_at || Date.parse(grant.expires_at) > at)
970
971
  );
972
+ for (const grant of candidates) {
973
+ if (await this.verifyGrantSignature(grant)) return true;
974
+ }
975
+ return false;
971
976
  }
972
977
  // Audited read. Returns the object iff canReadObject; else fails closed.
973
978
  async readObject(objectId, subjectAgentId) {
@@ -1557,6 +1562,7 @@ var EdgeBookStore = class {
1557
1562
  const contacts = await this.contacts();
1558
1563
  const contact = contacts[peerAgentId];
1559
1564
  if (!contact) throw new EdgeBookError("unknown_contact", `Unknown contact: ${peerAgentId}`);
1565
+ if (contact.relationship_state === "blocked") throw new EdgeBookError("blocked", `Peer ${peerAgentId} is blocked`);
1560
1566
  if (contact.relationship_state !== "friend") throw new EdgeBookError("not_friend", `Feed denied for relationship_state=${contact.relationship_state}`);
1561
1567
  const grants = await this.grants();
1562
1568
  const grant = Object.values(grants).find(
@@ -3735,6 +3741,138 @@ async function revokeOneSession(options) {
3735
3741
  }
3736
3742
  }
3737
3743
 
3744
+ // src/resolver.ts
3745
+ function nextAction(result, target) {
3746
+ switch (result.status) {
3747
+ case "resolved":
3748
+ return `friend request ${target} --deliver`;
3749
+ case "approval_required":
3750
+ case "candidates": {
3751
+ const first = result.candidates?.[0];
3752
+ return first ? `candidates list # then: friend request ${first.candidate_id}` : "candidates list";
3753
+ }
3754
+ default:
3755
+ return "(no match \u2014 check the target)";
3756
+ }
3757
+ }
3758
+ var localContactProvider = {
3759
+ name: "local",
3760
+ priority: 100,
3761
+ async resolve(store, target) {
3762
+ const contacts = await store.contacts();
3763
+ const match = Object.values(contacts).find(
3764
+ (c) => c.peer_agent_id === target || c.aliases.includes(target) || c.display_name === target
3765
+ );
3766
+ if (!match) return null;
3767
+ return {
3768
+ kind: "card",
3769
+ agent_id: match.peer_agent_id,
3770
+ provenance: {
3771
+ source: "local",
3772
+ confidence: "high",
3773
+ display_name: match.display_name,
3774
+ reason: `known contact (relationship_state=${match.relationship_state})`
3775
+ }
3776
+ };
3777
+ }
3778
+ };
3779
+ function cardProvider(name, source, match) {
3780
+ return {
3781
+ name,
3782
+ priority: 90,
3783
+ async resolve(_store, target) {
3784
+ if (!match(target)) return null;
3785
+ const card = await loadCard(target);
3786
+ return {
3787
+ kind: "card",
3788
+ card,
3789
+ agent_id: card.agent_id,
3790
+ provenance: { source, confidence: "high", display_name: card.handle, reason: `${source} card verified` }
3791
+ };
3792
+ }
3793
+ };
3794
+ }
3795
+ var inviteProvider = cardProvider("invite", "invite", (t) => t.startsWith("edgebook:invite:"));
3796
+ var cardUrlProvider = cardProvider("card_url", "card_url", (t) => /^https?:\/\//.test(t));
3797
+ var cardFileProvider = cardProvider(
3798
+ "card_file",
3799
+ "card_file",
3800
+ (t) => t.startsWith("file://") || t.startsWith("/") || t.startsWith("./") || t.endsWith(".json")
3801
+ );
3802
+ var CANDIDATES_FILE = "candidates.json";
3803
+ function candidateKey(c) {
3804
+ return `${c.source}:${c.card_url ?? c.agent_id ?? ""}`;
3805
+ }
3806
+ async function readCandidates(store) {
3807
+ return readJson(store.file(CANDIDATES_FILE), {});
3808
+ }
3809
+ async function listCandidates(store) {
3810
+ return Object.values(await readCandidates(store));
3811
+ }
3812
+ async function getCandidate(store, id) {
3813
+ return (await readCandidates(store))[id];
3814
+ }
3815
+ async function writeCandidate(store, input) {
3816
+ const map = await readCandidates(store);
3817
+ const existing = Object.values(map).find((c) => candidateKey(c) === candidateKey(input));
3818
+ if (existing) return existing;
3819
+ const candidate = {
3820
+ candidate_id: randomId("cand"),
3821
+ approved: false,
3822
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
3823
+ ...input
3824
+ };
3825
+ map[candidate.candidate_id] = candidate;
3826
+ await writeJson(store.file(CANDIDATES_FILE), map);
3827
+ await store.audit("candidate.write", candidate.agent_id ?? "", { candidate_id: candidate.candidate_id, source: candidate.source });
3828
+ return candidate;
3829
+ }
3830
+ function defaultProviders(registryLookup = async () => null) {
3831
+ return [localContactProvider, inviteProvider, cardUrlProvider, cardFileProvider, makeRegistryProvider(registryLookup)];
3832
+ }
3833
+ async function resolveTarget(store, target, opts) {
3834
+ const ordered = [...opts.providers].sort((a, b) => b.priority - a.priority);
3835
+ for (const provider of ordered) {
3836
+ const r = await provider.resolve(store, target);
3837
+ if (!r) continue;
3838
+ if (r.kind === "card") {
3839
+ const result2 = { status: "resolved", card: r.card, agent_id: r.agent_id, provenance: r.provenance, next_action: "" };
3840
+ result2.next_action = nextAction(result2, target);
3841
+ return result2;
3842
+ }
3843
+ const candidate = await writeCandidate(store, r.candidate);
3844
+ const result = { status: "approval_required", candidates: [candidate], provenance: r.provenance, next_action: "" };
3845
+ result.next_action = nextAction(result, target);
3846
+ return result;
3847
+ }
3848
+ return { status: "not_found", next_action: "(no match \u2014 check the target)" };
3849
+ }
3850
+ async function markCandidateApproved(store, candidateId, agentId) {
3851
+ const map = await readJson(store.file(CANDIDATES_FILE), {});
3852
+ if (!map[candidateId]) return;
3853
+ map[candidateId].approved = true;
3854
+ map[candidateId].agent_id = agentId;
3855
+ await writeJson(store.file(CANDIDATES_FILE), map);
3856
+ }
3857
+ function makeRegistryProvider(lookup) {
3858
+ return {
3859
+ name: "registry",
3860
+ priority: 50,
3861
+ async resolve(_store, target) {
3862
+ if (!target.startsWith("registry:")) return null;
3863
+ const cardTarget = await lookup(target);
3864
+ if (!cardTarget) return null;
3865
+ const card = await loadCard(cardTarget);
3866
+ return {
3867
+ kind: "card",
3868
+ card,
3869
+ agent_id: card.agent_id,
3870
+ provenance: { source: "registry", confidence: "medium", display_name: card.handle, reason: "registry handle lookup" }
3871
+ };
3872
+ }
3873
+ };
3874
+ }
3875
+
3738
3876
  // src/cli.ts
3739
3877
  function usage() {
3740
3878
  return `Edge Book
@@ -3921,13 +4059,33 @@ share_owner_label: ${id.share_owner_label ? "true" : "false"} (${shared})`,
3921
4059
  return { text: inviteUrl, json: { invite_url: inviteUrl, agent_id: card.agent_id } };
3922
4060
  }
3923
4061
  }
4062
+ if (command === "resolve") {
4063
+ const target = requireArg(args.shift(), "target");
4064
+ const result = await resolveTarget(store, target, { providers: defaultProviders() });
4065
+ const label = result.agent_id ?? result.candidates?.[0]?.candidate_id ?? "";
4066
+ return { text: `${result.status} ${label}
4067
+ next: ${result.next_action}`, json: result };
4068
+ }
4069
+ if (command === "candidates") {
4070
+ const action = args.shift() || "list";
4071
+ if (action === "list") {
4072
+ const candidates = await listCandidates(store);
4073
+ const text = candidates.length ? candidates.map((c) => `${c.candidate_id} ${c.source} ${c.display_name} ${c.approved ? "[approved]" : ""}`).join("\n") : "No candidates.";
4074
+ return { text, json: { candidates } };
4075
+ }
4076
+ }
3924
4077
  if (command === "friend") {
3925
4078
  const action = args.shift();
3926
4079
  if (action === "request") {
3927
4080
  const deliver = takeBoolFlag(args, "--deliver");
3928
- const target = requireArg(args.shift(), "card-path-or-url");
3929
- const card = await loadCard(target);
4081
+ const target = requireArg(args.shift(), "card-path-url-or-candidate");
4082
+ const candidate = await getCandidate(store, target);
4083
+ if (candidate && !candidate.card_url) {
4084
+ throw new EdgeBookError("candidate_not_resolvable", "Candidate has no card_url to verify; cannot request");
4085
+ }
4086
+ const card = candidate ? await loadCard(candidate.card_url) : await loadCard(target);
3930
4087
  const envelope = await store.createFriendRequest(card);
4088
+ if (candidate) await markCandidateApproved(store, candidate.candidate_id, card.agent_id);
3931
4089
  if (deliver) {
3932
4090
  const direct = card.transports.find((entry) => entry.mode === "direct")?.endpoint;
3933
4091
  if (direct) return { text: await deliverToEndpoint(envelope, direct), json: envelope };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-book",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Run your own Edge Book agent and connect it to the hosted reader.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,7 +12,9 @@
12
12
  "test": "node --test test/*.test.ts",
13
13
  "harness": "node bin/edge-book.js harness two-agent",
14
14
  "harness:e2e": "node scripts/convergence-e2e.ts",
15
- "prepublishOnly": "npm run build"
15
+ "prepublishOnly": "npm run build",
16
+ "smoke": "node scripts/smoke-2agent.ts",
17
+ "smoke:host": "node scripts/smoke-2agent.ts --host"
16
18
  },
17
19
  "openclaw": {
18
20
  "extensions": [