agents-can-communicate 0.1.17 → 0.2.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/README.md +76 -138
- package/SECURITY.md +31 -0
- package/bin/acc-hook.mjs +96 -12
- package/bin/acc-mcp.mjs +6 -2
- package/bin/acc.mjs +6 -1
- package/docs/ADAPTER_AUTHORING.md +172 -0
- package/docs/ARCHITECTURE.md +131 -0
- package/docs/CAPABILITIES.md +105 -197
- package/docs/CLI.md +157 -0
- package/docs/CONCEPTS.md +134 -0
- package/docs/CONFIGURATION.md +143 -0
- package/docs/DESIGN_DECISIONS.md +89 -0
- package/docs/GETTING_STARTED.md +145 -0
- package/docs/GLOSSARY.md +26 -0
- package/docs/MCP.md +94 -0
- package/docs/PROTOCOL.md +200 -0
- package/docs/RELEASING.md +109 -0
- package/docs/SECURITY_MODEL.md +131 -0
- package/docs/TROUBLESHOOTING.md +102 -0
- package/docs/WHY_ACC.md +61 -0
- package/docs/index.md +42 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/certification.json +78 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/PreToolUse-Edit.json +19 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/PreToolUse.json +17 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/SessionEnd.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/SessionStart.json +7 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/UserPromptSubmit.json +9 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/certification-provenance.json +77 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.252.json +19 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +9 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +80 -160
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +15 -5
- package/node_modules/@agents-can-communicate/adapter-codex/certification.json +117 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/PreToolUse.json +14 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/SessionEnd.json +7 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/SessionStart.json +9 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/UserPromptSubmit.json +10 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/certification-provenance.json +66 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.0.json +19 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +8 -1
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +80 -160
- package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +21 -12
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/certification.json +52 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md +80 -160
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/BeforeAgent.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/BeforeTool-shell.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/BeforeTool.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/SessionEnd.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/SessionStart.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/certification-provenance.json +66 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +8 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/adapter.mjs +10 -4
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +4 -2
- package/node_modules/@agents-can-communicate/adapter-grok/certification.json +3 -0
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +14 -0
- package/node_modules/@agents-can-communicate/adapter-grok/plugin/hooks/hooks.json +61 -0
- package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +152 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/adapter.mjs +61 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/hooks.mjs +127 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +101 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/certification.json +52 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/PreToolUse-Bash.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/PreToolUse-Write.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/SessionHeartbeat.json +7 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/SessionStart.json +9 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/UserPromptSubmit.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/certification-provenance.json +66 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +8 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +80 -160
- package/node_modules/@agents-can-communicate/adapter-kimi/src/adapter.mjs +10 -4
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +34 -18
- package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +158 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +139 -224
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +7 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +13 -4
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +13 -29
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +3 -0
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +5 -6
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +12 -3
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +109 -109
- package/node_modules/@agents-can-communicate/cli/src/session-owner.mjs +1 -1
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/attention.mjs +106 -0
- package/node_modules/@agents-can-communicate/core/src/conversations.mjs +276 -0
- package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +81 -0
- package/node_modules/@agents-can-communicate/core/src/finish-retries.mjs +97 -0
- package/node_modules/@agents-can-communicate/core/src/inbox.mjs +118 -0
- package/node_modules/@agents-can-communicate/core/src/index.mjs +3 -2
- package/node_modules/@agents-can-communicate/core/src/intents.mjs +0 -1
- package/node_modules/@agents-can-communicate/core/src/ports.mjs +3 -2
- package/node_modules/@agents-can-communicate/core/src/receipts.mjs +109 -0
- package/node_modules/@agents-can-communicate/core/src/service.mjs +14 -10
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +70 -20
- package/node_modules/@agents-can-communicate/core/src/status.mjs +11 -9
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +3 -258
- package/node_modules/@agents-can-communicate/delivery-router/package.json +12 -0
- package/node_modules/@agents-can-communicate/delivery-router/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +117 -0
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +20 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +156 -60
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +23 -7
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +20 -5
- package/node_modules/@agents-can-communicate/installer/src/index.mjs +3 -2
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +108 -12
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +19 -2
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/src/input-validator.mjs +79 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/resources.mjs +23 -28
- package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +109 -71
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +74 -93
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/config.mjs +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/conversations.mjs +64 -0
- package/node_modules/@agents-can-communicate/protocol/src/index.mjs +4 -1
- package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +49 -90
- package/node_modules/@agents-can-communicate/protocol/src/states.mjs +13 -40
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/active-journal.mjs +230 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +77 -28
- package/node_modules/@agents-can-communicate/storage-filesystem/src/identity.mjs +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/journal.mjs +83 -35
- package/node_modules/@agents-can-communicate/storage-filesystem/src/retention.mjs +112 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-file.mjs +18 -8
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +86 -28
- package/node_modules/@agents-can-communicate/storage-filesystem/src/writer-mutex.mjs +121 -27
- package/package.json +22 -1
- package/node_modules/@agents-can-communicate/core/src/communication.mjs +0 -334
- package/node_modules/@agents-can-communicate/core/src/notify.mjs +0 -95
- package/node_modules/@agents-can-communicate/core/src/tasks.mjs +0 -244
- package/node_modules/@agents-can-communicate/core/src/workstreams.mjs +0 -109
package/README.md
CHANGED
|
@@ -4,171 +4,109 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
work.**
|
|
7
|
+
ACC connects independently opened AI sessions so they can discover, ask, answer, acknowledge, and hand off without becoming one managed agent team.
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
It is a local-first communication layer for sessions you already run. Each session keeps
|
|
10
|
+
its own client, model, checkout, permissions, context, and human direction. ACC supplies a
|
|
11
|
+
shared room with presence, intent, narrow file claims, durable conversation threads, and
|
|
12
|
+
truthful per-recipient receipts. It does not launch, steer, supervise, or terminate agents.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
The project uses Node's standard library with **zero runtime dependencies**. Coordination
|
|
15
|
+
state stays in platform app data outside the repository, and ACC never collects raw
|
|
16
|
+
transcripts.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
flowchart TB
|
|
19
|
-
Y["You<br/>set direction"]
|
|
20
|
-
A["Agent session A"]
|
|
21
|
-
B["Agent session B"]
|
|
22
|
-
C["Agent session C"]
|
|
23
|
-
R["ACC shared room<br/>presence · intent · claims<br/>messages · work requests"]
|
|
24
|
-
O["Coordinated work<br/>with shared context"]
|
|
25
|
-
|
|
26
|
-
Y --> A
|
|
27
|
-
Y --> B
|
|
28
|
-
Y --> C
|
|
29
|
-
A <--> R
|
|
30
|
-
B <--> R
|
|
31
|
-
C <--> R
|
|
32
|
-
R --> O
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## You opened more agents. You became the coordinator.
|
|
36
|
-
|
|
37
|
-
One session implements. Another writes tests. A third reviews. At first, more agents means
|
|
38
|
-
more work gets done.
|
|
18
|
+
## Stop relaying between windows
|
|
39
19
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
20
|
+
One session finds that removing `item.drive` will break another area. A second session is
|
|
21
|
+
working there, but neither client knows the other exists. Without ACC, the warning stops at
|
|
22
|
+
you: copy it to the other window, copy the answer back, and repeat for every question.
|
|
43
23
|
|
|
44
|
-
ACC
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
## A handoff the agents carry themselves
|
|
49
|
-
|
|
50
|
-
One agent finishes building a feature and sees that its final tests still need work. It
|
|
51
|
-
asks the testing agent to take over, including a short summary of what is ready and what
|
|
52
|
-
remains.
|
|
24
|
+
With ACC, the first session sends an attributed question. The second retrieves it, replies
|
|
25
|
+
in the same thread, and thereby acknowledges the original. The first retrieves the answer.
|
|
26
|
+
The message is durable throughout; neither model gains authority over the other.
|
|
53
27
|
|
|
54
28
|
```mermaid
|
|
55
29
|
flowchart LR
|
|
56
|
-
A["
|
|
57
|
-
B
|
|
58
|
-
|
|
59
|
-
|
|
30
|
+
A["session A — independently opened"] <--> R["ACC room<br/>presence · messages · receipts · claims"]
|
|
31
|
+
B["session B — independently opened"] <--> R
|
|
32
|
+
U["You — direction and authority"] --> A
|
|
33
|
+
U --> B
|
|
60
34
|
```
|
|
61
35
|
|
|
62
|
-
The
|
|
63
|
-
|
|
64
|
-
direction and review the outcome; the agents carry the context between them.
|
|
36
|
+
The product's canonical activation event is simple: a second independently opened session
|
|
37
|
+
completes a useful acknowledged interaction without the human copying peer message content.
|
|
65
38
|
|
|
66
39
|
## Install
|
|
67
40
|
|
|
68
|
-
|
|
69
|
-
run:
|
|
41
|
+
On macOS or Linux with Node 24 or newer:
|
|
70
42
|
|
|
71
43
|
```bash
|
|
72
44
|
npm install -g agents-can-communicate
|
|
73
45
|
acc install
|
|
74
46
|
```
|
|
75
47
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`acc install` names every client setting it activated and how to undo it.
|
|
82
|
-
|
|
83
|
-
Open or restart your agent client inside a project. Each new session joins that project's
|
|
84
|
-
room automatically. Open another session in the same project and the two can coordinate;
|
|
85
|
-
run `acc status` from the project directory whenever you want to see the room yourself.
|
|
86
|
-
|
|
87
|
-
ACC stores coordination data in the standard application-data location for your system.
|
|
88
|
-
The defaults are `~/Library/Application Support/acc` on macOS and `~/.local/share/acc` on
|
|
89
|
-
Linux. `XDG_DATA_HOME` relocates the Linux default; `ACC_DATA_HOME` overrides either
|
|
90
|
-
platform, as described in [configuration](docs/CONFIGURATION.md). Project files stay
|
|
91
|
-
unchanged. Git worktrees from one repository share a room, and plain folders receive the
|
|
92
|
-
same coordination experience.
|
|
93
|
-
|
|
94
|
-
Keep ACC current with `acc update --apply`. It installs the latest release and refreshes
|
|
95
|
-
the client integrations together. `acc doctor` points to that action when their versions
|
|
96
|
-
drift.
|
|
97
|
-
|
|
98
|
-
Run `acc uninstall` to remove ACC's client integrations. Settings you changed remain
|
|
99
|
-
yours.
|
|
100
|
-
|
|
101
|
-
## What changes after installation
|
|
102
|
-
|
|
103
|
-
**Agents know who is around.** Each session can see the other participants, their current
|
|
104
|
-
focus, and the files they have claimed.
|
|
48
|
+
Restart the clients whose hooks were installed; Codex also requires trusting the plugin.
|
|
49
|
+
Then open two sessions in the same repository or plain directory as usual. They remain
|
|
50
|
+
independent and join the same ACC workspace. Use `acc doctor` to see exact versions,
|
|
51
|
+
installation health, and delivery downgrades.
|
|
105
52
|
|
|
106
|
-
|
|
107
|
-
|
|
53
|
+
Runtime state lives in `~/Library/Application Support/acc` on macOS or the XDG data
|
|
54
|
+
directory on Linux. `ACC_DATA_HOME` can override it, but ACC refuses a location inside a
|
|
55
|
+
workspace. Git is optional; worktrees of one repository share awareness.
|
|
108
56
|
|
|
109
|
-
|
|
110
|
-
the intended agent across session restarts, and results return to the agent that asked.
|
|
57
|
+
## The communication loop
|
|
111
58
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
Shared context appears when another participant or pending handoff makes it useful.
|
|
117
|
-
|
|
118
|
-
## Fits the workflow you already have
|
|
119
|
-
|
|
120
|
-
Your agent client remains the place where sessions start, permissions are granted, and
|
|
121
|
-
work happens. ACC joins at natural moments, shares the relevant context, and returns
|
|
122
|
-
control to the client. Forward progress stays the priority during any coordination delay.
|
|
59
|
+
```bash
|
|
60
|
+
# Session A publishes awareness and reserves only what it will edit.
|
|
61
|
+
acc work --summary "changing the item schema" --mode edit --hint 'file:src/item.mjs'
|
|
62
|
+
acc claim --resource 'file:src/item.mjs' --reason "changing the item schema"
|
|
123
63
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
64
|
+
# Session A asks a participant named by `acc status`.
|
|
65
|
+
acc message --to models --type question --subject "item.drive" \
|
|
66
|
+
--body "Can your code stop reading item.drive before I remove it?"
|
|
127
67
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
68
|
+
# Session B reads and answers the exact message.
|
|
69
|
+
acc inbox --message message_x
|
|
70
|
+
acc reply --message message_x --body "Yes. Commit abc123 removes the final read."
|
|
131
71
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
72
|
+
# Either session records a handoff while context is still available.
|
|
73
|
+
acc finish --goal "remove item.drive" --status complete \
|
|
74
|
+
--completed "schema and reader updated" --remaining "none"
|
|
75
|
+
```
|
|
136
76
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
77
|
+
Messages commit before any delivery attempt. `queued`, `offered`, `retrieved`, and
|
|
78
|
+
`acknowledged` describe different observable facts: an offer is not a read, retrieval is
|
|
79
|
+
not proof of model attention, and a reply resolves communication rather than proving a
|
|
80
|
+
requested task finished.
|
|
140
81
|
|
|
141
|
-
|
|
82
|
+
Durable inbox recovery is the baseline for every client. Certified next-turn injection can
|
|
83
|
+
reduce the polling, but only for the exact client version and platform captured by the
|
|
84
|
+
adapter. Native live push is opt-in and would also require a current reachable binding;
|
|
85
|
+
**no shipped adapter currently has passing live-push certification**. The failed Codex
|
|
86
|
+
0.152.0 and Claude Code 2.1.252 captures are shipped as evidence, so ACC reports fallback
|
|
87
|
+
instead of claiming realtime delivery.
|
|
142
88
|
|
|
143
|
-
|
|
144
|
-
and complete handoffs. These commands give you a direct view and control when you want it:
|
|
89
|
+
## What ACC owns
|
|
145
90
|
|
|
146
|
-
|
|
|
91
|
+
| ACC owns | ACC does not own |
|
|
147
92
|
|---|---|
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
[
|
|
167
|
-
|
|
168
|
-
## Requirements
|
|
169
|
-
|
|
170
|
-
Node 24+, macOS or Linux. Git optional.
|
|
171
|
-
|
|
172
|
-
## License
|
|
173
|
-
|
|
174
|
-
Free and MIT-licensed. Use it, fork it, keep it — see [LICENSE](LICENSE).
|
|
93
|
+
| participant and session presence | process or model lifecycle |
|
|
94
|
+
| current intent and resource claims | prompts, permissions, or token budgets |
|
|
95
|
+
| messages, threads, replies, acknowledgements | work queues or execution state |
|
|
96
|
+
| delivery evidence and visible fallback | raw transcripts or shared model memory |
|
|
97
|
+
|
|
98
|
+
A peer message is untrusted input, never system authority. A guarded claim can stop only
|
|
99
|
+
the write paths a client actually exposes; `acc status` reports `advisory` when that cannot
|
|
100
|
+
be guaranteed.
|
|
101
|
+
|
|
102
|
+
## Documentation
|
|
103
|
+
|
|
104
|
+
Start with [Getting started](docs/GETTING_STARTED.md), then use the
|
|
105
|
+
[documentation map](docs/index.md). The exact surfaces are in the [CLI](docs/CLI.md),
|
|
106
|
+
[MCP](docs/MCP.md), [Protocol](docs/PROTOCOL.md), and
|
|
107
|
+
[Capabilities](docs/CAPABILITIES.md) references. Adapter evidence lives beside each
|
|
108
|
+
adapter in its `COMPATIBILITY.md` and `certification.json`.
|
|
109
|
+
|
|
110
|
+
Contributing starts with the repository's
|
|
111
|
+
[AGENTS.md](https://github.com/automatis-tools/agents-can-communicate/blob/main/AGENTS.md).
|
|
112
|
+
Node 24+, Git optional, MIT licensed.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Reporting
|
|
4
|
+
|
|
5
|
+
Report privately via [GitHub security advisories](https://github.com/automatis-tools/agents-can-communicate/security/advisories/new).
|
|
6
|
+
|
|
7
|
+
Please do not open a public issue first.
|
|
8
|
+
|
|
9
|
+
Include: what you ran, what happened, what you expected, and the client and
|
|
10
|
+
version. A reproduction against a throwaway `ACC_DATA_HOME` is ideal.
|
|
11
|
+
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
| In scope | Out of scope |
|
|
15
|
+
|---|---|
|
|
16
|
+
| Peer text escaping its quoted block | An attacker who already has write access to your data home |
|
|
17
|
+
| Any path escaping the managed root | A model choosing to obey persuasive peer text |
|
|
18
|
+
| Uninstall deleting files ACC did not write | Vulnerabilities in Codex, Claude Code, Gemini, Grok, or Kimi themselves |
|
|
19
|
+
| ACC writing into a repository | Denial of service by a trusted peer |
|
|
20
|
+
| Session impersonation across MCP | |
|
|
21
|
+
|
|
22
|
+
Reasoning behind each: [docs/SECURITY_MODEL.md](docs/SECURITY_MODEL.md).
|
|
23
|
+
|
|
24
|
+
## Supported versions
|
|
25
|
+
|
|
26
|
+
Pre-1.0: the latest release only.
|
|
27
|
+
|
|
28
|
+
## What we will do
|
|
29
|
+
|
|
30
|
+
Acknowledge, reproduce, and tell you whether it is in scope. If it is, the fix
|
|
31
|
+
ships with a test in `tests/security/` so it cannot come back quietly.
|
package/bin/acc-hook.mjs
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// someone's session stops working. Unknown adapter, malformed payload, broken
|
|
7
7
|
// store, missing binding - all of them end in "allow, exit 0".
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
9
|
+
import { realpathSync } from "node:fs";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
9
11
|
|
|
10
12
|
import { createId } from "@agents-can-communicate/protocol";
|
|
11
13
|
import { runHook } from "@agents-can-communicate/hook-runner";
|
|
@@ -13,12 +15,14 @@ import { runHook } from "@agents-can-communicate/hook-runner";
|
|
|
13
15
|
import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
|
|
14
16
|
import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
|
|
15
17
|
import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
|
|
18
|
+
import { createGrokAdapter } from "@agents-can-communicate/adapter-grok";
|
|
16
19
|
import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
|
|
17
20
|
|
|
18
21
|
const adapters = {
|
|
19
22
|
claude_code: createClaudeCodeAdapter(),
|
|
20
23
|
codex: createCodexAdapter(),
|
|
21
24
|
gemini_cli: createGeminiCliAdapter(),
|
|
25
|
+
grok: createGrokAdapter(),
|
|
22
26
|
kimi: createKimiAdapter(),
|
|
23
27
|
};
|
|
24
28
|
|
|
@@ -34,20 +38,100 @@ const readStdin = () => new Promise(resolve => {
|
|
|
34
38
|
return undefined;
|
|
35
39
|
});
|
|
36
40
|
|
|
37
|
-
const
|
|
41
|
+
export const writeOutput = (stream, output, { deadlineAt } = {}) => {
|
|
42
|
+
if (output === "") return Promise.resolve();
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
let settled = false;
|
|
45
|
+
let timer;
|
|
46
|
+
const finish = error => {
|
|
47
|
+
if (settled) return;
|
|
48
|
+
settled = true;
|
|
49
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
50
|
+
if (error === undefined || error === null) resolve();
|
|
51
|
+
else reject(error);
|
|
52
|
+
};
|
|
53
|
+
if (deadlineAt !== undefined) {
|
|
54
|
+
const remaining = deadlineAt - Date.now();
|
|
55
|
+
if (remaining <= 0) {
|
|
56
|
+
finish(new Error("hook budget exhausted before stdout write"));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
timer = setTimeout(() => finish(
|
|
60
|
+
new Error("hook budget exhausted waiting for stdout callback")), remaining);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
stream.write(output, finish);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
finish(error);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const DIAGNOSTIC_BYTES = 512;
|
|
71
|
+
|
|
72
|
+
function boundedDiagnostic(label, error) {
|
|
73
|
+
const detail = String(error?.message ?? error).replace(/[\u0000-\u001f\u007f]/g, " ");
|
|
74
|
+
let line = `acc: ${label}: ${detail}`;
|
|
75
|
+
while (Buffer.byteLength(`${line}\n`, "utf8") > DIAGNOSTIC_BYTES && line.length > 0) {
|
|
76
|
+
line = line.slice(0, -1);
|
|
77
|
+
}
|
|
78
|
+
return `${line}\n`;
|
|
79
|
+
}
|
|
38
80
|
|
|
39
|
-
|
|
81
|
+
function tryWrite(stream, output) {
|
|
82
|
+
if (output === "") return;
|
|
83
|
+
try {
|
|
84
|
+
stream.write(output, () => {});
|
|
85
|
+
} catch {
|
|
86
|
+
// A broken diagnostic stream must not turn a failed-open hook into a crash.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function completeHookOutput(result,
|
|
91
|
+
{ stdout = process.stdout, stderr = process.stderr } = {}) {
|
|
92
|
+
try {
|
|
93
|
+
await writeOutput(stdout, result.stdout ?? "", { deadlineAt: result.deadlineAt });
|
|
94
|
+
} catch (error) {
|
|
95
|
+
tryWrite(stderr, boundedDiagnostic("stdout write failed", error));
|
|
96
|
+
return { exitCode: 0, wroteStdout: false, committedOffers: false };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let committedOffers = true;
|
|
100
|
+
try {
|
|
101
|
+
await result.commitOffers?.();
|
|
102
|
+
} catch (error) {
|
|
103
|
+
committedOffers = false;
|
|
104
|
+
tryWrite(stderr, boundedDiagnostic("offer commit failed", error));
|
|
105
|
+
}
|
|
106
|
+
if (result.stderr) tryWrite(stderr, `${result.stderr}\n`);
|
|
107
|
+
return { exitCode: 0, wroteStdout: true, committedOffers };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function main() {
|
|
111
|
+
const [adapterId] = process.argv.slice(2);
|
|
112
|
+
|
|
113
|
+
let payload = null;
|
|
114
|
+
try {
|
|
115
|
+
payload = JSON.parse(await readStdin());
|
|
116
|
+
} catch {
|
|
117
|
+
payload = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = await runHook({ adapterId, payload, adapters,
|
|
121
|
+
runtime: { clock: { now: () => new Date().toISOString() },
|
|
122
|
+
ids: { next: kind => createId(kind, randomBytes) } },
|
|
123
|
+
env: process.env });
|
|
124
|
+
|
|
125
|
+
const completed = await completeHookOutput(result);
|
|
126
|
+
process.exitCode = result.exitCode ?? completed.exitCode;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let isMain = false;
|
|
40
130
|
try {
|
|
41
|
-
|
|
131
|
+
isMain = process.argv[1] !== undefined
|
|
132
|
+
&& realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
42
133
|
} catch {
|
|
43
|
-
|
|
134
|
+
isMain = false;
|
|
44
135
|
}
|
|
45
136
|
|
|
46
|
-
|
|
47
|
-
runtime: { clock: { now: () => new Date().toISOString() },
|
|
48
|
-
ids: { next: kind => createId(kind, randomBytes) } },
|
|
49
|
-
env: process.env });
|
|
50
|
-
|
|
51
|
-
if (result.stdout !== "") process.stdout.write(result.stdout);
|
|
52
|
-
if (result.stderr) process.stderr.write(`${result.stderr}\n`);
|
|
53
|
-
process.exitCode = result.exitCode ?? 0;
|
|
137
|
+
if (isMain) await main();
|
package/bin/acc-mcp.mjs
CHANGED
|
@@ -3,8 +3,9 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
|
|
4
4
|
import { EXIT, createId } from "@agents-can-communicate/protocol";
|
|
5
5
|
import { createCoordinationService } from "@agents-can-communicate/core";
|
|
6
|
+
import { createDeliveryRouter } from "@agents-can-communicate/delivery-router";
|
|
6
7
|
import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
|
|
7
|
-
import { createGitProbe, discoverWorkspace, platformDataHome, runtimePaths }
|
|
8
|
+
import { ALL_ADAPTERS, createGitProbe, discoverWorkspace, platformDataHome, runtimePaths }
|
|
8
9
|
from "@agents-can-communicate/cli";
|
|
9
10
|
import { serve } from "@agents-can-communicate/mcp-server";
|
|
10
11
|
|
|
@@ -40,13 +41,16 @@ const paths = runtimePaths({
|
|
|
40
41
|
});
|
|
41
42
|
const store = await openFilesystemStore({ root: paths.root, clock, ids,
|
|
42
43
|
workspaceId: descriptor.id });
|
|
44
|
+
const service = createCoordinationService({ store, clock, ids });
|
|
45
|
+
const adapters = Object.fromEntries(ALL_ADAPTERS().map(adapter => [adapter.id, adapter]));
|
|
43
46
|
|
|
44
47
|
await serve({
|
|
45
48
|
input: process.stdin,
|
|
46
49
|
output: process.stdout,
|
|
47
50
|
log: message => process.stderr.write(`acc-mcp: ${message}\n`),
|
|
48
51
|
context: {
|
|
49
|
-
service
|
|
52
|
+
service,
|
|
53
|
+
deliveryRouter: createDeliveryRouter({ service, adapters, clock }),
|
|
50
54
|
workspaceId: descriptor.id,
|
|
51
55
|
participantId,
|
|
52
56
|
descriptor,
|
package/bin/acc.mjs
CHANGED
|
@@ -3,7 +3,10 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
|
|
5
5
|
import { createId } from "@agents-can-communicate/protocol";
|
|
6
|
-
import {
|
|
6
|
+
import { createDeliveryRouter } from "@agents-can-communicate/delivery-router";
|
|
7
|
+
import { ALL_ADAPTERS, askConfirmation, main } from "@agents-can-communicate/cli";
|
|
8
|
+
|
|
9
|
+
const adapters = Object.fromEntries(ALL_ADAPTERS().map(adapter => [adapter.id, adapter]));
|
|
7
10
|
|
|
8
11
|
// The composition root is the only place allowed to reach for ambient time and
|
|
9
12
|
// randomness; everything below it receives them as ports.
|
|
@@ -15,6 +18,8 @@ const runtime = {
|
|
|
15
18
|
stderr: process.stderr,
|
|
16
19
|
clock: { now: () => new Date().toISOString() },
|
|
17
20
|
ids: { next: kind => createId(kind, randomBytes) },
|
|
21
|
+
createDeliveryRouter: ({ service, clock }) =>
|
|
22
|
+
createDeliveryRouter({ service, adapters, clock }),
|
|
18
23
|
// Asked only by `acc config init`, and only when stdout is a terminal. There
|
|
19
24
|
// was no port here at all, so the question went to the fallback that always
|
|
20
25
|
// answers no: in a real terminal the command printed "not written" and never
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Writing an adapter
|
|
2
|
+
|
|
3
|
+
An adapter teaches ACC one client. Nothing else in ACC knows that client exists. This page
|
|
4
|
+
assumes the vocabulary in [Protocol](PROTOCOL.md#identity-hierarchy) — session, participant,
|
|
5
|
+
claim — and points to [Capabilities](CAPABILITIES.md) for what was actually measured per
|
|
6
|
+
client; see the [documentation map](index.md) for where both fit among the rest.
|
|
7
|
+
|
|
8
|
+
```mermaid
|
|
9
|
+
graph LR
|
|
10
|
+
H[client hook] --> R[acc-hook runtime]
|
|
11
|
+
R -->|normalizeHook| E[normalised event]
|
|
12
|
+
E --> CO[core: attach, claims, sync]
|
|
13
|
+
CO -->|denyOutcome / injectOutcome| R
|
|
14
|
+
R --> H
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## The manifest
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
export function createExampleAdapter() {
|
|
21
|
+
return defineAdapter({
|
|
22
|
+
id: "example", // portable id
|
|
23
|
+
displayName: "Example CLI",
|
|
24
|
+
client: { command: "example", certificationName: "example-cli",
|
|
25
|
+
versionArgs: ["--version"] },
|
|
26
|
+
certification, // imported package-local certification.json
|
|
27
|
+
capabilities: { delivery: { nextTurn: true } },
|
|
28
|
+
|
|
29
|
+
detect, install, uninstall, doctor,
|
|
30
|
+
planInstall, // what install would write
|
|
31
|
+
normalizeHook, // client payload -> normalised event
|
|
32
|
+
renderContext, // SyncResult -> text
|
|
33
|
+
renderContextResult, // text + ids of complete rendered groups
|
|
34
|
+
denyOutcome, injectOutcome, // how this client is answered
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`renderContextResult` is required wherever an adapter renders peer messages. It returns
|
|
40
|
+
`{ text, includedMessageIds, includedAttentionIds }`, and the [delivery
|
|
41
|
+
lifecycle](PROTOCOL.md#delivery-lifecycle) advances only from those ids — never by searching
|
|
42
|
+
`text` for one, because peer text is untrusted and can imitate another message's header.
|
|
43
|
+
`projectContextResult()` implements this contract; `projectContext()` remains the text-only
|
|
44
|
+
convenience API for adapters that don't need it. An adapter with only the older
|
|
45
|
+
`renderContext()` gets pending bodies withheld and a visible `acc inbox` degradation warning
|
|
46
|
+
instead: repeating an untracked body every turn would be quieter in code and dishonest about
|
|
47
|
+
what was actually delivered.
|
|
48
|
+
|
|
49
|
+
`client.command` does double duty. `detect.mjs` uses it as the version-probe binary, and
|
|
50
|
+
presence liveness separately walks the hook's process ancestry for the first ancestor whose
|
|
51
|
+
executable basename matches it, to learn the client's own pid. Declare the binary the client
|
|
52
|
+
actually runs as — `command: "claude"` for a client that really runs as `node` resolves
|
|
53
|
+
nothing, and the failure is silent: the session gets `pid: null` and falls back to reading
|
|
54
|
+
presence by age alone, with nothing telling you why.
|
|
55
|
+
|
|
56
|
+
## Capabilities
|
|
57
|
+
|
|
58
|
+
Fourteen booleans in four groups, declared in the manifest's `capabilities` object:
|
|
59
|
+
|
|
60
|
+
| Group | Entries |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `lifecycle` | `sessionStart` `sessionResume` `sessionEnd` `heartbeat` `childSessions` |
|
|
63
|
+
| `context` | `startupInjection` `beforeTurnInjection` `safePointInjection` |
|
|
64
|
+
| `guards` | `beforeRead` `beforeWrite` `beforeShell` |
|
|
65
|
+
| `delivery` | `nextTurn` `livePush` `replyRoute` |
|
|
66
|
+
|
|
67
|
+
**False by default. `true` requires a backing method *and* an observed capture.**
|
|
68
|
+
`defineAdapter` enforces the method — declaring `guards.beforeWrite: true` without
|
|
69
|
+
`guardWrite()` is a usage error at construction. It also requires a passing entry in the
|
|
70
|
+
validated `certification.json`; method existence is never evidence. What each shipped
|
|
71
|
+
client was actually observed doing against this list is [Capabilities](CAPABILITIES.md#matrix).
|
|
72
|
+
|
|
73
|
+
`lifecycle.heartbeat` is deliberately not a flavour of `delivery.nextTurn`. Next-turn
|
|
74
|
+
delivery happens only when the client reaches a normal turn boundary; heartbeat fires on a
|
|
75
|
+
timer even while the session is idle.
|
|
76
|
+
|
|
77
|
+
### Certification evidence
|
|
78
|
+
|
|
79
|
+
Every adapter ships `certification.json` and every referenced capture under `fixtures/`.
|
|
80
|
+
Each evidence entry contains `client`, exact `version`, exact `platform`, `observedAt`,
|
|
81
|
+
`capability`, package-relative `fixture`, `idleBehavior`, `busyBehavior`,
|
|
82
|
+
`authorityLevel`, `limitations`, and `result` (`pass` or `fail`). A copied documentation
|
|
83
|
+
example is not a capture. Failed experiments stay in the manifest as `fail`; they explain
|
|
84
|
+
the false value and can never enable it.
|
|
85
|
+
|
|
86
|
+
`effectiveCapabilities(adapter, { clientVersion, platform })` returns the full boolean
|
|
87
|
+
shape for the installed client. Only an exact passing version/platform match remains true.
|
|
88
|
+
Unreadable, unknown, or mismatched clients degrade every uncertified row to false.
|
|
89
|
+
|
|
90
|
+
The backing methods for delivery are `renderContextResult()` for `nextTurn`,
|
|
91
|
+
`offerMessage()` for `livePush`, and `routeReply()` for `replyRoute`.
|
|
92
|
+
|
|
93
|
+
## How far you can get
|
|
94
|
+
|
|
95
|
+
| Tier | You register | You get | You do not get |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| 0 | nothing — humans run `acc` | durable messages, status, claims | anything automatic |
|
|
98
|
+
| 1 | the MCP server | attach on first call, read, claim, message | guards, session end |
|
|
99
|
+
| 2 | hooks + skill | automatic attach, turn context, write guards, cleanup | realtime |
|
|
100
|
+
| 3 | + realtime surface | delivery receipts, safe-point injection, child sessions | — |
|
|
101
|
+
|
|
102
|
+
Installed hook wiring may reach tier 2, but the effective capability is still limited to
|
|
103
|
+
an exact certified client/version/platform. No client currently certifies native live push
|
|
104
|
+
or native reply routing.
|
|
105
|
+
|
|
106
|
+
## normalizeHook
|
|
107
|
+
|
|
108
|
+
Whitelist, never a filter. Every client hands hooks the prompt, the transcript path, or the
|
|
109
|
+
tool output; none of it may survive.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
return normalizedEvent({
|
|
113
|
+
kind, sessionId, cwd, model, parentSessionId, tool,
|
|
114
|
+
targets, // paths this call would WRITE. For a shell call, pass the command to
|
|
115
|
+
// shellWriteTargets() — it reads write positions only, never reads.
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Refuse an unrecognised payload. Inventing a session attaches the wrong one, or a new one
|
|
120
|
+
every hook, and looks like it is working.
|
|
121
|
+
|
|
122
|
+
## Response contracts do not port
|
|
123
|
+
|
|
124
|
+
Measure them. Every client differs, and a wrong shape fails **silently**:
|
|
125
|
+
|
|
126
|
+
| | deny | inject |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| Codex | exit 2 + stderr | plain stdout (`developer` message) |
|
|
129
|
+
| Claude Code | `hookSpecificOutput.permissionDecision` | same envelope |
|
|
130
|
+
| Gemini CLI | `{"decision":"block"}` | `hookSpecificOutput` envelope |
|
|
131
|
+
| Grok | `{"decision":"deny","reason"}` (documented; deny not yet captured) | UserPromptSubmit stdout discarded on 1.0.13 |
|
|
132
|
+
| Kimi Code | `hookSpecificOutput.permissionDecision` | plain stdout |
|
|
133
|
+
|
|
134
|
+
`denyOutcome(reason)` returns `{ stdout, stderr, exitCode }`, so the runtime never has to
|
|
135
|
+
know which client it is talking to. This table is only the shape each shipped adapter
|
|
136
|
+
actually uses; the full experimental grid — every candidate shape tried against every
|
|
137
|
+
client, including which ones are silently ignored — is measured in
|
|
138
|
+
[Capabilities](CAPABILITIES.md#response-contracts-which-do-not-port).
|
|
139
|
+
|
|
140
|
+
## Install and ownership
|
|
141
|
+
|
|
142
|
+
```mermaid
|
|
143
|
+
graph TB
|
|
144
|
+
P[planInstall] -->|artifacts| K{kind}
|
|
145
|
+
K -->|tree| T[a directory ACC creates<br/>removable if unchanged]
|
|
146
|
+
K -->|merge| M[a file the user owns<br/>never deleted]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Rules that are not negotiable:
|
|
150
|
+
|
|
151
|
+
- idempotent — installing twice equals installing once;
|
|
152
|
+
- reversible — uninstall restores the user's file byte for byte;
|
|
153
|
+
- absolute command paths — a hook's environment carries no PATH;
|
|
154
|
+
- honour `keep`: uninstall receives paths the user has since edited.
|
|
155
|
+
|
|
156
|
+
`planInstall` must use the same path helpers as `install`. A conformance test compares
|
|
157
|
+
them, because a plan that drifts makes `--dry-run` a decoration.
|
|
158
|
+
|
|
159
|
+
## Conformance
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
node --test tests/conformance/*.test.mjs
|
|
163
|
+
node --test tests/process/hook-wiring.test.mjs
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The second one *executes* what your install wrote. Three adapters once shipped a hook
|
|
167
|
+
command that did not exist anywhere; every test was green.
|
|
168
|
+
|
|
169
|
+
## Record what you learned
|
|
170
|
+
|
|
171
|
+
One `COMPATIBILITY.md` per adapter: client version, event names, payload fields, the deny
|
|
172
|
+
matrix, and what you could **not** observe. The next person's alternative is guessing.
|