@snowyroad/arp 0.7.0 → 0.8.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/dist/cli.js +139 -3
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -125,13 +125,17 @@ function parseStoredAgent(file) {
|
|
|
125
125
|
}
|
|
126
126
|
const tm = a.toolMode;
|
|
127
127
|
const toolMode = tm === "readonly" || tm === "full" ? tm : void 0;
|
|
128
|
+
const signingPrivateJwk = typeof a.signingPrivateJwk === "string" && a.signingPrivateJwk.trim() !== "" ? a.signingPrivateJwk : void 0;
|
|
129
|
+
const signingKid = typeof a.signingKid === "string" && a.signingKid.trim() !== "" ? a.signingKid : void 0;
|
|
128
130
|
return {
|
|
129
131
|
relayUrl: a.relayUrl.trim(),
|
|
130
132
|
agentId: a.agentId.trim(),
|
|
131
133
|
agentName: a.agentName.trim(),
|
|
132
134
|
agentUuid: a.agentUuid.trim(),
|
|
133
135
|
agentKey: a.agentKey.trim(),
|
|
134
|
-
...toolMode ? { toolMode } : {}
|
|
136
|
+
...toolMode ? { toolMode } : {},
|
|
137
|
+
...signingPrivateJwk ? { signingPrivateJwk } : {},
|
|
138
|
+
...signingKid ? { signingKid } : {}
|
|
135
139
|
};
|
|
136
140
|
}
|
|
137
141
|
function listAgents(dir) {
|
|
@@ -505,6 +509,98 @@ async function chooseFirstRunToolMode(agentName, io = { input: process.stdin, ou
|
|
|
505
509
|
return { mode: "readonly", persist: false };
|
|
506
510
|
}
|
|
507
511
|
|
|
512
|
+
// src/cardSigning.ts
|
|
513
|
+
import canonicalize from "canonicalize";
|
|
514
|
+
import {
|
|
515
|
+
FlattenedSign,
|
|
516
|
+
flattenedVerify,
|
|
517
|
+
importJWK,
|
|
518
|
+
exportJWK,
|
|
519
|
+
calculateJwkThumbprint,
|
|
520
|
+
generateKeyPair
|
|
521
|
+
} from "jose";
|
|
522
|
+
var SIGNING_ALG = "EdDSA";
|
|
523
|
+
var REQUIRED_TOP = /* @__PURE__ */ new Set(["name", "description", "skills"]);
|
|
524
|
+
var RELAY_OWNED_TOP = ["version", "provider"];
|
|
525
|
+
function isEmptyValue(v) {
|
|
526
|
+
if (v === "" || v === null || v === void 0) return true;
|
|
527
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
528
|
+
if (typeof v === "object") return Object.keys(v).length === 0;
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
function pruneNode(node, requiredKeys) {
|
|
532
|
+
if (Array.isArray(node)) return node.map((e) => e && typeof e === "object" ? pruneNode(e, /* @__PURE__ */ new Set()) : e);
|
|
533
|
+
if (!node || typeof node !== "object") return node;
|
|
534
|
+
const out = {};
|
|
535
|
+
for (const [k, v] of Object.entries(node)) {
|
|
536
|
+
const pv = v && typeof v === "object" ? pruneNode(v, /* @__PURE__ */ new Set()) : v;
|
|
537
|
+
if (requiredKeys.has(k)) {
|
|
538
|
+
out[k] = pv;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (isEmptyValue(pv)) continue;
|
|
542
|
+
out[k] = pv;
|
|
543
|
+
}
|
|
544
|
+
return out;
|
|
545
|
+
}
|
|
546
|
+
function pruneForSigning(card) {
|
|
547
|
+
const clone = JSON.parse(JSON.stringify(card ?? {}));
|
|
548
|
+
delete clone.signatures;
|
|
549
|
+
for (const f of RELAY_OWNED_TOP) delete clone[f];
|
|
550
|
+
if (Array.isArray(clone.supportedInterfaces)) {
|
|
551
|
+
clone.supportedInterfaces = clone.supportedInterfaces.map((i) => {
|
|
552
|
+
if (i && typeof i === "object") {
|
|
553
|
+
const { url, ...rest } = i;
|
|
554
|
+
return rest;
|
|
555
|
+
}
|
|
556
|
+
return i;
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return pruneNode(clone, REQUIRED_TOP);
|
|
560
|
+
}
|
|
561
|
+
function canonicalCardString(card) {
|
|
562
|
+
return canonicalize(pruneForSigning(card));
|
|
563
|
+
}
|
|
564
|
+
function publicOkpJwk(jwk) {
|
|
565
|
+
return { kty: jwk.kty, crv: jwk.crv, x: jwk.x };
|
|
566
|
+
}
|
|
567
|
+
async function generateSigningKey() {
|
|
568
|
+
const { privateKey } = await generateKeyPair(SIGNING_ALG, { crv: "Ed25519", extractable: true });
|
|
569
|
+
const privateJwk = await exportJWK(privateKey);
|
|
570
|
+
const kid = await calculateJwkThumbprint(publicOkpJwk(privateJwk));
|
|
571
|
+
return { privateJwk, kid };
|
|
572
|
+
}
|
|
573
|
+
async function signCard(card, privateJwk) {
|
|
574
|
+
const key = await importJWK(privateJwk, SIGNING_ALG);
|
|
575
|
+
const pub = publicOkpJwk(privateJwk);
|
|
576
|
+
const kid = await calculateJwkThumbprint(pub);
|
|
577
|
+
const payload = new TextEncoder().encode(canonicalCardString(card));
|
|
578
|
+
const jws = await new FlattenedSign(payload).setProtectedHeader({ alg: SIGNING_ALG, typ: "JOSE", kid, jwk: pub }).sign(key);
|
|
579
|
+
return [{ protected: jws.protected, signature: jws.signature }];
|
|
580
|
+
}
|
|
581
|
+
async function verifyCardSignature(card) {
|
|
582
|
+
const sigs = card?.signatures;
|
|
583
|
+
if (!Array.isArray(sigs) || sigs.length === 0) return { status: "unsigned" };
|
|
584
|
+
const payload = Buffer.from(canonicalCardString(card), "utf8").toString("base64url");
|
|
585
|
+
for (const sig of sigs) {
|
|
586
|
+
try {
|
|
587
|
+
if (!sig || typeof sig.protected !== "string" || typeof sig.signature !== "string") continue;
|
|
588
|
+
const header = JSON.parse(Buffer.from(sig.protected, "base64url").toString("utf8"));
|
|
589
|
+
if (header.alg !== SIGNING_ALG || !header.jwk) continue;
|
|
590
|
+
const pub = publicOkpJwk(header.jwk);
|
|
591
|
+
const kid = await calculateJwkThumbprint(pub);
|
|
592
|
+
if (header.kid && header.kid !== kid) continue;
|
|
593
|
+
const key = await importJWK(pub, SIGNING_ALG);
|
|
594
|
+
await flattenedVerify({ protected: sig.protected, payload, signature: sig.signature }, key, {
|
|
595
|
+
algorithms: [SIGNING_ALG]
|
|
596
|
+
});
|
|
597
|
+
return { status: "valid", kid };
|
|
598
|
+
} catch {
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return { status: "invalid" };
|
|
602
|
+
}
|
|
603
|
+
|
|
508
604
|
// src/config.ts
|
|
509
605
|
var DEFAULT_MODEL = "claude-opus-4-8";
|
|
510
606
|
var DEFAULT_AGENT_MODE = "acp";
|
|
@@ -654,6 +750,26 @@ async function buildFromStoredAgent(dir, stored, env) {
|
|
|
654
750
|
return inflight;
|
|
655
751
|
};
|
|
656
752
|
const token = await mintToken();
|
|
753
|
+
let signingPrivateJwk = current.signingPrivateJwk;
|
|
754
|
+
if (!signingPrivateJwk) {
|
|
755
|
+
try {
|
|
756
|
+
const gen = await generateSigningKey();
|
|
757
|
+
signingPrivateJwk = JSON.stringify(gen.privateJwk);
|
|
758
|
+
current = { ...current, signingPrivateJwk, signingKid: gen.kid };
|
|
759
|
+
saveAgent(dir, current);
|
|
760
|
+
} catch (e) {
|
|
761
|
+
console.warn("[arp-bridge] signing key generation failed; cards will be unsigned:", String(e));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const signCardFn = signingPrivateJwk ? async (card) => {
|
|
765
|
+
try {
|
|
766
|
+
const sigs = await signCard(card, JSON.parse(signingPrivateJwk));
|
|
767
|
+
return { ...card, signatures: sigs };
|
|
768
|
+
} catch (e) {
|
|
769
|
+
console.warn("[arp-bridge] card signing failed; publishing unsigned:", String(e));
|
|
770
|
+
return card;
|
|
771
|
+
}
|
|
772
|
+
} : void 0;
|
|
657
773
|
return {
|
|
658
774
|
relayWsUrl,
|
|
659
775
|
relayHttpUrl,
|
|
@@ -668,7 +784,8 @@ async function buildFromStoredAgent(dir, stored, env) {
|
|
|
668
784
|
catchUpTtlMs: positiveIntEnv(env.ARP_CATCHUP_TTL_MS, 72e5),
|
|
669
785
|
catchUpMaxMentions: positiveIntEnv(env.ARP_CATCHUP_MAX_MENTIONS, 3),
|
|
670
786
|
mintToken,
|
|
671
|
-
agentFile: file
|
|
787
|
+
agentFile: file,
|
|
788
|
+
...signCardFn ? { signCard: signCardFn } : {}
|
|
672
789
|
};
|
|
673
790
|
}
|
|
674
791
|
async function loadConfigFromInvite(code, env) {
|
|
@@ -1290,6 +1407,14 @@ var RelayClient = class {
|
|
|
1290
1407
|
case "roster_update": {
|
|
1291
1408
|
const m = msg.member ?? {};
|
|
1292
1409
|
if (typeof m.name === "string" && m.name.length > 0) {
|
|
1410
|
+
if (m.card && typeof m.card === "object") {
|
|
1411
|
+
void verifyCardSignature(m.card).then((r) => {
|
|
1412
|
+
if (r.status === "invalid") {
|
|
1413
|
+
console.warn(`[arp-bridge] peer card signature invalid for "${m.name}" (card not trusted for integrity)`);
|
|
1414
|
+
}
|
|
1415
|
+
}).catch(() => {
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1293
1418
|
const entry = normalizeRosterEntry(m.name, m.description, m.card);
|
|
1294
1419
|
this.rosterCbs.forEach((cb) => cb(entry));
|
|
1295
1420
|
}
|
|
@@ -1463,7 +1588,17 @@ var RelayClient = class {
|
|
|
1463
1588
|
}
|
|
1464
1589
|
const body = await res.json();
|
|
1465
1590
|
const members = body?.channel?.members ?? [];
|
|
1466
|
-
return members.filter((m) => m?.type === "bot" && typeof m.id === "string").map((m) =>
|
|
1591
|
+
return members.filter((m) => m?.type === "bot" && typeof m.id === "string").map((m) => {
|
|
1592
|
+
if (m.card && typeof m.card === "object") {
|
|
1593
|
+
void verifyCardSignature(m.card).then((r) => {
|
|
1594
|
+
if (r.status === "invalid") {
|
|
1595
|
+
console.warn(`[arp-bridge] peer card signature invalid for "${m.id}" (card not trusted for integrity)`);
|
|
1596
|
+
}
|
|
1597
|
+
}).catch(() => {
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
return normalizeRosterEntry(m.id, m.description, m.card);
|
|
1601
|
+
});
|
|
1467
1602
|
} catch (err) {
|
|
1468
1603
|
console.warn("[arp-bridge] roster fetch failed:", sanitizeForTty(String(err)));
|
|
1469
1604
|
return [];
|
|
@@ -3165,6 +3300,7 @@ async function publishSelfCard(cfg, relay, session) {
|
|
|
3165
3300
|
} else {
|
|
3166
3301
|
card = buildPartialCard(cfg.agentName, { description: "", skills: [] });
|
|
3167
3302
|
}
|
|
3303
|
+
if (cfg.signCard) card = await cfg.signCard(card);
|
|
3168
3304
|
await relay.putAgentCard(card);
|
|
3169
3305
|
}
|
|
3170
3306
|
function learnRoster(relay, channelId, session) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/arp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Connect your own coding agent (Claude Code, Codex, Gemini, Grok) to an Agent Relay Protocol channel and collaborate with other agents and humans.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "SnowyRoad",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"@anthropic-ai/claude-agent-sdk": "0.3.177",
|
|
34
34
|
"@anthropic-ai/sdk": "0.104.1",
|
|
35
35
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
36
|
+
"canonicalize": "^3.0.0",
|
|
37
|
+
"jose": "^6.2.3",
|
|
36
38
|
"ws": "8.21.0",
|
|
37
39
|
"zod": "4.4.3"
|
|
38
40
|
},
|