agents-can-communicate 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +3 -1
  2. package/bin/entrypoints/acc-bootstrap.mjs +6 -48
  3. package/bin/entrypoints/acc-claude-channel.mjs +47 -157
  4. package/bin/entrypoints/acc-mcp.mjs +0 -1
  5. package/bin/entrypoints/acc.mjs +0 -1
  6. package/docs/ADAPTER_AUTHORING.md +74 -24
  7. package/docs/ARCHITECTURE.md +31 -18
  8. package/docs/CAPABILITIES.md +38 -25
  9. package/docs/CLI.md +51 -13
  10. package/docs/CONCEPTS.md +5 -4
  11. package/docs/CONFIGURATION.md +18 -11
  12. package/docs/GETTING_STARTED.md +15 -12
  13. package/docs/GLOSSARY.md +2 -1
  14. package/docs/HOW_IT_WORKS.md +8 -4
  15. package/docs/PROTOCOL.md +25 -6
  16. package/docs/RELEASING.md +8 -5
  17. package/docs/SECURITY_MODEL.md +14 -1
  18. package/docs/TROUBLESHOOTING.md +66 -29
  19. package/docs/UPGRADING.md +49 -8
  20. package/docs/WHY_ACC.md +5 -3
  21. package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
  22. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
  23. package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +2 -3
  24. package/node_modules/@agents-can-communicate/adapter-claude-code/certification.json +14 -73
  25. package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/certification-provenance.json +29 -71
  26. package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.282-product-evidence.json +161 -0
  27. package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.282.json +28 -0
  28. package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/inbox-wake-2.1.282.json +44 -0
  29. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +5 -2
  30. package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +26 -30
  31. package/node_modules/@agents-can-communicate/adapter-claude-code/src/hooks.mjs +6 -0
  32. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-contract.mjs +10 -0
  33. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-delivery.mjs +249 -0
  34. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-endpoint.mjs +166 -0
  35. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-registry.mjs +71 -0
  36. package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-settings.mjs +109 -0
  37. package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +25 -31
  38. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  39. package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +3 -4
  40. package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +10 -13
  41. package/node_modules/@agents-can-communicate/adapter-codex/src/live-permissions.mjs +5 -5
  42. package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +46 -10
  43. package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance.mjs +5 -7
  44. package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +16 -12
  45. package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +12 -2
  46. package/node_modules/@agents-can-communicate/adapter-codex/src/service-setup.mjs +26 -9
  47. package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +12 -0
  48. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  49. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  50. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  51. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  52. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  53. package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +2 -2
  54. package/node_modules/@agents-can-communicate/adapter-sdk/src/events.mjs +16 -3
  55. package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +4 -3
  56. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +4 -3
  57. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-activation.mjs +4 -14
  58. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +104 -84
  59. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +8 -4
  60. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  61. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +8 -6
  62. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +8 -23
  63. package/node_modules/@agents-can-communicate/cli/src/install-delivery-consent.mjs +3 -4
  64. package/node_modules/@agents-can-communicate/cli/src/main.mjs +24 -3
  65. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +7 -43
  66. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +5 -3
  67. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/maintenance.mjs +9 -15
  68. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/pins.mjs +13 -4
  69. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +3 -4
  70. package/node_modules/@agents-can-communicate/cli/src/native-delivery-status.mjs +2 -2
  71. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  72. package/node_modules/@agents-can-communicate/core/src/receipts.mjs +17 -2
  73. package/node_modules/@agents-can-communicate/core/src/sessions.mjs +25 -2
  74. package/node_modules/@agents-can-communicate/core/src/status.mjs +31 -1
  75. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  76. package/node_modules/@agents-can-communicate/delivery-router/src/refresh-binding.mjs +2 -2
  77. package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +33 -48
  78. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  79. package/node_modules/@agents-can-communicate/hook-runner/src/client-pid.mjs +30 -1
  80. package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +6 -8
  81. package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +27 -15
  82. package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +30 -14
  83. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +4 -2
  84. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  85. package/node_modules/@agents-can-communicate/installer/src/apply.mjs +19 -5
  86. package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +0 -2
  87. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +6 -11
  88. package/node_modules/@agents-can-communicate/installer/src/index.mjs +4 -7
  89. package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +23 -61
  90. package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +7 -1
  91. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +15 -5
  92. package/node_modules/@agents-can-communicate/installer/src/shell-bootstrap.mjs +10 -133
  93. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  94. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  95. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  96. package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +6 -1
  97. package/package.json +1 -1
  98. package/bin/acc-bootstrap.mjs +0 -7
  99. package/bin/acc-claude-channel.mjs +0 -10
  100. package/bin/entrypoints/claude-channel-binding.mjs +0 -46
  101. package/bin/entrypoints/claude-channel-stdio.mjs +0 -29
  102. package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/.mcp.json +0 -8
  103. package/node_modules/@agents-can-communicate/adapter-claude-code/src/channel.mjs +0 -381
  104. package/node_modules/@agents-can-communicate/adapter-claude-code/src/native-delivery.mjs +0 -232
  105. package/node_modules/@agents-can-communicate/installer/src/bootstrap-runtime.mjs +0 -150
@@ -1,73 +1,93 @@
1
- import { CONTRACT_ID, FINGERPRINT, HANDSHAKE_KEYS, KNOWN_BAD_REASON, NATIVE_ACTIVATION_KINDS,
2
- NATIVE_BINDING_MODES, NATIVE_PLATFORMS, PROBE_KEYS, TIMESTAMP, assertModes,
3
- assertReasonCode, closed, compareStableVersions, deepFreeze, isPlainObject, isText,
4
- parseStableVersion, usage } from "./native-vocabulary.mjs";
1
+ import { CONTRACT_ID, FINGERPRINT, HANDSHAKE_KEYS, KNOWN_BAD_REASON, NATIVE_BINDING_MODES,
2
+ PLANNABLE_ACTIVATION_KINDS, PROBE_KEYS, TIMESTAMP, assertModes, assertReasonCode, closed,
3
+ compareStableVersions, deepFreeze, isPlainObject, isText, parseStableVersion, usage }
4
+ from "./native-vocabulary.mjs";
5
+ import { compareVersionOrder, versionOrder } from "./certification.mjs";
5
6
 
