agents-can-communicate 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +112 -91
  2. package/SECURITY.md +8 -5
  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 +73 -0
  16. package/docs/ADAPTER_AUTHORING.md +62 -24
  17. package/docs/ARCHITECTURE.md +79 -20
  18. package/docs/CAPABILITIES.md +52 -30
  19. package/docs/CLI.md +164 -35
  20. package/docs/CONCEPTS.md +97 -93
  21. package/docs/CONFIGURATION.md +66 -45
  22. package/docs/DESIGN_DECISIONS.md +24 -15
  23. package/docs/GETTING_STARTED.md +124 -118
  24. package/docs/GLOSSARY.md +12 -6
  25. package/docs/HOW_IT_WORKS.md +136 -229
  26. package/docs/MCP.md +98 -31
  27. package/docs/PROTOCOL.md +76 -14
  28. package/docs/RELEASING.md +128 -86
  29. package/docs/SECURITY_MODEL.md +52 -15
  30. package/docs/TROUBLESHOOTING.md +88 -18
  31. package/docs/UPGRADING.md +119 -0
  32. package/docs/WHY_ACC.md +19 -16
  33. package/docs/index.md +40 -24
  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 +112 -9
  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 +111 -8
  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 +111 -8
  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 +111 -8
  68. package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +2 -2
  69. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  70. package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +111 -8
  71. package/node_modules/@agents-can-communicate/adapter-kimi/src/hooks.mjs +1 -1
  72. package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +2 -2
  73. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  74. package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +5 -0
  75. package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +26 -4
  76. package/node_modules/@agents-can-communicate/adapter-sdk/src/decision-text.mjs +19 -0
  77. package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +5 -3
  78. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -0
  79. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +9 -2
  80. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +2 -6
  81. package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +29 -9
  82. package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-block.mjs +8 -6
  83. package/node_modules/@agents-can-communicate/cli/package.json +4 -2
  84. package/node_modules/@agents-can-communicate/cli/src/args.mjs +49 -22
  85. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +11 -4
  86. package/node_modules/@agents-can-communicate/cli/src/git-probe.mjs +9 -3
  87. package/node_modules/@agents-can-communicate/cli/src/help.mjs +109 -14
  88. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +66 -18
  89. package/node_modules/@agents-can-communicate/cli/src/main.mjs +35 -12
  90. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/activation.mjs +110 -0
  91. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/command.mjs +63 -0
  92. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/download.mjs +71 -0
  93. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +110 -0
  94. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +167 -0
  95. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/install.mjs +73 -0
  96. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +60 -0
  97. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/leases.mjs +60 -0
  98. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/location.mjs +55 -0
  99. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/mutex.mjs +145 -0
  100. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/policy.mjs +4 -0
  101. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +40 -0
  102. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/schedule.mjs +21 -0
  103. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +122 -0
  104. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/worker.mjs +65 -0
  105. package/node_modules/@agents-can-communicate/cli/src/session-owner.mjs +33 -108
  106. package/node_modules/@agents-can-communicate/cli/src/update-command.mjs +25 -7
  107. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  108. package/node_modules/@agents-can-communicate/core/src/attention.mjs +3 -0
  109. package/node_modules/@agents-can-communicate/core/src/claims.mjs +13 -8
  110. package/node_modules/@agents-can-communicate/core/src/conversations.mjs +48 -24
  111. package/node_modules/@agents-can-communicate/core/src/decision-changes.mjs +36 -0
  112. package/node_modules/@agents-can-communicate/core/src/decision-state.mjs +42 -0
  113. package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +11 -4
  114. package/node_modules/@agents-can-communicate/core/src/inbox.mjs +48 -17
  115. package/node_modules/@agents-can-communicate/core/src/intents.mjs +35 -15
  116. package/node_modules/@agents-can-communicate/core/src/materialisation.mjs +7 -5
  117. package/node_modules/@agents-can-communicate/core/src/message-pages.mjs +75 -0
  118. package/node_modules/@agents-can-communicate/core/src/next-turn-delivery.mjs +31 -0
  119. package/node_modules/@agents-can-communicate/core/src/session-opening.mjs +128 -0
  120. package/node_modules/@agents-can-communicate/core/src/sessions.mjs +58 -132
  121. package/node_modules/@agents-can-communicate/core/src/sync.mjs +31 -2
  122. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  123. package/node_modules/@agents-can-communicate/delivery-router/src/refresh-binding.mjs +38 -0
  124. package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +86 -10
  125. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  126. package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +1 -0
  127. package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +6 -4
  128. package/node_modules/@agents-can-communicate/hook-runner/src/native-retirement.mjs +48 -0
  129. package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +1 -1
  130. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +175 -67
  131. package/node_modules/@agents-can-communicate/hook-runner/src/session-lifecycle.mjs +13 -0
  132. package/node_modules/@agents-can-communicate/installer/package.json +4 -2
  133. package/node_modules/@agents-can-communicate/installer/src/apply.mjs +17 -2
  134. package/node_modules/@agents-can-communicate/installer/src/bootstrap-runtime.mjs +11 -5
  135. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +1 -1
  136. package/node_modules/@agents-can-communicate/installer/src/index.mjs +1 -0
  137. package/node_modules/@agents-can-communicate/installer/src/live-policy.mjs +21 -0
  138. package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +20 -6
  139. package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +10 -4
  140. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +3 -3
  141. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  142. package/node_modules/@agents-can-communicate/mcp-server/src/protocol.mjs +85 -0
  143. package/node_modules/@agents-can-communicate/mcp-server/src/resources.mjs +3 -2
  144. package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +59 -57
  145. package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +47 -33
  146. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  147. package/node_modules/@agents-can-communicate/protocol/src/conversations.mjs +3 -0
  148. package/node_modules/@agents-can-communicate/protocol/src/decision-changes.mjs +15 -0
  149. package/node_modules/@agents-can-communicate/protocol/src/index.mjs +3 -1
  150. package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +14 -5
  151. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  152. package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +5 -1
  153. package/node_modules/@agents-can-communicate/storage-filesystem/src/deadline.mjs +7 -0
  154. package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +2 -0
  155. package/node_modules/@agents-can-communicate/storage-filesystem/src/session-read.mjs +25 -0
  156. package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +28 -25
  157. package/package.json +2 -1
