agents-can-communicate 0.5.4 → 0.5.5
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/docs/ARCHITECTURE.md +6 -1
- package/docs/TROUBLESHOOTING.md +18 -2
- package/docs/UPGRADING.md +32 -6
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation-files.mjs +25 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +1 -22
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/leases.mjs +2 -2
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +23 -1
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/package.json +1 -1
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -174,7 +174,12 @@ The CLI owns installation generations under `<dataHome>/acc/runtime`, outside wo
|
|
|
174
174
|
All five launch paths select a generation and publish an actual-process lease under one
|
|
175
175
|
admission mutex before loading workspace-capable code. Immutable launcher modules and
|
|
176
176
|
runtime directories preserve in-progress imports. Leases survive `main()` returning and
|
|
177
|
-
are removed only after confirmed process death.
|
|
177
|
+
are removed only after confirmed process death. Older updaters can omit the store contract
|
|
178
|
+
when publishing a newer generation. The reader recovers missing contracts only from the
|
|
179
|
+
referenced generation, after checking its package identity and content hash, including file
|
|
180
|
+
modes. It preserves explicit declarations and does not rewrite live leases. Missing or
|
|
181
|
+
changed generation files and unknown native bindings remain unverified holds.
|
|
182
|
+
A session is also pinned to the generation
|
|
178
183
|
it started with: its hooks delegate to that generation while the pin declares the same
|
|
179
184
|
store contract this one does, and run the active generation otherwise. A generation
|
|
180
185
|
directory is reclaimed once no control pointer, live lease, live pin or staging hold names
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -238,8 +238,9 @@ Next: [Getting started](GETTING_STARTED.md) · [Capabilities](CAPABILITIES.md)
|
|
|
238
238
|
|
|
239
239
|
Run `acc doctor` to see the update policy and pending notice. A hold keeps an update
|
|
240
240
|
pending only while the store contract it declares differs from the incoming version's or
|
|
241
|
-
is unknown
|
|
242
|
-
|
|
241
|
+
is unknown. ACC can recover a missing contract from the process's unchanged managed
|
|
242
|
+
generation when that generation declares one. A generation that predates the declaration
|
|
243
|
+
still needs its processes to exit. `acc update` names each remaining hold with its process and
|
|
243
244
|
its declared contract. ACC process leases, including persistent MCP servers, end on
|
|
244
245
|
confirmed process exit. Native bindings clear on observed SessionEnd or confirmed process
|
|
245
246
|
death; a vendor daemon may remain alive after SessionEnd. Unknown PIDs remain holds until
|
|
@@ -250,6 +251,21 @@ unrelated holds still require lifecycle cleanup or confirmed process exit; safet
|
|
|
250
251
|
do not expire merely by elapsed time. See
|
|
251
252
|
[maintenance and recovery](UPGRADING.md#confirmed-client-service-maintenance).
|
|
252
253
|
|
|
254
|
+
If ACC 0.5.3 or 0.5.4 reports `acc-claude-channel; contract unknown` after an earlier
|
|
255
|
+
0.4.x upgrade, its active pointer may have lost the contract field. This can affect newly
|
|
256
|
+
opened Channels too. A pending old updater retries its downloaded release without looking
|
|
257
|
+
for a newer fix. Install the current global CLI, then use that CLI to complete the update:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npm install -g agents-can-communicate@latest
|
|
261
|
+
acc update
|
|
262
|
+
acc doctor
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The new reader can activate a compatible release while those Channels remain open.
|
|
266
|
+
It still waits for different contracts, unknown native bindings, or generation files it
|
|
267
|
+
cannot verify. See [legacy contract recovery](UPGRADING.md#recover-missing-runtime-contracts).
|
|
268
|
+
|
|
253
269
|
Use `acc update` to retry a failed download or finish an interrupted integration refresh.
|
|
254
270
|
A download failure keeps the working version. A partial integration refresh blocks
|
|
255
271
|
workspace commands until recovery completes, while hooks let the client continue without
|
package/docs/UPGRADING.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Upgrading to 0.5.0
|
|
2
2
|
|
|
3
|
+
## Recover missing runtime contracts
|
|
4
|
+
|
|
5
|
+
ACC 0.5.5 fixes an upgrade defect that can leave 0.5.3 or 0.5.4 waiting for
|
|
6
|
+
`acc-claude-channel` processes with `contract unknown`. The 0.4.x updater can activate
|
|
7
|
+
a newer generation without copying its store contract into the active pointer. Channels
|
|
8
|
+
started afterward copy that incomplete pointer even though their runtime declares a contract.
|
|
9
|
+
|
|
10
|
+
For an installation already waiting on an older pending release, run:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g agents-can-communicate@latest
|
|
14
|
+
acc update
|
|
15
|
+
acc version
|
|
16
|
+
acc doctor
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The global CLI supplies the new management reader before runtime activation. This step is
|
|
20
|
+
needed because the older updater retries its pending release before checking npm for another
|
|
21
|
+
version. If a version pin selects an older release, clear it or select 0.5.5 first.
|
|
22
|
+
|
|
23
|
+
The reader verifies the referenced generation's package identity, complete contents and
|
|
24
|
+
file modes before recovering a missing contract. Compatible Channels can keep running on
|
|
25
|
+
their original generation during activation. Explicit differing contracts and unknown
|
|
26
|
+
native bindings still block. A generation without a contract, missing files, or changed
|
|
27
|
+
contents remains unknown. ACC does not terminate these processes or edit their lease records.
|
|
28
|
+
|
|
3
29
|
## From 0.4.4, 0.4.3, 0.4.2, 0.4.1 or 0.4.0
|
|
4
30
|
|
|
5
31
|
This release keeps the existing workspace data format. It changes when an update is
|
|
@@ -56,17 +82,17 @@ only two kinds keep an update pending:
|
|
|
56
82
|
- A hold whose declared store contract differs from the incoming version's. This is the
|
|
57
83
|
case the gate exists for, and a release that changes the store contract still needs
|
|
58
84
|
every live process to exit.
|
|
59
|
-
- A hold
|
|
60
|
-
|
|
85
|
+
- A hold with no verifiable contract. Older native bindings and generations that predate
|
|
86
|
+
the declaration remain unknown. A missing field can be recovered when the referenced
|
|
87
|
+
managed generation declares its contract and passes integrity verification.
|
|
61
88
|
|
|
62
89
|
A hold declaring the same store contract as the incoming version proceeds. Several open
|
|
63
90
|
Claude Code sessions, a Codex daemon and an idle ACC MCP server no longer have to be
|
|
64
91
|
closed together to move between releases that share a contract.
|
|
65
92
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
maintenance offer described below. Updates after that do not need it.
|
|
93
|
+
Processes still running a generation from before the contract field existed must exit once.
|
|
94
|
+
Close the relevant clients and persistent ACC processes, or accept the eligible Codex service
|
|
95
|
+
maintenance offer described below. Later compatible runtimes do not require that restart.
|
|
70
96
|
|
|
71
97
|
`acc update` names each remaining hold with its process and its declared contract, so a
|
|
72
98
|
wait now states its reason rather than only the PID. `acc doctor` reports the same
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export async function collectFile(source, relative, collected) {
|
|
6
|
+
const stat = await lstat(source);
|
|
7
|
+
if (stat.isSymbolicLink()) throw new Error(`symbolic link inside runtime files: ${relative}`);
|
|
8
|
+
if (stat.isDirectory()) {
|
|
9
|
+
for (const entry of (await readdir(source)).sort()) {
|
|
10
|
+
await collectFile(path.join(source, entry), path.posix.join(relative, entry), collected);
|
|
11
|
+
}
|
|
12
|
+
} else if (stat.isFile()) {
|
|
13
|
+
collected.set(relative, { bytes: await readFile(source), mode: stat.mode & 0o777 });
|
|
14
|
+
} else throw new Error(`unsupported runtime file: ${relative}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** This format also names existing generations; keep it stable across upgrades. */
|
|
18
|
+
export function fingerprint(files) {
|
|
19
|
+
const hash = createHash("sha256");
|
|
20
|
+
for (const [name, file] of [...files].sort(([a], [b]) => a.localeCompare(b))) {
|
|
21
|
+
hash.update(JSON.stringify([name, file.mode, file.bytes.length]));
|
|
22
|
+
hash.update(file.bytes);
|
|
23
|
+
}
|
|
24
|
+
return hash.digest("hex");
|
|
25
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm }
|
|
3
2
|
from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
|
|
5
|
+
import { collectFile, fingerprint } from "./generation-files.mjs";
|
|
6
6
|
import { attachStagingTemp, holdStagedGeneration, releaseStagingHold } from "./staging.mjs";
|
|
7
7
|
|
|
8
8
|
const NAME = "agents-can-communicate";
|
|
@@ -37,18 +37,6 @@ function declaredPath(value) {
|
|
|
37
37
|
return value.replace(/\/+$/, "");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
async function collectFile(source, relative, collected) {
|
|
41
|
-
const stat = await lstat(source);
|
|
42
|
-
if (stat.isSymbolicLink()) throw new Error(`symbolic link inside runtime files: ${relative}`);
|
|
43
|
-
if (stat.isDirectory()) {
|
|
44
|
-
for (const entry of (await readdir(source)).sort()) {
|
|
45
|
-
await collectFile(path.join(source, entry), path.posix.join(relative, entry), collected);
|
|
46
|
-
}
|
|
47
|
-
} else if (stat.isFile()) {
|
|
48
|
-
collected.set(relative, { bytes: await readFile(source), mode: stat.mode & 0o777 });
|
|
49
|
-
} else throw new Error(`unsupported runtime file: ${relative}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
40
|
async function checkDeclaredComponents(source, relative) {
|
|
53
41
|
let current = source;
|
|
54
42
|
for (const component of relative.split("/")) {
|
|
@@ -84,15 +72,6 @@ async function collectPackage(source, prefix, collected, expectedName, version)
|
|
|
84
72
|
return manifest;
|
|
85
73
|
}
|
|
86
74
|
|
|
87
|
-
function fingerprint(files) {
|
|
88
|
-
const hash = createHash("sha256");
|
|
89
|
-
for (const [name, file] of [...files].sort(([a], [b]) => a.localeCompare(b))) {
|
|
90
|
-
hash.update(JSON.stringify([name, file.mode, file.bytes.length]));
|
|
91
|
-
hash.update(file.bytes);
|
|
92
|
-
}
|
|
93
|
-
return hash.digest("hex");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
75
|
async function existingMatches(target, files) {
|
|
97
76
|
try {
|
|
98
77
|
const actual = new Map();
|
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { ENTRY_KINDS } from "./entry.mjs";
|
|
5
5
|
import { managedDirectory, syncDirectory } from "./state.mjs";
|
|
6
6
|
|
|
7
|
-
const MODULES = ["entry.mjs", "state.mjs", "mutex.mjs", "leases.mjs", "schedule.mjs", "policy.mjs"];
|
|
7
|
+
const MODULES = ["entry.mjs", "state.mjs", "generation-files.mjs", "mutex.mjs", "leases.mjs", "schedule.mjs", "policy.mjs"];
|
|
8
8
|
async function durableFile(file, bytes, mode = 0o600) {
|
|
9
9
|
const handle = await open(file, "wx", mode);
|
|
10
10
|
try { await handle.writeFile(bytes); await handle.sync(); }
|
|
@@ -49,11 +49,11 @@ export async function listRuntimeHolds(root, { pidIsAlive = defaultPidIsAlive }
|
|
|
49
49
|
|| typeof lease.token !== "string" || name !== `${lease.token}.json`
|
|
50
50
|
|| typeof lease.kind !== "string" || !lease.kind
|
|
51
51
|
|| !Number.isFinite(Date.parse(lease.createdAt))) throw new Error("invalid runtime lease");
|
|
52
|
-
await validateRuntime(root, lease.runtime);
|
|
52
|
+
const runtime = await validateRuntime(root, lease.runtime);
|
|
53
53
|
if (await confirmedDead(lease.pid, pidIsAlive)) {
|
|
54
54
|
await rm(file, { force: true });
|
|
55
55
|
removed = true;
|
|
56
|
-
} else holds.push(lease);
|
|
56
|
+
} else holds.push({ ...lease, runtime });
|
|
57
57
|
}
|
|
58
58
|
if (removed) await syncDirectory(directory);
|
|
59
59
|
return holds;
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { lstat, mkdir, open, realpath, rename, rm } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { collectFile, fingerprint } from "./generation-files.mjs";
|
|
5
6
|
|
|
6
7
|
/** Resolve existing ancestors without creating an uninitialized manager. */
|
|
7
8
|
export async function canonicalManagerRoot(root) {
|
|
@@ -74,6 +75,25 @@ export async function writeManagedJson(file, value) {
|
|
|
74
75
|
const nonempty = value => typeof value === "string" && value.length > 0;
|
|
75
76
|
const nullableString = value => value === null || nonempty(value);
|
|
76
77
|
|
|
78
|
+
/** Older updaters dropped the contract while publishing a newer generation.
|
|
79
|
+
* Recover only from that exact, unchanged generation, never from the active
|
|
80
|
+
* version or a loose manifest. Explicit lease declarations remain historical
|
|
81
|
+
* facts. Missing or damaged files leave an unknown hold without failing hooks.
|
|
82
|
+
*/
|
|
83
|
+
async function recoverStoreVersion(generations, runtime) {
|
|
84
|
+
if (path.dirname(runtime.root) !== generations
|
|
85
|
+
|| !/^\d+\.\d+\.\d+-[a-f0-9]{24}$/.test(path.basename(runtime.root))) return null;
|
|
86
|
+
try {
|
|
87
|
+
const files = new Map();
|
|
88
|
+
await collectFile(runtime.root, "", files);
|
|
89
|
+
if (path.basename(runtime.root) !== `${runtime.version}-${fingerprint(files).slice(0, 24)}`) return null;
|
|
90
|
+
const manifest = JSON.parse(files.get("package.json")?.bytes.toString("utf8"));
|
|
91
|
+
return manifest.name === "agents-can-communicate" && manifest.version === runtime.version
|
|
92
|
+
&& Number.isSafeInteger(manifest.accStoreVersion) && manifest.accStoreVersion > 0
|
|
93
|
+
? manifest.accStoreVersion : null;
|
|
94
|
+
} catch { return null; }
|
|
95
|
+
}
|
|
96
|
+
|
|
77
97
|
export async function validateRuntime(root, runtime) {
|
|
78
98
|
if (!runtime || !nonempty(runtime.version) || typeof runtime.root !== "string"
|
|
79
99
|
|| !path.isAbsolute(runtime.root)) throw new Error("invalid control generation");
|
|
@@ -95,7 +115,9 @@ export async function validateRuntime(root, runtime) {
|
|
|
95
115
|
if (await managedDirectory(generations)) {
|
|
96
116
|
if (await realpath(generations) !== generations) throw new Error("invalid control generations directory");
|
|
97
117
|
}
|
|
98
|
-
|
|
118
|
+
const normalized = { version: runtime.version, root: resolved, storeVersion: runtime.storeVersion ?? null };
|
|
119
|
+
normalized.storeVersion ??= await recoverStoreVersion(generations, normalized);
|
|
120
|
+
return normalized;
|
|
99
121
|
}
|
|
100
122
|
|
|
101
123
|
async function validateControl(root, value) {
|