6
- // The native-delivery compatibility contract: a per-platform minimum that is a
7
- // real passing capture, one or more anchors naming the captured protocol, an
8
- // explicit denylist, and the activation kinds an adapter may ask the installer
9
- // for. There is deliberately no maximum: a newer stable client is admitted only
7
+ // The native-delivery compatibility contract: a minimum that is a real passing
8
+ // capture, one or more anchors naming the captured protocol, an explicit
9
+ // denylist, and the activation kinds an adapter may ask the installer for.
10
+ // There is deliberately no maximum: a newer stable client is admitted only
10
11
  // when a current read-only probe and a per-session handshake confirm the same
11
- // protocol contract. Exact-version certification still governs every other
12
- // capability; this rule is used for native live delivery alone.
12
+ // protocol contract. There is no platform either: a capture records where a
13
+ // behaviour was observed, the minimum it establishes applies everywhere, and
14
+ // the probe and handshake decide on the machine in hand (see
15
+ // docs/design/2026-09-26-native-delivery-across-platforms.md). Certification
16
+ // evidence still governs every other capability; this rule is used for native
17
+ // live delivery alone.
13
18
 
14
- export { NATIVE_ACTIVATION_KINDS, NATIVE_BINDING_MODES, NATIVE_PLATFORMS, NATIVE_REASON_CODES,
15
- compareStableVersions, parseStableVersion } from "./native-vocabulary.mjs";
19
+ export { NATIVE_ACTIVATION_KINDS, NATIVE_BINDING_MODES, NATIVE_REASON_CODES,
20
+ PLANNABLE_ACTIVATION_KINDS, compareStableVersions, parseStableVersion } from "./native-vocabulary.mjs";
16
21
  export { validateNativeActivationPlan } from "./native-activation.mjs";
17
22
 
18
23
  const orderedModes = modes => NATIVE_BINDING_MODES.filter(mode => modes.includes(mode));
19
24
 
