@sentropic/h2a 0.97.0 → 0.97.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 (49) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.mcp.json +1 -1
  4. package/dist/cli-command-map.d.ts.map +1 -1
  5. package/dist/cli-command-map.js +1 -0
  6. package/dist/cli-command-map.js.map +1 -1
  7. package/dist/cli-contract.d.ts.map +1 -1
  8. package/dist/cli-contract.js +11 -3
  9. package/dist/cli-contract.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +115 -10
  12. package/dist/cli.js.map +1 -1
  13. package/dist/index.d.ts +3 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/mcp.d.ts +1 -1
  18. package/dist/mcp.d.ts.map +1 -1
  19. package/dist/mcp.js +1 -0
  20. package/dist/mcp.js.map +1 -1
  21. package/dist/runtime/identity/index.d.ts +1 -1
  22. package/dist/runtime/identity/index.d.ts.map +1 -1
  23. package/dist/runtime/identity/index.js +1 -1
  24. package/dist/runtime/identity/index.js.map +1 -1
  25. package/dist/runtime/identity/live.d.ts +4 -0
  26. package/dist/runtime/identity/live.d.ts.map +1 -1
  27. package/dist/runtime/identity/live.js +3 -3
  28. package/dist/runtime/identity/live.js.map +1 -1
  29. package/dist/runtime/mcp/handlers.d.ts +5 -0
  30. package/dist/runtime/mcp/handlers.d.ts.map +1 -1
  31. package/dist/runtime/mcp/handlers.js +19 -0
  32. package/dist/runtime/mcp/handlers.js.map +1 -1
  33. package/dist/runtime/mcp/server.d.ts +3 -0
  34. package/dist/runtime/mcp/server.d.ts.map +1 -1
  35. package/dist/runtime/mcp/server.js +3 -1
  36. package/dist/runtime/mcp/server.js.map +1 -1
  37. package/dist/runtime/mcp/stdio.d.ts +3 -0
  38. package/dist/runtime/mcp/stdio.d.ts.map +1 -1
  39. package/dist/runtime/mcp/stdio.js +1 -0
  40. package/dist/runtime/mcp/stdio.js.map +1 -1
  41. package/dist/runtime/mcp/tools.d.ts.map +1 -1
  42. package/dist/runtime/mcp/tools.js +13 -0
  43. package/dist/runtime/mcp/tools.js.map +1 -1
  44. package/dist/runtime/send.d.ts +40 -0
  45. package/dist/runtime/send.d.ts.map +1 -0
  46. package/dist/runtime/send.js +108 -0
  47. package/dist/runtime/send.js.map +1 -0
  48. package/package.json +3 -3
  49. package/skills/h2a/SKILL.md +11 -33
