agents-can-communicate 0.5.7 → 0.5.9

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 (27) hide show
  1. package/README.md +99 -161
  2. package/bin/entrypoints/acc-claude-channel.mjs +8 -1
  3. package/bin/entrypoints/claude-channel-binding.mjs +46 -0
  4. package/docs/ARCHITECTURE.md +7 -0
  5. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  6. package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +9 -6
  7. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  8. package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +9 -5
  9. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  10. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  11. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  12. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  13. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  14. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -1
  15. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +1 -1
  16. package/node_modules/@agents-can-communicate/adapter-sdk/src/own-version.mjs +11 -3
  17. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  18. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/install.mjs +6 -1
  19. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +6 -1
  20. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  21. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  22. package/node_modules/@agents-can-communicate/hook-runner/package.json +4 -2
  23. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  24. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  25. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  26. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  27. package/package.json +1 -1
package/README.md CHANGED
@@ -1,186 +1,124 @@
1
- # agents-can-communicate
2
-
3
- **Independent AI sessions. Shared work context.**
1
+ # Agents Can Communicate (ACC)
2
+
3
+ **Let your AI coding sessions talk to each other.**
4
+
5
+ ACC connects independent AI coding sessions in the clients you already use. They can ask each
6
+ other questions, exchange reviews, and leave handoffs for another session to continue.
7
+
8
+ You open each client normally and choose its work. Every session keeps its own model,
9
+ conversation, and permissions. Coordination runs locally, with no lead agent managing the
10
+ others.
11
+
12
+ ```mermaid
13
+ flowchart TB
14
+ you["You choose the tools and work"]
15
+ codex["Codex<br/>Feature A"]
16
+ claude["Claude Code<br/>Feature B"]
17
+ gemini["Gemini CLI<br/>Feature C"]
18
+ acc["Communication via ACC"]
19
+
20
+ you -.-> codex
21
+ you -.-> claude
22
+ you -.-> gemini
23
+ codex <--> acc
24
+ claude <--> acc
25
+ gemini <--> acc
26
+
27
+ subgraph handoff["When you switch models"]
28
+ direction LR
29
+ current["Current<br/>session"] --> saved["Handoff<br/>Done · Decisions<br/>Next steps"]
30
+ saved --> next["Next<br/>session"]
31
+ end
32
+ acc ~~~ handoff
33
+
34
+ classDef human fill:#f1f5f9,stroke:#8593a3,color:#202a35
35
+ classDef session fill:#fff,stroke:#ced4d9,color:#202a35
36
+ classDef shared fill:#e8edff,stroke:#385cde,color:#202a35
37
+ class you human
38
+ class codex,claude,gemini,current,next session
39
+ class acc,saved shared
40
+ style handoff fill:transparent,stroke:#ced4d9
41
+ linkStyle 3,4,5,6,7 stroke:#385cde,stroke-width:2px
42
+ ```
4
43
 
5
- Give Codex the backend. Ask Claude Code to build the interface. Keep using the clients
6
- and models you prefer.
44
+ ## When ACC helps
7
45
 
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.
46
+ - **Switch models mid-feature.** When a limit approaches or you want another model’s approach,
47
+ leave a handoff with decisions and unfinished work for the next session.
48
+ - **Get a second opinion.** Ask another session to review a specific change and send its
49
+ findings directly to the implementing agent.
50
+ - **Bring parallel features together.** Let sessions working on frontend and backend ask each
51
+ other about a shared API before building around different assumptions.
12
52
 
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.
53
+ ## Try one handoff
15
54
 
