agents-can-communicate 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/README.md +95 -52
  2. package/SECURITY.md +1 -1
  3. package/bin/acc-bootstrap.mjs +5 -54
  4. package/bin/acc-claude-channel.mjs +8 -175
  5. package/bin/acc-hook.mjs +4 -133
  6. package/bin/acc-mcp.mjs +4 -59
  7. package/bin/acc-update-worker.mjs +8 -0
  8. package/bin/acc.mjs +5 -39
  9. package/bin/entrypoints/acc-bootstrap.mjs +47 -0
  10. package/bin/entrypoints/acc-claude-channel.mjs +149 -0
  11. package/bin/entrypoints/acc-hook.mjs +58 -0
  12. package/bin/entrypoints/acc-mcp.mjs +70 -0
  13. package/bin/entrypoints/acc.mjs +51 -0
  14. package/bin/entrypoints/claude-channel-stdio.mjs +29 -0
  15. package/bin/entrypoints/hook-output.mjs +77 -0
  16. package/docs/ADAPTER_AUTHORING.md +52 -8
  17. package/docs/ARCHITECTURE.md +64 -11
  18. package/docs/CAPABILITIES.md +41 -37
  19. package/docs/CLI.md +145 -25
  20. package/docs/CONCEPTS.md +13 -6
  21. package/docs/CONFIGURATION.md +37 -22
  22. package/docs/DESIGN_DECISIONS.md +16 -11
  23. package/docs/GETTING_STARTED.md +41 -7
  24. package/docs/GLOSSARY.md +9 -3
  25. package/docs/HOW_IT_WORKS.md +32 -11
  26. package/docs/MCP.md +91 -24
  27. package/docs/PROTOCOL.md +69 -12
  28. package/docs/RELEASING.md +28 -10
  29. package/docs/SECURITY_MODEL.md +47 -13
  30. package/docs/TROUBLESHOOTING.md +102 -20
  31. package/docs/UPGRADING.md +154 -0
  32. package/docs/WHY_ACC.md +5 -4
  33. package/docs/index.md +8 -0
  34. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  35. package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +106 -5
  36. package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +6 -6
  37. package/node_modules/@agents-can-communicate/adapter-claude-code/src/native-delivery.mjs +2 -1
  38. package/node_modules/@agents-can-communicate/adapter-codex/certification.json +38 -13
  39. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/certification-provenance.json +220 -0
  40. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-current-binding-product-evidence.json +1549 -0
  41. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-current-binding-product.json +27 -0
  42. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-product-evidence.json +1549 -0
  43. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-product.json +27 -0
  44. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-transport-evidence.json +259 -0
  45. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-transport.json +21 -0
  46. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-current-binding-product-evidence.json +1549 -0
  47. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-current-binding-product.json +27 -0
  48. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-product-evidence.json +1549 -0
  49. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-product.json +27 -0
  50. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-transport-evidence.json +259 -0
  51. package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-transport.json +21 -0
  52. package/node_modules/@agents-can-communicate/adapter-codex/package.json +14 -2
  53. package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +106 -5
  54. package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +26 -44
  55. package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +70 -17
  56. package/node_modules/@agents-can-communicate/adapter-codex/src/config-block.mjs +231 -0
  57. package/node_modules/@agents-can-communicate/adapter-codex/src/hooks.mjs +1 -1
  58. package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +71 -63
  59. package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +107 -103
  60. package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +96 -0
  61. package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +3 -2
  62. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  63. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md +106 -5
  64. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  65. package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +2 -2
  66. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  67. package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +122 -8
  68. package/node_modules/@agents-can-communicate/adapter-grok/src/adapter.mjs +5 -3
  69. package/node_modules/@agents-can-communicate/adapter-grok/src/hooks.mjs +9 -0
  70. package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +2 -2
  71. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  72. package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +106 -5
  73. package/node_modules/@agents-can-communicate/adapter-kimi/src/hooks.mjs +1 -1
  74. package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +2 -2
  75. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  76. package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +5 -0
  77. package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +26 -4
  78. package/node_modules/@agents-can-communicate/adapter-sdk/src/decision-text.mjs +19 -0
  79. package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +5 -3
  80. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -0
  81. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +9 -2
  82. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +2 -6
  83. package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +29 -9
  84. package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-block.mjs +8 -6
  85. package/node_modules/@agents-can-communicate/cli/package.json +4 -2
  86. package/node_modules/@agents-can-communicate/cli/src/args.mjs +49 -22
  87. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +11 -4
  88. package/node_modules/@agents-can-communicate/cli/src/git-probe.mjs +9 -3
  89. package/node_modules/@agents-can-communicate/cli/src/help.mjs +109 -14
  90. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +24 -5
  91. package/node_modules/@agents-can-communicate/cli/src/main.mjs +35 -12
  92. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/activation.mjs +110 -0
  93. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/command.mjs +64 -0
  94. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/download.mjs +71 -0
  95. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +110 -0
  96. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +167 -0
  97. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/install.mjs +78 -0
  98. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +60 -0
  99. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/leases.mjs +60 -0
  100. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/location.mjs +55 -0
  101. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/mutex.mjs +145 -0
  102. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/policy.mjs +4 -0
  103. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +40 -0
  104. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/schedule.mjs +21 -0
  105. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +123 -0
  106. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/worker.mjs +65 -0
  107. package/node_modules/@agents-can-communicate/cli/src/runtime-paths.mjs +1 -1
  108. package/node_modules/@agents-can-communicate/cli/src/session-owner.mjs +33 -108
  109. package/node_modules/@agents-can-communicate/cli/src/update-command.mjs +25 -7
  110. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  111. package/node_modules/@agents-can-communicate/core/src/attention.mjs +3 -0
  112. package/node_modules/@agents-can-communicate/core/src/claims.mjs +13 -8
  113. package/node_modules/@agents-can-communicate/core/src/conversations.mjs +9 -25
  114. package/node_modules/@agents-can-communicate/core/src/decision-changes.mjs +36 -0
  115. package/node_modules/@agents-can-communicate/core/src/decision-state.mjs +42 -0
  116. package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +11 -4
  117. package/node_modules/@agents-can-communicate/core/src/inbox.mjs +48 -17
  118. package/node_modules/@agents-can-communicate/core/src/intents.mjs +35 -15
  119. package/node_modules/@agents-can-communicate/core/src/materialisation.mjs +7 -5
  120. package/node_modules/@agents-can-communicate/core/src/message-pages.mjs +75 -0
  121. package/node_modules/@agents-can-communicate/core/src/next-turn-delivery.mjs +31 -0
  122. package/node_modules/@agents-can-communicate/core/src/session-opening.mjs +128 -0
  123. package/node_modules/@agents-can-communicate/core/src/sessions.mjs +58 -132
  124. package/node_modules/@agents-can-communicate/core/src/sync.mjs +31 -2
  125. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  126. package/node_modules/@agents-can-communicate/delivery-router/src/refresh-binding.mjs +38 -0
  127. package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +86 -10
  128. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  129. package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +1 -0
  130. package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +6 -4
  131. package/node_modules/@agents-can-communicate/hook-runner/src/native-retirement.mjs +48 -0
  132. package/node_modules/@agents-can-communicate/hook-runner/src/owner-context.mjs +30 -0
  133. package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +1 -1
  134. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +172 -67
  135. package/node_modules/@agents-can-communicate/hook-runner/src/session-lifecycle.mjs +13 -0
  136. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  137. package/node_modules/@agents-can-communicate/installer/src/apply.mjs +17 -2
  138. package/node_modules/@agents-can-communicate/installer/src/bootstrap-runtime.mjs +11 -5
  139. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +1 -1
  140. package/node_modules/@agents-can-communicate/installer/src/index.mjs +1 -0
  141. package/node_modules/@agents-can-communicate/installer/src/live-policy.mjs +21 -0
  142. package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +20 -6
  143. package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +10 -4
  144. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +3 -3
  145. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  146. package/node_modules/@agents-can-communicate/mcp-server/src/protocol.mjs +85 -0
  147. package/node_modules/@agents-can-communicate/mcp-server/src/resources.mjs +3 -2
  148. package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +59 -57
  149. package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +47 -33
  150. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  151. package/node_modules/@agents-can-communicate/protocol/src/conversations.mjs +3 -0
  152. package/node_modules/@agents-can-communicate/protocol/src/decision-changes.mjs +15 -0
  153. package/node_modules/@agents-can-communicate/protocol/src/index.mjs +3 -1
  154. package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +14 -5
  155. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  156. package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +5 -1
  157. package/node_modules/@agents-can-communicate/storage-filesystem/src/deadline.mjs +7 -0
  158. package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +2 -0
  159. package/node_modules/@agents-can-communicate/storage-filesystem/src/session-read.mjs +25 -0
  160. package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +28 -25
  161. package/package.json +2 -1