@@ -0,0 +1,108 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createEnvelope, signEnvelope, verifyEnvelopeSignature } from "@sentropic/h2a";
3
+ import { assertHostQualifiedAddress, canonicalAddress, resolveRecipient } from "./local-files/paths.js";
4
+ import { listPresence } from "./local-files/presence.js";
5
+ export const H2A_SEND_MAX_MESSAGE_BYTES = 64 * 1024;
6
+ function requiredText(value, label) {
7
+ if (typeof value !== "string" || value.trim().length === 0) {
8
+ throw new Error(`h2a send: ${label} must be a non-empty string`);
9
+ }
10
+ if (value.includes("\0")) {
11
+ throw new Error(`h2a send: ${label} must not contain NUL bytes`);
12
+ }
13
+ return value;
14
+ }
15
+ function resolveRegisteredName(target, registrations) {
16
+ if (target.includes(":") || target.includes("~"))
17
+ return { target, resolved: false };
18
+ const key = target.trim().toLocaleLowerCase();
19
+ const matches = registrations.filter((registration) => registration.name?.trim().toLocaleLowerCase() === key);
20
+ if (matches.length > 1) {
21
+ throw new Error(`'${target}' is ambiguous — ${matches.length} registered agents share it; ` +
22
+ `address the exact instance (${matches.map((entry) => entry.instance).join(", ")}).`);
23
+ }
24
+ return matches.length === 1
25
+ ? { target: matches[0].instance, resolved: true }
26
+ : { target, resolved: false };
27
+ }
28
+ /**
29
+ * Create and persist one authenticated local message. This is the only send
30
+ * primitive used by the CLI and MCP adapters: resolution, signing and the
31
+ * active-key check cannot drift between surfaces.
32
+ */
33
+ export function sendLocalMessage(input) {
34
+ const requestedRecipient = requiredText(input.to, "target").trim();
35
+ const message = requiredText(input.message, "message");
36
+ if (Buffer.byteLength(message, "utf8") > H2A_SEND_MAX_MESSAGE_BYTES) {
37
+ throw new Error(`h2a send: message exceeds ${H2A_SEND_MAX_MESSAGE_BYTES} UTF-8 bytes`);
38
+ }
39
+ const registration = input.store.findInstance(input.signer.instance);
40
+ if (!registration) {
41
+ throw new Error(`h2a send: sender is not registered: ${input.signer.instance}`);
42
+ }
43
+ const registrations = input.store.listInstances();
44
+ const live = listPresence(input.store.paths.root);
45
+ const registeredName = resolveRegisteredName(requestedRecipient, registrations);
46
+ const resolution = resolveRecipient({
47
+ target: registeredName.target,
48
+ liveInstances: live,
49
+ registeredInstances: registrations.map((entry) => entry.instance)
50
+ });
51
+ if (resolution.kind === "refuse" || resolution.kind === "list") {
52
+ throw new Error(resolution.reason);
53
+ }
54
+ let recipient = registeredName.target;
55
+ if (resolution.kind === "deliver-resolved")
56
+ recipient = resolution.recipient;
57
+ if (resolution.kind === "deliver-hint")
58
+ recipient = resolution.liveCandidate;
59
+ assertHostQualifiedAddress(recipient, "recipient");
60
+ const registeredRecipient = registrations.some((entry) => canonicalAddress(entry.instance) === canonicalAddress(recipient));
61
+ const liveRecipient = live.filter((session) => canonicalAddress(session.instance) === canonicalAddress(recipient));
62
+ if (!registeredRecipient && liveRecipient.length === 0) {
63
+ throw new Error(`h2a send: '${requestedRecipient}' matches no live or registered agent; resolve the peer first.`);
64
+ }
65
+ const role = (registration.roles[0] ?? "AGENTS");
66
+ const scope = registration.scopes[0] ?? "scope:default";
67
+ const createdAt = new Date((input.now ?? Date.now)()).toISOString();
68
+ const unsigned = createEnvelope({
69
+ id: `env:send:${(input.randomId ?? randomUUID)()}`,
70
+ type: "event",
71
+ actor: { instance: input.signer.instance, role, scope },
72
+ target: { instance: recipient },
73
+ body: { kind: "message", topic: "MESSAGE", text: message },
74
+ createdAt
75
+ });
76
+ const envelope = signEnvelope(unsigned, {
77
+ by: input.signer.instance,
78
+ privateKeyPem: input.signer.privateKeyPem
79
+ });
80
+ const keyIsActive = input.store
81
+ .listInstanceKeys(input.signer.instance)
82
+ .some((publicKeyPem) => verifyEnvelopeSignature(envelope, publicKeyPem, { by: input.signer.instance }));
83
+ if (!keyIsActive) {
84
+ throw new Error(`h2a send: private key is not active for ${input.signer.instance}`);
85
+ }
86
+ input.store.putInboxMessage(recipient, envelope);
87
+ const resolutionKind = registeredName.resolved ? "registered-name" : resolution.kind;
88
+ const reason = registeredName.resolved
89
+ ? `registered name resolved to ${recipient}.`
90
+ : resolution.kind === "deliver-hint"
91
+ ? `live alias resolved directly to ${recipient}.`
92
+ : "reason" in resolution
93
+ ? resolution.reason
94
+ : undefined;
95
+ return {
96
+ ok: true,
97
+ from: input.signer.instance,
98
+ requestedRecipient,
99
+ recipient,
100
+ recipientLive: liveRecipient.length > 0,
101
+ freshSessions: liveRecipient.length,
102
+ resolution: resolutionKind,
103
+ dormant: liveRecipient.length === 0,
104
+ ...(reason ? { reason } : {}),
105
+ envelope
106
+ };
107
+ }
108
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/runtime/send.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentropic/h2a",
3
- "version": "0.97.0",
3
+ "version": "0.97.1",
4
4
  "description": "h2a — the unified CLI + core for human-to-agent coordination.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -55,10 +55,10 @@
55
55
  "@sentropic/cluster-mesh": "0.9.0",
56
56
  "@sentropic/contracts": "^0.3.0",
57
57
  "@sentropic/events": "^0.2.0",
58
- "@sentropic/track": "^0.97.0",
58
+ "@sentropic/track": "^0.97.1",
59
59
  "hono": "^4.13.7"
60
60
  },
61
61
  "peerDependencies": {
62
- "@sentropic/h2a-runtime": "^0.97.0"
62
+ "@sentropic/h2a-runtime": "^0.97.1"
63
63
  }
64
64
  }
@@ -148,7 +148,7 @@ Return shape (all cases exit 0 except user errors):
148
148
 
149
149
  ### `/h2a send <peer> "<text>"`