16
- [Try it](#try-it) · [Client support](#when-messages-arrive) · [Update ACC](#update-or-remove-acc) · [Documentation](docs/index.md)
55
+ You’ll need **macOS or Linux, Node.js 24 or newer**, and supported coding clients on the same
56
+ machine and operating-system user.
17
57
 
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)
19
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
20
- [![Node](https://img.shields.io/badge/node-%E2%89%A524-brightgreen.svg)](https://nodejs.org)
58
+ ```bash
59
+ npm install -g agents-can-communicate
60
+ acc install
61
+ ```
21
62
 
22
- ## You describe the work. They work out the details.
63
+ The installer connects supported clients it finds. Follow its activation instructions, review
64
+ any required hook or plugin trust, then restart your clients from the project directory. The
65
+ [setup guide](docs/GETTING_STARTED.md) covers client-specific steps.
23
66
 
24
- Open two sessions in your project and give each its task:
67
+ For example, when pausing work on an account-registration feature, ask:
25
68
 
26
- | Session | Your prompt |
27
- |---|---|
28
- | Codex | Build the backend for account registration. |
29
- | Claude Code | Build the registration screen. |
69
+ ```text
70
+ Save a partial handoff in ACC for the registration feature. Include
71
+ what is done, our decisions, what remains, and what you actually
72
+ verified. I will continue in another session.
73
+ ```
30
74
 
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:
75
+ Open another supported client in the same project and ask:
34
76
 
35
77
  ```text
36
- Claude → Codex I'm building the registration screen. What will your endpoint accept?
37
- Codex → Claude Email and password. I'll return the new user and handle validation.
38
- Claude → Codex I'll use that shape and keep my changes in the UI files.
78
+ Continue the registration feature from its ACC handoff. Check the
79
+ saved decisions against the current files, then take the next
80
+ unfinished step. Ask me if the scope is unclear.
39
81
  ```
40
82
 
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.
83
+ The next session should identify the saved decision and begin the remaining work. It can find
84
+ the handoff even if it was opened after the previous session stopped.
45
85
 
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).
86
+ Save the handoff while the first model can still respond. ACC preserves explicitly recorded
87
+ context; it cannot recover details that were never saved.
52
88
 
53
- ## Try it
89
+ ## Client support
54
90
 
55
- You'll need **macOS or Linux, Node.js 24 or newer**, and two AI sessions in the same
56
- project. Install once on the machine:
91
+ Integrations are available for **Claude Code, Codex, Gemini CLI, Grok, and Kimi Code**. Other
92
+ clients can connect through [MCP](docs/MCP.md) with their own configuration and coordination
93
+ instructions.
57
94
 
58
- ```bash
59
- npm install -g agents-can-communicate
60
- acc install
61
- ```
62
-
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.
67
-
68
- For Codex live delivery on Apple Silicon macOS, installation also configures outgoing
69
- local socket access on Codex 0.153.4 or newer when using default workspace permissions.
70
- Custom policies are preserved. One default-No choice covers all selected clients that need
71
- live-delivery consent. On Codex 0.154.0 or newer, the choice includes downloading a missing
72
- standalone package from OpenAI and starting its local service. ACC installs the matching
73
- version and preserves your existing `codex` command and shell profiles. The client still owns session
74
- startup, hook trust, and Claude Channels approval. Doctor reports service infrastructure,
75
- session binding, and outgoing permissions separately.
76
-
77
- Open two sessions and give them ordinary tasks, as above. Look for an agent discovering a
78
- peer, checking who is changing a file, asking about a shared dependency, or replying to a
79
- review request.
80
-
81
- Run `acc doctor` from the project if a peer is missing. A directory containing ACC's own
82
- state, commonly your home directory, cannot be used as a workspace; start the client in a
83
- project directory. See [Troubleshooting](docs/TROUBLESHOOTING.md).
84
-
85
- Already using ACC? Follow the [upgrade guide](docs/UPGRADING.md), including the 0.4.x →
86
- 0.5.0 update and the data-format boundary when moving from 0.3.1.
87
-
88
- ## When messages arrive
89
-
90
- Messages are saved locally before delivery is attempted. A durable inbox remains
91
- available when a faster route cannot be used.
92
-
93
- | Client | How the agent receives a message |
94
- |---|---|
95
- | 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. |
96
- | Grok | Through explicit ACC inbox reads, using its installed hooks and skill. CLI ownership was verified on Grok 1.0.24. |
97
- | Other clients connected through [MCP](docs/MCP.md) | Through ACC tools and inbox reads. Generic MCP requires its own client configuration and coordination instructions. |
98
-
99
- Grok's updated skill first runs public status through the terminal. The ACC hook reminder
100
- after that result supplies the session's own CLI arguments for subsequent inbox reads and
101
- mutations. It adds no automatic peer-message injection or idle delivery. A relocated
102
- `GROK_HOME` is respected by install, doctor, and uninstall.
103
-
104
- **Optional live delivery can start a turn in an idle Codex or Claude Code session.** It is
105
- experimental, off by default, and can spend model tokens. On Apple Silicon Macs, Codex
106
- 0.152.1 or newer requires LocalDaemon infrastructure and a verified session;
107
- Claude Code 2.1.258 or newer requires zsh and client-side Channels activation; check its
108
- startup notice for ACC and accept the development warning when shown. An MCP connection
109
- alone does not verify inbound delivery. Messages arriving mid-turn wait for the turn to
110
- finish. The receiving session's
111
- opt-in policy and current reachability determine whether delivery can proceed.
112
- `acc install` reports each client's delivery state and can save Codex consent before its
113
- service is available. Use `--delivery actionable|all` for explicit automation and
114
- `--delivery off` to disable incoming automatic requests. `acc doctor` also names each session’s last native binding result,
115
- including missing launch consent, an unidentified client process or a failed handshake.
116
- It distinguishes a disabled policy from an unavailable service or a missing live channel
117
- in the current project.
118
-
119
- A Codex thread retained by LocalDaemon can receive opted-in messages after its terminal
120
- exits. Turning ACC delivery off prevents new native offers; already accepted queue entries
121
- remain with the client. [Compatibility and delivery controls](docs/CAPABILITIES.md) describe
122
- the exact evidence, versions, platforms, and fallback paths.
123
-
124
- A recorded message is send success. An offer is not proof of reading, and an
125
- acknowledgement or reply is not proof that the requested work is complete.
126
-
127
- ## Keep the workflow you like
128
-
129
- - **Your usual tools.** Start clients with their normal commands. You choose each agent's
130
- task, model, and permissions.
131
- - **Separate checkouts, one project.** Git worktrees share an ACC workspace. Plain folders
132
- work too; optional workspace configuration can supply a shared identity and roots.
133
- - **Agree before editing.** Agents can claim files and identify overlapping work. CLI claims
134
- default to advisory. Guarded claims require certified guards from every live participant.
135
- [How claims work](docs/CONCEPTS.md#intent-is-awareness-a-claim-commits).
136
- - **Focused context and durable history.** Normal turn context is bounded. Inbox and history
137
- return summary pages, with message bodies fetched by id. Agents can supersede or withdraw
138
- old decisions and recover prior handoffs when needed.
139
- - **Local coordination.** State lives in app data outside your project. ACC never collects
140
- or shares raw transcripts; peer messages are untrusted input. Your clients keep using
141
- their usual model providers.
142
-
143
- One npm package. ACC needs no separate account, model API key, or hosted service.
144
-
145
- ## Update or remove ACC
146
-
147
- Initial installation enables automatic updates. ACC downloads stable releases in the
148
- background, then switches the runtime and refreshes integrations once nothing blocks. A
149
- live client or ACC process blocks only while the store contract it declares differs from
150
- the incoming version's or is unknown; one declaring a matching contract keeps running
151
- through the switch. When a verified Codex service needs a restart,
152
- `acc update` asks once, then completes maintenance in a separate process. Open clients
153
- disconnect; `acc doctor` reports progress and the result. See [update and recovery details](docs/UPGRADING.md#confirmed-client-service-maintenance).
154
- Restart or resume clients afterward and complete any requested hook or plugin trust review.
155
-
156
- For an immediate update:
95
+ Automatic delivery depends on the client version and platform. Verified integrations can
96
+ provide messages at the next normal turn; other sessions read their ACC inbox explicitly.
157
97
 
158
- ```bash
159
- acc update
160
- ```
98
+ Experimental live delivery can wake eligible Claude Code and Codex sessions on Apple Silicon
99
+ macOS. It requires opt-in and an active verified connection, is off by default, and can spend
100
+ model tokens. Messages for busy sessions queue until the current turn ends.
161
101
 
162
- An existing managed installation selects its new runtime through this command. Updates
163
- also support an explicit opt-out and version pinning. Reinstalling preserves your update
164
- preference; see [update controls](docs/UPGRADING.md#automatic-updates-after-installation).
165
- If you fully uninstalled ACC 0.4.0 before upgrading and want automatic updates back, run
166
- `acc update --auto on` once: that version's uninstall record lost the previous preference.
102
+ See [client capabilities](docs/CAPABILITIES.md) for exact support. Run `acc doctor` from your
103
+ project if a peer is missing or delivery differs from what you expect; see
104
+ [troubleshooting](docs/TROUBLESHOOTING.md). Agents decide when to coordinate; ACC does not
105
+ guarantee they will notice every dependency.
167
106
 
168
- To remove the integrations:
107
+ ## Local and independent
169
108
 
170
- ```bash
171
- acc uninstall
172
- ```
109
+ Sessions share coordination within the same ACC workspace. Git is optional; worktrees of one
110
+ repository share that workspace while keeping separate files. Messages and handoffs are stored
111
+ in local app data outside your project. ACC never collects or shares raw session transcripts.
173
112
 
174
- ACC removes owned artifacts that still match its install record, preserves your edits and
175
- coordination history, and pauses automatic updates. A later install remembers the update
176
- preference.
113
+ ACC needs no separate account, model API key, or hosted service. Your coding clients keep using
114
+ their existing provider access.
177
115
 
178
- ## Go further
116
+ Automatic updates are enabled on first install. Use `acc update --auto off` to disable them,
117
+ `acc update` to update manually, and `acc uninstall` to remove integrations. See
118
+ [update controls](docs/UPGRADING.md) for details.
179
119
 
180
- [Getting started](docs/GETTING_STARTED.md) · [CLI reference](docs/CLI.md) ·
181
- [Connect an MCP client](docs/MCP.md) · [How ACC works](docs/HOW_IT_WORKS.md) ·
182
- [Security](docs/SECURITY_MODEL.md)
120
+ MIT-licensed and permanently noncommercial. Try it on one real task and
121
+ [tell us where you still had to carry messages yourself](https://github.com/automatis-tools/agents-can-communicate/issues).
183
122
 
184
- Want to contribute or add a client?
185
- Start with [AGENTS.md](https://github.com/automatis-tools/agents-can-communicate/blob/main/AGENTS.md)
186
- and the [adapter guide](docs/ADAPTER_AUTHORING.md).
123
+ [Documentation](docs/index.md) ·
124
+ [Contributing](https://github.com/automatis-tools/agents-can-communicate/blob/main/AGENTS.md)
@@ -25,6 +25,7 @@ import { createAccChannel, endpointDir, routeAck, routeReply }
25
25
  from "@agents-can-communicate/adapter-claude-code/channel";
26
26
 
27
27
  import { pump, startInertChannel } from "./claude-channel-stdio.mjs";
28
+ import { activateClaudeChannel } from "./claude-channel-binding.mjs";
28
29
 
29
30
  const clock = { now: () => new Date().toISOString() };
30
31
  const ids = { next: kind => createId(kind, randomBytes) };
@@ -70,7 +71,8 @@ export async function resolveSession({ runtimeDir, service, env, ownClientPid,
70
71
  const matched = mine.filter(binding => exported.has(binding.harnessSessionId));
71
72
  const chosen = mine.length === 1 ? mine[0] : matched.length === 1 ? matched[0] : null;
72
73
  return chosen === null ? null
73
- : { sessionId: chosen.accSessionId, generation: chosen.generation, clientPid: chosen.clientPid };
74
+ : { sessionId: chosen.accSessionId, generation: chosen.generation, clientPid: chosen.clientPid,
75
+ harnessSessionId: chosen.harnessSessionId };
74
76
  }
75
77
 
76
78
  /** The client process this Channel was spawned by, or null when nobody knows. */
@@ -151,6 +153,11 @@ async function compose() {
151
153
  });
152
154
  await channel.listen();
153
155
  pump(channel.handleLine, () => { channel.close(); process.exit(0); });
156
+ // Serve MCP immediately; a slow or failed ACC binding must not stop the
157
+ // client connecting. The listening endpoint lets an in-flight hook finish
158
+ // before this owner acquires the same lifecycle lock and completes startup.
159
+ await activateClaudeChannel({ session, service, runtimeDir: paths.root, dataHome,
160
+ env: process.env }).catch(() => {});
154
161
  return channel;
155
162
  }
156
163
 
@@ -0,0 +1,46 @@
1
+ import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
2
+ import { loadSessionBinding, storeNativeAttempt } from "@agents-can-communicate/adapter-sdk";
3
+ import { createCoordinationService } from "@agents-can-communicate/core";
4
+ import { establishNativeBinding, livePolicyFrom } from "@agents-can-communicate/hook-runner/native-binding";
5
+ import { withSessionLifecycle } from "@agents-can-communicate/hook-runner/session-lifecycle";
6
+ import { readInstalledLivePolicy } from "@agents-can-communicate/installer";
7
+ import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
8
+
9
+ // SessionStart cannot wait for MCP indefinitely. The endpoint's owner completes
10
+ // the same handshake when it starts later, without a prompt or an LLM turn.
11
+ // Serialize with the hooks, then reread ownership: a queued startup must never
12
+ // publish for a replaced/closed session or undo SessionEnd's retirement.
13
+ export async function activateClaudeChannel({ session, service, runtimeDir, dataHome, env,
14
+ deadlineAt = Date.now() + 5_000 }) {
15
+ if (typeof session.harnessSessionId !== "string") return null;
16
+ return withSessionLifecycle({ root: runtimeDir, sessionId: session.harnessSessionId,
17
+ clock: service.clock, deadlineAt }, async () => {
18
+ // A prior hook can die after committing its journal. Recover that close
19
+ // before reading ownership, just as the hook runner does under this lock.
20
+ const { clock, ids } = service;
21
+ const store = await openFilesystemStore({ root: runtimeDir, clock, ids,
22
+ workspaceId: service.store.workspaceId, deadlineAt });
23
+ service = createCoordinationService({ store, clock, ids });
24
+ const current = await loadSessionBinding({ runtimeDir,
25
+ harnessSessionId: session.harnessSessionId });
26
+ if (current?.accSessionId !== session.sessionId || current.generation !== session.generation
27
+ || current.clientPid !== session.clientPid) return null;
28
+ const located = await service.locateSession(session.sessionId);
29
+ if (located?.record.state !== "open" || located.record.generation !== session.generation) return null;
30
+ const policy = livePolicyFrom(env);
31
+ if (policy === "off" || await readInstalledLivePolicy({ dataHome,
32
+ adapterId: "claude_code" }) === "off") return null;
33
+ if (Date.now() >= deadlineAt) return null;
34
+ const result = await establishNativeBinding({ adapter: createClaudeCodeAdapter(),
35
+ event: { kind: "channelReady", sessionId: session.harnessSessionId },
36
+ hookBinding: current, clientVersion: current.clientVersion, platform: current.platform,
37
+ livePolicy: policy, service, runtimeDir, clock: service.clock, env,
38
+ timeoutMs: Math.max(1, Math.min(750, deadlineAt - Date.now())) });
39
+ await storeNativeAttempt({ runtimeDir, harnessSessionId: session.harnessSessionId,
40
+ accSessionId: session.sessionId, generation: session.generation, deadlineAt,
41
+ nativeAttempt: { at: service.clock.now(), event: "channelReady", state: result.state,
42
+ reasonCode: result.reasonCode, policy, policySource: "bootstrap-environment",
43
+ policyStatus: "enabled", clientProcess: "identified" } });
44
+ return result;
45
+ });
46
+ }
@@ -78,6 +78,13 @@ or projected into agent context; session restart replaces it and SessionEnd atte
78
78
  cleanup. Doctor ignores closed or superseded generations even if cleanup could not finish. Diagnostic disk I/O runs in an
79
79
  unreferenced worker, so a stalled write cannot hold the hook process open.
80
80
 
81
+ Claude's MCP process can become ready after the bounded SessionStart handshake
82
+ has ended. Once its endpoint listens, the Channel completes the same validated
83
+ binding without a user prompt. It acquires the session lifecycle lock, recovers
84
+ any committed journal, and rechecks its exact owner and current consent before
85
+ publication. The optional diagnostic records `channelReady`; MCP remains responsive
86
+ if registration fails. This does not create a session or revive a closed one.
87
+
81
88
  ## Certified capability versus current reachability
82
89
 
83
90
  Ordinary hook capabilities say an exact client version on an exact platform passed a
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-claude-code",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -9,7 +9,7 @@ import { fileURLToPath } from "node:url";
9
9
 
10
10
  import { acccreatedFile, bakeSkillCommand, blankJson, defaultBootstrap, defaultChannel,
11
11
  mergeOwnedEntries, ownedEntries,
12
- keepOnlyVersion, ownVersion, stampPluginVersion,
12
+ keepVersions, ownVersion, stampPluginVersion,
13
13
  removeIfEmpty,
14
14
  removeInstalledTree,
15
15
  removeOwnedEntries, writeCliShim, writeForeignJson, writeHookShim }
@@ -181,7 +181,7 @@ async function layOutPlugin(target, { runner, node, cli, channel, live }) {
181
181
  version: await pluginVersion(), io: { readFile, writeFile } });
182
182
  }
183
183
 
184
- export async function installClaudePlugin({ configDir, runner, cli, preserveVersions = false, node = process.execPath,
184
+ export async function installClaudePlugin({ configDir, runner, cli, keepPreviousVersion = null, node = process.execPath,
185
185
  channel = defaultChannel(), livePolicy = "off", now = new Date() }) {
186
186
  const live = livePolicy === "actionable" || livePolicy === "all";
187
187
  // Everything this will merge into, read before a byte is written. A settings
@@ -206,10 +206,13 @@ export async function installClaudePlugin({ configDir, runner, cli, preserveVers
206
206
  // run `claude plugin install`, exactly as the Codex adapter does, because the
207
207
  // command's only effect is this copy plus the two registry entries below.
208
208
  await layOutPlugin(cached, { runner, node, cli, channel, live });
209
- // One copy, the one just written. A client caches a plugin under its version,
210
- // so every upgrade would otherwise leave the previous release's tree beside
211
- // this one - invisible while the version never moved, three deep once it did.
212
- if (!preserveVersions) await keepOnlyVersion({ root: path.dirname(cached), version,
209
+ // The copy just written, plus the one an upgrade moved off. A client caches a
210
+ // plugin under its version, so every upgrade would otherwise leave the previous
211
+ // release's tree beside this one - invisible while the version never moved,
212
+ // three deep once it did. The one it moved off stays because a session open
213
+ // across the upgrade still runs hooks from the path it recorded at start;
214
+ // anything older than that holds no session and is litter.
215
+ await keepVersions({ root: path.dirname(cached), keep: [version, keepPreviousVersion],
213
216
  io: { readdir, rm } });
214
217
 
215
218
  await writeClientJson(knownMarketplacesPath(configDir), {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-codex",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  import { bakeSkillCommand, blankJson, blankText, removeIfEmpty, removeInstalledTree,
8
- keepOnlyVersion, ownVersion, stampPluginVersion,
8
+ keepVersions, ownVersion, stampPluginVersion,
9
9
  tomlString, writeCliShim, writeForeignJson, writeHookShim }
10
10
  from "@agents-can-communicate/adapter-sdk";
11
11
  import { AccError, EXIT } from "@agents-can-communicate/protocol";
@@ -160,7 +160,7 @@ const sandboxReview = (config, file, stateRoot) =>
160
160
  : [];
161
161
 
162
162
  export async function installCodexPlugin({ home, agentsHome = home,
163
- codexHome = path.join(home, ".codex"), dataHome, stateRoot, runner, node, cli, preserveVersions = false,
163
+ codexHome = path.join(home, ".codex"), dataHome, stateRoot, runner, node, cli, keepPreviousVersion = null,
164
164
  requestedLivePolicy, livePolicy, clientVersion, platform }) {
165
165
  // Read before writing, so a manifest that will not parse is found before a
166
166
  // plugin tree is laid down that nothing will then be able to remove.
@@ -228,9 +228,13 @@ export async function installCodexPlugin({ home, agentsHome = home,
228
228
  const cached = cachedVersionPath(codexHome, version);
229
229
  await rm(cached, { recursive: true, force: true });
230
230
  await cp(target, cached, { recursive: true });
231
- // One copy, the one just written - and only inside this plugin's own
232
- // directory. The marketplace cache root above it holds other people's plugins.
233
- if (!preserveVersions) await keepOnlyVersion({ root: path.dirname(cached), version, io: { readdir, rm } });
231
+ // The copy just written, plus the one an upgrade moved off - and only inside
232
+ // this plugin's own directory. The marketplace cache root above it holds other
233
+ // people's plugins. A session open across the upgrade still fires its hooks
234
+ // from the path it started with, so removing that one now would silence it
235
+ // until restart; anything older than it holds no session and is litter.
236
+ await keepVersions({ root: path.dirname(cached), keep: [version, keepPreviousVersion],
237
+ io: { readdir, rm } });
234
238
 
235
239
  // The plugin's own directory in the cache. Not the versioned one inside it,
236
240
  // which goes stale the moment the version changes - and not the marketplace
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-can-communicate",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "description": "Coordinate this Gemini CLI session with other AI agent sessions working in the same workspace.",
5
5
  "contextFileName": "skills/acc/SKILL.md"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-gemini-cli",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-grok",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-kimi",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/adapter-sdk",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -15,7 +15,7 @@ export { BEGIN, END, removeTomlBlock, renderBlock, stripBlock, tomlString, write
15
15
  export { projectContext, projectContextResult } from "./context-projector.mjs";
16
16
  export { decisionBody, decisionLines } from "./decision-text.mjs";
17
17
  export { shellWriteTargets } from "./shell-writes.mjs";
18
- export { keepOnlyVersion, ownVersion, stampPluginVersion } from "./own-version.mjs";
18
+ export { keepVersions, ownVersion, stampPluginVersion } from "./own-version.mjs";
19
19
  export { editJson, readJson } from "./json-text.mjs";
20
20
  export { formatJsonAs, jsonStyleOf, mergeOwnedConfig, mergeOwnedEntries, ownedEntries, ownedKeys,
21
21
  acccreatedFile, removeIfEmpty, removeOwnedConfig, removeOwnedEntries, writeForeignJson,
@@ -11,7 +11,7 @@ import { NATIVE_REASON_CODES, TIMESTAMP } from "./native-vocabulary.mjs";
11
11
  export function nativeAttemptFrom(value) {
12
12
  if (!value || typeof value.at !== "string" || !TIMESTAMP.test(value.at)
13
13
  || !Number.isFinite(Date.parse(value.at))
14
- || !["sessionStart", "beforeTurn"].includes(value.event)
14
+ || !["sessionStart", "beforeTurn", "channelReady"].includes(value.event)
15
15
  || !["active", "off", "degraded", "unsupported"].includes(value.state)
16
16
  || !(value.reasonCode === null || NATIVE_REASON_CODES.includes(value.reasonCode))
17
17
  || !["off", "actionable", "all"].includes(value.policy)
@@ -57,23 +57,31 @@ export async function stampPluginVersion({ file, version, io }) {
57
57
  }
58
58
 
59
59
  /**
60
- * Leave one copy of a versioned plugin, the one just written.
60
+ * Leave the named copies of a versioned plugin and remove the rest.
61
61
  *
62
62
  * These clients cache a plugin under its version. Until the version tracked the
63
63
  * package it never changed, every install landed in the same directory and
64
64
  * overwrote itself, and nothing accumulated. Once it started moving, the first
65
65
  * upgrade left three copies of ACC in a home that should hold one.
66
66
  *
67
+ * `keep` is a list rather than the single version just written, because these
68
+ * clients pin one `installPath` per plugin and a session reads it once. A
69
+ * session already open when an upgrade lands still holds the path it started
70
+ * with, so an upgrade names the version it moved off as well as the one it
71
+ * wrote, and that session keeps firing hooks until it is restarted. An install
72
+ * with nothing to hold passes a null, which is dropped here.
73
+ *
67
74
  * Scoped to the plugin's own directory. The marketplace cache root above it
68
75
  * holds every plugin installed from that marketplace, and removing that root
69
76
  * once took a plugin the user had installed themselves - so a sibling here is
70
77
  * an older ACC, and a sibling one level up is somebody else's.
71
78
  */
72
- export async function keepOnlyVersion({ root, version, io }) {
79
+ export async function keepVersions({ root, keep, io }) {
80
+ const wanted = new Set(keep.filter(version => typeof version === "string" && version !== ""));
73
81
  const entries = await io.readdir(root, { withFileTypes: true }).catch(() => []);
74
82
  const removed = [];
75
83
  for (const entry of entries) {
76
- if (!entry.isDirectory() || entry.name === version) continue;
84
+ if (!entry.isDirectory() || wanted.has(entry.name)) continue;
77
85
  await io.rm(path.join(root, entry.name), { recursive: true, force: true });
78
86
  removed.push(entry.name);
79
87
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/cli",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -56,7 +56,12 @@ export async function installManaged({ packageRoot, managerRoot, dataHome, home,
56
56
  targets: [...new Set([...(previous?.targets ?? []), ...targets])], notice: null };
57
57
  await writeControl(root, control);
58
58
  const paths = await writeLaunchers(root, runtime.root);
59
- const result = await apply({ ...paths, preserveVersions: true });
59
+ // The guard above admits only an install from the active generation, so
60
+ // this names the version being written and the adapters keep one copy.
61
+ // It is read from the pointer rather than assumed, so a future relaxation
62
+ // of that guard keeps the outgoing copy instead of silently removing it.
63
+ const result = await apply({ ...paths,
64
+ keepPreviousVersion: previous?.active.version ?? null });
60
65
  // Service readiness is separate from integration installation. Its failure
61
66
  // must not fence a fully installed runtime or make the durable inbox unusable.
62
67
  const integrationFailed = result.failed.some(failure => failure.stage !== "service-setup"
@@ -12,8 +12,13 @@ export async function prepareRefresh({ control, root, env = process.env, callerP
12
12
  const wanted = new Set(control.targets);
13
13
  const adapters = ALL_ADAPTERS().filter(adapter => wanted.has(adapter.id));
14
14
  if (adapters.length !== wanted.size) throw new Error("candidate cannot refresh every installed integration");
15
+ // The version this refresh is moving off. Clients that cache a plugin under
16
+ // its version record one path per plugin and read it once per session, so a
17
+ // session open across the update keeps running from the outgoing copy until it
18
+ // restarts. Naming it here keeps that copy; everything older than it goes.
15
19
  const context = { ...clientContext(control.home, path.join(dataHome, "acc"),
16
- { env, shell: shellOf(env), dataHome }), ...stablePaths(root), preserveVersions: true };
20
+ { env, shell: shellOf(env), dataHome }), ...stablePaths(root),
21
+ keepPreviousVersion: control.active.version };
17
22
  const recorded = (await loadOwnership({ dataHome })).installs;
18
23
  const detected = await detectInstallation({ adapters, context, probeTimeoutMs: probeTimeout(env) });
19
24
  const deliveryByAdapter = Object.fromEntries(adapters.map(adapter => [adapter.id,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/core",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/delivery-router",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/hook-runner",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./src/runner.mjs",
8
8
  "./client-pid": "./src/client-pid.mjs",
9
- "./process-table": "./src/process-table.mjs"
9
+ "./process-table": "./src/process-table.mjs",
10
+ "./native-binding": "./src/native-binding.mjs",
11
+ "./session-lifecycle": "./src/session-lifecycle.mjs"
10
12
  },
11
13
  "files": [
12
14
  "src/"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/installer",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/mcp-server",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/protocol",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/storage-filesystem",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-can-communicate",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "accManagedUpdateProtocol": 2,
5
5
  "accStoreVersion": 6,
6
6
  "type": "module",