agent-comms 8.15.9 → 8.15.11
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/bridges/user/web/server.d.ts.map +1 -1
- package/dist/bridges/user/web/server.js +44 -24
- package/dist/bridges/user/web/server.js.map +1 -1
- package/dist/bridges/user/web/test/web-assets.unit.test.d.ts +5 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.js +90 -0
- package/dist/bridges/user/web/test/web-assets.unit.test.js.map +1 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts +2 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts.map +1 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js +75 -0
- package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js.map +1 -0
- package/dist/bridges/user/web/web-assets.d.ts +28 -0
- package/dist/bridges/user/web/web-assets.d.ts.map +1 -0
- package/dist/bridges/user/web/web-assets.js +67 -0
- package/dist/bridges/user/web/web-assets.js.map +1 -0
- package/dist/test/pi-manifest-packed.integration.test.d.ts +2 -0
- package/dist/test/pi-manifest-packed.integration.test.d.ts.map +1 -0
- package/dist/test/pi-manifest-packed.integration.test.js +68 -0
- package/dist/test/pi-manifest-packed.integration.test.js.map +1 -0
- package/package.json +8 -2
- package/src/account-cli.ts +195 -0
- package/src/account-join-cli.ts +191 -0
- package/src/bridges/cc-peer/approval-commands.ts +82 -0
- package/src/bridges/cc-peer/bridge.ts +138 -0
- package/src/bridges/cc-peer/default-front.ts +58 -0
- package/src/bridges/cc-peer/front-controller.ts +160 -0
- package/src/bridges/cc-peer/front-relay.ts +191 -0
- package/src/bridges/cc-peer/front-runtime.ts +167 -0
- package/src/bridges/cc-peer/front.ts +59 -0
- package/src/bridges/cc-peer/reply-aliases.ts +90 -0
- package/src/bridges/cc-peer/run.ts +100 -0
- package/src/bridges/cc-peer/shared-peer.ts +28 -0
- package/src/bridges/claude-code/channel.ts +318 -0
- package/src/bridges/codex/tool.ts +132 -0
- package/src/bridges/mcp/server.ts +137 -0
- package/src/bridges/opencode/plugin.ts +130 -0
- package/src/bridges/pi/extension.ts +330 -0
- package/src/bridges/registry.ts +17 -0
- package/src/bridges/user/cli.ts +87 -0
- package/src/bridges/user/controller.ts +345 -0
- package/src/bridges/user/tui.ts +300 -0
- package/src/bridges/user/web/actions.ts +173 -0
- package/src/bridges/user/web/contract.ts +222 -0
- package/src/bridges/user/web/dist/bundle.js +49 -0
- package/src/bridges/user/web/dist/icons/icon-192x192.svg +9 -0
- package/src/bridges/user/web/dist/icons/icon-512x512.svg +9 -0
- package/src/bridges/user/web/dist/icons/icon-96x96.svg +9 -0
- package/src/bridges/user/web/dist/index.html +21 -0
- package/src/bridges/user/web/dist/manifest.json +26 -0
- package/src/bridges/user/web/dist/mesh-worker.js +64 -0
- package/src/bridges/user/web/dist/sw.js +1 -0
- package/src/bridges/user/web/event-publisher.ts +114 -0
- package/src/bridges/user/web/port-discovery.ts +49 -0
- package/src/bridges/user/web/reads.ts +35 -0
- package/src/bridges/user/web/router.ts +158 -0
- package/src/bridges/user/web/server.ts +782 -0
- package/src/bridges/user/web/vite.config.ts +71 -0
- package/src/bridges/user/web/web-access.ts +404 -0
- package/src/bridges/user/web/web-assets.ts +90 -0
- package/src/bridges/user/web/web-beacon.ts +69 -0
- package/src/bridges/user/web/web-console-static.ts +90 -0
- package/src/cli-main.ts +635 -0
- package/src/cli.ts +13 -0
- package/src/core/account-bundle.ts +264 -0
- package/src/core/account-join.ts +289 -0
- package/src/core/account-ledger-crypto.ts +154 -0
- package/src/core/account-ledger-store.ts +66 -0
- package/src/core/account-ledger.ts +385 -0
- package/src/core/agent-details.ts +216 -0
- package/src/core/agent-list-format.ts +97 -0
- package/src/core/agent-registry.ts +282 -0
- package/src/core/agent-table.ts +58 -0
- package/src/core/atomic-file.ts +46 -0
- package/src/core/bind-retry.ts +38 -0
- package/src/core/bridge-mesh.ts +176 -0
- package/src/core/bridge.ts +718 -0
- package/src/core/capability-ask.ts +162 -0
- package/src/core/capability-bubble-up.ts +237 -0
- package/src/core/comms-store.ts +130 -0
- package/src/core/connection-approval.ts +134 -0
- package/src/core/connection-code-tool.ts +92 -0
- package/src/core/connection-code.ts +296 -0
- package/src/core/coordinator-role.ts +224 -0
- package/src/core/data-directory.ts +32 -0
- package/src/core/data-frame-routing.ts +309 -0
- package/src/core/default-command.ts +12 -0
- package/src/core/delegation-policy.ts +41 -0
- package/src/core/delivery-engine.ts +649 -0
- package/src/core/device-membership-verification.ts +125 -0
- package/src/core/device-membership.ts +130 -0
- package/src/core/directory-admission.ts +155 -0
- package/src/core/discovery-mdns.ts +247 -0
- package/src/core/discovery-tailscale.ts +224 -0
- package/src/core/discovery.ts +303 -0
- package/src/core/display-name.ts +73 -0
- package/src/core/dm-grant-actions.ts +72 -0
- package/src/core/dm-send-delegation.ts +73 -0
- package/src/core/dm-token-verification.ts +76 -0
- package/src/core/election-sessions.ts +135 -0
- package/src/core/error-reporting.ts +85 -0
- package/src/core/first-contact.ts +242 -0
- package/src/core/gateway-trust-actions.ts +184 -0
- package/src/core/gateway-trust.ts +305 -0
- package/src/core/gossip-directory.ts +276 -0
- package/src/core/gossip-extensions.ts +123 -0
- package/src/core/group-proofs.ts +205 -0
- package/src/core/handshake.ts +354 -0
- package/src/core/hub-forwarding.ts +24 -0
- package/src/core/hub-link.ts +176 -0
- package/src/core/hub-session.ts +374 -0
- package/src/core/identity-store.ts +715 -0
- package/src/core/identity.ts +489 -0
- package/src/core/index.ts +65 -0
- package/src/core/issuer-grouping.ts +165 -0
- package/src/core/issuer-identity-file.ts +187 -0
- package/src/core/lan-web-actions.ts +34 -0
- package/src/core/lan-web-uis.ts +55 -0
- package/src/core/ledger-lock.ts +81 -0
- package/src/core/listener-registry.ts +133 -0
- package/src/core/loopback-address.ts +22 -0
- package/src/core/machine-identity.ts +77 -0
- package/src/core/machine-list-groups.ts +53 -0
- package/src/core/mcp-registry-retry.ts +69 -0
- package/src/core/mcp-registry-version.ts +18 -0
- package/src/core/membership-proof.ts +139 -0
- package/src/core/membership-proofs.ts +149 -0
- package/src/core/mesh-graph-trace-tool.ts +81 -0
- package/src/core/mesh-graph.ts +89 -0
- package/src/core/mesh-network-actions.ts +183 -0
- package/src/core/mesh-store-options.ts +23 -0
- package/src/core/mesh-store-shared.ts +134 -0
- package/src/core/mesh-store.ts +1114 -0
- package/src/core/name-claim.ts +145 -0
- package/src/core/naming-actions.ts +90 -0
- package/src/core/naming.ts +138 -0
- package/src/core/nanoid.ts +20 -0
- package/src/core/network-interfaces.ts +24 -0
- package/src/core/package-version.ts +34 -0
- package/src/core/peer-lifecycle.ts +377 -0
- package/src/core/peer-link-error.ts +41 -0
- package/src/core/peer-versions.ts +98 -0
- package/src/core/pending-connection.ts +53 -0
- package/src/core/petnames.ts +121 -0
- package/src/core/pgp-keyserver.ts +32 -0
- package/src/core/pid-lock-file.ts +122 -0
- package/src/core/push-manager.ts +76 -0
- package/src/core/random-id.ts +9 -0
- package/src/core/receiver-set.ts +42 -0
- package/src/core/relay-role.ts +182 -0
- package/src/core/relay-selection.ts +120 -0
- package/src/core/relay-server.ts +198 -0
- package/src/core/relay-uplink.ts +78 -0
- package/src/core/request-timeouts.ts +37 -0
- package/src/core/room-discovery.ts +48 -0
- package/src/core/room-lifecycle.ts +838 -0
- package/src/core/room-lookup.ts +41 -0
- package/src/core/room-messaging.ts +247 -0
- package/src/core/room-notice-log.ts +61 -0
- package/src/core/room-path.ts +84 -0
- package/src/core/room-protocol.ts +893 -0
- package/src/core/room-router.ts +180 -0
- package/src/core/room-token-verification.ts +85 -0
- package/src/core/room-wire-extensions.ts +60 -0
- package/src/core/self-name-claim.ts +67 -0
- package/src/core/send-outcome.ts +69 -0
- package/src/core/shutdown-signals.ts +57 -0
- package/src/core/stale-agent-checker.ts +108 -0
- package/src/core/store.ts +704 -0
- package/src/core/token-text.ts +40 -0
- package/src/core/tool.ts +853 -0
- package/src/core/transport-events.ts +73 -0
- package/src/core/transport-queries.ts +54 -0
- package/src/core/transport.ts +367 -0
- package/src/core/types.ts +550 -0
- package/src/core/user-identity.ts +290 -0
- package/src/core/vapid.ts +81 -0
- package/src/core/version-check.ts +147 -0
- package/src/core/version-report-actions.ts +79 -0
- package/src/core/web-beacon.ts +31 -0
- package/src/core/web-push.ts +322 -0
- package/src/core/wire-mesh-core-version.ts +25 -0
- package/src/core/wire-mesh-identity.ts +36 -0
- package/src/core/wire-mesh-transport-options.ts +82 -0
- package/src/core/wire-mesh-transport.ts +1007 -0
- package/src/core/wire-protocol.ts +153 -0
- package/src/core/ws-dial.ts +191 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A self display name claim (agent-comms#345): the name a key asserts for itself, signed by that key, carried as one line of text beside the proofs in an agent/self advert. The issuer is authentic (the claim carries the public key it verifies under, and that key must hash to the subject's device-id, the same self-certifying check a capability token's issuer-key gets), but the content is whatever the issuer chose, so a receiver shows it as that subject's own claim, never as a fact about it. A machine names itself this way; an agent's name already rides in its own signed advert.
|
|
3
|
+
*
|
|
4
|
+
* The claim expires, like the proofs it rides beside, so a renamed machine's old name ages out of every peer's view without anything having to revoke it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Clock } from "wire-mesh-core/ports/clock";
|
|
8
|
+
import type { IdentityPort } from "wire-mesh-core/ports/identity";
|
|
9
|
+
import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
|
|
10
|
+
import { parseDisplayName } from "./display-name.js";
|
|
11
|
+
|
|
12
|
+
/** Separates a name claim's signature from every other signature the same key makes, so no other signed payload can be replayed as one. */
|
|
13
|
+
const NAME_CLAIM_CONTEXT = "agent-comms/name-claim/v1";
|
|
14
|
+
|
|
15
|
+
/** The longest claim text a receiver will look at: a claim is a short name, a public key and a signature, well under a quarter of this, so anything longer is something else. */
|
|
16
|
+
export const MAX_NAME_CLAIM_LENGTH = 2048;
|
|
17
|
+
|
|
18
|
+
interface NameClaimWire {
|
|
19
|
+
subject: string;
|
|
20
|
+
name: string;
|
|
21
|
+
expires: number;
|
|
22
|
+
alg: number;
|
|
23
|
+
publicKey: string;
|
|
24
|
+
signature: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isNameClaimWire(value: unknown): value is NameClaimWire {
|
|
28
|
+
if (typeof value !== "object" || value === null) return false;
|
|
29
|
+
return (
|
|
30
|
+
"subject" in value &&
|
|
31
|
+
typeof value.subject === "string" &&
|
|
32
|
+
"name" in value &&
|
|
33
|
+
typeof value.name === "string" &&
|
|
34
|
+
"expires" in value &&
|
|
35
|
+
typeof value.expires === "number" &&
|
|
36
|
+
"alg" in value &&
|
|
37
|
+
typeof value.alg === "number" &&
|
|
38
|
+
"publicKey" in value &&
|
|
39
|
+
typeof value.publicKey === "string" &&
|
|
40
|
+
"signature" in value &&
|
|
41
|
+
typeof value.signature === "string"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The exact bytes a claim's signature covers: the context, then every signed field, in a fixed order. */
|
|
46
|
+
function signedBytes(
|
|
47
|
+
claim: Readonly<Pick<NameClaimWire, "subject" | "name" | "expires">>,
|
|
48
|
+
): Uint8Array {
|
|
49
|
+
return new TextEncoder().encode(
|
|
50
|
+
JSON.stringify([
|
|
51
|
+
NAME_CLAIM_CONTEXT,
|
|
52
|
+
claim.subject,
|
|
53
|
+
claim.name,
|
|
54
|
+
claim.expires,
|
|
55
|
+
]),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface MintNameClaimOptions {
|
|
60
|
+
/** The key naming itself: the claim's subject is its device-id. */
|
|
61
|
+
issuer: IdentityPort;
|
|
62
|
+
clock: Clock;
|
|
63
|
+
/** Must already satisfy parseDisplayName. */
|
|
64
|
+
name: string;
|
|
65
|
+
lifetimeMs: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A fresh claim, as one line of text, that options.issuer calls itself options.name. */
|
|
69
|
+
export async function mintNameClaim(
|
|
70
|
+
options: Readonly<MintNameClaimOptions>,
|
|
71
|
+
): Promise<string> {
|
|
72
|
+
const unsigned = {
|
|
73
|
+
subject: deviceIdToHex(options.issuer.deviceId),
|
|
74
|
+
name: options.name,
|
|
75
|
+
expires: options.clock.now() + options.lifetimeMs,
|
|
76
|
+
};
|
|
77
|
+
const signature = await options.issuer.sign(signedBytes(unsigned));
|
|
78
|
+
const wire: NameClaimWire = {
|
|
79
|
+
...unsigned,
|
|
80
|
+
alg: options.issuer.identityKey.alg,
|
|
81
|
+
publicKey: Buffer.from(options.issuer.identityKey["public-key"]).toString(
|
|
82
|
+
"base64url",
|
|
83
|
+
),
|
|
84
|
+
signature: Buffer.from(signature).toString("base64url"),
|
|
85
|
+
};
|
|
86
|
+
return Buffer.from(JSON.stringify(wire), "utf-8").toString("base64url");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface VerifyNameClaimOptions {
|
|
90
|
+
claim: string;
|
|
91
|
+
/** The device-id (hex) the claim must be about: the machine a proof has already identified. */
|
|
92
|
+
subject: string;
|
|
93
|
+
/** The verifying node's own identity (for signature checks) and clock. */
|
|
94
|
+
identity: IdentityPort;
|
|
95
|
+
clock: Clock;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type NameClaimVerdict =
|
|
99
|
+
{ ok: true; name: string; expires: number } | { ok: false; reason: string };
|
|
100
|
+
|
|
101
|
+
/** Whether options.claim is a current name claim signed by options.subject's own key, and the name it asserts. Never throws for a bad claim: text that is not a claim, one about another subject, one whose key is not the subject's, a bad signature, an expired claim or an unusable name all come back as a refusal. */
|
|
102
|
+
export async function verifyNameClaim(
|
|
103
|
+
options: Readonly<VerifyNameClaimOptions>,
|
|
104
|
+
): Promise<NameClaimVerdict> {
|
|
105
|
+
if (options.claim.length > MAX_NAME_CLAIM_LENGTH) {
|
|
106
|
+
return { ok: false, reason: "too_long" };
|
|
107
|
+
}
|
|
108
|
+
let parsed: unknown;
|
|
109
|
+
try {
|
|
110
|
+
parsed = JSON.parse(
|
|
111
|
+
Buffer.from(options.claim, "base64url").toString("utf-8"),
|
|
112
|
+
);
|
|
113
|
+
} catch {
|
|
114
|
+
return { ok: false, reason: "not_a_claim" };
|
|
115
|
+
}
|
|
116
|
+
if (!isNameClaimWire(parsed)) return { ok: false, reason: "not_a_claim" };
|
|
117
|
+
if (parsed.subject !== options.subject.toLowerCase()) {
|
|
118
|
+
return { ok: false, reason: "wrong_subject" };
|
|
119
|
+
}
|
|
120
|
+
if (parsed.expires <= options.clock.now()) {
|
|
121
|
+
return { ok: false, reason: "expired" };
|
|
122
|
+
}
|
|
123
|
+
const name = parseDisplayName(parsed.name);
|
|
124
|
+
if (name === undefined || name !== parsed.name) {
|
|
125
|
+
return { ok: false, reason: "unusable_name" };
|
|
126
|
+
}
|
|
127
|
+
const publicKey = Uint8Array.from(Buffer.from(parsed.publicKey, "base64url"));
|
|
128
|
+
const keyOwner = deviceIdToHex(
|
|
129
|
+
await options.identity.deriveDeviceId(publicKey),
|
|
130
|
+
);
|
|
131
|
+
if (keyOwner !== parsed.subject) return { ok: false, reason: "wrong_key" };
|
|
132
|
+
let signed: boolean;
|
|
133
|
+
try {
|
|
134
|
+
signed = await options.identity.verify(
|
|
135
|
+
{ alg: parsed.alg, "public-key": publicKey },
|
|
136
|
+
signedBytes(parsed),
|
|
137
|
+
Uint8Array.from(Buffer.from(parsed.signature, "base64url")),
|
|
138
|
+
);
|
|
139
|
+
} catch {
|
|
140
|
+
// An algorithm the verifier does not support, or key bytes it cannot import: either way the claim cannot be checked, which is a refusal.
|
|
141
|
+
return { ok: false, reason: "unusable_key" };
|
|
142
|
+
}
|
|
143
|
+
if (!signed) return { ok: false, reason: "bad_signature" };
|
|
144
|
+
return { ok: true, name, expires: parsed.expires };
|
|
145
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommsTool's naming actions (agent-comms#345): setting, clearing and listing the viewer's petnames, naming this machine and naming this account. Split out of tool.ts to keep that file under the repo's max-lines cap, as gateway-trust-actions.ts is.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CommsAction } from "./types.js";
|
|
6
|
+
import type { CommsResult } from "./tool.js";
|
|
7
|
+
import { idWithNames, type Namer, type Naming } from "./naming.js";
|
|
8
|
+
|
|
9
|
+
/** The result for a bridge constructed without naming wired. */
|
|
10
|
+
function namingUnavailable(): CommsResult {
|
|
11
|
+
return {
|
|
12
|
+
content: "Naming is not available on this bridge.",
|
|
13
|
+
isError: true,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Labels action.device with the viewer's own petname. */
|
|
18
|
+
export function petnameSet(
|
|
19
|
+
naming: Naming | undefined,
|
|
20
|
+
action: CommsAction & { action: "petname_set" },
|
|
21
|
+
): CommsResult {
|
|
22
|
+
if (naming === undefined) return namingUnavailable();
|
|
23
|
+
const label = naming.setPetname(action.device, action.name);
|
|
24
|
+
return {
|
|
25
|
+
content: `You now call ${action.device} "${label}". Only you see this name.`,
|
|
26
|
+
isError: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Removes the viewer's petname for action.device. */
|
|
31
|
+
export function petnameClear(
|
|
32
|
+
naming: Naming | undefined,
|
|
33
|
+
action: CommsAction & { action: "petname_clear" },
|
|
34
|
+
): CommsResult {
|
|
35
|
+
if (naming === undefined) return namingUnavailable();
|
|
36
|
+
return {
|
|
37
|
+
content: naming.clearPetname(action.device)
|
|
38
|
+
? `Cleared your name for ${action.device}.`
|
|
39
|
+
: `You had no name for ${action.device}.`,
|
|
40
|
+
isError: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Every petname the viewer has set, each with its full id and the display name it now produces. */
|
|
45
|
+
export function petnameList(
|
|
46
|
+
naming: Naming | undefined,
|
|
47
|
+
namer: Namer,
|
|
48
|
+
): CommsResult {
|
|
49
|
+
if (naming === undefined) return namingUnavailable();
|
|
50
|
+
const petnames = [...naming.listPetnames().keys()];
|
|
51
|
+
if (petnames.length === 0) {
|
|
52
|
+
return { content: "You have not named anything.", isError: false };
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
content: `Your names:\n${petnames.map((id) => ` ${idWithNames(id, namer)}`).join("\n")}`,
|
|
56
|
+
isError: false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Names this machine, or clears its name when action.name is omitted. */
|
|
61
|
+
export async function machineName(
|
|
62
|
+
naming: Naming | undefined,
|
|
63
|
+
action: CommsAction & { action: "machine_name" },
|
|
64
|
+
): Promise<CommsResult> {
|
|
65
|
+
if (naming === undefined) return namingUnavailable();
|
|
66
|
+
const name = await naming.setMachineName(action.name);
|
|
67
|
+
return {
|
|
68
|
+
content:
|
|
69
|
+
name === undefined
|
|
70
|
+
? "This machine no longer asserts a name."
|
|
71
|
+
: `This machine now calls itself "${name}"; peers see it as its own claim, beside any name they gave it.`,
|
|
72
|
+
isError: false,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Names this account (the user principal every one of its machines shares), or clears its name when action.name is omitted. */
|
|
77
|
+
export async function principalName(
|
|
78
|
+
naming: Naming | undefined,
|
|
79
|
+
action: CommsAction & { action: "principal_name" },
|
|
80
|
+
): Promise<CommsResult> {
|
|
81
|
+
if (naming === undefined) return namingUnavailable();
|
|
82
|
+
const name = await naming.setPrincipalName(action.name);
|
|
83
|
+
return {
|
|
84
|
+
content:
|
|
85
|
+
name === undefined
|
|
86
|
+
? "This account no longer asserts a name."
|
|
87
|
+
: `This account now calls itself "${name}"; peers see it as its own claim, beside any name they gave it.`,
|
|
88
|
+
isError: false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Naming (agent-comms#345): the viewer's petnames, the machine's and the account's own self display names, and the one namer every surface formats an id with. A MeshStore collaborator handed to CommsTool the way discovery is, so the tool's naming actions and every listing read through the same object.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here is gossiped by this module. Petnames never leave the viewer's storage; the machine's and the account's names reach peers only as the claims GroupProofs signs and puts in the advert.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
formatDisplayName,
|
|
9
|
+
formatNames,
|
|
10
|
+
type DisplayNameParts,
|
|
11
|
+
} from "./display-name.js";
|
|
12
|
+
import type { Petnames } from "./petnames.js";
|
|
13
|
+
import { requireDisplayName } from "./store.js";
|
|
14
|
+
|
|
15
|
+
export interface NameOptions {
|
|
16
|
+
/** The name the caller already knows the subject asserts (an agent's registered name); otherwise the namer supplies the one it knows, if any. */
|
|
17
|
+
selfName?: string | undefined;
|
|
18
|
+
/** The surface prints the full id beside the name, so the short id is left out (formatNames rather than formatDisplayName), and an id with no name gives the empty string. */
|
|
19
|
+
besideFullId?: boolean | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Formats one id by the display convention. */
|
|
23
|
+
export type Namer = (id: string, options?: Readonly<NameOptions>) => string;
|
|
24
|
+
|
|
25
|
+
/** Formats parts by options: formatNames beside a full id, formatDisplayName otherwise. */
|
|
26
|
+
function formatFor(
|
|
27
|
+
parts: Readonly<DisplayNameParts>,
|
|
28
|
+
options: Readonly<NameOptions> | undefined,
|
|
29
|
+
): string {
|
|
30
|
+
return options?.besideFullId === true
|
|
31
|
+
? formatNames(parts)
|
|
32
|
+
: formatDisplayName(parts);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The namer for a surface with no naming wired: no petnames and no known self names, only what the caller passes. */
|
|
36
|
+
export const plainNamer: Namer = (id, options) =>
|
|
37
|
+
formatFor({ id, selfName: options?.selfName }, options);
|
|
38
|
+
|
|
39
|
+
/** id followed by its names, the form every listing whose entries are acted on by full id prints: two spaces between them, and the id alone when it has no name. */
|
|
40
|
+
export function idWithNames(id: string, namer: Namer): string {
|
|
41
|
+
const names = namer(id, { besideFullId: true });
|
|
42
|
+
return names === "" ? id : `${id} ${names}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface NamingDeps {
|
|
46
|
+
petnames: Petnames;
|
|
47
|
+
/** Every machine's and user principal's verified self display name, keyed by issuer id. */
|
|
48
|
+
issuerNames: () => Promise<Map<string, string>>;
|
|
49
|
+
/** The agents requesterId may see, whose registered names are their self-asserted names. */
|
|
50
|
+
listAgents: (
|
|
51
|
+
requesterId: string,
|
|
52
|
+
) => Promise<readonly Readonly<{ id: string; name: string }>[]>;
|
|
53
|
+
/** Persists this machine's own name (undefined clears it) and re-signs its claim. */
|
|
54
|
+
saveMachineName: (name: string | undefined) => Promise<void>;
|
|
55
|
+
/** Persists this account's own name (undefined clears it) and re-signs its claim. */
|
|
56
|
+
saveUserName: (name: string | undefined) => Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class Naming {
|
|
60
|
+
constructor(private readonly deps: Readonly<NamingDeps>) {}
|
|
61
|
+
|
|
62
|
+
/** Labels deviceHex as name in the viewer's own petnames. Throws INVALID_NAME for an unusable name. Returns the label as stored. */
|
|
63
|
+
setPetname(deviceHex: string, name: string): string {
|
|
64
|
+
return this.deps.petnames.set(deviceHex, name);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Removes the viewer's label for deviceHex. Returns whether there was one. */
|
|
68
|
+
clearPetname(deviceHex: string): boolean {
|
|
69
|
+
return this.deps.petnames.clear(deviceHex);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Every label the viewer has set, keyed by device-id. */
|
|
73
|
+
listPetnames(): Map<string, string> {
|
|
74
|
+
return this.deps.petnames.list();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Names this machine (undefined clears the name). Throws INVALID_NAME for an unusable name. Returns the name as stored. */
|
|
78
|
+
async setMachineName(name: string | undefined): Promise<string | undefined> {
|
|
79
|
+
if (name === undefined) {
|
|
80
|
+
await this.deps.saveMachineName(undefined);
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const parsed = requireDisplayName(name, "A machine name");
|
|
84
|
+
await this.deps.saveMachineName(parsed);
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Names this account (undefined clears the name). Throws INVALID_NAME for an unusable name. Returns the name as stored. */
|
|
89
|
+
async setPrincipalName(
|
|
90
|
+
name: string | undefined,
|
|
91
|
+
): Promise<string | undefined> {
|
|
92
|
+
if (name === undefined) {
|
|
93
|
+
await this.deps.saveUserName(undefined);
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const parsed = requireDisplayName(name, "An account name");
|
|
97
|
+
await this.deps.saveUserName(parsed);
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Every id requesterId has a name for, a petname or a known self name, with those names: the parts a surface outside this process (the dashboard) formats with formatDisplayName. */
|
|
102
|
+
async nameParts(requesterId: string): Promise<DisplayNameParts[]> {
|
|
103
|
+
const petnames = this.deps.petnames.list();
|
|
104
|
+
const selfNames = await this.selfNames(requesterId);
|
|
105
|
+
const ids = new Set([...petnames.keys(), ...selfNames.keys()]);
|
|
106
|
+
return [...ids].map((id) => ({
|
|
107
|
+
id,
|
|
108
|
+
...(petnames.has(id) ? { petname: petnames.get(id) } : {}),
|
|
109
|
+
...(selfNames.has(id) ? { selfName: selfNames.get(id) } : {}),
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Every self-asserted name this store knows, keyed by device-id: verified machine and principal names and the registered names of the agents requesterId may see. */
|
|
114
|
+
private async selfNames(requesterId: string): Promise<Map<string, string>> {
|
|
115
|
+
const names = await this.deps.issuerNames();
|
|
116
|
+
for (const agent of await this.deps.listAgents(requesterId)) {
|
|
117
|
+
names.set(agent.id, agent.name);
|
|
118
|
+
}
|
|
119
|
+
return names;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The namer for requesterId's view right now: the viewer's petnames, then the self names this store knows, then the short id. */
|
|
123
|
+
async namer(requesterId: string): Promise<Namer> {
|
|
124
|
+
const petnames = this.deps.petnames.list();
|
|
125
|
+
const selfNames = await this.selfNames(requesterId);
|
|
126
|
+
return (id, options) => {
|
|
127
|
+
const key = id.toLowerCase();
|
|
128
|
+
return formatFor(
|
|
129
|
+
{
|
|
130
|
+
id,
|
|
131
|
+
petname: petnames.get(key),
|
|
132
|
+
selfName: options?.selfName ?? selfNames.get(key),
|
|
133
|
+
},
|
|
134
|
+
options,
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal nanoid — URL-safe alphanumeric, no dependencies.
|
|
3
|
+
* Uses crypto.getRandomValues for cryptographic randomness.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ALPHABET =
|
|
7
|
+
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-";
|
|
8
|
+
const SIZE = 21;
|
|
9
|
+
|
|
10
|
+
export function nanoid(size: number = SIZE): string {
|
|
11
|
+
const bytes = new Uint8Array(size);
|
|
12
|
+
crypto.getRandomValues(bytes);
|
|
13
|
+
let id = "";
|
|
14
|
+
for (let i = 0; i < size; i++) {
|
|
15
|
+
const byte = bytes[i];
|
|
16
|
+
if (byte === undefined) continue;
|
|
17
|
+
id += ALPHABET.charAt(byte % ALPHABET.length);
|
|
18
|
+
}
|
|
19
|
+
return id;
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This host's network interfaces in the provider-neutral shape the mesh_interfaces action reports (MeshStore.getNetworkInterfaces's body, split out to keep mesh-store.ts under the repo's max-lines cap).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as os from "node:os";
|
|
6
|
+
import type { NetworkInterface } from "./types.js";
|
|
7
|
+
|
|
8
|
+
/** Every address on every interface of this host, as os.networkInterfaces reports them. */
|
|
9
|
+
export function listNetworkInterfaces(): NetworkInterface[] {
|
|
10
|
+
const interfaces = os.networkInterfaces();
|
|
11
|
+
const result: NetworkInterface[] = [];
|
|
12
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
13
|
+
if (addrs === undefined) continue;
|
|
14
|
+
for (const addr of addrs) {
|
|
15
|
+
result.push({
|
|
16
|
+
name,
|
|
17
|
+
address: addr.address,
|
|
18
|
+
family: addr.family,
|
|
19
|
+
internal: addr.internal,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This package's own version, read once from its own package.json and cached -- the single source of truth whoami/update output (see tool.ts) and the npm version-drift check (see version-check.ts) both read from, rather than each independently locating and parsing the file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
|
|
9
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
/** Narrows a parsed package.json into the shape every caller here needs -- shared by getOwnPackageVersion and wire-mesh-core-version.ts's getWireMeshCoreVersion, which reads a dependency's installed package.json the identical way. */
|
|
12
|
+
export function isVersionedPackageJson(
|
|
13
|
+
value: unknown,
|
|
14
|
+
): value is { version: string } {
|
|
15
|
+
if (typeof value !== "object" || value === null) return false;
|
|
16
|
+
if (!("version" in value)) return false;
|
|
17
|
+
return typeof value.version === "string";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let cachedVersion: string | undefined;
|
|
21
|
+
|
|
22
|
+
/** This package's own version, read from the package.json two directories up from this file -- tsconfig's rootDir/outDir mirror src/dist at the same depth, so the relative path resolves identically whether this runs from source or from the compiled dist output. Cached after the first successful read. */
|
|
23
|
+
export function getOwnPackageVersion(): string {
|
|
24
|
+
if (cachedVersion !== undefined) return cachedVersion;
|
|
25
|
+
const packageJsonPath = path.join(moduleDir, "..", "..", "package.json");
|
|
26
|
+
const parsed: unknown = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
27
|
+
if (!isVersionedPackageJson(parsed)) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`package.json at ${packageJsonPath} has no "version" string`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
cachedVersion = parsed.version;
|
|
33
|
+
return cachedVersion;
|
|
34
|
+
}
|