150
150
 
151
- Compose and route an envelope to a named peer.
151
+ Send an authenticated message to a named peer through the native core tool.
152
152
 
153
153
  **Resolve-before-send rule (0.59.0+):** Always resolve the peer to its LIVE full id (`host:label:uuid12`) via `h2a_discover_sessions` BEFORE sending. A bare `host:label` sent to an ambiguous target (>1 live agent sharing that alias) or a phantom target (0 live, 0 registered) is now **REFUSED** by `h2a_inbox put` / `h2a` CLI with exit 1. Never invent a sub-label. The safe pattern is: discover → pick the exact live `host:label:uuid12` → send to that. A bare alias is only acceptable for a dormant/wake-drop or exactly 1 live match (the tool surfaces the live candidate in `liveCandidate`).
154
154
 
@@ -160,41 +160,19 @@ Steps:
160
160
  - If the user names a peer by PURPOSE/role, call `h2a_discover_sessions` with `{ scope: "<purpose>" }`. If the user names a peer by FRIENDLY NAME, call `h2a_discover_sessions` with `{ name: "<substring>" }`. NEVER grep the registry for a name: instance ids are workspace-derived (`host:slug(workspace):uuid`) so a text search misses. If several match, list and ask; if none, the agent hasn't advertised that scope/name.
161
161
  - If `<peer>` is missing, list discover and ask the user to pick.
162
162
  2. If `"<text>"` is missing, prompt the user for the content.
163
- 3. Compose an `H2AEnvelope` JSON:
164
- ```json
165
- {
166
- "protocol": "sentropic.h2a",
167
- "version": "0.1",
168
- "id": "env:<epoch-ms>:<4hex>",
169
- "type": "event",
170
- "actor": {
171
- "instance": "<this-agent-instance>",
172
- "role": "<this-agent-role-or-AGENTS>",
173
- "scope": "<shared-scope-or-default>"
174
- },
175
- "body": { "kind": "message", "text": "<text>" },
176
- "createdAt": "<ISO-8601-now>"
177
- }
178
- ```
179
- 4. **Hard rule: the recipient MUST be host-qualified (`<host>:<label>`, e.g. `claude:radar-immobilier`). A bare label (e.g. `radar-immobilier`) is rejected — the same label can live on several hosts (claude, codex, …), so a bare label is ambiguous and routes to an orphan inbox nobody reads.**
180
-
181
- Call `h2a_inbox` with `{ action: "put", instance: "<peer>", envelope }`.
182
-
183
- **Conversation threading (optional, lightweight):** To continue an existing back-and-forth as a thread, add two top-level fields to the envelope JSON before putting it:
184
- - `"threadId": "<id>"` — reuse the `threadId` from the peer's previous envelope, or mint a fresh one as `thr:<epoch-ms>:<4hex>` to start a new thread.
185
- - `"replyTo": "<prev-envelope-id>"` — the `id` of the envelope you are replying to.
186
-
187
- To reconstruct the ordered fil of a thread (for supervision or before opening a formal negotiation):
188
- ```sh
189
- h2a thread --id <threadId> --instance <self-instance> --root <root>
190
- ```
191
- This returns the envelopes (from your inbox + outbox) that share that `threadId`, sorted ascending by `createdAt`, deduped. No new store — storage is derived on the fly.
192
-
193
- 5. **Report honestly per the target's liveness** (h2a writes the inbox unconditionally — it does NOT yet error on a dead target, so YOU must say which it was):
163
+ 3. Call `h2a_send` with exactly `{ to: "<peer>", message: "<text>" }`.
164
+ The local MCP sidecar supplies its trusted signing identity; never request,
165
+ construct, or pass a private key or sender field. The tool resolves the peer,
166
+ constructs the canonical envelope, verifies the sender's active key, and only
167
+ then writes the inbox. If the tool is unavailable because the sidecar has no
168
+ auto-open signing identity, report that error; do not fall back to unsigned
169
+ `h2a_inbox put`.
170
+ 4. **Report honestly from the tool result:**
194
171
  - target was **live** in discover → *"Delivered to `<peer>` (live) — push fires if subscribed."*
195
172
  - target was **NOT live** → *"`<peer>` is dormant — envelope deposited for its wake (no live session; it will only see this once woken)."* Never claim "Delivered" to a dormant/unknown peer.
196
173
 
197
- For richer payloads (file pointer, deliverable, status update), set `body.kind` to a category and add the relevant fields; ask the user if ambiguous.
174
+ For richer payloads, use the formal negotiation/inbox contracts; `h2a_send` is
175
+ deliberately the small signed text-message primitive.
198
176
 
199
177
  ### `/h2a receive`
200
178