package/README.md CHANGED
@@ -1,18 +1,19 @@
1
1
  # agents-can-communicate
2
2
 
3
- **Open your agents. Let them coordinate.**
3
+ **Independent AI sessions. Shared work context.**
4
4
 
5
- Give Codex the backend. Ask Claude Code to build the interface. Keep working as usual.
5
+ Give Codex the backend. Ask Claude Code to build the interface. Keep using the clients
6
+ and models you prefer.
6
7
 
7
- ACC makes supported sessions in the same project aware of each other and gives them
8
- instructions for coordinating. Agents discover their peers, share what they're working
9
- on, and decide when to ask questions, agree on changes, or avoid overlapping edits.
8
+ ACC is a local coordination layer for AI sessions you open yourself. It gives them peer
9
+ presence, work intent, file claims, messages, review requests, and durable handoffs.
10
+ Each session keeps its own conversation, permissions, and task. The agents decide when
11
+ coordination helps their work.
10
12
 
11
- Think of the coordination you expect from subagents, extended across the independent
12
- sessions you open yourself — with different clients and models. You keep giving them
13
- ordinary tasks; they handle the conversations around their work.
13
+ Sessions can use different clients or multiple instances of the same one. They meet in
14
+ the same workspace on the same machine and operating-system user; Git is optional.
14
15
 
