agents-can-communicate 0.1.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/LICENSE +21 -0
- package/README.md +176 -0
- package/bin/acc-hook.mjs +53 -0
- package/bin/acc-mcp.mjs +46 -0
- package/bin/acc.mjs +24 -0
- package/docs/CAPABILITIES.md +153 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +13 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/.claude-plugin/plugin.json +5 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/hooks/hooks.json +56 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +217 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +64 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/hooks.mjs +106 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +278 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +13 -0
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/.codex-plugin/plugin.json +17 -0
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/hooks.json +55 -0
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +217 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +81 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/hooks.mjs +133 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +235 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +6 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/hooks/hooks.json +69 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md +217 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +13 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/adapter.mjs +74 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/hooks.mjs +109 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +149 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +13 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/plugin/.kimi-plugin/plugin.json +9 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +217 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/src/adapter.mjs +73 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/src/hooks.mjs +125 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +216 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +101 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/config-merge.mjs +215 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +217 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/events.mjs +55 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +148 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +16 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +95 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-block.mjs +71 -0
- package/node_modules/@agents-can-communicate/cli/package.json +12 -0
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +163 -0
- package/node_modules/@agents-can-communicate/cli/src/claim-spelling.mjs +79 -0
- package/node_modules/@agents-can-communicate/cli/src/config-command.mjs +149 -0
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +85 -0
- package/node_modules/@agents-can-communicate/cli/src/git-probe.mjs +41 -0
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +78 -0
- package/node_modules/@agents-can-communicate/cli/src/index.mjs +10 -0
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +107 -0
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +344 -0
- package/node_modules/@agents-can-communicate/cli/src/platform-paths.mjs +109 -0
- package/node_modules/@agents-can-communicate/cli/src/runtime-paths.mjs +58 -0
- package/node_modules/@agents-can-communicate/cli/src/session-owner.mjs +126 -0
- package/node_modules/@agents-can-communicate/cli/src/workspace-discovery.mjs +151 -0
- package/node_modules/@agents-can-communicate/core/package.json +12 -0
- package/node_modules/@agents-can-communicate/core/src/claims.mjs +180 -0
- package/node_modules/@agents-can-communicate/core/src/communication.mjs +334 -0
- package/node_modules/@agents-can-communicate/core/src/index.mjs +7 -0
- package/node_modules/@agents-can-communicate/core/src/intents.mjs +75 -0
- package/node_modules/@agents-can-communicate/core/src/materialisation.mjs +86 -0
- package/node_modules/@agents-can-communicate/core/src/notify.mjs +95 -0
- package/node_modules/@agents-can-communicate/core/src/participants.mjs +48 -0
- package/node_modules/@agents-can-communicate/core/src/ports.mjs +56 -0
- package/node_modules/@agents-can-communicate/core/src/service.mjs +44 -0
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +190 -0
- package/node_modules/@agents-can-communicate/core/src/status.mjs +132 -0
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +273 -0
- package/node_modules/@agents-can-communicate/core/src/tasks.mjs +238 -0
- package/node_modules/@agents-can-communicate/core/src/workstreams.mjs +109 -0
- package/node_modules/@agents-can-communicate/hook-runner/package.json +12 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +379 -0
- package/node_modules/@agents-can-communicate/installer/package.json +10 -0
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +58 -0
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +79 -0
- package/node_modules/@agents-can-communicate/installer/src/index.mjs +6 -0
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +162 -0
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +62 -0
- package/node_modules/@agents-can-communicate/mcp-server/package.json +12 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/resources.mjs +58 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +250 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +201 -0
- package/node_modules/@agents-can-communicate/protocol/package.json +12 -0
- package/node_modules/@agents-can-communicate/protocol/src/config.mjs +151 -0
- package/node_modules/@agents-can-communicate/protocol/src/envelopes.mjs +25 -0
- package/node_modules/@agents-can-communicate/protocol/src/errors.mjs +30 -0
- package/node_modules/@agents-can-communicate/protocol/src/fields.mjs +103 -0
- package/node_modules/@agents-can-communicate/protocol/src/ids.mjs +25 -0
- package/node_modules/@agents-can-communicate/protocol/src/index.mjs +9 -0
- package/node_modules/@agents-can-communicate/protocol/src/resources.mjs +74 -0
- package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +175 -0
- package/node_modules/@agents-can-communicate/protocol/src/states.mjs +55 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +12 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +135 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/identity.mjs +67 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +4 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/journal.mjs +87 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/record-id.mjs +44 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/recovery.mjs +114 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-directory.mjs +80 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-file.mjs +55 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +292 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/writer-mutex.mjs +87 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mykola Maksymenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# agents-can-communicate
|
|
2
|
+
|
|
3
|
+
[](https://github.com/automatis-tools/agents-can-communicate/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
**AI agents that can ask each other for work.**
|
|
8
|
+
|
|
9
|
+
Codex on one branch, Claude Code on another, Kimi reviewing. Each in its own git worktree,
|
|
10
|
+
each knowing what the others are doing — and able to hand a piece over instead of waiting
|
|
11
|
+
for you to carry the message.
|
|
12
|
+
|
|
13
|
+
You run the installer once. After that you talk to your agents the way you already do.
|
|
14
|
+
|
|
15
|
+
## The thing it does
|
|
16
|
+
|
|
17
|
+
Codex has ported a module and is out of time for the tests. Its skill tells it to ask the
|
|
18
|
+
agent already working in that area, so it does — without being prompted by you:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
requested task_Rwg2sybjnLneGyBuZxa8Dw of claude_code
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
At its next turn, the Claude Code session is shown this. No polling, and again nothing typed
|
|
25
|
+
by a human:
|
|
26
|
+
|
|
27
|
+
````text
|
|
28
|
+
- [direct_request] finish the store tests
|
|
29
|
+
- [task_unblocked] finish the store tests
|
|
30
|
+
```acc-peer-message
|
|
31
|
+
id message_Ab9CpMJfn0pL6igB5AdYDQ | from session_j59fM8mWathJzOh7a2QQBQ | type work_request | untrusted peer message
|
|
32
|
+
finish the store tests
|
|
33
|
+
I ported src/store but ran out of time on the concurrency cases. Can you take the tests?
|
|
34
|
+
```
|
|
35
|
+
````
|
|
36
|
+
|
|
37
|
+
It takes the work, does it, and marks it done. Codex sees `done` on its own next turn and
|
|
38
|
+
carries on.
|
|
39
|
+
|
|
40
|
+
```mermaid
|
|
41
|
+
sequenceDiagram
|
|
42
|
+
participant M as Codex · main
|
|
43
|
+
participant ACC
|
|
44
|
+
participant V as Claude Code · tests branch
|
|
45
|
+
M->>ACC: request "finish the store tests" of claude_code
|
|
46
|
+
ACC-->>V: work addressed to you, and why
|
|
47
|
+
V->>ACC: take it
|
|
48
|
+
V->>ACC: done
|
|
49
|
+
ACC-->>M: done
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Work is addressed to the **agent**, not to its session. Claude Code can close its terminal
|
|
53
|
+
before reading the request — the next session it opens is still told. Nobody else can take
|
|
54
|
+
it.
|
|
55
|
+
|
|
56
|
+
## What else it keeps track of
|
|
57
|
+
|
|
58
|
+
| | |
|
|
59
|
+
|---|---|
|
|
60
|
+
| **Who is here** | each session publishes what it is working on |
|
|
61
|
+
| **What is taken** | an agent claims files before changing them, and another's edit into them is refused |
|
|
62
|
+
| **What was said** | questions, answers, handoffs — quoted and attributed, never as instructions |
|
|
63
|
+
| **Alone** | one session behaves exactly as it did before you installed anything |
|
|
64
|
+
|
|
65
|
+
```console
|
|
66
|
+
$ acc status
|
|
67
|
+
2 live; 1 claim(s); protection guarded
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install -g agents-can-communicate
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then wire up the clients you have:
|
|
77
|
+
|
|
78
|
+
<!-- test:command -->
|
|
79
|
+
```bash
|
|
80
|
+
acc install --dry-run
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This prints every file it would touch. Run `acc install` to apply it, then open your clients
|
|
84
|
+
in the project — in one directory or in several worktrees — and work normally.
|
|
85
|
+
|
|
86
|
+
## Commands
|
|
87
|
+
|
|
88
|
+
Coordination needs none from you. Requesting work, taking it, claiming files and messaging
|
|
89
|
+
are things the agents do, taught by the skill each adapter installs.
|
|
90
|
+
|
|
91
|
+
What is left for a person is the install and looking in on it:
|
|
92
|
+
|
|
93
|
+
| | |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `acc help` | every command, one line each |
|
|
96
|
+
| `acc status` | who is here, what is claimed, what is in flight |
|
|
97
|
+
| `acc doctor` | what is installed, what is missing, what to do next |
|
|
98
|
+
| `acc install` · `acc uninstall` | wire clients up, or take it back out |
|
|
99
|
+
|
|
100
|
+
Uninstall removes only files ACC wrote, and only where they still match what it wrote.
|
|
101
|
+
Every operation is in the [CLI reference](docs/CLI.md) if you want to drive it yourself.
|
|
102
|
+
|
|
103
|
+
## Supported clients
|
|
104
|
+
|
|
105
|
+
| | Sees others | Blocks edits | Receives work and updates |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| Codex | yes | yes¹ | yes |
|
|
108
|
+
| Claude Code | yes | yes | yes |
|
|
109
|
+
| Gemini CLI | yes | yes² | yes |
|
|
110
|
+
| Kimi Code | yes | yes | yes |
|
|
111
|
+
| Any MCP client | yes | – | yes, when it polls |
|
|
112
|
+
|
|
113
|
+
¹ models editing through `apply_patch` · ² approval modes that expose edit tools ·
|
|
114
|
+
[what was measured](docs/CAPABILITIES.md)
|
|
115
|
+
|
|
116
|
+
## Limits
|
|
117
|
+
|
|
118
|
+
- A claim blocks file edits. It does not block an agent that edits by running a shell
|
|
119
|
+
command, since the command names no file.
|
|
120
|
+
- `protection guarded` applies while every session present is one ACC can stop. One that
|
|
121
|
+
cannot changes it to `advisory`.
|
|
122
|
+
- Codex requires you to trust the plugin before its hooks run. `acc doctor` reports this.
|
|
123
|
+
- Nothing is pruned yet. A workspace that has carried thousands of messages makes each turn
|
|
124
|
+
slower to build; a project's worth of coordination is fine, an archive is not.
|
|
125
|
+
- Windows does not work: the store fsyncs a directory after a rename, which Windows
|
|
126
|
+
refuses, and `O_NOFOLLOW` does not hold there. Last measured at 86 failures out of 587
|
|
127
|
+
tests; the suite has grown a good deal since and nobody has run it there again. macOS and
|
|
128
|
+
Linux are supported and both run in CI.
|
|
129
|
+
|
|
130
|
+
## How it works
|
|
131
|
+
|
|
132
|
+
```mermaid
|
|
133
|
+
graph LR
|
|
134
|
+
C[your client] -->|hook| H[acc-hook]
|
|
135
|
+
H --> K["core<br/>sessions · work · claims · messages"]
|
|
136
|
+
K --> S[(state, outside your repo)]
|
|
137
|
+
K -->|answer| H
|
|
138
|
+
H --> C
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Clients call out when a session starts and ends, when a turn begins, and before a tool
|
|
142
|
+
runs — plus a heartbeat, on the one client that sends them. ACC answers at those and is
|
|
143
|
+
idle otherwise. A hook that does not answer within five seconds lets the tool run, so ACC
|
|
144
|
+
can be slow or broken without stopping anyone's work.
|
|
145
|
+
|
|
146
|
+
State lives beside your other tool settings, never inside the repository:
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
~/Library/Application Support/acc macOS
|
|
150
|
+
~/.local/share/acc Linux
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Inside it, one directory per workspace — keyed by the repository rather than the folder, so
|
|
154
|
+
every worktree of it is one workspace and two unrelated projects share nothing. Deleting a
|
|
155
|
+
workspace directory loses that project's coordination history and nothing else.
|
|
156
|
+
|
|
157
|
+
## Documentation
|
|
158
|
+
|
|
159
|
+
| Using it | Understanding it | Building on it |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| [Getting started](docs/GETTING_STARTED.md) | [Concepts](docs/CONCEPTS.md) | [Writing an adapter](docs/ADAPTER_AUTHORING.md) |
|
|
162
|
+
| [CLI](docs/CLI.md) | [Architecture](docs/ARCHITECTURE.md) | [Protocol](docs/PROTOCOL.md) |
|
|
163
|
+
| [Configuration](docs/CONFIGURATION.md) | [Capabilities](docs/CAPABILITIES.md) | [Security](docs/SECURITY_MODEL.md) |
|
|
164
|
+
| [MCP](docs/MCP.md) | [Decisions](docs/DESIGN_DECISIONS.md) | [Threat model](docs/THREAT_MODEL.md) |
|
|
165
|
+
| [Troubleshooting](docs/TROUBLESHOOTING.md) | [Prior art](docs/PRIOR_ART.md) | [Contributing](AGENTS.md) |
|
|
166
|
+
|
|
167
|
+
Examples: [three workstreams](examples/three-workstreams.md) ·
|
|
168
|
+
[research without Git](examples/non-git-research.md)
|
|
169
|
+
|
|
170
|
+
## Requirements
|
|
171
|
+
|
|
172
|
+
Node 24+, macOS or Linux. Git optional.
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT — see [LICENSE](LICENSE).
|
package/bin/acc-hook.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Hook entry point: `acc-hook <adapter-id> <kind>`, payload on stdin.
|
|
3
|
+
//
|
|
4
|
+
// Every harness runs this as a short-lived child process in front of the user's
|
|
5
|
+
// turn. The one rule that overrides all others here: never be the reason
|
|
6
|
+
// someone's session stops working. Unknown adapter, malformed payload, broken
|
|
7
|
+
// store, missing binding - all of them end in "allow, exit 0".
|
|
8
|
+
import { randomBytes } from "node:crypto";
|
|
9
|
+
|
|
10
|
+
import { createId } from "@agents-can-communicate/protocol";
|
|
11
|
+
import { runHook } from "@agents-can-communicate/hook-runner";
|
|
12
|
+
|
|
13
|
+
import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
|
|
14
|
+
import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
|
|
15
|
+
import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
|
|
16
|
+
import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
|
|
17
|
+
|
|
18
|
+
const adapters = {
|
|
19
|
+
claude_code: createClaudeCodeAdapter(),
|
|
20
|
+
codex: createCodexAdapter(),
|
|
21
|
+
gemini_cli: createGeminiCliAdapter(),
|
|
22
|
+
kimi: createKimiAdapter(),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const readStdin = () => new Promise(resolve => {
|
|
26
|
+
// A hook is always given its payload on stdin, but a client that closes it
|
|
27
|
+
// without writing must not leave this process hanging in front of a turn.
|
|
28
|
+
if (process.stdin.isTTY) return resolve("");
|
|
29
|
+
let raw = "";
|
|
30
|
+
process.stdin.setEncoding("utf8");
|
|
31
|
+
process.stdin.on("data", chunk => { raw += chunk; });
|
|
32
|
+
process.stdin.on("end", () => resolve(raw));
|
|
33
|
+
process.stdin.on("error", () => resolve(""));
|
|
34
|
+
return undefined;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const [adapterId] = process.argv.slice(2);
|
|
38
|
+
|
|
39
|
+
let payload = null;
|
|
40
|
+
try {
|
|
41
|
+
payload = JSON.parse(await readStdin());
|
|
42
|
+
} catch {
|
|
43
|
+
payload = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = await runHook({ adapterId, payload, adapters,
|
|
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;
|
package/bin/acc-mcp.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
import { createId } from "@agents-can-communicate/protocol";
|
|
5
|
+
import { createCoordinationService } from "@agents-can-communicate/core";
|
|
6
|
+
import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
|
|
7
|
+
import { createGitProbe, discoverWorkspace, platformDataHome, runtimePaths }
|
|
8
|
+
from "@agents-can-communicate/cli";
|
|
9
|
+
import { serve } from "@agents-can-communicate/mcp-server";
|
|
10
|
+
|
|
11
|
+
// The composition root is the only place allowed to reach for ambient time and
|
|
12
|
+
// randomness. The participant name comes from configuration, never from the
|
|
13
|
+
// client: the protocol says clientInfo is self-reported and must not drive
|
|
14
|
+
// behaviour, and the session is derived from this configuration alone.
|
|
15
|
+
const participantId = process.env.ACC_MCP_PARTICIPANT ?? "mcp";
|
|
16
|
+
const clock = { now: () => new Date().toISOString() };
|
|
17
|
+
const ids = { next: kind => createId(kind, randomBytes) };
|
|
18
|
+
|
|
19
|
+
const descriptor = await discoverWorkspace({
|
|
20
|
+
cwd: process.env.ACC_MCP_WORKSPACE ?? process.cwd(),
|
|
21
|
+
env: process.env,
|
|
22
|
+
gitProbe: createGitProbe(),
|
|
23
|
+
});
|
|
24
|
+
const paths = runtimePaths({
|
|
25
|
+
dataHome: platformDataHome({ platform: process.platform, env: process.env }),
|
|
26
|
+
workspaceId: descriptor.id,
|
|
27
|
+
workspaceRoots: descriptor.roots,
|
|
28
|
+
});
|
|
29
|
+
const store = await openFilesystemStore({ root: paths.root, clock, ids,
|
|
30
|
+
workspaceId: descriptor.id });
|
|
31
|
+
|
|
32
|
+
await serve({
|
|
33
|
+
input: process.stdin,
|
|
34
|
+
output: process.stdout,
|
|
35
|
+
log: message => process.stderr.write(`acc-mcp: ${message}\n`),
|
|
36
|
+
context: {
|
|
37
|
+
service: createCoordinationService({ store, clock, ids }),
|
|
38
|
+
workspaceId: descriptor.id,
|
|
39
|
+
participantId,
|
|
40
|
+
descriptor,
|
|
41
|
+
runtimeDir: paths.root,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Exiting on stdin EOF is the only portable graceful shutdown in this binding.
|
|
46
|
+
process.exitCode = 0;
|
package/bin/acc.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
|
|
5
|
+
import { createId } from "@agents-can-communicate/protocol";
|
|
6
|
+
import { main } from "@agents-can-communicate/cli";
|
|
7
|
+
|
|
8
|
+
// The composition root is the only place allowed to reach for ambient time and
|
|
9
|
+
// randomness; everything below it receives them as ports.
|
|
10
|
+
const runtime = {
|
|
11
|
+
cwd: process.cwd(),
|
|
12
|
+
env: process.env,
|
|
13
|
+
platform: process.platform,
|
|
14
|
+
stdout: process.stdout,
|
|
15
|
+
stderr: process.stderr,
|
|
16
|
+
clock: { now: () => new Date().toISOString() },
|
|
17
|
+
ids: { next: kind => createId(kind, randomBytes) },
|
|
18
|
+
// Asked for only by `acc version`, so a package missing its own manifest
|
|
19
|
+
// fails that one command rather than every command.
|
|
20
|
+
version: async () => JSON.parse(
|
|
21
|
+
await readFile(new URL("../package.json", import.meta.url), "utf8")).version,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
process.exitCode = await main(process.argv.slice(2), runtime);
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Capabilities
|
|
2
|
+
|
|
3
|
+
What each harness was **observed** doing, on the versions named here. Nothing in this
|
|
4
|
+
table is inferred from documentation: every `yes` has a fixture captured from a real
|
|
5
|
+
session, and every `no` means it was not seen, not that it is impossible.
|
|
6
|
+
|
|
7
|
+
Certified 2026-08-16 on macOS 15 (darwin 25.5.0, arm64). No other operating system has
|
|
8
|
+
been tested, and at least one finding here is filesystem- and path-shaped, so the table
|
|
9
|
+
should be re-run before claiming another platform.
|
|
10
|
+
|
|
11
|
+
## Clients
|
|
12
|
+
|
|
13
|
+
| Adapter | Client | Version |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `codex` | `codex-cli` | 0.147.0 |
|
|
16
|
+
| `claude_code` | Claude Code | 2.1.233 |
|
|
17
|
+
| `gemini_cli` | Gemini CLI | 0.37.0 and 0.55.1 |
|
|
18
|
+
| `kimi` | Kimi Code | 0.36.1 |
|
|
19
|
+
|
|
20
|
+
## Matrix
|
|
21
|
+
|
|
22
|
+
| Capability | codex | claude_code | gemini_cli | kimi |
|
|
23
|
+
|---|---|---|---|---|
|
|
24
|
+
| `lifecycle.sessionStart` | yes | yes | yes | yes |
|
|
25
|
+
| `lifecycle.sessionResume` | no | no | no | no |
|
|
26
|
+
| `lifecycle.sessionEnd` | yes | yes | yes | no |
|
|
27
|
+
| `lifecycle.heartbeat` | no | no | no | yes |
|
|
28
|
+
| `lifecycle.childSessions` | no | no | no | no |
|
|
29
|
+
| `context.startupInjection` | no | no | no | no |
|
|
30
|
+
| `context.beforeTurnInjection` | yes | yes | yes | yes |
|
|
31
|
+
| `context.safePointInjection` | no | no | no | no |
|
|
32
|
+
| `guards.beforeRead` | no | no | no | no |
|
|
33
|
+
| `guards.beforeWrite` | yes | yes | yes | yes |
|
|
34
|
+
| `guards.beforeShell` | yes | yes | yes | yes |
|
|
35
|
+
| `delivery.polling` | yes | yes | yes | yes |
|
|
36
|
+
| `delivery.activeNotification` | no | no | no | no |
|
|
37
|
+
| `delivery.wakeDormantSession` | no | no | no | no |
|
|
38
|
+
| `execution.launch` | no | no | no | no |
|
|
39
|
+
| `execution.resume` | no | no | no | no |
|
|
40
|
+
| `execution.terminate` | no | no | no | no |
|
|
41
|
+
|
|
42
|
+
## What the yes values do not promise
|
|
43
|
+
|
|
44
|
+
A capability says the client can do the thing. Several of them are conditional on how the
|
|
45
|
+
client is being run, and the conditions differ per harness. These are the ones that bite.
|
|
46
|
+
|
|
47
|
+
**`guards.beforeWrite` on `codex` depends on the model.** Whether the client offers
|
|
48
|
+
`apply_patch` at all is a property of the model's metadata (`apply_patch_tool_type`), not
|
|
49
|
+
a user setting. With a model that does not have it, edits run through `exec_command`,
|
|
50
|
+
which reaches hooks as `tool_name: "Bash"` carrying a command string. A command names no
|
|
51
|
+
resource, so there is nothing to compare against a claim. Observed on 0.147.0: the default
|
|
52
|
+
toolset contained no `apply_patch`.
|
|
53
|
+
|
|
54
|
+
**`guards.beforeWrite` on `gemini_cli` depends on the approval mode.** In the default and
|
|
55
|
+
`plan` modes the client declares no write tool to the model at all. `write_file` and
|
|
56
|
+
`replace` appear under `auto_edit`; `run_shell_command` under `yolo`.
|
|
57
|
+
|
|
58
|
+
**`guards.beforeShell` is never resource-aware.** A shell command can write anywhere, and
|
|
59
|
+
ACC does not parse commands. The guard fires and is allowed through, because guessing a
|
|
60
|
+
path out of a command would block work at random and still miss real writes. Shell calls
|
|
61
|
+
therefore declare no targets on every adapter.
|
|
62
|
+
|
|
63
|
+
Where the guard cannot help, the turn context does: it names the claims other sessions
|
|
64
|
+
hold and says which way this session stands with them. Two facts decide the wording -
|
|
65
|
+
what the claim's owner asked for, and whether ACC can stop this session at all:
|
|
66
|
+
|
|
67
|
+
| Claim | This session | Note |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| guarded | can be guarded | `file edits are blocked; edits made through a shell are not` |
|
|
70
|
+
| guarded | cannot be guarded | `not enforced for this session; do not edit it` |
|
|
71
|
+
| advisory | either | `advisory; nothing will stop you, the owner is asking` |
|
|
72
|
+
|
|
73
|
+
Unenforceable is not the same as unknown - and neither is it the same as unclaimed.
|
|
74
|
+
|
|
75
|
+
**`lifecycle.sessionEnd` on `kimi` is false and it matters.** Each `kimi -p` run leaves an
|
|
76
|
+
attached session that only ages out on its declared 60s cadence, so a peer reading the
|
|
77
|
+
roster inside that window sees sessions that have already exited. Interactive sessions
|
|
78
|
+
heartbeat and do not have this problem.
|
|
79
|
+
|
|
80
|
+
**`lifecycle.heartbeat` is Kimi's alone.** It fires on a timer - observed at 60002, 120004
|
|
81
|
+
and 180006 ms of uptime - so an idle Kimi session keeps its presence honest. The other
|
|
82
|
+
three reach a hook only when the user takes a turn, so their idle sessions go stale while
|
|
83
|
+
alive. This is why it is a capability of its own rather than a flavour of
|
|
84
|
+
`delivery.polling`.
|
|
85
|
+
|
|
86
|
+
## Response contracts, which do not port
|
|
87
|
+
|
|
88
|
+
The single most portable-looking mistake an adapter can make. Measured by running each
|
|
89
|
+
candidate against a real session of each client and checking whether the tool actually
|
|
90
|
+
ran.
|
|
91
|
+
|
|
92
|
+
A dash means the candidate was never run against that client, not that it fails. Only the
|
|
93
|
+
shape each adapter actually uses was measured on every client.
|
|
94
|
+
|
|
95
|
+
| Reply to a guard hook | codex | claude_code | gemini_cli | kimi |
|
|
96
|
+
|---|---|---|---|---|
|
|
97
|
+
| exit code 2 | denies | - | denies | denies |
|
|
98
|
+
| `{"hookSpecificOutput":{…,"permissionDecision":"deny"}}` | - | denies | **ignored** | denies |
|
|
99
|
+
| `{"decision":"block","reason":…}` | - | - | denies | **ignored** |
|
|
100
|
+
| `{"permission":"deny"}` | - | - | ignored | ignored |
|
|
101
|
+
| exit code 1 | - | - | ignored | ignored |
|
|
102
|
+
|
|
103
|
+
Codex has no structured reply at all: it denies by exiting 2 with the reason on stderr.
|
|
104
|
+
Gemini ignores the shape that Claude Code and Kimi Code both honour, and Kimi ignores the
|
|
105
|
+
shape Gemini needs. Each ignored case fails silently - the write goes through and the
|
|
106
|
+
client reports nothing.
|
|
107
|
+
|
|
108
|
+
Context injection does not follow the deny contract even within one client:
|
|
109
|
+
|
|
110
|
+
| Injection | codex | claude_code | gemini_cli | kimi |
|
|
111
|
+
|---|---|---|---|---|
|
|
112
|
+
| `hookSpecificOutput.additionalContext` | - | works | works | works, but **not unwrapped** |
|
|
113
|
+
| plain text on stdout | works | - | dropped | works |
|
|
114
|
+
|
|
115
|
+
Codex delivers a hook's stdout as a `developer` role message, verbatim - the most direct
|
|
116
|
+
of the four channels, and a reason for care rather than comfort: at that role a model
|
|
117
|
+
reads text as instruction, so peer-authored text has to stay framed as data.
|
|
118
|
+
|
|
119
|
+
Kimi Code shows the model whatever a hook printed, wrapped in
|
|
120
|
+
`<hook_result hook_event="…">`, so the JSON envelope itself would end up in the
|
|
121
|
+
conversation. Gemini unwraps the envelope and appends `<hook_context>…</hook_context>` to
|
|
122
|
+
the user turn, and drops a bare string entirely.
|
|
123
|
+
|
|
124
|
+
## Installation is not uniform either
|
|
125
|
+
|
|
126
|
+
| | codex | claude_code | gemini_cli | kimi |
|
|
127
|
+
|---|---|---|---|---|
|
|
128
|
+
| Where hooks live | marketplace plugin | plugin | `settings.json` | `config.toml` |
|
|
129
|
+
| Project-level config | no | no | yes | **no** |
|
|
130
|
+
| Hook `timeout` unit | - | - | milliseconds | **seconds** (max 600) |
|
|
131
|
+
| Command path | absolute required | `${CLAUDE_PLUGIN_ROOT}` | absolute required | absolute required |
|
|
132
|
+
| Extra step by the user | hook trust | - | - | - |
|
|
133
|
+
|
|
134
|
+
Kimi Code is the only one with no project-level config, so ACC edits the user's global
|
|
135
|
+
`config.toml` - as a delimited block it owns, because ACC ships without dependencies and a
|
|
136
|
+
hand-written TOML round-tripper would take the user's comments and formatting with it.
|
|
137
|
+
|
|
138
|
+
Codex needs four things before a hook runs, not one: the plugin directory, a parseable
|
|
139
|
+
marketplace, both `[marketplaces.…]` and `[plugins."…"]` registered in its config, and the
|
|
140
|
+
plugin copied into `plugins/cache/<marketplace>/<plugin>/<version>/`. ACC does all four -
|
|
141
|
+
that last copy is exactly and only what `codex plugin add` does, measured by diffing the
|
|
142
|
+
home around it. Hook trust remains a manual step, which is the client's security model.
|
|
143
|
+
|
|
144
|
+
## What a participant declares about itself
|
|
145
|
+
|
|
146
|
+
Every session records `enforcement` (`guarded` | `advisory`) and `lifecycle`
|
|
147
|
+
(`managed` | `manual`), taken from the adapter's proven capabilities rather than from the
|
|
148
|
+
harness name. Both default to the weaker reading, so a generic MCP client or a human at
|
|
149
|
+
the CLI reads as advisory and manual.
|
|
150
|
+
|
|
151
|
+
A workspace reports `protection: guarded` only when every live session can be stopped. One
|
|
152
|
+
MCP client, one Kimi session, or one Codex session on a shell-editing model, and a guarded
|
|
153
|
+
claim is advice - so the workspace says `advisory`, whatever its claims were declared as.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Attach this session to the local coordination plane.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" sessionStart"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"UserPromptSubmit": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" beforeTurn"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"PreToolUse": [
|
|
25
|
+
{
|
|
26
|
+
"matcher": "Write|Edit|Bash",
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" guard"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"Stop": [
|
|
36
|
+
{
|
|
37
|
+
"hooks": [
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" finish"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"SessionEnd": [
|
|
46
|
+
{
|
|
47
|
+
"hooks": [
|
|
48
|
+
{
|
|
49
|
+
"type": "command",
|
|
50
|
+
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" sessionEnd"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|