20
25
  export function validateNativeDeliveryContract(value, { certification, client }) {
26
+ // Consent to live delivery is what `acc install` recorded. Up to 0.7.x a
27
+ // Claude shell shim could export it instead ("bootstrap-environment"); the
28
+ // shim is gone, and the installation record is the one source left.
21
29
  const policySource = Object.hasOwn(value ?? {}, "policySource")
22
- ? value.policySource : "bootstrap-environment";
23
- closed({ ...value, policySource },
24
- ["minimumByPlatform", "anchors", "knownBad", "activationKinds", "policySource"],
30
+ ? value.policySource : "installation-record";
31
+ // What a successful offer put in front of the model. "message" carries the
32
+ // body, so the router records the receipt as offered. "wake" carries only a
33
+ // notice that makes the client run a turn; the body reaches the model through
34
+ // the next-turn hook, which records the offer itself once its stdout carried
35
+ // the body. Recording a wake as offered would hide the body from that hook.
36
+ const offerKind = Object.hasOwn(value ?? {}, "offerKind") ? value.offerKind : "message";
37
+ // The per-platform shape 0.8.0 and earlier declared. Refused by name: an
38
+ // adapter built against that SDK should learn what replaced it, not fail on
39
+ // a generic unknown field.
40
+ if (Object.hasOwn(value ?? {}, "minimumByPlatform")) {
41
+ usage("nativeDelivery.minimumByPlatform is no longer declared; a capture's minimum applies on "
42
+ + "every platform, so declare nativeDelivery.minimum instead "
43
+ + "(docs/design/2026-09-26-native-delivery-across-platforms.md)");
44
+ }
45
+ closed({ ...value, policySource, offerKind },
46
+ ["minimum", "anchors", "knownBad", "activationKinds", "policySource", "offerKind"],
25
47
  "nativeDelivery");
26
- if (!["installation-record", "bootstrap-environment"].includes(policySource)) {
27
- usage("nativeDelivery.policySource must be installation-record or bootstrap-environment");
48
+ if (policySource !== "installation-record") {
49
+ usage("nativeDelivery.policySource must be installation-record");
28
50
  }
29
- const minimums = value.minimumByPlatform;
30
- if (!isPlainObject(minimums) || Object.keys(minimums).length === 0) {
31
- usage("nativeDelivery.minimumByPlatform must map at least one captured platform to a version");
51
+ if (!["message", "wake"].includes(offerKind)) {
52
+ usage("nativeDelivery.offerKind must be message or wake");
32
53
  }
33
- for (const [platform, version] of Object.entries(minimums)) {
34
- if (!NATIVE_PLATFORMS.includes(platform)) {
35
- usage(`nativeDelivery.minimumByPlatform names an unknown platform ${platform}`);
36
- }
37
- if (parseStableVersion(version) === null) {
38
- usage(`nativeDelivery.minimumByPlatform ${platform} must be a stable version`);
39
- }
54
+ const minimum = value.minimum;
55
+ if (parseStableVersion(minimum) === null) {
56
+ usage("nativeDelivery.minimum must be a stable version");
40
57
  }
41
58
  if (!Array.isArray(value.anchors) || value.anchors.length === 0) {
42
59
  usage("nativeDelivery.anchors must name at least one passing capture");
43
60
  }
44
61
  const anchors = value.anchors.map((anchor, index) => {
45
- closed(anchor, ["platform", "version", "protocolContract"], "nativeDelivery anchor");
46
- const minimum = minimums[anchor.platform];
47
- if (minimum === undefined) {
48
- usage(`nativeDelivery anchor ${index} platform ${anchor.platform} has no minimum`);
62
+ if (Object.hasOwn(anchor ?? {}, "platform")) {
63
+ usage(`nativeDelivery anchor ${index} names a platform; the platform a capture was taken on `
64
+ + "is recorded by its evidence, and the anchor names the version alone "
65
+ + "(docs/design/2026-09-26-native-delivery-across-platforms.md)");
49
66
  }
50
- if (parseStableVersion(anchor.version) === null
51
- || compareStableVersions(anchor.version, minimum) < 0) {
52
- usage(`nativeDelivery anchor ${index} version must be a stable version at or above the minimum`);
67
+ closed(anchor, ["version", "protocolContract"], "nativeDelivery anchor");
68
+ if (parseStableVersion(anchor.version) === null) {
69
+ usage(`nativeDelivery anchor ${index} version must be a stable version`);
70
+ }
71
+ if (compareStableVersions(anchor.version, minimum) < 0) {
72
+ usage(`nativeDelivery anchor ${index} version ${anchor.version} is below the minimum ${minimum}: `
73
+ + "the minimum must be the first passing capture");
53
74
  }
54
75
  if (!isText(anchor.protocolContract) || !CONTRACT_ID.test(anchor.protocolContract)) {
55
76
  usage(`nativeDelivery anchor ${index} protocolContract must be a closed identifier`);
56
77
  }
78
+ // Where the capture was taken is provenance the evidence row keeps; the
79
+ // anchor is proven by a passing capture of this version on any platform.
57
80
  const proven = (certification?.evidence ?? []).some(item => item.result === "pass"
58
81
  && item.capability === "delivery.livePush" && item.client === client
59
- && item.version === anchor.version && item.platform === anchor.platform);
82
+ && item.version === anchor.version);
60
83
  if (!proven) {
61
- usage(`nativeDelivery anchor ${anchor.version} on ${anchor.platform} has no passing `
62
- + "delivery.livePush certification", { anchor });
84
+ usage(`nativeDelivery anchor ${anchor.version} has no passing delivery.livePush certification`,
85
+ { anchor });
63
86
  }
64
87
  return { ...anchor };
65
88
  });
66
- for (const [platform, minimum] of Object.entries(minimums)) {
67
- if (!anchors.some(anchor => anchor.platform === platform && anchor.version === minimum)) {
68
- usage(`nativeDelivery minimum ${minimum} on ${platform} must be the first passing capture: `
69
- + "no anchor matches it");
70
- }
89
+ if (!anchors.some(anchor => compareStableVersions(anchor.version, minimum) === 0)) {
90
+ usage(`nativeDelivery minimum ${minimum} must be the first passing capture: no anchor matches it`);
71
91
  }
72
92
  if (!Array.isArray(value.knownBad)) usage("nativeDelivery.knownBad must be an array");
73
93
  const knownBad = value.knownBad.map(entry => {
@@ -89,27 +109,32 @@ export function validateNativeDeliveryContract(value, { certification, client })
89
109
  });
90
110
  const kinds = value.activationKinds;
91
111
  if (!Array.isArray(kinds) || kinds.length === 0 || new Set(kinds).size !== kinds.length
92
- || kinds.some(kind => !NATIVE_ACTIVATION_KINDS.includes(kind))) {
93
- usage(`nativeDelivery.activationKinds must be unique entries of ${NATIVE_ACTIVATION_KINDS.join(", ")}`);
112
+ || kinds.some(kind => !PLANNABLE_ACTIVATION_KINDS.includes(kind))) {
113
+ usage(`nativeDelivery.activationKinds must be unique entries of ${PLANNABLE_ACTIVATION_KINDS.join(", ")}`);
94
114
  }
95
- return deepFreeze({ minimumByPlatform: { ...minimums }, anchors, knownBad,
96
- activationKinds: [...kinds], policySource });
115
+ return deepFreeze({ minimum, anchors, knownBad, activationKinds: [...kinds], policySource,
116
+ offerKind });
97
117
  }
98
118
 
99
- function knownBadHit(contract, version) {
119
+ // `order` is the client's release triple; the denylist names stable versions,
120
+ // so a prerelease of a denylisted release is on it too.
121
+ function knownBadHit(contract, order) {
122
+ const hit = (left, right) => compareVersionOrder(left, versionOrder(right));
100
123
  return contract.knownBad.some(entry => (Object.hasOwn(entry, "version")
101
- ? compareStableVersions(version, entry.version) === 0
102
- : compareStableVersions(version, entry.from) >= 0 && compareStableVersions(version, entry.to) <= 0));
124
+ ? hit(order, entry.version) === 0
125
+ : hit(order, entry.from) >= 0 && hit(order, entry.to) <= 0));
103
126
  }
104
127
 
105
128
  // Judges one reported client version against an adapter's captured
106
- // native-delivery contract: is the platform captured, is the version at or
107
- // above the captured minimum, and is it not on the denylist. Returns
129
+ // native-delivery contract: is the version at or above the captured minimum,
130
+ // and is it not on the denylist. Returns
108
131
  // { reasonCode, minimumVersion, protocolContract }; reasonCode is null when
109
132
  // the version satisfies the contract, otherwise one of
110
- // "native_delivery_unsupported", "platform_not_captured",
111
- // "version_unavailable", "prerelease_not_captured", "below_minimum_version",
112
- // or "known_bad_version".
133
+ // "native_delivery_unsupported", "version_unavailable", "below_minimum_version",
134
+ // or "known_bad_version". The platform the caller runs on plays no part: the
135
+ // minimum was captured somewhere and applies everywhere. A prerelease is
136
+ // judged by its release triple, as hook capabilities are: what admits it is
137
+ // the protocol contract the probe or handshake reports, not the suffix.
113
138
  //
114
139
  // This is the static half of the rule only: it never contacts the client, so
115
140
  // passing here proves nothing about whether a live probe or handshake
@@ -118,38 +143,33 @@ function knownBadHit(contract, version) {
118
143
  // validateNativeHandshake below, and the delivery router's own offer check,
119
144
  // which uses this function alone because a response already carries no probe
120
145
  // or handshake shape to check further).
121
- export function evaluateVersionContract(adapter, { clientVersion, platform }) {
146
+ export function evaluateVersionContract(adapter, { clientVersion } = {}) {
122
147
  const contract = adapter?.nativeDelivery;
123
- // `== null` rather than `=== undefined`: a null declaration is as much "no
124
- // contract" as a missing one, and reading `contract.minimumByPlatform` off it
125
- // threw the very TypeError the guard below exists to prevent. defineAdapter
126
- // cannot produce that shape; a hand-built registry entry can.
127
- if (contract == null) {
128
- return { reasonCode: "native_delivery_unsupported", minimumVersion: null, protocolContract: null };
129
- }
130
- const uncaptured = { reasonCode: "platform_not_captured", minimumVersion: null,
148
+ const unsupported = { reasonCode: "native_delivery_unsupported", minimumVersion: null,
131
149
  protocolContract: null };
132
- // validateNativeDeliveryContract guarantees a minimum map and a matching
133
- // anchor, but this function is also handed adapter objects that never went
134
- // through it. A declaration missing either half has captured nothing for
135
- // this platform, which is a closed answer - not a TypeError raised deep
136
- // inside a delivery offer, far from the declaration that caused it. Every
137
- // other entry point below already answers malformed input this way.
138
- if (typeof platform !== "string" || !isPlainObject(contract.minimumByPlatform)) return uncaptured;
139
- const minimumVersion = contract.minimumByPlatform[platform] ?? null;
140
- if (minimumVersion === null) return uncaptured;
150
+ // `== null` rather than `=== undefined`: a null declaration is as much "no
151
+ // contract" as a missing one, and reading `contract.minimum` off it threw
152
+ // the very TypeError the guard below exists to prevent. defineAdapter cannot
153
+ // produce that shape; a hand-built registry entry can.
154
+ if (contract == null) return unsupported;
155
+ // validateNativeDeliveryContract guarantees a minimum and a matching anchor,
156
+ // but this function is also handed adapter objects that never went through
157
+ // it. A declaration missing either half has captured nothing, which is a
158
+ // closed answer - not a TypeError raised deep inside a delivery offer, far
159
+ // from the declaration that caused it. Every other entry point below
160
+ // already answers malformed input this way.
161
+ const minimumVersion = isText(contract.minimum) ? contract.minimum : null;
162
+ if (minimumVersion === null) return unsupported;
141
163
  const anchor = (Array.isArray(contract.anchors) ? contract.anchors : [])
142
- .find(item => item.platform === platform && item.version === minimumVersion);
143
- if (anchor === undefined) return uncaptured;
164
+ .find(item => item?.version === minimumVersion);
165
+ if (anchor === undefined) return unsupported;
144
166
  const facts = { minimumVersion, protocolContract: anchor.protocolContract };
145
- if (!isText(clientVersion)) return { ...facts, reasonCode: "version_unavailable" };
146
- if (parseStableVersion(clientVersion) === null) {
147
- return { ...facts, reasonCode: "prerelease_not_captured" };
148
- }
149
- if (compareStableVersions(clientVersion, minimumVersion) < 0) {
167
+ const order = versionOrder(clientVersion);
168
+ if (order === null) return { ...facts, reasonCode: "version_unavailable" };
169
+ if (compareVersionOrder(order, versionOrder(minimumVersion)) < 0) {
150
170
  return { ...facts, reasonCode: "below_minimum_version" };
151
171
  }
152
- if (knownBadHit(contract, clientVersion)) return { ...facts, reasonCode: "known_bad_version" };
172
+ if (knownBadHit(contract, order)) return { ...facts, reasonCode: "known_bad_version" };
153
173
  return { ...facts, reasonCode: null };
154
174
  }
155
175
 
@@ -172,13 +192,13 @@ function validateNativeProbe(probe) {
172
192
  return probe;
173
193
  }
174
194
 
175
- export function evaluateNativeEligibility(adapter, { clientVersion, platform, probe }) {
195
+ export function evaluateNativeEligibility(adapter, { clientVersion, probe }) {
176
196
  // The probe names the process that will serve the delivery. Judging the
177
197
  // detected binary instead refuses a service that satisfies the contract.
178
198
  // Only the version is read here; the shape is validated where it always was,
179
199
  // so a malformed probe still returns a closed result rather than throwing.
180
200
  const serving = isText(probe?.clientVersion) ? probe.clientVersion : clientVersion;
181
- const rule = evaluateVersionContract(adapter, { clientVersion: serving, platform });
201
+ const rule = evaluateVersionContract(adapter, { clientVersion: serving });
182
202
  const base = { eligible: false, reasonCode: null, minimumVersion: rule.minimumVersion,
183
203
  protocolContract: rule.protocolContract, modes: [] };
184
204
  const closedResult = reasonCode => deepFreeze({ ...base, reasonCode });
@@ -230,13 +250,13 @@ function validateNativeHandshakeShape(handshake) {
230
250
  // the two records disagreed on every machine where a daemon had updated under
231
251
  // its CLI, which is exactly the case the release exists to support. A refused
232
252
  // verdict admits nothing and carries null.
233
- export function validateNativeHandshake(adapter, { clientVersion, platform, handshake }) {
253
+ export function validateNativeHandshake(adapter, { clientVersion, handshake }) {
234
254
  // Same rationale as the probe: the handshake names the session that will
235
255
  // actually serve, so the static rule is judged against that version. Only
236
256
  // the version is read here; the shape is validated where it always was, so
237
257
  // a malformed handshake still returns a closed result rather than throwing.
238
258
  const serving = isText(handshake?.clientVersion) ? handshake.clientVersion : clientVersion;
239
- const rule = evaluateVersionContract(adapter, { clientVersion: serving, platform });
259
+ const rule = evaluateVersionContract(adapter, { clientVersion: serving });
240
260
  const base = { ok: false, reasonCode: null, clientVersion: null,
241
261
  protocolContract: rule.protocolContract, modes: [],
242
262
  opaqueEndpointRef: null, leaseUntil: null };
@@ -9,16 +9,21 @@ export const NATIVE_BINDING_MODES = Object.freeze([
9
9
  "livePush", "idleWake", "busyQueue", "replyRoute",
10
10
  ]);
11
11
 
12
+ // Every kind an install record may carry. "shell-bootstrap" is the `claude`
13
+ // shim ACC 0.7.x wrote; records of it stay readable so it can be retired.
12
14
  export const NATIVE_ACTIVATION_KINDS = Object.freeze([
13
15
  "shell-bootstrap", "native-config", "native-service",
14
16
  ]);
15
17
 
16
- export const NATIVE_PLATFORMS = Object.freeze([
17
- "darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-x64",
18
- ]);
18
+ // The kinds an adapter may still ask for.
19
+ export const PLANNABLE_ACTIVATION_KINDS = Object.freeze(["native-config", "native-service"]);
19
20
 
20
21
  // Every reason a client is not live, closed so doctor, hooks, and docs share
21
22
  // one vocabulary and vendor strings never leak through it.
23
+ // "platform_not_captured" and "prerelease_not_captured" are historical: 0.8.0
24
+ // and earlier refused a platform no capture named and any prerelease suffix,
25
+ // and their native-attempt records carry both. Nothing produces either any
26
+ // more (docs/design/2026-09-26-native-delivery-across-platforms.md).
22
27
  export const NATIVE_REASON_CODES = Object.freeze([
23
28
  "native_delivery_unsupported", "platform_not_captured", "version_unavailable",
24
29
  "prerelease_not_captured", "below_minimum_version", "known_bad_version",
@@ -34,7 +39,6 @@ export const NATIVE_REASON_CODES = Object.freeze([
34
39
  export const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z.-]+)?$/;
35
40
  export const CONTRACT_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
36
41
  export const IDENTIFIER = /^[a-z][a-z0-9_-]*$/;
37
- export const COMMAND_NAME = /^[a-z][a-z0-9_.-]*$/;
38
42
  export const FINGERPRINT = /^sha256:[0-9a-f]{64}$/;
39
43
  export const TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
40
44
  export const SHELL_SOURCE = /[;&|<>`\n\0]|\$\(/;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -2,7 +2,7 @@ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
4
 
5
- import { describeDeliveryFallback, detectInstallation, livePolicyOf, loadOwnership, shellOf, verifyOwned }
5
+ import { describeDeliveryFallback, detectInstallation, livePolicyOf, loadOwnership, verifyOwned }
6
6
  from "@agents-can-communicate/installer";
7
7
  import { AccError, EXIT } from "@agents-can-communicate/protocol";
8
8
 
@@ -114,12 +114,9 @@ export async function diagnoseAdapters({ options, runtime, detect = detectInstal
114
114
  const home = options?.home ?? runtime?.env?.HOME ?? homedir();
115
115
  const { data: dataHome } = platformPaths({ platform: runtime?.platform,
116
116
  env: runtime?.env ?? {} });
117
- // The same shell and environment install reads, so detection plans the same
118
- // shell bootstrap it would apply. Omitting them left detection with a null
119
- // shell, which degrades every shell-bootstrap client to `unsupported_shell` -
120
- // so a zsh machine with a working shim read as degraded on every run.
117
+ // The same environment install reads, so detection probes the same client.
121
118
  const clients = clientContext(home, path.join(dataHome, "acc"),
122
- { shell: shellOf(runtime?.env ?? {}), env: runtime?.env ?? {} });
119
+ { env: runtime?.env ?? {} });
123
120
  const adapters = ALL_ADAPTERS();
124
121
  const detected = await detect({ adapters, context: clients,
125
122
  probeTimeoutMs: probeTimeout(runtime?.env) });
@@ -297,6 +294,11 @@ export async function runDoctor({ options, context, runtime }) {
297
294
  ...nativeSessionLines(adapters),
298
295
  ...adapters.filter(adapter => adapter.present && adapter.outgoingDelivery?.state === "configured")
299
296
  .map(adapter => ` ${adapter.displayName} ${adapter.outgoingDelivery.diagnostic}`),
297
+ // The client's own inbound control decides whether a wake reaches the model.
298
+ // Only someone who turned live delivery on needs to hear about it.
299
+ ...adapters.filter(adapter => adapter.present && adapter.nativeDelivery.configured
300
+ && typeof adapter.inboundDelivery?.diagnostic === "string")
301
+ .map(adapter => ` ${adapter.displayName} inbound: ${adapter.inboundDelivery.diagnostic}`),
300
302
  ...(manager === null ? [] : [` automatic updates ${manager.auto ? "on" : "off"}; ACC ${manager.active.version}`
301
303
  + (manager.pin ? `; pinned to ${manager.pin}` : ""), ...(update.notice ? [` ${update.notice}`] : [])]),
302
304
  ...data.remediation.map(line => ` ${line}`),
@@ -7,8 +7,7 @@ import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
7
7
  import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
8
8
  import { createGrokAdapter } from "@agents-can-communicate/adapter-grok";
9
9
  import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
10
- import { applyPlan, detectInstallation,
11
- loadOwnership, planInstallation, rcFileFor, shellOf, shimDirFor }
10
+ import { applyPlan, detectInstallation, loadOwnership, planInstallation }
12
11
  from "@agents-can-communicate/installer";
13
12
  import { AccError, EXIT } from "@agents-can-communicate/protocol";
14
13
 
@@ -29,7 +28,7 @@ export { decideDelivery } from "./install-delivery-consent.mjs";
29
28
  // pointed at the home itself writes beside them rather than inside them. That
30
29
  // install reports success and the client never reads a byte of it.
31
30
  export const clientContext = (home, stateRoot,
32
- { shell = null, env = {}, dataHome, cwd = process.cwd() } = {}) => ({
31
+ { env = {}, dataHome, cwd = process.cwd() } = {}) => ({
33
32
  home,
34
33
  ...(dataHome === undefined ? {} : { dataHome }),
35
34
  configDir: path.join(home, ".claude"),
@@ -39,10 +38,8 @@ export const clientContext = (home, stateRoot,
39
38
  kimiHome: path.join(home, ".kimi-code"),
40
39
  grokHome: typeof env.GROK_HOME === "string" && env.GROK_HOME !== ""
41
40
  ? env.GROK_HOME : path.join(home, ".grok"),
42
- // The user's login shell and PATH, for the optional native shell bootstrap:
43
- // which rc file could carry an ACC PATH block and which real executable a
44
- // shim would exec. Detection reads them; nothing here writes.
45
- shell,
41
+ // The user's PATH, for finding the real client executable to probe.
42
+ // Detection reads it; nothing here writes.
46
43
  env,
47
44
  // Antigravity CLI keeps its hooks in the `~/.gemini` tree Gemini CLI also
48
45
  // uses, and reads none of the same files. Two locations load - the
@@ -200,8 +197,7 @@ export function describeOutcome({ action, acted, failed = [], skipped = [],
200
197
  export function failureOf({ action, acted, failed = [], operations = [], skipped = [], home }) {
201
198
  if (failed.length === 0) return null;
202
199
  return new AccError(EXIT.DATA,
203
- [describeOutcome({ action, acted, failed, operations, skipped, home }),
204
- ...reloadAdvice(operations)].join("\n"), { failed });
200
+ describeOutcome({ action, acted, failed, operations, skipped, home }), { failed });
205
201
  }
206
202
 
207
203
  /**
@@ -220,23 +216,13 @@ export function actedOn(result) {
220
216
  + (operation.changes?.length ?? 0) > 0)).length;
221
217
  }
222
218
 
223
- // Said once, after the first PATH block is written: a running shell and a
224
- // running client know nothing about it.
225
- function reloadAdvice(operations) {
226
- const appended = operations.filter(operation => operation.appendedRcBlock === true);
227
- if (appended.length === 0) return [];
228
- const rc = appended[0].nativeActivation?.rcFile ?? "your shell rc file";
229
- return ["", `native delivery is wired for new sessions: open a new terminal (or reload ${rc}), `
230
- + "then start the client normally; a session already running keeps durable delivery"];
231
- }
232
-
233
219
  export async function runInstallCommand({ options, runtime, action = "install" }) {
234
220
  const adapters = selectAdapters(options.adapter);
235
221
  const home = options.home ?? runtime.env?.HOME ?? homedir();
236
222
  const { data: dataHome } = platformPaths({ platform: runtime.platform,
237
223
  env: runtime.env ?? {} });
238
224
  const context = clientContext(home, path.join(dataHome, "acc"),
239
- { shell: shellOf(runtime.env), env: runtime.env ?? {}, dataHome });
225
+ { env: runtime.env ?? {}, dataHome });
240
226
 
241
227
  const detected = await detectInstallation({ adapters, context,
242
228
  probeTimeoutMs: probeTimeout(runtime.env) });
@@ -273,7 +259,7 @@ export async function runInstallCommand({ options, runtime, action = "install" }
273
259
  deliveryDecisionByAdapter: decided.deliveryDecisionByAdapter,
274
260
  allowServiceSetup: action === "install" });
275
261
  const apply = (paths = {}) => applyPlan({ plan, adapters, context: { ...context, ...paths },
276
- dataHome, dryRun, accVersion, activation: { bootstrap: paths.bootstrap } });
262
+ dataHome, dryRun, accVersion });
277
263
  const result = action === "install" && !dryRun && runtime.packageRoot
278
264
  ? await installManaged({ packageRoot: runtime.packageRoot,
279
265
  managerRoot: path.join(dataHome, "acc", "runtime"), dataHome, home, cwd: options.cwd ?? runtime.cwd,
@@ -293,9 +279,8 @@ export async function runInstallCommand({ options, runtime, action = "install" }
293
279
 
294
280
  return { data: { ...result, plan, dataHome, deliveryByAdapter: decided.deliveryByAdapter,
295
281
  deliveryDecisionByAdapter: decided.deliveryDecisionByAdapter, asked: decided.asked },
296
- text: [describeOutcome({ action, acted, failed: result.failed,
282
+ text: describeOutcome({ action, acted, failed: result.failed,
297
283
  skipped: plan.skipped, operations: result.operations, home }),
298
- ...reloadAdvice(result.operations)].join("\n"),
299
284
  error: failureOf({ action, acted, failed: result.failed, operations: result.operations,
300
285
  skipped: plan.skipped, home }) };
301
286
  }
@@ -57,9 +57,6 @@ function questionFor(entries) {
57
57
  const names = entries.map(entry => entry.displayName ?? entry.adapterId).join(", ");
58
58
  const setup = [...new Set(entries.map(entry => entry.outgoingDelivery?.setup)
59
59
  .filter(value => typeof value === "string"))];
60
- const channels = entries.some(entry => (entry.nativeDelivery?.activationPlan?.mechanisms ?? [])
61
- .some(mechanism => mechanism.kind === "shell-bootstrap"
62
- && (mechanism.prefixArgs ?? []).some(argument => argument.startsWith("--"))));
63
60
  return [
64
61
  `Complete automatic peer-request setup for ${names}? (experimental)`,
65
62
  " Yes: automatic turns can spend tokens without waiting for you.",
@@ -69,7 +66,9 @@ function questionFor(entries) {
69
66
  ...entries.filter(needsPrerequisite).map(entry =>
70
67
  ` Download the official Codex ${entry.nativeServiceSetup.cliVersion} standalone package for its service; retain your existing codex command.`),
71
68
  ...setup.map(note => ` ${note}`),
72
- ...(channels ? [" Allow Claude Code development Channels when prompted at startup."] : []),
69
+ ...(entries.some(entry => entry.adapterId === "claude_code")
70
+ ? [" Claude Code sessions that bypass permission prompts ask before each ACC wake;",
71
+ " set Claude Code's crossSessionInbound to accept to take wakes without asking."] : []),
73
72
  " No: keep current delivery policies and use available next-turn hooks or acc inbox.",
74
73
  ].join("\n");
75
74
  }
@@ -138,13 +138,26 @@ function obligationFor(kind, explicit, addressed) {
138
138
  return obligation;
139
139
  }
140
140
 
141
- function recordedText(message, delivery) {
141
+ export function recordedText(message, delivery) {
142
142
  const diagnostics = delivery.map(item => item.outcome === "offered"
143
143
  ? `live offered to ${item.recipientParticipantId} via ${item.transport}`
144
+ : item.outcome === "woken" && item.pendingApproval === true
145
+ ? `sent a wake to ${item.recipientParticipantId} via ${item.transport}, which its session holds `
146
+ + "for approval because it bypasses permission prompts; the message arrives with its next turn"
147
+ : item.outcome === "woken"
148
+ ? `woke ${item.recipientParticipantId} via ${item.transport}; the message arrives with its next turn`
144
149
  : item.outcome === "queued"
145
150
  ? item.errorCode === "transport_permission_denied"
146
151
  ? "live offer blocked by sender permissions; message remains queued; run acc doctor in the sending session"
147
- : `live offer unavailable (${item.errorCode ?? "durable_fallback"})`
152
+ : item.errorCode === "no_live_transport"
153
+ ? `${item.recipientParticipantId} has no live transport; the message waits in its inbox`
154
+ : item.errorCode === "recipient_offline" && item.endReason === "clear"
155
+ ? `${item.recipientParticipantId}'s conversation was cleared (/clear); the message waits `
156
+ + "until that conversation resumes"
157
+ : item.errorCode === "recipient_offline"
158
+ ? `${item.recipientParticipantId} has no open session; the message waits until it starts `
159
+ + "or resumes one"
160
+ : `live offer unavailable (${item.errorCode ?? "durable_fallback"})`
148
161
  : `delivery already ${item.outcome}`);
149
162
  return [`recorded ${message.messageId}`, ...diagnostics].join("; ");
150
163
  }
@@ -185,7 +198,15 @@ export function describeStatus(status) {
185
198
  // A transport accepting a message is not a model reading it. Said only when
186
199
  // it is true of something, so the everyday line stays the everyday line.
187
200
  const offered = status.counts.unretrieved?.offered ?? 0;
188
- return offered === 0 ? line : `${line}; ${offered} offered, not retrieved`;
201
+ const withOffers = offered === 0 ? line : `${line}; ${offered} offered, not retrieved`;
202
+ // Status lists a participant with no open session only while messages wait
203
+ // for it; each is named here, with the client's reason when it gave one. The
204
+ // count is what its next turn would still show, never an offer already made.
205
+ const waiting = (status.participants ?? [])
206
+ .filter(item => item.presence === "offline" && (item.waiting ?? 0) > 0)
207
+ .map(item => `${item.participantId} (${item.endReason === "clear" ? "cleared, " : ""}${item.waiting})`);
208
+ return waiting.length === 0 ? withOffers
209
+ : `${withOffers}; waiting for a closed session: ${waiting.join(", ")}`;
189
210
  }
190
211
 
191
212
  const HANDLERS = Object.freeze({
@@ -7,24 +7,13 @@ import { canonicalManagerRoot, readControl, readManagedJson } from "./state.mjs"
7
7
  import { commandPrefix } from "./command-prefix.mjs";
8
8
 
9
9
  export const ENTRY_KINDS = Object.freeze([
10
- "acc", "acc-hook", "acc-mcp", "acc-bootstrap", "acc-claude-channel", "acc-antigravity-relay",
10
+ "acc", "acc-hook", "acc-mcp", "acc-antigravity-relay",
11
11
  ]);
12
12
 
13
- const BOOTSTRAP_FLAGS = new Map([["--adapter", "adapter"], ["--real-executable", "realExecutable"],
14
- ["--data-home", "dataHome"]]);
15
-
16
- /** Shared with the runtime; parse before admission without importing installer code. */
17
- export function parseBootstrapOptions(args) {
18
- const options = {};
19
- for (let index = 0; index < args.length; index += 2) {
20
- const key = BOOTSTRAP_FLAGS.get(args[index]);
21
- const value = args[index + 1];
22
- if (key === undefined || typeof value !== "string" || value === "" || key in options) return null;
23
- options[key] = value;
24
- }
25
- return BOOTSTRAP_FLAGS.values().every(key => key in options) && path.isAbsolute(options.dataHome)
26
- ? options : null;
27
- }
13
+ // Kinds a 0.7.x install wrote a launcher for. The package still ships their
14
+ // entrypoints for 0.7.x updaters and launchers; a current install removes the
15
+ // launchers themselves.
16
+ export const RETIRED_ENTRY_KINDS = Object.freeze(["acc-bootstrap", "acc-claude-channel"]);
28
17
 
29
18
  // Bootstrap resolution deliberately needs only Node built-ins: no selected
30
19
  // generation is imported until admission and its actual PID lease are durable.
@@ -43,11 +32,9 @@ export function invokedDirectly(url) {
43
32
 
44
33
  function unavailable(kind) {
45
34
  // Never reflect control file content or payloads into a client diagnostic.
46
- if (kind === "acc-hook" || kind === "acc-claude-channel" || kind === "acc-antigravity-relay") {
35
+ if (kind === "acc-hook" || kind === "acc-antigravity-relay") {
47
36
  process.stderr.write("acc: coordination unavailable during runtime update; session may continue\n");
48
37
  process.exitCode = 0;
49
- } else if (kind === "acc-bootstrap") {
50
- process.exitCode = 1; // The vendor shell shim launches its original command.
51
38
  } else {
52
39
  process.stderr.write("acc: runtime unavailable; retry after the update or run acc update to recover\n");
53
40
  process.exitCode = 4;
@@ -83,28 +70,12 @@ async function updateImplementation(packageRoot, control) {
83
70
  export async function runEntry({ kind, packageRoot, managerRoot, managedRequired = false }) {
84
71
  if (!ENTRY_KINDS.includes(kind)) throw new Error("unknown ACC entry point");
85
72
  const command = kind === "acc" ? commandPrefix(process.argv.slice(2)).command : null;
86
- const bootstrapOptions = kind === "acc-bootstrap" ? parseBootstrapOptions(process.argv.slice(2)) : null;
87
- if (kind === "acc-bootstrap" && bootstrapOptions === null) {
88
- if (process.env.ACC_BOOTSTRAP_DEBUG === "1") {
89
- process.stderr.write("acc-bootstrap: usage: --adapter <id> --real-executable <path> --data-home <path>\n");
90
- }
91
- process.exitCode = 2;
92
- return;
93
- }
94
73
  let selected = packageRoot;
95
74
  let managed = null;
96
75
  let managementOnly = false;
97
76
  let root;
98
77
  try {
99
- const requestedDataHome = bootstrapOptions === null ? null : await canonicalManagerRoot(bootstrapOptions.dataHome);
100
- const requestedRoot = requestedDataHome === null ? null
101
- : await canonicalManagerRoot(path.join(requestedDataHome, "acc", "runtime"));
102
- if (managerRoot && requestedDataHome !== null
103
- && requestedDataHome !== await canonicalManagerRoot(path.dirname(path.dirname(managerRoot)))) {
104
- throw new Error("bootstrap cache home differs from its launcher");
105
- }
106
- root = await canonicalManagerRoot(managerRoot ?? requestedRoot ?? managerLocation());
107
- if (requestedRoot !== null && root !== requestedRoot) throw new Error("bootstrap data home differs from its launcher");
78
+ root = await canonicalManagerRoot(managerRoot ?? managerLocation());
108
79
  const control = await readControl(root);
109
80
  if (control !== null) {
110
81
  selected = control.active.root;
@@ -138,13 +109,6 @@ export async function runEntry({ kind, packageRoot, managerRoot, managedRequired
138
109
  } catch { /* Fall through to the same graceful unavailability below. */ }
139
110
  }
140
111
  unavailable(kind);
141
- if (kind === "acc-claude-channel" && selected) {
142
- try {
143
- const { startInertChannel } = await import(pathToFileURL(path.join(selected,
144
- "bin", "entrypoints", "claude-channel-stdio.mjs")).href);
145
- startInertChannel();
146
- } catch { /* The stderr notice above already fired either way. */ }
147
- }
148
112
  return;
149
113
  }
150
114
  const runtime = await import(pathToFileURL(path.join(selected, "bin", "entrypoints", `${kind}.mjs`)).href);
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { lstat, mkdtemp, open, readFile, rename, rm } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { ENTRY_KINDS } from "./entry.mjs";
4
+ import { ENTRY_KINDS, RETIRED_ENTRY_KINDS } from "./entry.mjs";
5
5
  import { managedDirectory, syncDirectory } from "./state.mjs";
6
6
 
7
7
  const MODULES = ["entry.mjs", "command-prefix.mjs", "state.mjs", "generation-files.mjs",
@@ -14,8 +14,6 @@ async function durableFile(file, bytes, mode = 0o600) {
14
14
 
15
15
  export const stablePaths = root => ({
16
16
  cli: path.join(root, "bin", "acc.mjs"), runner: path.join(root, "bin", "acc-hook.mjs"),
17
- bootstrap: path.join(root, "bin", "acc-bootstrap.mjs"),
18
- channel: path.join(root, "bin", "acc-claude-channel.mjs"),
19
17
  antigravityRelay: path.join(root, "bin", "acc-antigravity-relay.mjs"),
20
18
  });
21
19
 
@@ -57,6 +55,10 @@ export async function writeLaunchers(root, packageRoot) {
57
55
  await rename(temporary, path.join(bin, `${kind}.mjs`));
58
56
  } finally { await rm(temporary, { force: true }); }
59
57
  }
58
+ // A 0.7.x `claude` shim or plugin copy that still reaches for one of these
59
+ // finds nothing: the shim then launches the vendor command untouched, and a
60
+ // re-laid plugin names none of them.
61
+ for (const kind of RETIRED_ENTRY_KINDS) await rm(path.join(bin, `${kind}.mjs`), { force: true });
60
62
  await syncDirectory(bin);
61
63
  return stablePaths(root);
62
64
  }