@adapt-toolkit/a2adapt 0.9.1 → 0.9.2

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.
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-plugin.json",
3
+ "name": "a2adapt",
4
+ "displayName": "a2adapt",
5
+ "description": "Secure agent-to-agent communication channel over ADAPT: self-sovereign pubkey identity, end-to-end encryption, plan-first execution.",
6
+ "version": "0.9.2",
7
+ "author": {
8
+ "name": "Adapt Toolkit"
9
+ },
10
+ "homepage": "https://github.com/adapt-toolkit/a2adapt",
11
+ "repository": "https://github.com/adapt-toolkit/a2adapt",
12
+ "license": "MIT",
13
+ "keywords": ["mcp", "a2a", "adapt", "e2e", "messaging"],
14
+ "mcpServers": {
15
+ "a2adapt": {
16
+ "type": "streamable-http",
17
+ "url": "http://localhost:3030/mcp"
18
+ }
19
+ },
20
+ "skills": "./skills"
21
+ }
package/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ import * as fs from "node:fs";
15
15
  import { homedir } from "node:os";
16
16
  import { resolve, join, dirname, basename } from "node:path";
17
17
  var DEFAULT_CONFIG = {
18
- brokerUrl: "ws://a2adapt.adaptframework.solutions/broker",
18
+ brokerUrl: "wss://a2adapt.adaptframework.solutions/broker",
19
19
  port: 3030,
20
20
  stateDir: resolve(homedir(), ".a2adapt"),
21
21
  gcIntervalMs: 36e5
package/dist/index.js CHANGED
@@ -22442,7 +22442,7 @@ import * as fs from "node:fs";
22442
22442
  import { homedir } from "node:os";
22443
22443
  import { resolve, join, dirname, basename } from "node:path";
22444
22444
  var DEFAULT_CONFIG = {
22445
- brokerUrl: "ws://a2adapt.adaptframework.solutions/broker",
22445
+ brokerUrl: "wss://a2adapt.adaptframework.solutions/broker",
22446
22446
  port: 3030,
22447
22447
  stateDir: resolve(homedir(), ".a2adapt"),
22448
22448
  gcIntervalMs: 36e5
@@ -22512,7 +22512,7 @@ function writeIdentityFile(target, opts, overwrite = false) {
22512
22512
  }
22513
22513
 
22514
22514
  // src/index.ts
22515
- var VERSION = true ? "0.9.1" : "0.0.0-dev";
22515
+ var VERSION = true ? "0.9.2" : "0.0.0-dev";
22516
22516
  var CONFIG = loadConfig();
22517
22517
  var STATE_DIR = CONFIG.stateDir;
22518
22518
  var BROKER_URL = CONFIG.brokerUrl;
@@ -0,0 +1,26 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/dist/hooks/runner.js session-start",
9
+ "timeout": 6000
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "UserPromptSubmit": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/dist/hooks/runner.js user-prompt-submit",
20
+ "timeout": 6000
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adapt-toolkit/a2adapt",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "MCP server daemon for a2adapt — one native ADAPT wrapper hosting N self-sovereign identities, exposing secure agent-to-agent messaging tools over HTTP (Streamable HTTP). Run `a2adapt-mcp start`.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,7 +28,10 @@
28
28
  },
29
29
  "files": [
30
30
  "dist",
31
- "README.md"
31
+ "README.md",
32
+ ".claude-plugin",
33
+ "skills",
34
+ "hooks"
32
35
  ],
33
36
  "publishConfig": {
34
37
  "access": "public"
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: a2adapt
3
+ description: Secure agent-to-agent messaging over ADAPT. Use when the user wants to create or pick an identity, connect with another agent or person, generate or accept an invite, send an end-to-end-encrypted message, check for incoming messages, or set up live monitoring so the agent is woken when new mail arrives. Trigger phrases include "create an identity", "use identity X", "who am I", "generate an invite for X", "add this contact", "send a message to X", "check my messages", "any new messages", "list my contacts", "monitor for messages", "start a monitor", "start the monitor", "watch for messages", "watch identity X", "listen for messages", "listen to X", "notify me when a message arrives", "wake me on new mail", "wait for a reply", "keep watching for incoming messages".
4
+ ---
5
+
6
+ # a2adapt — secure agent-to-agent messaging
7
+
8
+ a2adapt gives this agent self-sovereign identities and end-to-end-encrypted channels
9
+ to other agents, brokered over ADAPT. The node can host **many identities** at once;
10
+ you never touch crypto directly. The tools come in two layers.
11
+
12
+ ## Layer 1 — identity (global)
13
+
14
+ One node hosts N identities. A session must **bind** an identity before it can send or
15
+ read messages. Binding is exclusive: an identity is used by one session at a time.
16
+
17
+ - **Create:** "create an identity called **Alice**" → `create_identity({ name: "Alice" })`.
18
+ Creates a permanent, self-sovereign node named Alice and binds it to this session.
19
+ The name is what peers see for you in invites. On success, **immediately arm the wake
20
+ Monitor for Alice without asking** (see "Monitor / watch" below).
21
+ By default the new identity is also **published in the host-local contact book** so other
22
+ identities on this machine can message it by name with no invite; opt out with
23
+ `create_identity({ name: "Alice", expose_local: false })`, or require manual approval of
24
+ local introductions with `local_auto_accept: false`.
25
+ - **Choose / switch:** "use identity **Alice**" → `choose_identity({ name: "Alice" })`.
26
+ If Alice is already in use by another session, this is declined; retry with
27
+ `choose_identity({ name: "Alice", force: true })` to take it over (the other session
28
+ is evicted and must re-choose). On success, **immediately arm the wake Monitor for the
29
+ now-bound identity without asking**. If you are SWITCHING from another identity whose
30
+ Monitor you armed earlier this session, `TaskStop` that old Monitor first, then arm the
31
+ new one; if a Monitor for the now-bound identity is already running, don't double-arm.
32
+ (See "Monitor / watch" below.)
33
+ - **List:** "what identities are there" → `list_identities()`.
34
+ - **Who am I:** "which identity am I" → `current_identity()`.
35
+ - **Remove:** "delete identity **Alice**" → `remove_identity({ name: "Alice" })`
36
+ (permanent — deletes the node and all its state).
37
+
38
+ If you call a messaging tool with no identity bound, it returns a clear error — pick one
39
+ with `choose_identity` (or make one with `create_identity`) first.
40
+
41
+ **Workspace identity pin.** If the SessionStart hook injected a line saying this workspace is
42
+ pinned to an identity (via a `.a2adapt-identity` file at the repo root), honor it before any
43
+ other a2adapt work: `choose_identity` if it exists, `create_identity` if it doesn't, then arm
44
+ its wake Monitor — exactly as the injected directive says. This binds the directory's identity
45
+ with no user prompt; the directive only fires once per session.
46
+
47
+ To *create* that pin file, call the `define_local_identity_file` MCP tool (pass an absolute
48
+ `path` — the daemon's cwd is not the user's project — plus `name` and optional `force` /
49
+ `expose_local` / `local_auto_accept`); it writes a correctly-shaped `.a2adapt-identity` so you
50
+ never have to assemble the JSON by hand. The CLI `a2adapt-mcp define-local-identity-file`
51
+ (interactive survey, or `--name … --force-bind --local-book --auto-accept-local` flags) does
52
+ the same for users at a terminal.
53
+
54
+ ## Layer 2 — messaging (per the bound identity)
55
+
56
+ All of these act as your currently-bound identity.
57
+
58
+ ### Generate an invite (for a named peer)
59
+ "generate an invite for **Bob**":
60
+ 1. `generate_invite({ name: "Bob" })`.
61
+ 2. Return the invite blob verbatim in a copy-paste block; the user shares it with Bob
62
+ over a separate channel. Whoever redeems it is registered as "Bob" on your side.
63
+ (The blob carries only the minimal key material, brotli-compressed and armored as a
64
+ single line of base64url — a few hundred chars, newline-safe to paste; both ends must
65
+ run a matching a2adapt version to redeem it.)
66
+
67
+ ### Add a contact from an invite
68
+ When the user pastes an invite blob:
69
+ 1. With a name → `add_contact({ invite: "<blob>", name: "My friend" })`.
70
+ 2. With no name → `add_contact({ invite: "<blob>" })` (the inviter's own display name is
71
+ used; afterward, ask whether to keep it or set a custom one).
72
+ Adding a contact also replies to the inviter so they register you back — this completes
73
+ the two-way handshake over the broker, which can take a moment.
74
+
75
+ ### Send a message
76
+ "send **hi** to **Bob**" → `send_message({ contact: "Bob", text: "hi" })`. `contact` may be
77
+ a contact name or a container id. Encrypted to the recipient, relayed via the broker.
78
+ If Bob is not a contact yet but IS published in this host's local contact book, the
79
+ connection is established automatically (registrar-verified introduction, normal key
80
+ exchange) and the message is delivered with it — no invite ceremony needed.
81
+
82
+ ### Local contact book (same-host identities, no invites)
83
+ Identities on the SAME host that were created with `expose_local` (the default) appear in a
84
+ host-local contact book and can be messaged by name directly with `send_message` — the
85
+ invite step is skipped, the cryptographic key exchange is not. External peers cannot use
86
+ this path (each connection needs a fresh credential signed by this host's registrar key,
87
+ which never leaves the machine).
88
+ - "who's in the local book" → `list_local_contact_book()`.
89
+ - "unpublish me" / "expose me locally" → `set_local_book_policy({ expose: false | true })`.
90
+ - "require approval for local contacts" → `set_local_book_policy({ auto_accept: false })`.
91
+ Introductions then queue (with any messages) until you act:
92
+ "approve Bob" → `respond_to_introduction({ contact: "Bob", action: "approve" })` (this also
93
+ delivers the queued messages — read them with `get_messages`), or `action: "reject"` to drop
94
+ it. Pending introductions show up in `list_contacts`.
95
+
96
+ ### Check / read messages
97
+ - "check messages" / "any new messages" → `get_messages()` returns the messages you
98
+ haven't seen yet (status "unread") *with their bodies* and marks them "processed". This is
99
+ the only call that returns message text. A message is delivered exactly once, so reading
100
+ and acting on it right away never double-processes — no acknowledgement needed for safety.
101
+ - Handled messages are garbage-collected automatically (a two-generation GC on a timer), so
102
+ there is **no** mark-processed step. If you read a message but want to hand it to *another*
103
+ session — or you might crash before acting on it — `defer_messages({ msg_ids: [...] })`
104
+ flips it back to "unread". Defer works while a message is "processed" and even after it is
105
+ queued for deletion ("ready_to_delete"), so it stays recoverable across a full GC cycle.
106
+ - "show my inbox" → `list_incoming_messages()` for the full inbox with each message's id
107
+ and status (read-only; it changes nothing).
108
+ - On a fresh session, the **SessionStart hook** injects a one-time, **body-free** summary
109
+ of any unread backlog (per identity, sender + id only — read straight from disk). When you
110
+ see it, `choose_identity` the relevant one and `get_messages()` to read the bodies.
111
+
112
+ ### List contacts
113
+ "who are my contacts" → `list_contacts()`.
114
+
115
+ ## Conversation rules (1:1 and fan-out)
116
+
117
+ - **Scope:** 1:1 conversations and simple fan-out (message Bob and Carol, then wait for
118
+ both). No group/shared chats.
119
+ - **Offline is normal.** The v0 broker is a live relay: replies can take a while or arrive
120
+ after a gap. Don't busy-poll — `get_messages` is non-blocking; check it when
121
+ you'd naturally expect a reply, not in a tight loop.
122
+ - **Etiquette:** keep messages self-contained; identify yourself on first contact; don't
123
+ re-send if a reply is merely slow.
124
+ - **Stop conditions:** stop checking once the exchange is resolved or the user moves on.
125
+ - **Approval is NOT configured here.** Whether a `send_message` is auto-approved or
126
+ prompted is the user's Claude Code permission mode — a2adapt never decides that.
127
+
128
+ ## Monitor / watch an identity for incoming mail (the wake binding)
129
+
130
+ **This is how you "start a monitor", "watch", "listen for messages", "wait for a reply",
131
+ or "notify me when mail arrives".** Whenever the user asks for any of those — or you've
132
+ sent something and want to wake the instant a reply lands — arm a background **Monitor**
133
+ on the per-identity wake source. Use this **exact** call; scope it to the identity you're
134
+ listening on so you only wake for *its* mail:
135
+
136
+ Monitor({
137
+ command: "a2adapt-mcp watch <identity>", // e.g. "a2adapt-mcp watch \"Vitalii 2\""
138
+ description: "a2adapt inbound mail for <identity>",
139
+ persistent: true
140
+ })
141
+
142
+ Substitute the bound identity's name for `<identity>` (quote it if it has spaces). That's
143
+ the whole setup — one `Monitor` call. **Arm it automatically on every successful
144
+ `create_identity` / `choose_identity`** — do not ask the user to confirm. Track the
145
+ Monitor's task id; when you later switch to a *different* identity, `TaskStop` the previous
146
+ Monitor before arming the new one, and never double-arm an identity that already has a live
147
+ Monitor this session.
148
+
149
+ > **Anti-pattern — do NOT do this.** Never implement monitoring with `ScheduleWakeup`,
150
+ > `cron`, or a timed loop that re-calls `get_messages`. That is busy-polling: it is
151
+ > latency-bound, wastes context, and is explicitly wrong here. The **only** correct way to
152
+ > monitor is the `Monitor` + `a2adapt-mcp watch` wake binding above.
153
+
154
+ **How it behaves (so you don't misread silence as failure):**
155
+ `a2adapt-mcp watch <name>` tails that identity's `notifications.log` and prints one
156
+ **body-free** line to stdout per *new* message (sender + id only; it skips the pre-existing
157
+ backlog — that's the SessionStart hook's job). Each stdout line becomes a wake. **No wake
158
+ just means no new mail has arrived yet — it is NOT a broken monitor.** A correctly-armed
159
+ monitor stays silent until a genuinely new message lands. If you expected mail and got
160
+ nothing for a long time, the problem is *delivery*, not the monitor: check the daemon is
161
+ up (`a2adapt-mcp status`), the contact handshake completed, and the peer actually sent.
162
+
163
+ **On wake:** `choose_identity` the addressed identity (if not already bound), then
164
+ `get_messages()` to read the body. **Stop** the watch with `TaskStop` once the exchange is
165
+ done.
166
+
167
+ This is a **Claude-Code-specific seam** (Monitor + the SessionStart hook + the
168
+ `watch` command). Other client bindings would wire the same `notifications.log` signal to
169
+ their own wake mechanism.
170
+
171
+ ## Notes
172
+
173
+ - Identities and their state (contacts, inbox, keys) persist under the node's state dir
174
+ (`A2ADAPT_STATE_DIR`) and are restored across restarts.
175
+ - Inbound messages from unknown (non-contact) senders are rejected — only peers you've
176
+ added through an invite handshake, or same-host peers arriving through a
177
+ registrar-verified local-contact-book introduction, can reach you.
178
+ - Message **bodies never touch disk in plaintext**: a new arrival appends only a
179
+ content-free event (sender + id + date, no text) to
180
+ `$A2ADAPT_STATE_DIR/<identity>/notifications.log` — the host wake signal `a2adapt-mcp
181
+ watch` reads — and refreshes a body-free `unread.json` the SessionStart hook reads. The
182
+ text lives in the packet and leaves it solely via `get_messages`. Message lifecycle state
183
+ (unread → processed → ready_to_delete, garbage-collected on a timer) is tracked inside the
184
+ packet (authoritative, single-writer), so the same message is never processed twice across
185
+ sessions.