@@ -6,6 +6,7 @@ import { defaultBootstrap } from "@agents-can-communicate/adapter-sdk";
6
6
 
7
7
  import { installShellBootstrap, planShellBootstrap, uninstallShellBootstrap }
8
8
  from "./shell-bootstrap.mjs";
9
+ import { LIVE_POLICIES, livePolicyOf } from "./live-policy.mjs";
9
10
 
10
11
  // The installer's side of a native activation: which owned mechanisms an
11
12
  // eligible adapter asked for, how they are applied in a fixed order, what is
@@ -14,13 +15,10 @@ import { installShellBootstrap, planShellBootstrap, uninstallShellBootstrap }
14
15
  // adapter's own install; a vendor service is started only here, only during
15
16
  // apply, and only when it did not already exist.
16
17
 
17
- export const LIVE_POLICIES = Object.freeze(["off", "actionable", "all"]);
18
+ export { LIVE_POLICIES, livePolicyOf };
18
19
  const MECHANISM_ORDER = ["native-config", "native-service", "shell-bootstrap"];
19
20
  const SERVICE_TIMEOUT_MS = 15_000;
20
21
 
21
- export const livePolicyOf = install => (LIVE_POLICIES.includes(install?.nativeActivation?.livePolicy)
22
- ? install.nativeActivation.livePolicy : "off");
23
-
24
22
  export const shellOf = env => {
25
23
  const shell = env?.SHELL;
26
24
  return typeof shell === "string" && shell !== "" ? path.basename(shell) : null;
@@ -124,7 +122,7 @@ export async function applyNativeActivation({ adapter, activation, dataHome,
124
122
  const result = await installShellBootstrap({ plan });
125
123
  if (!result.ok) throw new Error(`shell bootstrap refused: ${result.reasonCode}`);
126
124
  shell = result;
127
- record.mechanisms.push({ kind: mechanism.kind, shimDir: activation.shimDir,
125
+ record.mechanisms.push({ kind: mechanism.kind, command: mechanism.command, shimDir: activation.shimDir,
128
126
  ownedFiles: result.shims.map(shim => ({ path: shim.path, sha256: shim.sha256 })),
129
127
  rcFile: result.rcFile });
130
128
  }
@@ -141,12 +139,28 @@ export async function applyNativeActivation({ adapter, activation, dataHome,
141
139
  }
142
140
  }
143
141
 
142
+ // Compare planned mechanisms with the deletion authority saved by older installs.
143
+ export function planActivationRetirements({ previous, desired }) {
144
+ const wanted = desired?.mechanisms ?? [];
145
+ const matches = (old, next) => old.kind === next.kind && (old.kind === "native-service"
146
+ ? old.serviceId === next.serviceId : old.kind === "native-config"
147
+ ? JSON.stringify([...old.artifactIds].sort()) === JSON.stringify([...next.artifactIds].sort())
148
+ : typeof old.command === "string" ? old.command === next.command
149
+ : old.ownedFiles?.length > 0
150
+ && old.ownedFiles.every(file => path.basename(file.path) === next.command));
151
+ return (previous?.mechanisms ?? []).filter(old => !wanted.some(next => matches(old, next)));
152
+ }
153
+
144
154
  export async function deactivateNative({ nativeActivation, exec = defaultExec }) {
145
- const report = { shell: null, services: [] };
155
+ const report = { shell: null, services: [], retainedMechanisms: [] };
146
156
  for (const mechanism of nativeActivation?.mechanisms ?? []) {
147
157
  if (mechanism.kind === "shell-bootstrap") {
148
158
  report.shell = await uninstallShellBootstrap({ ownership: { shims: mechanism.ownedFiles,
149
159
  shimDir: mechanism.shimDir, rcFile: mechanism.rcFile } });
160
+ if (report.shell.keptShims.length > 0 || report.shell.rcBlock === "modified") {
161
+ report.retainedMechanisms.push({ ...mechanism,
162
+ ownedFiles: mechanism.ownedFiles.filter(file => report.shell.keptShims.includes(file.path)) });
163
+ }
150
164
  } else if (mechanism.kind === "native-service") {
151
165
  if (mechanism.createdByAcc && mechanism.teardownCommand !== null) {
152
166
  await exec(mechanism.teardownCommand.executable, mechanism.teardownCommand.args);
@@ -110,7 +110,7 @@ async function saveOwnership({ dataHome, record }) {
110
110
  * runtime, and leaves the bundle inside the client exactly where it was.
111
111
  */
112
112
  export async function recordInstall({ dataHome, adapterId, version, accVersion = null,
113
- artifacts, createdDirectories = [], nativeActivation = null }) {
113
+ artifacts, createdDirectories = [], deliveryPolicy, nativeActivation = null }) {
114
114
  const stamped = await Promise.all(artifacts.map(async artifact => ({
115
115
  path: artifact.path,
116
116
  kind: artifact.kind ?? "file",
@@ -128,6 +128,7 @@ export async function recordInstall({ dataHome, adapterId, version, accVersion =
128
128
  installs: [...record.installs.filter(install => install.adapterId !== adapterId),
129
129
  { adapterId, version, accVersion, artifacts: stamped,
130
130
  ...(directories.length === 0 ? {} : { createdDirectories: directories }),
131
+ ...(deliveryPolicy === undefined ? {} : { deliveryPolicy }),
131
132
  // Present only for a consented native activation. A record without it
132
133
  // - every 0.2 install - reads as live policy off and is never rewritten
133
134
  // merely to add the field.
@@ -221,7 +222,10 @@ export async function verifyOwned({ dataHome, adapterId }) {
221
222
  const install = installFor(await loadOwnership({ dataHome }), adapterId);
222
223
  const result = { adapterId, present: install !== null, modified: [], missing: [],
223
224
  intact: [], delegated: [] };
224
- for (const artifact of install?.artifacts ?? []) {
225
+ const nativeFiles = (install?.nativeActivation?.mechanisms ?? [])
226
+ .filter(item => item.kind === "shell-bootstrap").flatMap(item => item.ownedFiles)
227
+ .map(file => ({ ...file, kind: "file" }));
228
+ for (const artifact of [...(install?.artifacts ?? []), ...nativeFiles]) {
225
229
  if (artifact.kind === "merge") { result.delegated.push(artifact.path); continue; }
226
230
  const current = await fingerprintFor(artifact);
227
231
  if (current === null) result.missing.push(artifact.path);
@@ -252,12 +256,14 @@ export async function removeOwnedArtifacts({ dataHome, adapterId }) {
252
256
  }
253
257
 
254
258
  /** Forget one install only after every adapter-owned cleanup step succeeded. */
255
- export async function finalizeRemoval({ dataHome, adapterId }) {
259
+ export async function finalizeRemoval({ dataHome, adapterId, retainedNativeActivation = null }) {
256
260
  const record = await loadOwnership({ dataHome });
257
261
  if (installFor(record, adapterId) === null) return false;
258
262
 
259
263
  await saveOwnership({ dataHome, record: { schemaVersion: SCHEMA_VERSION,
260
- installs: record.installs.filter(entry => entry.adapterId !== adapterId) } });
264
+ installs: record.installs.flatMap(entry => entry.adapterId !== adapterId ? [entry]
265
+ : retainedNativeActivation === null ? [] : [{ ...entry, artifacts: [], createdDirectories: [],
266
+ deliveryPolicy: "off", nativeActivation: retainedNativeActivation }]) } });
261
267
  return true;
262
268
  }
263
269
 
@@ -1,6 +1,6 @@
1
1
  import { AccError, EXIT } from "@agents-can-communicate/protocol";
2
2
 
3
- import { LIVE_POLICIES, describeActivation, describeDeactivation, rcFileFor, shimDirFor }
3
+ import { LIVE_POLICIES, describeActivation, describeDeactivation, planActivationRetirements, rcFileFor, shimDirFor }
4
4
  from "./native-activation.mjs";
5
5
 
6
6
  /**
@@ -124,8 +124,8 @@ export function planInstallation({ adapters, detected, context, action = "instal
124
124
  shimDir: typeof context?.stateRoot === "string" ? shimDirFor(context.stateRoot) : null,
125
125
  mechanisms: native.activationPlan.mechanisms }
126
126
  : null;
127
- const deactivation = previous !== null
128
- && (action === "uninstall" || effectiveLivePolicy === "off") ? previous : null;
127
+ const retirements = planActivationRetirements({ previous, desired: nativeActivation });
128
+ const deactivation = retirements.length > 0 ? { ...previous, mechanisms: retirements } : null;
129
129
  const artifacts = (record?.artifacts ?? adapter.planInstall(installContext))
130
130
  .map(artifact => ({ path: artifact.path, kind: artifact.kind ?? "file" }))
131
131
  .sort((a, b) => a.path.localeCompare(b.path));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,85 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ import { MCP_CAPABILITIES } from "./tools.mjs";
4
+
5
+ export const PROTOCOL_VERSION = "2026-07-28";
6
+ export const INITIALIZED_VERSIONS = Object.freeze(["2025-11-25", "2025-06-18"]);
7
+ export const SUPPORTED_VERSIONS = Object.freeze([PROTOCOL_VERSION, ...INITIALIZED_VERSIONS]);
8
+ const PACKAGE_VERSION = createRequire(import.meta.url)("../package.json").version;
9
+ const SERVER_INFO = Object.freeze({ name: "agents-can-communicate", version: PACKAGE_VERSION });
10
+ const CAPABILITIES = Object.freeze({ tools: {}, resources: {} });
11
+ const VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
12
+ const CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
13
+ const isObject = value => value !== null && typeof value === "object" && !Array.isArray(value);
14
+ const rpcError = (rpcCode, message, rpcData) =>
15
+ Object.assign(new Error(message), { rpcCode, rpcData });
16
+
17
+ const complete = result => ({ resultType: "complete",
18
+ _meta: { "io.modelcontextprotocol/serverInfo": SERVER_INFO }, ...result });
19
+
20
+ function requireProtocolMeta(params) {
21
+ const meta = params?._meta ?? {};
22
+ const version = meta[VERSION_KEY];
23
+ if (typeof version !== "string" || !isObject(meta[CAPABILITIES_KEY])) {
24
+ throw rpcError(-32602,
25
+ "each request requires _meta protocolVersion and clientCapabilities");
26
+ }
27
+ if (version !== PROTOCOL_VERSION) {
28
+ throw rpcError(-32022, `unsupported protocol version: ${version}`,
29
+ { supported: [...SUPPORTED_VERSIONS], requested: version });
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Dual-era stdio: 2025 clients initialize their transport; 2026 requests carry
35
+ * their own metadata. Only protocol readiness is connection-local. ACC identity
36
+ * and durable session continuity always come from the server's launch config.
37
+ */
38
+ export function createProtocol() {
39
+ let phase = "uninitialized";
40
+ return {
41
+ notify(message) {
42
+ if (message.method === "notifications/initialized" && phase === "initializing") {
43
+ phase = "ready";
44
+ }
45
+ },
46
+ async request({ method, params }, execute) {
47
+ if (method === "initialize") {
48
+ if (phase !== "uninitialized") throw rpcError(-32600, "already initialized");
49
+ if (typeof params?.protocolVersion !== "string" || !isObject(params.capabilities)
50
+ || !isObject(params.clientInfo) || typeof params.clientInfo.name !== "string"
51
+ || typeof params.clientInfo.version !== "string") {
52
+ throw rpcError(-32602, "initialize requires protocolVersion, capabilities and clientInfo");
53
+ }
54
+ const protocolVersion = INITIALIZED_VERSIONS.includes(params.protocolVersion)
55
+ ? params.protocolVersion : INITIALIZED_VERSIONS[0];
56
+ phase = "initializing";
57
+ return { protocolVersion, capabilities: CAPABILITIES, serverInfo: SERVER_INFO };
58
+ }
59
+
60
+ const meta = params?._meta;
61
+ const modern = method === "server/discover"
62
+ || (isObject(meta) && (Object.hasOwn(meta, VERSION_KEY)
63
+ || Object.hasOwn(meta, CAPABILITIES_KEY)));
64
+ if (modern) {
65
+ // Never inherit even one missing metadata field from an initialize.
66
+ requireProtocolMeta(params);
67
+ } else if (method !== "ping" && phase !== "ready") {
68
+ if (phase === "initializing") throw rpcError(-32600, "client has not sent initialized");
69
+ requireProtocolMeta(params);
70
+ }
71
+ const result = method === "server/discover"
72
+ ? { supportedVersions: [...SUPPORTED_VERSIONS], capabilities: CAPABILITIES,
73
+ serverInfo: SERVER_INFO, accCapabilities: MCP_CAPABILITIES }
74
+ : method === "ping" ? {} : await execute();
75
+ if (modern) return complete(result);
76
+ // 2025 structuredContent only accepts objects. Preserve the original
77
+ // JSON in text content for array/scalar tools such as acc_inbox.
78
+ if (Object.hasOwn(result, "structuredContent") && !isObject(result.structuredContent)) {
79
+ const { structuredContent, ...legacy } = result;
80
+ return legacy;
81
+ }
82
+ return result;
83
+ },
84
+ };
85
+ }
@@ -43,9 +43,10 @@ export async function readResource(uri, { service, participantId, workspaceId, s
43
43
  throw new AccError(EXIT.DATA, "the inbox resource requires a resolved session",
44
44
  { participantId });
45
45
  }
46
- const inbox = await service.readInbox({ workspaceId, sessionId: session.sessionId,
46
+ const inbox = await service.listInbox({ workspaceId, sessionId: session.sessionId,
47
47
  generation: session.generation });
48
- return inbox.map(item => attributedMessage(item.message));
48
+ return { ...inbox, items: inbox.items.map(item => ({ ...item,
49
+ message: attributedMessage(item.message) })) };
49
50
  }
50
51
  default:
51
52
  throw new AccError(EXIT.DATA, `unknown resource: ${uri}`, { uri });
@@ -1,5 +1,3 @@
1
- import { createRequire } from "node:module";
2
-
3
1
  import { AccError, EXIT, GENERIC_MESSAGE_KINDS, VALID_OBLIGATIONS }
4
2
  from "@agents-can-communicate/protocol";
5
3
  import { clearSessionBinding, loadSessionBinding, storeSessionBinding }
@@ -7,52 +5,34 @@ import { clearSessionBinding, loadSessionBinding, storeSessionBinding }
7
5
 
8
6
  import { readResource } from "./resources.mjs";
9
7
  import { validateToolInput } from "./input-validator.mjs";
10
- import { MCP_CAPABILITIES, PUBLIC_TOOLS, RESOURCES } from "./tools.mjs";
11
-
12
- export const PROTOCOL_VERSION = "2026-07-28";
13
- export const SUPPORTED_VERSIONS = Object.freeze([PROTOCOL_VERSION]);
14
- const PACKAGE_VERSION = createRequire(import.meta.url)("../package.json").version;
15
- const SERVER_INFO = Object.freeze({ name: "agents-can-communicate", version: PACKAGE_VERSION });
8
+ import { PUBLIC_TOOLS, RESOURCES } from "./tools.mjs";
9
+ import { createProtocol } from "./protocol.mjs";
16
10
 
17
- const META = "io.modelcontextprotocol";
11
+ export { PROTOCOL_VERSION, SUPPORTED_VERSIONS } from "./protocol.mjs";
18
12
  const HEARTBEAT_CADENCE_MS = 60_000;
19
13
 
20
- const complete = result => ({ resultType: "complete",
21
- _meta: { [`${META}/serverInfo`]: SERVER_INFO }, ...result });
22
-
23
- function requireProtocolMeta(params) {
24
- const meta = params?._meta ?? {};
25
- const version = meta[`${META}/protocolVersion`];
26
- const capabilities = meta[`${META}/clientCapabilities`];
27
- // The revision requires both on every request and mandates -32602 when one is
28
- // missing. No prior request may be used to supply them.
29
- if (typeof version !== "string" || capabilities === undefined) {
30
- throw Object.assign(new Error(
31
- "each request requires _meta protocolVersion and clientCapabilities"),
32
- { rpcCode: -32602 });
33
- }
34
- if (!SUPPORTED_VERSIONS.includes(version)) {
35
- throw Object.assign(new Error(`unsupported protocol version: ${version}`),
36
- { rpcCode: -32022, rpcData: { supported: [...SUPPORTED_VERSIONS] } });
37
- }
38
- return { version, capabilities };
39
- }
40
-
41
14
  /**
42
15
  * Resolve the ACC session for this server from its own launch configuration.
43
16
  *
44
- * Approved 2026-08-16. The protocol is stateless and forbids treating process or
17
+ * Approved 2026-08-16. The 2026 interface forbids treating process or
45
18
  * connection identity as session continuity, so the session cannot be anchored
46
19
  * to the stdio process. It is derived from the participant and workspace this
47
20
  * server was configured with - available identically on every request - and
48
21
  * persisted through a binding so a restarted process resolves to the same
49
22
  * session instead of creating a second participant.
50
23
  */
51
- async function resolveSession(context) {
24
+ async function resolveSession(context, { forFinish = false } = {}) {
52
25
  const key = `mcp:${context.participantId}:${context.workspaceId}`;
53
26
  const existing = await loadSessionBinding({ runtimeDir: context.runtimeDir,
54
27
  harnessSessionId: key });
55
28
  if (existing !== null) {
29
+ if (forFinish) {
30
+ const current = await context.service.locateSession(existing.accSessionId, context.workspaceId);
31
+ // Core validates and closes this exact generation, including retries.
32
+ // A prior heartbeat could race another finish and reopen a different owner.
33
+ if (current !== null
34
+ && current.record.generation === existing.generation) return current.record;
35
+ }
56
36
  try {
57
37
  return await context.service.heartbeatSession({ sessionId: existing.accSessionId,
58
38
  generation: existing.generation, workspaceId: context.workspaceId });
@@ -131,7 +111,7 @@ function obligationFor(kind, explicit, addressed) {
131
111
  * agreeing to it.
132
112
  */
133
113
  async function callTool(name, args, context) {
134
- const session = await resolveSession(context);
114
+ const session = await resolveSession(context, { forFinish: name === "acc_finish" });
135
115
  const owner = { sessionId: session.sessionId, generation: session.generation,
136
116
  workspaceId: context.workspaceId, descriptor: context.descriptor };
137
117
  const service = context.service;
@@ -141,7 +121,7 @@ async function callTool(name, args, context) {
141
121
  return service.collectStatus({});
142
122
  case "acc_sync":
143
123
  return service.sync({ ...owner, cursor: args.cursor ?? null,
144
- scope: args.scope, limit: args.limit });
124
+ scope: args.scope, limit: args.limit, messageId: args.messageId, kind: args.kind, current: args.current });
145
125
  case "acc_work":
146
126
  if (args.clear === true) {
147
127
  await service.clearIntent({ ...owner });
@@ -164,11 +144,19 @@ async function callTool(name, args, context) {
164
144
  const routed = await recordAndOffer({ router: context.deliveryRouter, record: () =>
165
145
  service.sendMessage({ ...owner, clientMessageId: clientMessageId(args, service),
166
146
  toParticipantIds, subject: args.subject, body: args.body, kind,
167
- obligation: obligationFor(kind, args.obligation, toParticipantIds.length > 0) }) });
147
+ supersedes: args.supersedes, withdraws: args.withdraws,
148
+ obligation: obligationFor(kind, args.obligation, toParticipantIds.length > 0
149
+ || args.supersedes !== undefined || args.withdraws !== undefined) }) });
168
150
  const message = routed.recorded;
169
151
  return { message, delivery: routed.delivery };
170
152
  }
171
153
  case "acc_inbox":
154
+ if (args.messageId === undefined) {
155
+ return service.listInbox({ ...owner, cursor: args.cursor, limit: args.limit });
156
+ }
157
+ if (args.cursor !== undefined || args.limit !== undefined) {
158
+ throw new AccError(EXIT.USAGE, "an exact inbox read cannot use cursor or limit");
159
+ }
172
160
  return service.readInbox({ ...owner, messageId: args.messageId });
173
161
  case "acc_reply": {
174
162
  const routed = await recordAndOffer({ router: context.deliveryRouter,
@@ -176,7 +164,8 @@ async function callTool(name, args, context) {
176
164
  record: () => service.replyToMessage({ ...owner, messageId: args.messageId,
177
165
  body: args.body, subject: args.subject,
178
166
  clientMessageId: clientMessageId(args, service) }) });
179
- return { message: routed.recorded.reply, delivery: routed.delivery };
167
+ return { message: routed.recorded.reply, receipt: routed.recorded.receipt,
168
+ delivery: routed.delivery };
180
169
  }
181
170
  case "acc_request": {
182
171
  const routed = await recordAndOffer({ router: context.deliveryRouter,
@@ -204,27 +193,20 @@ async function callTool(name, args, context) {
204
193
 
205
194
  async function handle(message, context) {
206
195
  const { method, params } = message;
207
- if (method === "server/discover") {
208
- requireProtocolMeta(params);
209
- return complete({ supportedVersions: [...SUPPORTED_VERSIONS], capabilities: {
210
- tools: {}, resources: {} }, serverInfo: SERVER_INFO, accCapabilities: MCP_CAPABILITIES });
211
- }
212
- requireProtocolMeta(params);
213
196
  switch (method) {
214
197
  case "tools/list":
215
- return complete({ tools: [...PUBLIC_TOOLS] });
198
+ return { tools: [...PUBLIC_TOOLS] };
216
199
  case "resources/list":
217
- return complete({ resources: [...RESOURCES] });
200
+ return { resources: [...RESOURCES] };
218
201
  case "resources/read": {
219
- // Snapshot and roster are observation-only. Inbox is a delivery boundary:
220
- // resolve this configured participant's durable session and let the core
221
- // inbox service record that the returned bodies were retrieved.
202
+ // Inbox discovery uses this configured participant's current owner but
203
+ // returns summaries only. Reading this resource never retrieves bodies.
222
204
  const resourceContext = params.uri === "acc://inbox"
223
205
  ? { ...context, session: await resolveSession(context) }
224
206
  : context;
225
207
  const value = await readResource(params.uri, resourceContext);
226
- return complete({ contents: [{ uri: params.uri, mimeType: "application/json",
227
- text: JSON.stringify(value, null, 2) }] });
208
+ return { contents: [{ uri: params.uri, mimeType: "application/json",
209
+ text: JSON.stringify(value, null, 2) }] };
228
210
  }
229
211
  case "tools/call": {
230
212
  try {
@@ -235,13 +217,13 @@ async function handle(message, context) {
235
217
  }
236
218
  validateToolInput(tool.inputSchema, args);
237
219
  const value = await callTool(params.name, args, context);
238
- return complete({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
239
- structuredContent: value });
220
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
221
+ structuredContent: value };
240
222
  } catch (error) {
241
223
  // A failing operation is a tool result, not a transport failure: the
242
224
  // model must see it and be able to react.
243
- return complete({ isError: true,
244
- content: [{ type: "text", text: `${params.name}: ${error.message}` }] });
225
+ return { isError: true,
226
+ content: [{ type: "text", text: `${params.name}: ${error.message}` }] };
245
227
  }
246
228
  }
247
229
  default:
@@ -255,6 +237,7 @@ async function handle(message, context) {
255
237
  */
256
238
  export async function serve({ input, output, log, context }) {
257
239
  const write = value => output.write(`${JSON.stringify(value)}\n`);
240
+ const protocol = createProtocol();
258
241
  let buffer = "";
259
242
 
260
243
  for await (const chunk of input) {
@@ -269,13 +252,32 @@ export async function serve({ input, output, log, context }) {
269
252
  try {
270
253
  message = JSON.parse(line);
271
254
  } catch {
272
- write({ jsonrpc: "2.0", error: { code: -32700, message: "parse error" } });
255
+ write({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } });
256
+ continue;
257
+ }
258
+ // Validate the envelope before accessing fields or treating it as a notification.
259
+ const id = typeof message?.id === "string" || typeof message?.id === "number"
260
+ ? message.id : null;
261
+ if (message === null || typeof message !== "object" || Array.isArray(message)
262
+ || message.jsonrpc !== "2.0" || typeof message.method !== "string"
263
+ || (message.id !== undefined && message.id !== null
264
+ && typeof message.id !== "string" && typeof message.id !== "number")) {
265
+ write({ jsonrpc: "2.0", id, error: { code: -32600, message: "invalid request" } });
266
+ continue;
267
+ }
268
+ // Once the envelope is valid, notifications get no reply, even for invalid params.
269
+ if (message.id === undefined || message.id === null) {
270
+ protocol.notify(message);
271
+ continue;
272
+ }
273
+ if (message.params !== undefined
274
+ && (message.params === null || typeof message.params !== "object")) {
275
+ write({ jsonrpc: "2.0", id, error: { code: -32602, message: "invalid params" } });
273
276
  continue;
274
277
  }
275
- // Notifications get no reply, by rule.
276
- if (message.id === undefined || message.id === null) continue;
277
278
  try {
278
- write({ jsonrpc: "2.0", id: message.id, result: await handle(message, context) });
279
+ write({ jsonrpc: "2.0", id: message.id,
280
+ result: await protocol.request(message, () => handle(message, context)) });
279
281
  } catch (error) {
280
282
  log?.(`${message.method}: ${error.message}`);
281
283
  write({ jsonrpc: "2.0", id: message.id, error: {
@@ -1,13 +1,14 @@
1
- import { GENERIC_MESSAGE_KINDS } from "@agents-can-communicate/protocol";
1
+ import { GENERIC_MESSAGE_KINDS, MESSAGE_KINDS } from "@agents-can-communicate/protocol";
2
2
 
3
3
  // The model-facing surface stays at a small set of high-level operations.
4
4
  //
5
- // Every description says that delivery is polled, because a tool description is
6
- // the only contract the model ever sees. MCP guarantees no lifecycle, no push,
7
- // and no write guard, so promising any of them here would be a lie the model
8
- // cannot check.
9
- const POLLED = "Delivery is polled: call this again to observe changes. "
10
- + "MCP provides no push notification and no wake.";
5
+ // MCP receives by polling. Outgoing messages can use the recipient's native
6
+ // adapter without giving this MCP client native lifecycle, context, or guards.
7
+ // Polling guidance must never tell the model to repeat a mutation to read state.
8
+ const POLLED = "Poll for changes; MCP has no incoming push or wake.";
9
+ const MUTATION_POLL = "Poll acc_inbox, acc_status, or acc_sync for changes instead of repeating this mutation.";
10
+ const OUTGOING = "Commits first; eligible, opted-in native offers are possible. Pending messages otherwise stay queued. "
11
+ + "Inspect delivery; retry with the same clientMessageId and payload.";
11
12
 
12
13
  const object = (properties, required = []) => ({
13
14
  type: "object",
@@ -29,21 +30,25 @@ export const PUBLIC_TOOLS = Object.freeze([
29
30
  {
30
31
  name: "acc_sync",
31
32
  description: `Read coordination state for this workspace: roster, attention items, and `
32
- + `events since a cursor. Use scope "full" to answer questions about the whole `
33
- + `workspace, including other participants' collapsed child sessions. Use acc_inbox `
34
- + `instead for addressed messages. ${POLLED}`,
33
+ + `events since a cursor. Use scope "history" for bounded message summaries, then `
34
+ + `messageId to read one complete historical message without changing receipts. `
35
+ + `Scope "full" is an unbounded forensic workspace snapshot. Use acc_inbox for `
36
+ + `your addressed messages. ${POLLED}`,
35
37
  inputSchema: object({
36
- cursor: string("Resume from this cursor; omit to start from the beginning."),
37
- scope: { type: "string", enum: ["delta", "full"],
38
- description: "delta is a bounded update; full returns the whole workspace snapshot." },
38
+ cursor: string("Event sequence for delta/full; nextCursor message id for history. Omit for the newest history page."),
39
+ scope: { type: "string", enum: ["delta", "full", "history"],
40
+ description: "delta: events; history: read-only message discovery; full: complete forensic snapshot." },
39
41
  limit: { type: "integer", minimum: 1, maximum: 500,
40
- description: "Maximum number of events to return." },
42
+ description: "Maximum events (default 100), or history summaries (default 20, also byte-bounded)." },
43
+ kind: { type: "string", enum: [...MESSAGE_KINDS], description: "Filter history before paging." },
44
+ current: { type: "boolean", description: "With history and kind decision, list only terminal decisions/withdrawals; conflicts remain visible." },
45
+ messageId: string("Read this complete history message; requires history scope and no list controls."),
41
46
  }),
42
47
  },
43
48
  {
44
49
  name: "acc_work",
45
50
  description: `Publish what this session is doing now as one concise Intent. Intent is `
46
- + `awareness, not authorisation: it never reserves a resource. ${POLLED}`,
51
+ + `awareness, not authorisation: it never reserves a resource. ${MUTATION_POLL}`,
47
52
  inputSchema: { ...object({
48
53
  summary: string("One line describing the current work."),
49
54
  mode: { type: "string",
@@ -63,7 +68,7 @@ export const PUBLIC_TOOLS = Object.freeze([
63
68
  name: "acc_claim",
64
69
  description: `Acquire or renew a claim on a resource URI. Claims are `
65
70
  + `workspace-wide and advisory here: this client has no write guard, so a claim `
66
- + `informs peers rather than preventing an edit. ${POLLED}`,
71
+ + `informs peers rather than preventing an edit. ${MUTATION_POLL}`,
67
72
  inputSchema: { ...object({
68
73
  resource: string("Resource URI, for example file:packages/core/**."),
69
74
  action: { type: "string", enum: ["acquire", "renew"] },
@@ -82,15 +87,14 @@ export const PUBLIC_TOOLS = Object.freeze([
82
87
  },
83
88
  {
84
89
  name: "acc_release",
85
- description: `Release a claim this session owns. ${POLLED}`,
90
+ description: `Release a claim this session owns. ${MUTATION_POLL}`,
86
91
  inputSchema: object({
87
92
  claimId: string("The claim to release."),
88
93
  }, ["claimId"]),
89
94
  },
90
95
  {
91
96
  name: "acc_message",
92
- description: `Durably record a typed message to other participants. Recipients read it `
93
- + `when they next poll; there is no push guarantee and no wake. ${POLLED}`,
97
+ description: `Durably record a typed message to other participants. ${OUTGOING} ${MUTATION_POLL}`,
94
98
  inputSchema: object({
95
99
  to: stringList("Recipient participant ids."),
96
100
  subject: string("Short subject line."),
@@ -99,28 +103,38 @@ export const PUBLIC_TOOLS = Object.freeze([
99
103
  enum: [...GENERIC_MESSAGE_KINDS] },
100
104
  obligation: { type: "string", enum: ["none", "acknowledge", "reply"],
101
105
  description: "Override only where the kind/obligation matrix permits it." },
102
- clientMessageId: string("Retry key; omit to generate one and return it in message."),
106
+ clientMessageId: string("Retry key; supply before sending if needed after a lost response. Omit to generate one returned in message."),
107
+ supersedes: { ...stringList("Decision IDs this new decision replaces. Inherits recipients and authors. Exclusive with withdraws."),
108
+ minItems: 1, maxItems: 16, uniqueItems: true },
109
+ withdraws: { ...stringList("Decision IDs explicitly withdrawn, with body as reason. Decision kind only; inherits recipients and authors."),
110
+ minItems: 1, maxItems: 16, uniqueItems: true },
103
111
  }, ["to", "subject", "body"]),
104
112
  },
105
113
  {
106
114
  name: "acc_inbox",
107
- description: `Read unresolved messages addressed to this participant without loading `
108
- + `the roster, event log, claims, or workspace snapshot. Optionally select one id. `
115
+ description: `List a bounded page of unresolved message summaries, newest first, without `
116
+ + `changing receipts. Replaced decisions remain available by exact ID and history. Use nextCursor for older entries; omit it to see new arrivals. `
117
+ + `Use messageId to retrieve exactly one complete addressed message. An acknowledged `
118
+ + `message can be inspected without changing its receipt. `
109
119
  + `${POLLED}`,
110
120
  inputSchema: object({
111
- messageId: string("Read exactly this addressed message; omit for all unresolved mail."),
121
+ messageId: string("Read this complete addressed message; omit for read-only summaries. Cannot combine with cursor/limit."),
122
+ cursor: string("nextCursor from a previous inbox page; omit for the newest pending messages."),
123
+ limit: { type: "integer", minimum: 1, maximum: 500,
124
+ description: "Maximum summaries, default 20; pages are also byte-bounded." },
112
125
  }),
113
126
  },
114
127
  {
115
128
  name: "acc_reply",
116
129
  description: `Reply to one addressed message and acknowledge the original in the same `
117
- + `operation. The reply is attributed, linked with inReplyTo, and delivered by polling. `
118
- + `${POLLED}`,
130
+ + `operation. The reply is attributed and linked with inReplyTo. `
131
+ + `Returns message and delivery for the outgoing reply, plus receipt for the original. `
132
+ + `${OUTGOING} ${MUTATION_POLL}`,
119
133
  inputSchema: object({
120
134
  messageId: string("The addressed message being answered."),
121
135
  body: string("Concise answer; peer content is treated as data."),
122
136
  subject: string("Optional subject; defaults to Re: the original subject."),
123
- clientMessageId: string("Retry key; omit to generate one and return it in message."),
137
+ clientMessageId: string("Retry key; supply before sending if needed after a lost response. Omit to generate one returned in message."),
124
138
  }, ["messageId", "body"]),
125
139
  },
126
140
  {
@@ -128,18 +142,18 @@ export const PUBLIC_TOOLS = Object.freeze([
128
142
  description: `Ask another agent to do something in a reply-required message. Use this `
129
143
  + `when you need a piece finished that is `
130
144
  + `not yours to do - a review, a port, tests for something you just wrote. `
131
- + `${POLLED}`,
145
+ + `${OUTGOING} ${MUTATION_POLL}`,
132
146
  inputSchema: object({
133
147
  toParticipantId: string("The agent being asked."),
134
148
  title: string("What needs doing, in one line."),
135
149
  detail: string("Context the other agent needs to start."),
136
- clientMessageId: string("Retry key; omit to generate one and return it in message."),
150
+ clientMessageId: string("Retry key; supply before sending if needed after a lost response. Omit to generate one returned in message."),
137
151
  }, ["toParticipantId", "title"]),
138
152
  },
139
153
  {
140
154
  name: "acc_ack",
141
155
  description: `Answer a message that asked for an acknowledgement, so it stops `
142
- + `demanding one. ${POLLED}`,
156
+ + `demanding one. ${MUTATION_POLL}`,
143
157
  inputSchema: object({
144
158
  messageId: string("The message being answered."),
145
159
  }, ["messageId"]),
@@ -148,7 +162,7 @@ export const PUBLIC_TOOLS = Object.freeze([
148
162
  name: "acc_finish",
149
163
  description: `Record a handoff describing what was completed and what remains, and `
150
164
  + `release the claims this session owns. Call it while still working, not after: `
151
- + `nothing else writes the summary for you. ${POLLED}`,
165
+ + `nothing else writes the summary for you. ${OUTGOING} ${MUTATION_POLL}`,
152
166
  inputSchema: object({
153
167
  goal: string("What this stretch of work was for."),
154
168
  status: { type: "string", enum: ["complete", "partial", "blocked"] },
@@ -156,7 +170,7 @@ export const PUBLIC_TOOLS = Object.freeze([
156
170
  remaining: stringList("What is left."),
157
171
  blockers: stringList("What is in the way."),
158
172
  toParticipantId: string("Participant taking over, if any."),
159
- clientMessageId: string("Retry key; omit to generate one and return it in message."),
173
+ clientMessageId: string("Retry key; supply before sending if needed after a lost response. Omit to generate one returned in message."),
160
174
  }, ["goal"]),
161
175
  },
162
176
  ]);
@@ -165,9 +179,9 @@ export const RESOURCES = Object.freeze([
165
179
  { uri: "acc://snapshot", name: "Workspace snapshot", mimeType: "application/json",
166
180
  description: "The whole coordination state: participants, intents, claims, and messages." },
167
181
  { uri: "acc://roster", name: "Participant roster", mimeType: "application/json",
168
- description: "Sessions with their harness and presence, including collapsed children." },
182
+ description: "Sessions with their harness and presence." },
169
183
  { uri: "acc://inbox", name: "Inbox", mimeType: "application/json",
170
- description: "Messages addressed to this participant, rendered as attributed data." },
184
+ description: "Read-only pending message headers. Continue with acc_inbox cursor; retrieve a body with messageId." },
171
185
  ]);
172
186
 
173
187
  // Declared, not assumed. Manual MCP tool polling is not next-turn injection,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/protocol",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {