agents-can-communicate 0.8.4 → 0.9.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.
- package/README.md +11 -7
- package/bin/entrypoints/acc-antigravity-relay.mjs +2 -4
- package/bin/entrypoints/acc-hook.mjs +33 -19
- package/bin/entrypoints/hook-output.mjs +14 -5
- package/docs/ADAPTER_AUTHORING.md +32 -0
- package/docs/ARCHITECTURE.md +7 -4
- package/docs/CAPABILITIES.md +7 -6
- package/docs/CLI.md +5 -0
- package/docs/CONFIGURATION.md +15 -10
- package/docs/DESIGN_DECISIONS.md +0 -2
- package/docs/GETTING_STARTED.md +39 -6
- package/docs/HOW_IT_WORKS.md +13 -8
- package/docs/SECURITY_MODEL.md +26 -5
- package/docs/TROUBLESHOOTING.md +74 -12
- package/docs/UPGRADING.md +50 -5
- package/node_modules/@agents-can-communicate/adapter-antigravity/certification.json +78 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/certification-provenance.json +111 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-desktop-2.19.1-product-evidence.json +156 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/delivery/antigravity-desktop-2.19.1-product.json +25 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/fixtures/desktop-2.19.1.json +141 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +4 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +13 -2
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/agentapi.mjs +7 -5
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/allow-rule.mjs +98 -17
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/client-process.mjs +64 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/desktop-allow-rule.mjs +140 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/desktop-delivery.mjs +110 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/desktop-endpoint.mjs +203 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +61 -30
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/native-delivery.mjs +61 -24
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-endpoint.mjs +30 -20
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay-start.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relay.mjs +20 -12
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/relays.mjs +18 -4
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +6 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-delivery.mjs +50 -37
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-endpoint.mjs +44 -31
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-registry.mjs +99 -14
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-settings.mjs +3 -7
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-socket-directories.mjs +6 -4
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +32 -7
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +68 -3
- package/node_modules/@agents-can-communicate/adapter-codex/src/daemon-layouts.mjs +13 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/embedded-host.mjs +87 -13
- package/node_modules/@agents-can-communicate/adapter-codex/src/hooks.mjs +16 -6
- package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +18 -9
- package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +68 -27
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +66 -26
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +39 -16
- package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +4 -2
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/adapter.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +15 -9
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +25 -14
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/src/adapter.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +15 -10
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +50 -5
- package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +11 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/executables.mjs +149 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +79 -12
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +11 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +6 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +82 -17
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +9 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/private-files.mjs +35 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/process-table.mjs +232 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +8 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/windows-command.mjs +69 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/windows-process-chain.wsf +45 -0
- package/node_modules/@agents-can-communicate/cli/package.json +2 -1
- package/node_modules/@agents-can-communicate/cli/src/config-command.mjs +2 -1
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +60 -4
- package/node_modules/@agents-can-communicate/cli/src/hook-support.mjs +9 -0
- package/node_modules/@agents-can-communicate/cli/src/hook-workspace.mjs +12 -3
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +2 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/activation.mjs +3 -2
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/download.mjs +12 -3
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +7 -5
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +7 -7
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +8 -5
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/maintenance.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/mutex.mjs +7 -6
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/portable-fs.mjs +165 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/schedule.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +18 -8
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/worker.mjs +18 -3
- package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +15 -4
- package/node_modules/@agents-can-communicate/cli/src/platform-paths.mjs +15 -10
- package/node_modules/@agents-can-communicate/cli/src/runtime-paths.mjs +3 -2
- package/node_modules/@agents-can-communicate/cli/src/update-command.mjs +11 -1
- package/node_modules/@agents-can-communicate/cli/src/workspace-discovery.mjs +4 -2
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +7 -0
- package/node_modules/@agents-can-communicate/core/src/materialisation.mjs +39 -1
- package/node_modules/@agents-can-communicate/core/src/session-opening.mjs +11 -0
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +8 -2
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/native-reason.mjs +6 -2
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +9 -5
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-pid.mjs +65 -6
- package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +80 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +7 -2
- package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +6 -3
- package/node_modules/@agents-can-communicate/hook-runner/src/owner-context.mjs +34 -6
- package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +3 -50
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +167 -34
- package/node_modules/@agents-can-communicate/installer/package.json +3 -2
- package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +11 -2
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +13 -3
- package/node_modules/@agents-can-communicate/installer/src/hook-support.mjs +5 -0
- package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +6 -14
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +14 -0
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/config.mjs +6 -2
- package/node_modules/@agents-can-communicate/protocol/src/resources.mjs +6 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/active-journal.mjs +5 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +41 -23
- package/node_modules/@agents-can-communicate/storage-filesystem/src/doomed-directory.mjs +4 -12
- package/node_modules/@agents-can-communicate/storage-filesystem/src/identity.mjs +7 -3
- package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/journal.mjs +24 -7
- package/node_modules/@agents-can-communicate/storage-filesystem/src/portable-fs.mjs +165 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/publication-path.mjs +8 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/reclaim.mjs +26 -17
- package/node_modules/@agents-can-communicate/storage-filesystem/src/recovery.mjs +7 -5
- package/node_modules/@agents-can-communicate/storage-filesystem/src/retention.mjs +7 -16
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-directory.mjs +138 -24
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-file.mjs +3 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/src/stage-sweep.mjs +9 -6
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +30 -8
- package/node_modules/@agents-can-communicate/storage-filesystem/src/writer-mutex.mjs +102 -32
- package/package.json +1 -5
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import { decisionBody } from "@agents-can-communicate/adapter-sdk";
|
|
5
|
+
import { decisionBody, isLaunchOption } from "@agents-can-communicate/adapter-sdk";
|
|
6
6
|
|
|
7
7
|
import { CODEX_QUEUE_MINIMUM, MINIMUM_VERSION, PROTOCOL_CONTRACT, QUEUE_MODES,
|
|
8
8
|
addCodexQueueMessage, canonicalCwd, compareVersions, controlSocketPath,
|
|
@@ -10,7 +10,7 @@ import { CODEX_QUEUE_MINIMUM, MINIMUM_VERSION, PROTOCOL_CONTRACT, QUEUE_MODES,
|
|
|
10
10
|
versionOrder } from "./app-server-client.mjs";
|
|
11
11
|
import { newEndpointId, readNativeEndpoint, readySocketPath, removeNativeEndpoint, socketIsReady,
|
|
12
12
|
writeNativeEndpoint } from "./native-endpoint.mjs";
|
|
13
|
-
import {
|
|
13
|
+
import { embeddedHost, hostArgv } from "./embedded-host.mjs";
|
|
14
14
|
|
|
15
15
|
// The receiver's hook supplies thread and cwd. Core holds only a random endpoint
|
|
16
16
|
// reference; sender environment never decides which daemon receives the message.
|
|
@@ -36,10 +36,12 @@ async function usingPeer(socketPath, timeoutMs, open, run) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export async function probeNativeDelivery({ timeoutMs = 750, env = process.env,
|
|
39
|
-
open = openCodexAppServer } = {}) {
|
|
39
|
+
open = openCodexAppServer, platform = process.platform } = {}) {
|
|
40
40
|
const unsupported = (reasonCode, clientVersion = null) => ({ supported: false, clientVersion,
|
|
41
41
|
protocolContract: PROTOCOL_CONTRACT, executableFingerprint: null, modes: [], reasonCode });
|
|
42
|
-
|
|
42
|
+
// Windows reaches the daemon's AF_UNIX socket through `codex app-server proxy`
|
|
43
|
+
// (openCodexAppServer); everything past the transport is the same.
|
|
44
|
+
const socketPath = await readySocketPath(controlSocketPath(env), { platform });
|
|
43
45
|
if (socketPath === null) return unsupported("native_endpoint_unavailable");
|
|
44
46
|
try {
|
|
45
47
|
return await usingPeer(socketPath, timeoutMs, open, async peer => {
|
|
@@ -107,22 +109,26 @@ export async function verifyReceiver(peer, endpoint, { probe = probeCodexQueue,
|
|
|
107
109
|
// A chat that runs embedded has no receiver for any check below to find. Its
|
|
108
110
|
// host process is read only after a check failed, so a bound session never pays
|
|
109
111
|
// for it, and the reason then names the cause instead of the symptom.
|
|
110
|
-
export async function bindNativeSession({ argvOf = hostArgv,
|
|
111
|
-
|
|
112
|
+
export async function bindNativeSession({ argvOf = hostArgv, platform = process.platform,
|
|
113
|
+
...options } = {}) {
|
|
114
|
+
const result = await bindAttempt({ ...options, platform });
|
|
112
115
|
if (result.supported || !Number.isInteger(options.clientPid) || options.clientPid <= 0) return result;
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
const host = await embeddedHost(options.clientPid, argvOf);
|
|
117
|
+
if (host === null) return result;
|
|
118
|
+
const refusal = closed(options.clientVersion, "client_session_embedded");
|
|
119
|
+
return host.launchOption === null ? refusal : { ...refusal, launchOption: host.launchOption };
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
async function bindAttempt({ event, clientPid, clientVersion, runtimeDir,
|
|
118
|
-
env = process.env, timeoutMs = 750, now = Date.now, open = openCodexAppServer
|
|
123
|
+
env = process.env, timeoutMs = 750, now = Date.now, open = openCodexAppServer,
|
|
124
|
+
platform = process.platform } = {}) {
|
|
119
125
|
const rejected = reason => closed(clientVersion, reason);
|
|
120
126
|
if (!Number.isInteger(clientPid) || clientPid <= 0) return rejected("client_process_unknown");
|
|
121
127
|
if (versionOrder(clientVersion) === null) return rejected("version_unavailable");
|
|
122
128
|
if (typeof event?.sessionId !== "string" || event.sessionId === "") return rejected("handshake_failed");
|
|
123
129
|
const cwd = await canonicalCwd(event.cwd);
|
|
124
130
|
if (cwd === null) return rejected("workspace_identity_unavailable");
|
|
125
|
-
const socketPath = await readySocketPath(controlSocketPath(env));
|
|
131
|
+
const socketPath = await readySocketPath(controlSocketPath(env), { platform });
|
|
126
132
|
if (socketPath === null) return rejected("handshake_failed");
|
|
127
133
|
try {
|
|
128
134
|
return await usingPeer(socketPath, timeoutMs, open, async peer => {
|
|
@@ -136,7 +142,7 @@ async function bindAttempt({ event, clientPid, clientVersion, runtimeDir,
|
|
|
136
142
|
// answered so the binding and its later reads reflect the serving
|
|
137
143
|
// process, not the caller's claim.
|
|
138
144
|
const verified = { ...endpoint, clientVersion: servingVersion };
|
|
139
|
-
await writeNativeEndpoint({ runtimeDir, record: verified });
|
|
145
|
+
await writeNativeEndpoint({ runtimeDir, record: verified, platform });
|
|
140
146
|
return handshake(verified, now);
|
|
141
147
|
});
|
|
142
148
|
} catch (error) {
|
|
@@ -161,15 +167,15 @@ async function bindAttempt({ event, clientPid, clientVersion, runtimeDir,
|
|
|
161
167
|
// support: the endpoint records what the daemon serves, the binding recorded
|
|
162
168
|
// what `codex --version` printed, and a daemon that had updated under its CLI
|
|
163
169
|
// was refused here on that difference alone.
|
|
164
|
-
async function receiverFor(binding, runtimeDir) {
|
|
165
|
-
const endpoint = await readNativeEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef });
|
|
166
|
-
return endpoint !== null && await socketIsReady(endpoint.socketPath) ? endpoint : null;
|
|
170
|
+
async function receiverFor(binding, runtimeDir, platform = process.platform) {
|
|
171
|
+
const endpoint = await readNativeEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef, platform });
|
|
172
|
+
return endpoint !== null && await socketIsReady(endpoint.socketPath, { platform }) ? endpoint : null;
|
|
167
173
|
}
|
|
168
174
|
|
|
169
175
|
export async function refreshNativeSession({ binding, runtimeDir, timeoutMs = 750,
|
|
170
|
-
now = Date.now, open = openCodexAppServer } = {}) {
|
|
176
|
+
now = Date.now, open = openCodexAppServer, platform = process.platform } = {}) {
|
|
171
177
|
const rejected = reason => closed(binding?.clientVersion, reason);
|
|
172
|
-
const endpoint = await receiverFor(binding, runtimeDir);
|
|
178
|
+
const endpoint = await receiverFor(binding, runtimeDir, platform);
|
|
173
179
|
if (endpoint === null) return rejected("handshake_failed");
|
|
174
180
|
try {
|
|
175
181
|
return await usingPeer(endpoint.socketPath, timeoutMs, open, async peer => {
|
|
@@ -183,10 +189,10 @@ export async function refreshNativeSession({ binding, runtimeDir, timeoutMs = 75
|
|
|
183
189
|
}
|
|
184
190
|
|
|
185
191
|
export async function offerMessage({ binding, message, runtimeDir, timeoutMs = 5_000,
|
|
186
|
-
open = openCodexAppServer } = {}) {
|
|
192
|
+
open = openCodexAppServer, platform = process.platform } = {}) {
|
|
187
193
|
const rejected = safeErrorCode => ({ accepted: false, transport: "codex-app-server",
|
|
188
194
|
clientVersion: binding?.clientVersion ?? null, safeErrorCode });
|
|
189
|
-
const endpoint = await receiverFor(binding, runtimeDir);
|
|
195
|
+
const endpoint = await receiverFor(binding, runtimeDir, platform);
|
|
190
196
|
if (endpoint === null) return rejected("recipient_unavailable");
|
|
191
197
|
try {
|
|
192
198
|
return await usingPeer(endpoint.socketPath, timeoutMs, open, async peer => {
|
|
@@ -205,17 +211,48 @@ export async function offerMessage({ binding, message, runtimeDir, timeoutMs = 5
|
|
|
205
211
|
}
|
|
206
212
|
}
|
|
207
213
|
|
|
208
|
-
export const retireNativeSession = ({ binding, runtimeDir }) =>
|
|
209
|
-
removeNativeEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef });
|
|
214
|
+
export const retireNativeSession = ({ binding, runtimeDir, platform = process.platform }) =>
|
|
215
|
+
removeNativeEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef, platform });
|
|
210
216
|
|
|
211
|
-
const
|
|
212
|
-
+ "
|
|
213
|
-
|
|
214
|
-
|
|
217
|
+
const OPEN_A_CHAT = "open a new Codex chat; if Codex does not start its app-server daemon itself, "
|
|
218
|
+
+ "run `codex app-server daemon start` first";
|
|
219
|
+
// Options a setting in config.toml can replace; the rest are simply left off.
|
|
220
|
+
const SETTING_OPTIONS = new Set(["-c", "--config", "--enable", "--disable", "--search", "-p",
|
|
221
|
+
"--profile"]);
|
|
222
|
+
|
|
223
|
+
// What the chat is told: one line for the model, one the client shows the user
|
|
224
|
+
// (Codex prints a hook's systemMessage, which never reaches the model).
|
|
225
|
+
function embeddedNotice(launchOption) {
|
|
226
|
+
if (!isLaunchOption(launchOption)) {
|
|
227
|
+
return { line: "ACC: live peer delivery is off in this Codex chat. It runs on its own embedded "
|
|
228
|
+
+ "app server, so peer messages reach it only with the user's next prompt. ACC has shown the "
|
|
229
|
+
+ `user how to get it: ${OPEN_A_CHAT}.`,
|
|
230
|
+
userMessage: "ACC: live peer delivery is off in this chat, which runs on its own app server. "
|
|
231
|
+
+ `For live delivery, ${OPEN_A_CHAT}.` };
|
|
232
|
+
}
|
|
233
|
+
const advice = `start Codex without \`${launchOption}\``
|
|
234
|
+
+ (SETTING_OPTIONS.has(launchOption) ? " and move what it sets into config.toml" : "");
|
|
235
|
+
return { line: `ACC: live peer delivery is off in this Codex chat. It was started with `
|
|
236
|
+
+ `\`${launchOption}\`, so it runs on its own embedded app server and peer messages reach it `
|
|
237
|
+
+ `only with the user's next prompt. ACC has shown the user how to get it: ${advice}.`,
|
|
238
|
+
userMessage: `ACC: live peer delivery is off in this chat: \`${launchOption}\` makes Codex run `
|
|
239
|
+
+ `it on its own app server. For live delivery, ${advice}.` };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// A chat the Codex app serves itself: nothing the user starts differently
|
|
243
|
+
// changes that, so the notice says what happens instead of what to do.
|
|
244
|
+
const DESKTOP_NOTICE = Object.freeze({
|
|
245
|
+
line: "ACC: live peer delivery is off in this Codex chat. The Codex app runs it on its own app "
|
|
246
|
+
+ "server, which no other process can reach, so peer messages arrive with the user's next "
|
|
247
|
+
+ "prompt. ACC has shown the user this.",
|
|
248
|
+
userMessage: "ACC: live peer delivery is off in this chat: the Codex app runs it on its own app "
|
|
249
|
+
+ "server. Peer messages arrive with your next prompt.",
|
|
250
|
+
});
|
|
215
251
|
|
|
216
252
|
// One delivered notice per chat. The marker is created exclusively, so a
|
|
217
253
|
// second turn finds it; a notice the runner did not deliver gives it back.
|
|
218
|
-
export async function nativeActivationHint({ event, nativeBinding, runtimeDir
|
|
254
|
+
export async function nativeActivationHint({ event, nativeBinding, runtimeDir, clientPid,
|
|
255
|
+
argvOf = hostArgv }) {
|
|
219
256
|
if (nativeBinding?.state !== "degraded" || nativeBinding.reasonCode !== "client_session_embedded"
|
|
220
257
|
|| typeof event?.sessionId !== "string" || event.sessionId === ""
|
|
221
258
|
|| typeof runtimeDir !== "string") return null;
|
|
@@ -227,7 +264,10 @@ export async function nativeActivationHint({ event, nativeBinding, runtimeDir })
|
|
|
227
264
|
} catch {
|
|
228
265
|
return null;
|
|
229
266
|
}
|
|
230
|
-
|
|
267
|
+
const host = Number.isInteger(clientPid) && clientPid > 0 && !isLaunchOption(nativeBinding.launchOption)
|
|
268
|
+
? await embeddedHost(clientPid, argvOf) : null;
|
|
269
|
+
const notice = host?.desktop === true ? DESKTOP_NOTICE : embeddedNotice(nativeBinding.launchOption);
|
|
270
|
+
return { ...notice, release: () => rm(marker, { force: true }) };
|
|
231
271
|
}
|
|
232
272
|
|
|
233
273
|
function renderText(message) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import { lstat, mkdir, open, realpath, rename, rm } from "node:fs/promises";
|
|
2
|
+
import { lstat, mkdir, open, readdir, realpath, rename, rm } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
|
|
5
|
+
import { closedTo, isWindowsPlatform, openRegularNoFollow } from "@agents-can-communicate/adapter-sdk";
|
|
6
|
+
|
|
6
7
|
import { PROTOCOL_CONTRACT, versionOrder } from "./app-server-client.mjs";
|
|
7
8
|
|
|
8
9
|
const KEYS = ["schemaVersion", "endpointId", "socketPath", "threadId", "cwd",
|
|
@@ -28,7 +29,7 @@ function valid(record) {
|
|
|
28
29
|
&& Number.isFinite(Date.parse(record.leaseUntil));
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
async function directory(runtimeDir, create = false) {
|
|
32
|
+
async function directory(runtimeDir, create = false, platform = process.platform) {
|
|
32
33
|
if (!absolute(runtimeDir)) throw invalid();
|
|
33
34
|
if (create) await mkdir(runtimeDir, { recursive: true, mode: 0o700 });
|
|
34
35
|
const root = await realpath(runtimeDir);
|
|
@@ -37,11 +38,25 @@ async function directory(runtimeDir, create = false) {
|
|
|
37
38
|
if (error.code !== "EEXIST") throw error;
|
|
38
39
|
});
|
|
39
40
|
const info = await lstat(dir);
|
|
40
|
-
if (!info.isDirectory() || info.isSymbolicLink() || !own(info) || (info
|
|
41
|
+
if (!info.isDirectory() || info.isSymbolicLink() || !own(info) || !closedTo(info, 0o022, { platform })) {
|
|
42
|
+
throw invalid();
|
|
43
|
+
}
|
|
41
44
|
return dir;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
/**
|
|
48
|
+
* An owned socket, never itself a link. On Windows Node's lstat refuses the
|
|
49
|
+
* daemon's AF_UNIX socket file with EACCES (measured on windows-latest, Codex
|
|
50
|
+
* 0.159.3), so there the socket counts while its directory lists it; Codex keeps
|
|
51
|
+
* that directory to its user.
|
|
52
|
+
*/
|
|
53
|
+
export async function socketIsReady(socketPath, { platform = process.platform } = {}) {
|
|
54
|
+
if (isWindowsPlatform(platform)) {
|
|
55
|
+
if (!absolute(socketPath)) return false;
|
|
56
|
+
const name = path.basename(socketPath).toLowerCase();
|
|
57
|
+
const names = await readdir(path.dirname(socketPath)).catch(() => []);
|
|
58
|
+
return names.some(entry => entry.toLowerCase() === name);
|
|
59
|
+
}
|
|
45
60
|
if (!absolute(socketPath)) return false;
|
|
46
61
|
try {
|
|
47
62
|
const info = await lstat(socketPath);
|
|
@@ -53,7 +68,14 @@ export async function socketIsReady(socketPath) {
|
|
|
53
68
|
* symlink at the path it reports and the socket itself under /private/tmp
|
|
54
69
|
* (measured 2026-09-26), so the path is followed once and the socket at its
|
|
55
70
|
* end is held to socketIsReady: an owned socket, never itself a link. */
|
|
56
|
-
export async function readySocketPath(socketPath) {
|
|
71
|
+
export async function readySocketPath(socketPath, { platform = process.platform } = {}) {
|
|
72
|
+
if (isWindowsPlatform(platform)) {
|
|
73
|
+
// The socket file itself refuses to be opened; its directory resolves.
|
|
74
|
+
if (!absolute(socketPath)) return null;
|
|
75
|
+
const directory = await realpath(path.dirname(socketPath)).catch(() => null);
|
|
76
|
+
const resolved = directory === null ? null : path.join(directory, path.basename(socketPath));
|
|
77
|
+
return resolved !== null && await socketIsReady(resolved, { platform }) ? resolved : null;
|
|
78
|
+
}
|
|
57
79
|
if (!absolute(socketPath)) return null;
|
|
58
80
|
const resolved = await realpath(socketPath).catch(() => null);
|
|
59
81
|
return resolved !== null && await socketIsReady(resolved) ? resolved : null;
|
|
@@ -61,11 +83,11 @@ export async function readySocketPath(socketPath) {
|
|
|
61
83
|
|
|
62
84
|
// A fresh, immutable registration for one hook binding. Refresh observes the
|
|
63
85
|
// same registration again; it cannot overwrite a successor's random endpoint.
|
|
64
|
-
export async function writeNativeEndpoint({ runtimeDir, record }) {
|
|
65
|
-
if (!valid(record) || !await socketIsReady(record.socketPath)) throw invalid();
|
|
86
|
+
export async function writeNativeEndpoint({ runtimeDir, record, platform = process.platform }) {
|
|
87
|
+
if (!valid(record) || !await socketIsReady(record.socketPath, { platform })) throw invalid();
|
|
66
88
|
const text = `${JSON.stringify(record)}\n`;
|
|
67
89
|
if (Buffer.byteLength(text) > MAX_BYTES) throw invalid();
|
|
68
|
-
const dir = await directory(runtimeDir, true);
|
|
90
|
+
const dir = await directory(runtimeDir, true, platform);
|
|
69
91
|
const file = path.join(dir, `${record.endpointId}.json`);
|
|
70
92
|
const temporary = path.join(dir, `.${record.endpointId}.${randomBytes(8).toString("hex")}.tmp`);
|
|
71
93
|
let handle;
|
|
@@ -82,25 +104,26 @@ export async function writeNativeEndpoint({ runtimeDir, record }) {
|
|
|
82
104
|
|
|
83
105
|
// Expired records are metadata for revalidation, never proof of reachability.
|
|
84
106
|
// Refuse links and oversized files before reading; no foreign error text escapes.
|
|
85
|
-
export async function readNativeEndpoint({ runtimeDir, endpointId }) {
|
|
107
|
+
export async function readNativeEndpoint({ runtimeDir, endpointId, platform = process.platform }) {
|
|
86
108
|
if (typeof endpointId !== "string" || !ENDPOINT.test(endpointId)) return null;
|
|
87
109
|
let handle;
|
|
88
110
|
try {
|
|
89
|
-
const dir = await directory(runtimeDir);
|
|
90
|
-
handle = await
|
|
91
|
-
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
111
|
+
const dir = await directory(runtimeDir, false, platform);
|
|
112
|
+
handle = await openRegularNoFollow(path.join(dir, `${endpointId}.json`), undefined, { platform });
|
|
92
113
|
const info = await handle.stat();
|
|
93
|
-
if (!info.isFile() || !own(info) || info.size > MAX_BYTES || (info
|
|
114
|
+
if (!info.isFile() || !own(info) || info.size > MAX_BYTES || !closedTo(info, 0o077, { platform })) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
94
117
|
const record = JSON.parse(await handle.readFile("utf8"));
|
|
95
118
|
return valid(record) && record.endpointId === endpointId ? record : null;
|
|
96
119
|
} catch { return null; }
|
|
97
120
|
finally { await handle?.close().catch(() => null); }
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
export async function removeNativeEndpoint({ runtimeDir, endpointId }) {
|
|
123
|
+
export async function removeNativeEndpoint({ runtimeDir, endpointId, platform = process.platform }) {
|
|
101
124
|
if (typeof endpointId !== "string" || !ENDPOINT.test(endpointId)) return;
|
|
102
125
|
try {
|
|
103
|
-
const dir = await directory(runtimeDir);
|
|
126
|
+
const dir = await directory(runtimeDir, false, platform);
|
|
104
127
|
await rm(path.join(dir, `${endpointId}.json`), { force: true });
|
|
105
128
|
} catch { /* retirement is already recorded; cleanup is best effort */ }
|
|
106
129
|
}
|
|
@@ -75,8 +75,10 @@ export function refusesConnections(socketPath, { timeoutMs = 1_000 } = {}) {
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// `connect` returns the byte stream to speak over: a socket by default, or on
|
|
79
|
+
// Windows the stdio of `codex app-server proxy`, which relays to the same server.
|
|
78
80
|
export function openWebSocketPeer({ socketPath, timeoutMs, path = "/", host = "localhost",
|
|
79
|
-
retainNotifications = true }) {
|
|
81
|
+
retainNotifications = true, connect = () => net.createConnection(socketPath) }) {
|
|
80
82
|
const notifications = [];
|
|
81
83
|
const pending = new Map();
|
|
82
84
|
const key = randomBytes(16).toString("base64");
|
|
@@ -90,7 +92,7 @@ export function openWebSocketPeer({ socketPath, timeoutMs, path = "/", host = "l
|
|
|
90
92
|
const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
|
|
91
93
|
ready.catch(() => {});
|
|
92
94
|
|
|
93
|
-
const socket =
|
|
95
|
+
const socket = connect();
|
|
94
96
|
socket.on("connect", () => socket.write(`GET ${path} HTTP/1.1\r\nHost: ${host}\r\n`
|
|
95
97
|
+ `Upgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: ${key}\r\n`
|
|
96
98
|
+ "Sec-WebSocket-Version: 13\r\n\r\n"));
|
|
@@ -43,7 +43,8 @@ export function createGeminiCliAdapter() {
|
|
|
43
43
|
// The binary this client actually installs. Probed for a version to
|
|
44
44
|
// decide whether the client is on this machine, so it has to be the
|
|
45
45
|
// real command rather than the adapter id: `0.57.0`.
|
|
46
|
-
client: { command: "gemini",
|
|
46
|
+
client: { command: "gemini", package: "@google/gemini-cli", certificationName: "gemini-cli",
|
|
47
|
+
versionArgs: ["--version"] },
|
|
47
48
|
certification,
|
|
48
49
|
capabilities: {
|
|
49
50
|
lifecycle: { sessionStart: true, sessionEnd: true },
|
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
|
|
7
7
|
import { acccreatedFile, bakeSkillCommand, blankJson, ownVersion, removeIfEmpty,
|
|
8
8
|
removeInstalledTree,
|
|
9
|
-
stampPluginVersion, writeCliShim, writeForeignJson, writeHookShim }
|
|
9
|
+
shellQuote, stampPluginVersion, windowsHookCommand, writeCliShim, writeForeignJson, writeHookShim }
|
|
10
10
|
from "@agents-can-communicate/adapter-sdk";
|
|
11
11
|
|
|
12
12
|
const bundle = fileURLToPath(new URL("../extension", import.meta.url));
|
|
@@ -46,11 +46,16 @@ const writeJson = (file, value) => writeForeignJson(file, value,
|
|
|
46
46
|
const isOurs = hook => typeof hook?.name === "string" && hook.name.startsWith(OWNER_PREFIX);
|
|
47
47
|
|
|
48
48
|
// Replace the bundle's placeholder command with the shim that was just written.
|
|
49
|
-
|
|
49
|
+
// On Windows this client runs hooks through PowerShell, which has no `sh`: the
|
|
50
|
+
// pinned node runs the Node shim there.
|
|
51
|
+
const withShim = (wiring, shim, windowsNode = null) => ({ hooks: Object.fromEntries(
|
|
50
52
|
Object.entries(wiring.hooks).map(([event, entries]) => [event, entries.map(entry => ({
|
|
51
53
|
...entry,
|
|
52
|
-
hooks: entry.hooks.map(hook =>
|
|
53
|
-
|
|
54
|
+
hooks: entry.hooks.map(hook => {
|
|
55
|
+
const kind = hook.command.split(" ").pop();
|
|
56
|
+
return { ...hook, command: windowsNode === null ? `sh ${shellQuote(shim)} ${kind}`
|
|
57
|
+
: windowsHookCommand("powershell", { node: windowsNode, shim, args: [kind] }) };
|
|
58
|
+
}),
|
|
54
59
|
}))])) });
|
|
55
60
|
|
|
56
61
|
/**
|
|
@@ -62,7 +67,8 @@ const withShim = (wiring, shim) => ({ hooks: Object.fromEntries(
|
|
|
62
67
|
* No environment variable is copied or persisted. The extension declares what
|
|
63
68
|
* it needs; secrets stay where the user put them.
|
|
64
69
|
*/
|
|
65
|
-
export async function installGeminiExtension({ home, runner, cli, node
|
|
70
|
+
export async function installGeminiExtension({ home, runner, cli, node,
|
|
71
|
+
hostPlatform = process.platform }) {
|
|
66
72
|
// Read before writing: a settings file that will not parse must not be found
|
|
67
73
|
// out after the extension tree is already on disk.
|
|
68
74
|
const found = await readJson(settingsPath(home), null);
|
|
@@ -82,15 +88,15 @@ export async function installGeminiExtension({ home, runner, cli, node }) {
|
|
|
82
88
|
await rm(path.join(target, "hooks", "hooks.json"), { force: true });
|
|
83
89
|
// The skill ships with a placeholder where the command belongs: `acc` is
|
|
84
90
|
// not on PATH everywhere, and an agent that cannot run it improvises.
|
|
85
|
-
const cliShim = await writeCliShim({ dir: target, cli, node });
|
|
86
|
-
await bakeSkillCommand({ root: target, cliShim });
|
|
91
|
+
const cliShim = await writeCliShim({ dir: target, cli, node, platform: hostPlatform });
|
|
92
|
+
await bakeSkillCommand({ root: target, cliShim, platform: hostPlatform });
|
|
87
93
|
// This client offers no plugin-root variable in a hook command, so the shim's
|
|
88
94
|
// absolute path is written in at install time.
|
|
89
95
|
const shim = await writeHookShim({ dir: path.join(target, "hooks"),
|
|
90
|
-
adapterId: "gemini_cli", runner, node });
|
|
96
|
+
adapterId: "gemini_cli", runner, node, platform: hostPlatform });
|
|
91
97
|
|
|
92
98
|
const ours = withShim(await readJson(path.join(bundle, "hooks", "hooks.json"),
|
|
93
|
-
{ hooks: {} }), shim);
|
|
99
|
+
{ hooks: {} }), shim, hostPlatform === "win32" ? node ?? process.execPath : null);
|
|
94
100
|
const file = settingsPath(home);
|
|
95
101
|
const merged = { ...existing, hooks: { ...(existing.hooks ?? {}) } };
|
|
96
102
|
// Recorded now: afterwards a settings file holding `{}` looks the same
|
|
@@ -3,7 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
5
5
|
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
6
|
-
import { bakeSkillCommand, removeInstalledTree,
|
|
6
|
+
import { bakeSkillCommand, removeInstalledTree, shellQuote, windowsHookCommand, writeCliShim,
|
|
7
|
+
writeHookShim }
|
|
7
8
|
from "@agents-can-communicate/adapter-sdk";
|
|
8
9
|
|
|
9
10
|
const bundle = fileURLToPath(new URL("../plugin", import.meta.url));
|
|
@@ -15,7 +16,9 @@ export const grokHomeOf = context =>
|
|
|
15
16
|
context.grokHome ?? path.join(context.home, ".grok");
|
|
16
17
|
|
|
17
18
|
export const hooksFile = home => path.join(home, "hooks", HOOKS_NAME);
|
|
18
|
-
|
|
19
|
+
// Windows gets the Node shim: no Windows client can be relied on to have `sh`.
|
|
20
|
+
export const shimPath = (home, platform = process.platform) => path.join(home, "hooks",
|
|
21
|
+
platform === "win32" ? SHIM_NAME.replace(/\.sh$/, ".mjs") : SHIM_NAME);
|
|
19
22
|
export const skillPath = home => path.join(home, "skills", "acc");
|
|
20
23
|
|
|
21
24
|
async function readJson(file, fallback) {
|
|
@@ -46,37 +49,44 @@ const writeJson = async (file, value) => {
|
|
|
46
49
|
// for plugins; we install into ~/.grok/hooks, which is always trusted and does
|
|
47
50
|
// not need [plugins].enabled. The shim's absolute path is written in at install
|
|
48
51
|
// time, the same lesson Codex taught: a relative hook command fails silently.
|
|
49
|
-
|
|
52
|
+
// On Windows this client runs hooks through the shell it detects - pwsh, Git
|
|
53
|
+
// Bash or cmd - so the command is written in the one form all three read.
|
|
54
|
+
const withShim = (wiring, shim, windowsNode = null) => ({
|
|
50
55
|
description: wiring.description,
|
|
51
56
|
hooks: Object.fromEntries(Object.entries(wiring.hooks).map(([event, entries]) =>
|
|
52
57
|
[event, entries.map(entry => ({
|
|
53
58
|
...entry,
|
|
54
|
-
hooks: entry.hooks.map(hook =>
|
|
55
|
-
|
|
59
|
+
hooks: entry.hooks.map(hook => {
|
|
60
|
+
const kind = hook.command.split(" ").pop();
|
|
61
|
+
return { ...hook, command: windowsNode === null ? `sh ${shellQuote(shim)} ${kind}`
|
|
62
|
+
: windowsHookCommand("portable", { node: windowsNode, shim, args: [kind] }) };
|
|
63
|
+
}),
|
|
56
64
|
}))])),
|
|
57
65
|
});
|
|
58
66
|
|
|
59
|
-
export async function installGrokHooks({ grokHome, home, runner, cli, node
|
|
67
|
+
export async function installGrokHooks({ grokHome, home, runner, cli, node,
|
|
68
|
+
hostPlatform = process.platform }) {
|
|
60
69
|
const root = grokHome ?? grokHomeOf({ home, grokHome });
|
|
61
70
|
const template = await readJson(path.join(bundle, "hooks", "hooks.json"), { hooks: {} });
|
|
62
71
|
const shim = await writeHookShim({ dir: path.join(root, "hooks"), adapterId: "grok",
|
|
63
|
-
runner, node, name: SHIM_NAME });
|
|
72
|
+
runner, node, name: SHIM_NAME, platform: hostPlatform });
|
|
64
73
|
|
|
65
74
|
const skills = skillPath(root);
|
|
66
75
|
await rm(skills, { recursive: true, force: true });
|
|
67
76
|
await mkdir(path.dirname(skills), { recursive: true });
|
|
68
77
|
await cp(path.join(bundle, "skills", "acc"), skills, { recursive: true });
|
|
69
|
-
const cliShim = await writeCliShim({ dir: skills, cli, node });
|
|
70
|
-
await bakeSkillCommand({ root: skills, cliShim });
|
|
78
|
+
const cliShim = await writeCliShim({ dir: skills, cli, node, platform: hostPlatform });
|
|
79
|
+
await bakeSkillCommand({ root: skills, cliShim, platform: hostPlatform });
|
|
71
80
|
|
|
72
|
-
await writeJson(hooksFile(root), withShim(template, shim
|
|
81
|
+
await writeJson(hooksFile(root), withShim(template, shim,
|
|
82
|
+
hostPlatform === "win32" ? node ?? process.execPath : null));
|
|
73
83
|
return { ok: true, changes: [hooksFile(root), shim, skills], diagnostics: [] };
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
export async function uninstallGrokHooks({ grokHome, home, keep = [] }) {
|
|
86
|
+
export async function uninstallGrokHooks({ grokHome, home, keep = [], hostPlatform }) {
|
|
77
87
|
const root = grokHome ?? grokHomeOf({ home, grokHome });
|
|
78
88
|
const changes = [];
|
|
79
|
-
for (const target of [hooksFile(root), shimPath(root), skillPath(root)]) {
|
|
89
|
+
for (const target of [hooksFile(root), shimPath(root, hostPlatform), skillPath(root)]) {
|
|
80
90
|
if (await removeInstalledTree(target, keep)) changes.push(target);
|
|
81
91
|
}
|
|
82
92
|
return { ok: true, changes, diagnostics: [] };
|
|
@@ -87,7 +97,8 @@ export async function detectGrok({ grokHome, home }) {
|
|
|
87
97
|
const wired = await readJson(hooksFile(root), null);
|
|
88
98
|
const installed = typeof wired?.hooks?.SessionStart?.[0]?.hooks?.[0]?.command
|
|
89
99
|
=== "string"
|
|
90
|
-
|
|
100
|
+
// The shell shim on POSIX, the Node shim on Windows.
|
|
101
|
+
&& /acc-hook\.(?:sh|mjs)\b/.test(wired.hooks.SessionStart[0].hooks[0].command);
|
|
91
102
|
return { ok: true, changes: [],
|
|
92
103
|
diagnostics: [installed ? "acc hooks registered" : "acc hooks not registered"] };
|
|
93
104
|
}
|
|
@@ -96,7 +107,7 @@ export function planGrokInstall(context) {
|
|
|
96
107
|
const root = grokHomeOf(context);
|
|
97
108
|
return [
|
|
98
109
|
{ path: hooksFile(root), kind: "tree" },
|
|
99
|
-
{ path: shimPath(root), kind: "tree" },
|
|
110
|
+
{ path: shimPath(root, context.hostPlatform), kind: "tree" },
|
|
100
111
|
{ path: skillPath(root), kind: "tree" },
|
|
101
112
|
];
|
|
102
113
|
}
|
|
@@ -32,7 +32,8 @@ export function createKimiAdapter() {
|
|
|
32
32
|
// The binary this client actually installs. Probed for a version to
|
|
33
33
|
// decide whether the client is on this machine, so it has to be the
|
|
34
34
|
// real command rather than the adapter id: `0.36.1`.
|
|
35
|
-
client: { command: "kimi",
|
|
35
|
+
client: { command: "kimi", package: "@moonshot-ai/kimi-code", certificationName: "kimi",
|
|
36
|
+
versionArgs: ["--version"] },
|
|
36
37
|
certification,
|
|
37
38
|
capabilities: {
|
|
38
39
|
lifecycle: { sessionStart: true, heartbeat: true },
|
|
@@ -4,9 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
|
-
import { assertRunner, bakeSkillCommand, blankText, defaultRunner, removeIfEmpty,
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { assertRunner, bakeSkillCommand, blankText, defaultRunner, ownVersion, removeIfEmpty,
|
|
8
|
+
removeInstalledTree, runnerExists, shellQuote, stampPluginVersion, windowsHookCommand,
|
|
9
|
+
writeCliShim, writeForeignJson }
|
|
10
10
|
from "@agents-can-communicate/adapter-sdk";
|
|
11
11
|
|
|
12
12
|
const bundle = fileURLToPath(new URL("../plugin", import.meta.url));
|
|
@@ -99,13 +99,17 @@ export function stripBlock(source) {
|
|
|
99
99
|
return kept.join("\n");
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
export function renderBlock(runner, node = process.execPath) {
|
|
102
|
+
export function renderBlock(runner, node = process.execPath, platform = process.platform) {
|
|
103
103
|
const entries = WIRING.map(({ event, kind, matcher }) => {
|
|
104
104
|
// Both paths are shell-quoted inside the TOML string: a hook runs through a
|
|
105
105
|
// shell, and a space in either path would otherwise split the command.
|
|
106
106
|
// The interpreter is named outright rather than left to PATH, which a hook's
|
|
107
|
-
// environment does not reliably carry.
|
|
108
|
-
|
|
107
|
+
// environment does not reliably carry. On Windows that shell is cmd.exe, and
|
|
108
|
+
// a TOML-escaped path inside the command would reach it with every
|
|
109
|
+
// backslash doubled.
|
|
110
|
+
const command = platform === "win32"
|
|
111
|
+
? windowsHookCommand("cmd", { node, shim: runner, args: ["kimi", kind] })
|
|
112
|
+
: `${shellQuote(node)} ${shellQuote(runner)} kimi ${kind}`;
|
|
109
113
|
const lines = ["[[hooks]]", `event = ${tomlString(event)}`,
|
|
110
114
|
`command = ${tomlString(command)}`];
|
|
111
115
|
if (matcher !== undefined) lines.push(`matcher = ${tomlString(matcher)}`);
|
|
@@ -121,7 +125,8 @@ const registerPlugin = (registry, root) => {
|
|
|
121
125
|
plugins: [...plugins, { id: PLUGIN_NAME, root, source: "local", enabled: true }] };
|
|
122
126
|
};
|
|
123
127
|
|
|
124
|
-
export async function installKimiPlugin({ home, runner = defaultRunner(), node, cli
|
|
128
|
+
export async function installKimiPlugin({ home, runner = defaultRunner(), node, cli,
|
|
129
|
+
hostPlatform = process.platform }) {
|
|
125
130
|
// A hook whose command does not exist fails silently, on every event, for as
|
|
126
131
|
// long as it stays installed: the client reports nothing and ACC simply never
|
|
127
132
|
// sees a session. Writing that entry and hoping is worse than refusing.
|
|
@@ -139,8 +144,8 @@ export async function installKimiPlugin({ home, runner = defaultRunner(), node,
|
|
|
139
144
|
version: await ownVersion(import.meta.url), io: { readFile, writeFile } });
|
|
140
145
|
// The skill ships with a placeholder where the command belongs: `acc` is
|
|
141
146
|
// not on PATH everywhere, and an agent that cannot run it improvises.
|
|
142
|
-
const cliShim = await writeCliShim({ dir: target, cli, node });
|
|
143
|
-
await bakeSkillCommand({ root: target, cliShim });
|
|
147
|
+
const cliShim = await writeCliShim({ dir: target, cli, node, platform: hostPlatform });
|
|
148
|
+
await bakeSkillCommand({ root: target, cliShim, platform: hostPlatform });
|
|
144
149
|
|
|
145
150
|
const file = configPath(home);
|
|
146
151
|
const existing = await readText(file, "");
|
|
@@ -149,7 +154,7 @@ export async function installKimiPlugin({ home, runner = defaultRunner(), node,
|
|
|
149
154
|
// user's last section cannot swallow our entries.
|
|
150
155
|
const separator = withoutOurs === "" || withoutOurs.endsWith("\n") ? "" : "\n";
|
|
151
156
|
await mkdir(path.dirname(file), { recursive: true });
|
|
152
|
-
await writeFile(file, `${withoutOurs}${separator}${renderBlock(runner, node)}\n`);
|
|
157
|
+
await writeFile(file, `${withoutOurs}${separator}${renderBlock(runner, node, hostPlatform)}\n`);
|
|
153
158
|
|
|
154
159
|
const registry = registryPath(home);
|
|
155
160
|
await mkdir(path.dirname(registry), { recursive: true });
|