15
- [Try it](#try-it) · [Supported clients](#when-messages-arrive) · [Documentation](docs/index.md)
16
+ [Try it](#try-it) · [Client support](#when-messages-arrive) · [Update ACC](#update-or-remove-acc) · [Documentation](docs/index.md)
16
17
 
17
18
  [![CI](https://github.com/automatis-tools/agents-can-communicate/actions/workflows/ci.yml/badge.svg)](https://github.com/automatis-tools/agents-can-communicate/actions/workflows/ci.yml)
18
19
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
@@ -27,9 +28,9 @@ Open two sessions in your project and give each its task:
27
28
  | Codex | Build the backend for account registration. |
28
29
  | Claude Code | Build the registration screen. |
29
30
 
30
- ACC's integration tells the agents that peers are present and teaches them how to find
31
- out what those peers are doing. As they work, they can notice the dependency and coordinate
32
- on their own. An illustrative exchange:
31
+ Supported integrations introduce peer awareness and teach agents how to find out what
32
+ others are doing. As they work, they can notice a dependency and coordinate. An
33
+ illustrative exchange:
33
34
 
34
35
  ```text
35
36
  Claude → Codex I'm building the registration screen. What will your endpoint accept?
@@ -37,71 +38,111 @@ Codex → Claude Email and password. I'll return the new user and handle valid
37
38
  Claude → Codex I'll use that shape and keep my changes in the UI files.
38
39
  ```
39
40
 
40
- The agents identify who to talk to and what to agree on. Your prompts stay focused on
41
- the feature you want to build.
41
+ Your prompts stay focused on the feature. Each agent decides which peers and messages
42
+ matter to its task; installing ACC does not guarantee that a model will coordinate on
43
+ every task. [Client support](#when-messages-arrive) determines how automatically that
44
+ awareness reaches it.
42
45
 
43
- Each session keeps its own conversation and instructions. Response timing depends on the
44
- receiving client: see [when messages arrive](#when-messages-arrive).
46
+ The same setup supports review and recovery. An author can request review of an
47
+ identified revision, receive defects or approval, and leave a handoff describing completed
48
+ work, remaining work, and blockers. A later session can look up that history. Git commits
49
+ can identify a revision; named files and versions work when Git is unavailable.
50
+ See [requests and replies](docs/CLI.md#messages-and-requests) and
51
+ [handoffs](docs/CLI.md#handoff).
45
52
 
46
53
  ## Try it
47
54
 
48
55
  You'll need **macOS or Linux, Node.js 24 or newer**, and two AI sessions in the same
49
- project. They can use different clients or the same one.
56
+ project. Install once on the machine:
50
57
 
51
58
  ```bash
52
59
  npm install -g agents-can-communicate
53
60
  acc install
54
61
  ```
55
62
 
56
- The installer connects the clients it finds. Open a new terminal and restart your AI
57
- clients to load the integration. If you use Codex, accept its plugin trust prompt.
63
+ The installer connects the supported clients it finds. Follow its activation instructions,
64
+ then restart your clients from the project directory. In Codex, check `/plugins` and
65
+ review the current ACC definitions in `/hooks`; changed hooks may need fresh trust.
66
+ [Getting started](docs/GETTING_STARTED.md) covers activation and preserved sandbox settings.
58
67
 
59
- Open two sessions in your project and give them ordinary tasks, as in the example above.
60
- On supported clients, ACC introduces peer awareness through the client's own integration;
61
- its installed instructions guide the agents to coordinate when their work overlaps.
68
+ Open two sessions and give them ordinary tasks, as above. Look for an agent discovering a
69
+ peer, checking who is changing a file, asking about a shared dependency, or replying to a
70
+ review request.
62
71
 
63
- Look for coordination in the agents' own activity: discovering a peer, checking who is
64
- changing a file, or asking about a shared dependency. Each agent decides what is relevant
65
- to its task; the integration provides awareness, not a guarantee that a model will
66
- coordinate on every task. [Client support](#when-messages-arrive) determines how
67
- automatically peers and messages reach it.
72
+ Run `acc doctor` from the project if a peer is missing. A directory containing ACC's own
73
+ state, commonly your home directory, cannot be used as a workspace; start the client in a
74
+ project directory. See [Troubleshooting](docs/TROUBLESHOOTING.md).
68
75
 
69
- If the other session doesn't appear, run `acc doctor` from your project directory.
70
- [Get help with setup](docs/TROUBLESHOOTING.md).
76
+ Already using ACC? Follow the [upgrade guide](docs/UPGRADING.md), including the 0.4.0 →
77
+ 0.4.1 update and the data-format boundary when moving from 0.3.1.
71
78
 
72
79
  ## When messages arrive
73
80
 
74
- ACC connects sessions **on the same machine** and saves messages locally, so a waiting
75
- session can pick them up later. Delivery depends on the client:
81
+ Messages are saved locally before delivery is attempted. A durable inbox remains
82
+ available when a faster route cannot be used.
76
83
 
77
84
  | Client | How the agent receives a message |
78
85
  |---|---|
79
- | Codex CLI, Claude Code, Gemini CLI, Kimi Code | At the next normal turn on verified versions and platforms; otherwise by checking its ACC inbox. |
80
- | Grok or another client connected through [MCP](docs/MCP.md) | By checking its ACC inbox. |
86
+ | Codex CLI, Claude Code, Gemini CLI, Kimi Code | At the next normal turn on the exact verified versions and platforms; otherwise through explicit ACC inbox reads. |
87
+ | Grok | Through explicit ACC inbox reads, using its installed hooks and skill. CLI ownership was verified on Grok 1.0.24. |
88
+ | Other clients connected through [MCP](docs/MCP.md) | Through ACC tools and inbox reads. Generic MCP requires its own client configuration and coordination instructions. |
89
+
90
+ Grok's updated skill first runs public status through the terminal. The ACC hook reminder
91
+ after that result supplies the session's own CLI arguments for subsequent inbox reads and
92
+ mutations. It adds no automatic peer-message injection or idle delivery. A relocated
93
+ `GROK_HOME` is respected by install, doctor, and uninstall.
94
+
95
+ **Optional live delivery can start a turn in an idle Codex or Claude Code session.** It is
96
+ experimental, off by default, and can spend model tokens. On Apple Silicon Macs, Codex
97
+ 0.152.1 or newer requires an already-running LocalDaemon and a verified session;
98
+ Claude Code 2.1.258 or newer requires zsh and shows its development-channel warning at
99
+ startup. Messages arriving mid-turn wait for the turn to finish. The receiving session's
100
+ opt-in policy and current reachability determine whether delivery can proceed.
101
+
102
+ A Codex thread retained by LocalDaemon can receive opted-in messages after its terminal
103
+ exits. Turning ACC delivery off prevents new native offers; already accepted queue entries
104
+ remain with the client. [Compatibility and delivery controls](docs/CAPABILITIES.md) describe
105
+ the exact evidence, versions, platforms, and fallback paths.
106
+
107
+ A recorded message is send success. An offer is not proof of reading, and an
108
+ acknowledgement or reply is not proof that the requested work is complete.
81
109
 
82
- **Claude Code can also reply while idle.** This optional mode is experimental and off by
83
- default. It supports Apple Silicon Macs with zsh and Claude Code 2.1.258 or newer, subject
84
- to compatibility checks. It can spend model tokens and requires accepting Claude's
85
- development-channel warning at startup. Messages arriving mid-turn wait for it to finish.
110
+ ## Keep the workflow you like
86
111
 
87
- Each direction follows the receiving client's rules: Claude's reply won't start a new
88
- Codex turn. Codex can pick it up on a supported next turn or by checking its inbox.
112
+ - **Your usual tools.** Start clients with their normal commands. You choose each agent's
113
+ task, model, and permissions.
114
+ - **Separate checkouts, one project.** Git worktrees share an ACC workspace. Plain folders
115
+ work too; optional workspace configuration can supply a shared identity and roots.
116
+ - **Agree before editing.** Agents can claim files and identify overlapping work. CLI claims
117
+ default to advisory. Guarded claims require certified guards from every live participant.
118
+ [How claims work](docs/CONCEPTS.md#intent-is-awareness-a-claim-commits).
119
+ - **Focused context and durable history.** Normal turn context is bounded. Inbox and history
120
+ return summary pages, with message bodies fetched by id. Agents can supersede or withdraw
121
+ old decisions and recover prior handoffs when needed.
122
+ - **Local coordination.** State lives in app data outside your project. ACC never collects
123
+ or shares raw transcripts; peer messages are untrusted input. Your clients keep using
124
+ their usual model providers.
89
125
 
90
- [Full compatibility details](docs/CAPABILITIES.md).
126
+ One npm package. ACC needs no separate account, model API key, or hosted service.
91
127
 
92
- ## Keep the workflow you like
128
+ ## Update or remove ACC
129
+
130
+ Initial installation enables automatic updates. ACC downloads stable releases in the
131
+ background and waits for active clients and ACC processes to exit before switching the
132
+ runtime and refreshing integrations. Restart clients afterward and complete any requested
133
+ hook or plugin trust review.
93
134
 
94
- - **Your usual tools.** Start clients with their normal commands. You choose each
95
- agent's task, model, and permissions.
96
- - **Separate checkouts, one project.** Git worktrees share an ACC workspace.
97
- Plain project folders work too; Git is optional.
98
- - **Agree before editing.** Agents can reserve files and identify overlapping work.
99
- Reservations are advisory unless the clients support enforcement.
100
- [How reservations work](docs/CONCEPTS.md#intent-is-awareness-a-claim-commits).
101
- - **Local coordination.** Messages live in app data outside your project. ACC doesn't
102
- collect or share raw transcripts. Your clients use their usual model providers.
135
+ For an immediate update:
136
+
137
+ ```bash
138
+ acc update
139
+ ```
103
140
 
104
- One npm package. No account or separate coordination service to set up.
141
+ An existing managed installation selects its new runtime through this command. Updates
142
+ also support an explicit opt-out and version pinning. Reinstalling preserves your update
143
+ preference; see [update controls](docs/UPGRADING.md#automatic-updates-after-installation).
144
+ If you fully uninstalled ACC 0.4.0 before upgrading and want automatic updates back, run
145
+ `acc update --auto on` once: that version's uninstall record lost the previous preference.
105
146
 
106
147
  To remove the integrations:
107
148
 
@@ -109,7 +150,9 @@ To remove the integrations:
109
150
  acc uninstall
110
151
  ```
111
152
 
112
- ACC preserves configuration changes you've made since installation.
153
+ ACC removes owned artifacts that still match its install record, preserves your edits and
154
+ coordination history, and pauses automatic updates. A later install remembers the update
155
+ preference.
113
156
 
114
157
  ## Go further
115
158
 
package/SECURITY.md CHANGED
@@ -19,7 +19,7 @@ version. A reproduction against a throwaway `ACC_DATA_HOME` is ideal.
19
19
  | Peer text escaping its quoted block | An attacker who already has write access to your data home |
20
20
  | Any path escaping the managed root | A model choosing to obey persuasive peer text |
21
21
  | Uninstall deleting files ACC did not write | Vulnerabilities in Codex, Claude Code, Gemini, Grok, or Kimi themselves |
22
- | ACC writing into a repository | Denial of service by a trusted peer |
22
+ | Unexpected ACC runtime/state or installer artifacts in a repository (excluding user-requested `acc.workspace.json`) | Denial of service by a trusted peer |
23
23
  | Session impersonation across MCP | |
24
24
 
25
25
  Reasoning behind each: [docs/SECURITY_MODEL.md](docs/SECURITY_MODEL.md).
@@ -1,56 +1,7 @@
1
1
  #!/usr/bin/env node
2
- // Internal entry point behind an ACC-owned shell shim: is this exact vendor
3
- // executable eligible for native delivery right now? Exit 0 means yes and the
4
- // shim exports its owned policy before `exec`; any other exit - including a
5
- // crash, a missing adapter, or a damaged install - means the shim launches the
6
- // vendor command untouched. Nothing is ever written to stdout, and stderr stays
7
- // empty unless ACC_BOOTSTRAP_DEBUG=1 asks for one safe line.
8
- import { checkNativeBootstrap } from "@agents-can-communicate/installer";
2
+ import { fileURLToPath } from "node:url";
3
+ import { invokedDirectly, runEntry } from "@agents-can-communicate/cli/managed-entry";
9
4
 
10
- import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
11
- import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
12
- import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
13
- import { createGrokAdapter } from "@agents-can-communicate/adapter-grok";
14
- import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
15
-
16
- const FLAGS = new Map([["--adapter", "adapter"], ["--real-executable", "realExecutable"],
17
- ["--data-home", "dataHome"]]);
18
-
19
- function debug(line) {
20
- if (process.env.ACC_BOOTSTRAP_DEBUG === "1") process.stderr.write(`acc-bootstrap: ${line}\n`);
21
- }
22
-
23
- function parse(args) {
24
- const options = {};
25
- for (let index = 0; index < args.length; index += 2) {
26
- const key = FLAGS.get(args[index]);
27
- const value = args[index + 1];
28
- if (key === undefined || typeof value !== "string" || value === "" || key in options) return null;
29
- options[key] = value;
30
- }
31
- return FLAGS.values().every(key => key in options) ? options : null;
32
- }
33
-
34
- try {
35
- const options = parse(process.argv.slice(2));
36
- if (options === null) {
37
- debug("usage: --adapter <id> --real-executable <path> --data-home <path>");
38
- process.exit(2);
39
- }
40
- const registry = {
41
- claude_code: createClaudeCodeAdapter, codex: createCodexAdapter,
42
- gemini_cli: createGeminiCliAdapter, grok: createGrokAdapter, kimi: createKimiAdapter,
43
- };
44
- const adapter = registry[options.adapter]?.();
45
- if (adapter === undefined) {
46
- debug(`${options.adapter}: unknown adapter`);
47
- process.exit(1);
48
- }
49
- const result = await checkNativeBootstrap({ adapter, realExecutable: options.realExecutable,
50
- platform: `${process.platform}-${process.arch}`, dataHome: options.dataHome, timeoutMs: 750 });
51
- debug(`${options.adapter}: ${result.supported ? "supported" : result.reasonCode}`);
52
- process.exit(result.supported ? 0 : 1);
53
- } catch (error) {
54
- debug(`failed: ${String(error?.code ?? error?.name ?? "error")}`);
55
- process.exit(1);
56
- }
5
+ if (invokedDirectly(import.meta.url)) await runEntry({
6
+ kind: "acc-bootstrap", packageRoot: fileURLToPath(new URL("..", import.meta.url)),
7
+ });
@@ -1,177 +1,10 @@
1
1
  #!/usr/bin/env node
2
- // The ACC Channel binary Claude Code spawns as a stdio MCP child. It composes
3
- // the same workspace discovery, session binding, core service, and local
4
- // data-home rules as acc-hook, opens a session-scoped endpoint for the delivery
5
- // router, and turns the model's explicit acc_reply / acc_ack into real ACC
6
- // records. It never collects a transcript or runs a model, and it fails open:
7
- // any composition error leaves Claude a plain MCP server that simply offers no
8
- // tools rather than crashing the session.
9
- import { randomBytes } from "node:crypto";
10
- import { realpathSync } from "node:fs";
11
- import path from "node:path";
12
2
  import { fileURLToPath } from "node:url";
13
-
14
- import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
15
- import { listSessionBindings } from "@agents-can-communicate/adapter-sdk";
16
- import { createCoordinationService } from "@agents-can-communicate/core";
17
- import { resolveClientPid } from "@agents-can-communicate/hook-runner/client-pid";
18
- import { readProcessTable } from "@agents-can-communicate/hook-runner/process-table";
19
- import { createId } from "@agents-can-communicate/protocol";
20
- import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
21
- import { createGitProbe, discoverWorkspace, platformDataHome, runtimePaths }
22
- from "@agents-can-communicate/cli";
23
-
24
- import { createAccChannel, createInertChannel, endpointDir, routeAck, routeReply }
25
- from "@agents-can-communicate/adapter-claude-code/channel";
26
-
27
- const clock = { now: () => new Date().toISOString() };
28
- const ids = { next: kind => createId(kind, randomBytes) };
29
-
30
- /**
31
- * The ACC session this Claude process opened - decided by which client process
32
- * this Channel is running under, never by how many sessions happen to be
33
- * visible.
34
- *
35
- * There used to be a fallback here: if exactly one session was live, it was
36
- * taken to be this one. On a single-session machine that looked safe, and with
37
- * two sessions it silently handed one client the other's identity. The Channel
38
- * starts while its own SessionStart hook is still writing its binding, so the
39
- * only binding a second client can see at that moment is the *first* client's -
40
- * and the wait that was added to survive that race then locks the wrong answer
41
- * in on the first attempt, because it is a complete, live, plausible binding.
42
- *
43
- * Measured on two real 2.1.259 sessions: both Channels registered an endpoint
44
- * under the first client's pid. `bindNativeSession` refuses two registrations
45
- * for one pid, so the session that had been receiving live messages dropped
46
- * back to the durable inbox - and an `acc_reply` from the second window would
47
- * have been recorded as the first session's answer, which is a false record
48
- * rather than a lost message.
49
- *
50
- * This process was spawned by its client, so its own ancestry names that
51
- * client, and no race can change that. Ownership is decided by it.
52
- */
53
- export async function resolveSession({ runtimeDir, service, env, ownClientPid,
54
- listBindings = listSessionBindings }) {
55
- if (!Number.isInteger(ownClientPid)) return null;
56
- const bindings = await listBindings({ runtimeDir });
57
- if (bindings.length === 0) return null;
58
- const status = await service.collectStatus({});
59
- const live = new Set(status.participants
60
- .filter(participant => participant.presence !== "offline")
61
- .map(participant => participant.sessionId));
62
- const mine = bindings.filter(binding => live.has(binding.accSessionId)
63
- && binding.clientPid === ownClientPid);
64
- // One client process can outlive a session and open another, so the pid alone
65
- // can still name two. The harness session id the client exports separates
66
- // them; with nothing to separate them the answer is nobody, as before.
67
- const exported = new Set(Object.values(env ?? {}).filter(value => typeof value === "string"));
68
- const matched = mine.filter(binding => exported.has(binding.harnessSessionId));
69
- const chosen = mine.length === 1 ? mine[0] : matched.length === 1 ? matched[0] : null;
70
- return chosen === null ? null
71
- : { sessionId: chosen.accSessionId, generation: chosen.generation, clientPid: chosen.clientPid };
72
- }
73
-
74
- /** The client process this Channel was spawned by, or null when nobody knows. */
75
- export async function ownClient({ table = null, from = process.pid,
76
- command = createClaudeCodeAdapter().client.command } = {}) {
77
- return resolveClientPid({ table: table ?? await readProcessTable(), from, command });
78
- }
79
-
80
- // How long the Channel will wait for the hook to publish this session's
81
- // binding. Claude spawns this child while SessionStart is still running - both
82
- // landed in the same second on a real 2.1.259 launch - and a single lookup
83
- // loses that race often enough to make native delivery activate or not per
84
- // launch. Bounded, because a session with no ACC binding must still be answered.
85
- const BINDING_WAIT_MS = 5_000;
86
- const BINDING_POLL_MS = 100;
87
-
88
- /**
89
- * The session binding, once it exists - or null when the wait runs out.
90
- *
91
- * A binding with no `clientPid` names no process, and the Channel binds an
92
- * exact one, so it does not count as resolved: the hook writes the identity
93
- * first and the certified facts after, and reading in between must keep waiting
94
- * rather than settle for the half-written answer.
95
- */
96
- export async function resolveWithin({ resolve, deadline, intervalMs = BINDING_POLL_MS,
97
- now = () => Date.now(), sleep = ms => new Promise(resolve_ => setTimeout(resolve_, ms)) }) {
98
- for (;;) {
99
- const session = await resolve();
100
- if (session !== null && Number.isInteger(session?.clientPid)) return session;
101
- if (now() >= deadline) return null;
102
- await sleep(intervalMs);
103
- }
104
- }
105
-
106
- async function main() {
107
- const descriptor = await discoverWorkspace({ cwd: process.cwd(), env: process.env,
108
- gitProbe: createGitProbe() });
109
- const paths = runtimePaths({ dataHome: platformDataHome({ platform: process.platform,
110
- env: process.env }), workspaceId: descriptor.id, workspaceRoots: descriptor.roots });
111
- const store = await openFilesystemStore({ root: paths.root, clock, ids,
112
- workspaceId: descriptor.id });
113
- const service = createCoordinationService({ store, clock, ids });
114
- const write = payload => process.stdout.write(`${JSON.stringify(payload)}\n`);
115
- // Read once: the ancestry that decides ownership cannot change while this
116
- // process runs, and re-reading it on every poll would spawn `ps` in a loop.
117
- const clientPid = await ownClient();
118
- const session = await resolveWithin({
119
- resolve: () => resolveSession({ runtimeDir: paths.root, service, env: process.env,
120
- ownClientPid: clientPid }),
121
- deadline: Date.now() + BINDING_WAIT_MS });
122
- // No binding to serve - but Claude is already speaking MCP to this child, and
123
- // returning here left the event loop empty: the process exited in 75ms
124
- // without answering `initialize`, and Claude reported the server as failed to
125
- // connect on every session that enables the plugin without ACC's shim.
126
- if (session === null || !Number.isInteger(session.clientPid)) {
127
- const inert = createInertChannel({ write });
128
- pump(inert.handleLine, () => process.exit(0));
129
- return inert;
130
- }
131
-
132
- const channel = createAccChannel({
133
- endpointDir: endpointDir(paths.root),
134
- clientPid: session.clientPid,
135
- write,
136
- routeReply: ({ messageId, body }) => routeReply({ service, session, messageId, body }),
137
- routeAck: ({ messageId }) => routeAck({ service, session, messageId }),
138
- // This process is the only party that knows the endpoint is still being
139
- // served, and Claude publishes no heartbeat, so nothing else would move
140
- // ACC's lease while the session sits idle - which is when live delivery is
141
- // worth having.
142
- refreshBinding: leaseUntil => service.refreshDeliveryBinding({
143
- sessionId: session.sessionId, generation: session.generation, leaseUntil }),
144
- });
145
- await channel.listen();
146
- pump(channel.handleLine, () => { channel.close(); process.exit(0); });
147
- return channel;
148
- }
149
-
150
- /** One line-delimited JSON pump, for the bound server and the unbound one alike. */
151
- function pump(handleLine, shutdown) {
152
- let buffer = "";
153
- process.stdin.setEncoding("utf8");
154
- process.stdin.on("data", async chunk => {
155
- buffer += chunk;
156
- let newline = buffer.indexOf("\n");
157
- while (newline !== -1) {
158
- const line = buffer.slice(0, newline).trim();
159
- buffer = buffer.slice(newline + 1);
160
- newline = buffer.indexOf("\n");
161
- if (line !== "") await handleLine(line);
162
- }
163
- });
164
- process.stdin.on("end", shutdown);
165
- process.once("SIGTERM", shutdown);
166
- process.once("SIGINT", shutdown);
167
- // Keeps the child answerable for as long as Claude holds the transport open.
168
- process.stdin.resume();
169
- }
170
-
171
- const invokedDirectly = typeof process.argv[1] === "string"
172
- && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
173
- if (invokedDirectly) {
174
- // Fail open: a Channel that cannot compose is simply absent, never a crash in
175
- // front of the user's session.
176
- await main().catch(() => { process.stdin.resume(); });
177
- }
3
+ import { invokedDirectly, runEntry } from "@agents-can-communicate/cli/managed-entry";
4
+ export const resolveSession = async options => (await import("./entrypoints/acc-claude-channel.mjs")).resolveSession(options);
5
+ export const ownClient = async options => (await import("./entrypoints/acc-claude-channel.mjs")).ownClient(options);
6
+ export const resolveWithin = async options => (await import("./entrypoints/acc-claude-channel.mjs")).resolveWithin(options);
7
+
8
+ if (invokedDirectly(import.meta.url)) await runEntry({
9
+ kind: "acc-claude-channel", packageRoot: fileURLToPath(new URL("..", import.meta.url)),
10
+ });
package/bin/acc-hook.mjs CHANGED
@@ -1,137 +1,8 @@
1
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
- import { realpathSync } from "node:fs";
10
2
  import { fileURLToPath } from "node:url";
3
+ import { invokedDirectly, runEntry } from "@agents-can-communicate/cli/managed-entry";
4
+ export { writeOutput, completeHookOutput } from "./entrypoints/hook-output.mjs";
11
5
 
12
- import { createId } from "@agents-can-communicate/protocol";
13
- import { runHook } from "@agents-can-communicate/hook-runner";
14
-
15
- import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
16
- import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
17
- import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
18
- import { createGrokAdapter } from "@agents-can-communicate/adapter-grok";
19
- import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
20
-
21
- const adapters = {
22
- claude_code: createClaudeCodeAdapter(),
23
- codex: createCodexAdapter(),
24
- gemini_cli: createGeminiCliAdapter(),
25
- grok: createGrokAdapter(),
26
- kimi: createKimiAdapter(),
27
- };
28
-
29
- const readStdin = () => new Promise(resolve => {
30
- // A hook is always given its payload on stdin, but a client that closes it
31
- // without writing must not leave this process hanging in front of a turn.
32
- if (process.stdin.isTTY) return resolve("");
33
- let raw = "";
34
- process.stdin.setEncoding("utf8");
35
- process.stdin.on("data", chunk => { raw += chunk; });
36
- process.stdin.on("end", () => resolve(raw));
37
- process.stdin.on("error", () => resolve(""));
38
- return undefined;
6
+ if (invokedDirectly(import.meta.url)) await runEntry({
7
+ kind: "acc-hook", packageRoot: fileURLToPath(new URL("..", import.meta.url)),
39
8
  });
40
-
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
- }
80
-
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;
130
- try {
131
- isMain = process.argv[1] !== undefined
132
- && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
133
- } catch {
134
- isMain = false;
135
- }
136
-
137
- if (isMain) await main();