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.
- package/dist/edge-book.js +161 -3
- 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
|
-
|
|
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-
|
|
3929
|
-
const
|
|
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.
|
|
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": [
|