agents-can-communicate 0.11.0 → 0.11.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.
- package/bin/entrypoints/acc-antigravity-relay.mjs +2 -2
- package/bin/entrypoints/acc-hook.mjs +4 -24
- package/bin/entrypoints/acc-mcp.mjs +8 -6
- package/bin/entrypoints/acc.mjs +22 -11
- package/bin/entrypoints/antigravity-relay-binding.mjs +1 -1
- package/docs/STATUS_INDICATOR.md +8 -3
- package/docs/TROUBLESHOOTING.md +2 -2
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/hooks/indicator.mjs +29 -13
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt-writer.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/process-table.mjs +40 -27
- package/node_modules/@agents-can-communicate/cli/package.json +7 -1
- package/node_modules/@agents-can-communicate/cli/src/adapter-catalog.mjs +33 -0
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +2 -3
- package/node_modules/@agents-can-communicate/cli/src/indicator-command.mjs +2 -2
- package/node_modules/@agents-can-communicate/cli/src/indicator.mjs +4 -4
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +38 -60
- package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/recorded-text.mjs +32 -0
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/next-turn-delivery.mjs +2 -1
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +5 -2
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +2 -1
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +5 -2
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-pid.mjs +23 -11
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +66 -26
- package/node_modules/@agents-can-communicate/installer/package.json +3 -2
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +3 -2
- package/node_modules/@agents-can-communicate/storage-filesystem/src/state-reads.mjs +24 -7
- package/package.json +1 -1
|
@@ -11,11 +11,11 @@ import { createRelay } from "@agents-can-communicate/adapter-antigravity/relay";
|
|
|
11
11
|
import { listRegistrations, newRelayId, relayDir } from "@agents-can-communicate/adapter-antigravity/relay-endpoint";
|
|
12
12
|
import { findConversation, spawnRelay, startRelay } from "@agents-can-communicate/adapter-antigravity/relay-start";
|
|
13
13
|
import { loadSessionBinding, readProcessArgs } from "@agents-can-communicate/adapter-sdk";
|
|
14
|
-
import { platformDataHome } from "@agents-can-communicate/cli";
|
|
14
|
+
import { platformDataHome } from "@agents-can-communicate/cli/runtime-paths";
|
|
15
15
|
import { createCoordinationService } from "@agents-can-communicate/core";
|
|
16
16
|
import { resolveClientPid } from "@agents-can-communicate/hook-runner/client-pid";
|
|
17
17
|
import { readProcessTable } from "@agents-can-communicate/hook-runner/process-table";
|
|
18
|
-
import { readInstalledLivePolicy } from "@agents-can-communicate/installer";
|
|
18
|
+
import { readInstalledLivePolicy } from "@agents-can-communicate/installer/live-policy";
|
|
19
19
|
import { createId } from "@agents-can-communicate/protocol";
|
|
20
20
|
import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
|
|
21
21
|
|
|
@@ -12,33 +12,13 @@ import { createId } from "@agents-can-communicate/protocol";
|
|
|
12
12
|
import { runHook } from "@agents-can-communicate/hook-runner";
|
|
13
13
|
import { hookEntrypointFor, resolvePinnedGeneration }
|
|
14
14
|
from "@agents-can-communicate/cli/hook-support";
|
|
15
|
+
import { loadAdapters } from "@agents-can-communicate/cli/adapters";
|
|
15
16
|
|
|
16
17
|
// A hook runs one client's adapter, and loads only that one: every other
|
|
17
18
|
// adapter was a set of modules loaded on each turn and never run, about a
|
|
18
|
-
// millisecond each on windows-latest.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// argument - the event name - because this client's payload has none.
|
|
22
|
-
antigravity: () => import("@agents-can-communicate/adapter-antigravity")
|
|
23
|
-
.then(module => module.createAntigravityAdapter()),
|
|
24
|
-
claude_code: () => import("@agents-can-communicate/adapter-claude-code")
|
|
25
|
-
.then(module => module.createClaudeCodeAdapter()),
|
|
26
|
-
codex: () => import("@agents-can-communicate/adapter-codex")
|
|
27
|
-
.then(module => module.createCodexAdapter()),
|
|
28
|
-
gemini_cli: () => import("@agents-can-communicate/adapter-gemini-cli")
|
|
29
|
-
.then(module => module.createGeminiCliAdapter()),
|
|
30
|
-
grok: () => import("@agents-can-communicate/adapter-grok")
|
|
31
|
-
.then(module => module.createGrokAdapter()),
|
|
32
|
-
kimi: () => import("@agents-can-communicate/adapter-kimi")
|
|
33
|
-
.then(module => module.createKimiAdapter()),
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// The adapters this hook can use: its own, or none for an id nobody registered,
|
|
37
|
-
// which runHook answers the way it always has.
|
|
38
|
-
async function adaptersFor(adapterId) {
|
|
39
|
-
if (!Object.hasOwn(ADAPTERS, adapterId)) return {};
|
|
40
|
-
return { [adapterId]: await ADAPTERS[adapterId]() };
|
|
41
|
-
}
|
|
19
|
+
// millisecond each on windows-latest. An id nobody registered gets none, which
|
|
20
|
+
// runHook answers the way it always has.
|
|
21
|
+
const adaptersFor = adapterId => loadAdapters([adapterId]);
|
|
42
22
|
|
|
43
23
|
const readStdin = () => new Promise(resolve => {
|
|
44
24
|
// A hook is always given its payload on stdin, but a client that closes it
|
|
@@ -3,12 +3,14 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
|
|
4
4
|
import { EXIT, createId } from "@agents-can-communicate/protocol";
|
|
5
5
|
import { createCoordinationService } from "@agents-can-communicate/core";
|
|
6
|
-
import { readInstalledLivePolicy } from "@agents-can-communicate/installer";
|
|
6
|
+
import { readInstalledLivePolicy } from "@agents-can-communicate/installer/live-policy";
|
|
7
7
|
import { createDeliveryRouter } from "@agents-can-communicate/delivery-router";
|
|
8
8
|
import { registerDiscoveredSessions } from "@agents-can-communicate/hook-runner";
|
|
9
9
|
import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import { loadAdapter, loadAdapters } from "@agents-can-communicate/cli/adapters";
|
|
11
|
+
import { createGitProbe } from "@agents-can-communicate/cli/git-probe";
|
|
12
|
+
import { platformDataHome, runtimePaths } from "@agents-can-communicate/cli/runtime-paths";
|
|
13
|
+
import { discoverWorkspace } from "@agents-can-communicate/cli/workspace-discovery";
|
|
12
14
|
import { serve } from "@agents-can-communicate/mcp-server";
|
|
13
15
|
|
|
14
16
|
export async function main({ managerRoot = null, packageRoot } = {}) {
|
|
@@ -46,7 +48,6 @@ const paths = runtimePaths({
|
|
|
46
48
|
const store = await openFilesystemStore({ root: paths.root, clock, ids,
|
|
47
49
|
workspaceId: descriptor.id });
|
|
48
50
|
const service = createCoordinationService({ store, clock, ids });
|
|
49
|
-
const adapters = Object.fromEntries(ALL_ADAPTERS().map(adapter => [adapter.id, adapter]));
|
|
50
51
|
|
|
51
52
|
await serve({
|
|
52
53
|
input: process.stdin,
|
|
@@ -54,14 +55,15 @@ await serve({
|
|
|
54
55
|
log: message => process.stderr.write(`acc-mcp: ${message}\n`),
|
|
55
56
|
context: {
|
|
56
57
|
service,
|
|
57
|
-
|
|
58
|
+
// Each recipient's adapter loads with the first message routed to it (#270).
|
|
59
|
+
deliveryRouter: createDeliveryRouter({ service, loadAdapter, clock,
|
|
58
60
|
readLivePolicy: ({ adapter }) => readInstalledLivePolicy({ dataHome,
|
|
59
61
|
adapterId: adapter.id }) }),
|
|
60
62
|
workspaceId: descriptor.id,
|
|
61
63
|
participantId,
|
|
62
64
|
descriptor,
|
|
63
65
|
runtimeDir: paths.root,
|
|
64
|
-
registerNativeSessions: () => registerDiscoveredSessions({
|
|
66
|
+
registerNativeSessions: () => registerDiscoveredSessions({ loadAdapters,
|
|
65
67
|
workspaceId: descriptor.id, dataHome, env: process.env }),
|
|
66
68
|
},
|
|
67
69
|
});
|
package/bin/entrypoints/acc.mjs
CHANGED
|
@@ -3,13 +3,26 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
|
|
5
5
|
import { createId } from "@agents-can-communicate/protocol";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
6
|
+
import { loadAdapter, loadAdapters } from "@agents-can-communicate/cli/adapters";
|
|
7
|
+
import { askConfirmation } from "@agents-can-communicate/cli/confirm";
|
|
8
|
+
import { main as runCli } from "@agents-can-communicate/cli/main";
|
|
9
|
+
|
|
10
|
+
// A command loads what it uses: `acc version` no client adapter, store or
|
|
11
|
+
// router, and a message only its recipients' adapters (#270).
|
|
12
|
+
const lazyRouter = ({ service, clock, dataHome }) => {
|
|
13
|
+
let router;
|
|
14
|
+
return { offer: async message => {
|
|
15
|
+
router ??= Promise.all([import("@agents-can-communicate/delivery-router"),
|
|
16
|
+
import("@agents-can-communicate/installer/live-policy")])
|
|
17
|
+
.then(([{ createDeliveryRouter }, { readInstalledLivePolicy }]) =>
|
|
18
|
+
createDeliveryRouter({ service, loadAdapter, clock,
|
|
19
|
+
readLivePolicy: ({ adapter }) => readInstalledLivePolicy({ dataHome,
|
|
20
|
+
adapterId: adapter.id }) }));
|
|
21
|
+
return (await router).offer(message);
|
|
22
|
+
} };
|
|
23
|
+
};
|
|
10
24
|
|
|
11
25
|
export async function main({ managerRoot = null, packageRoot, managementOnly = false } = {}) {
|
|
12
|
-
const adapters = Object.fromEntries(ALL_ADAPTERS().map(adapter => [adapter.id, adapter]));
|
|
13
26
|
|
|
14
27
|
// The composition root is the only place allowed to reach for ambient time and
|
|
15
28
|
// randomness; everything below it receives them as ports.
|
|
@@ -24,14 +37,12 @@ const runtime = {
|
|
|
24
37
|
stderr: process.stderr,
|
|
25
38
|
clock: { now: () => new Date().toISOString() },
|
|
26
39
|
ids: { next: kind => createId(kind, randomBytes) },
|
|
27
|
-
createDeliveryRouter:
|
|
28
|
-
createDeliveryRouter({ service, adapters, clock,
|
|
29
|
-
readLivePolicy: ({ adapter }) => readInstalledLivePolicy({ dataHome,
|
|
30
|
-
adapterId: adapter.id }) }),
|
|
40
|
+
createDeliveryRouter: lazyRouter,
|
|
31
41
|
// Chats a client holds before their first turn, registered before status
|
|
32
42
|
// is read so a peer can address them (#167).
|
|
33
|
-
registerNativeSessions: ({ workspaceId, dataHome }) =>
|
|
34
|
-
registerDiscoveredSessions({
|
|
43
|
+
registerNativeSessions: async ({ workspaceId, dataHome }) =>
|
|
44
|
+
(await import("@agents-can-communicate/hook-runner")).registerDiscoveredSessions({
|
|
45
|
+
loadAdapters, workspaceId, dataHome, env: process.env }),
|
|
35
46
|
// Asked only by `acc config init`, and only when stdout is a terminal. There
|
|
36
47
|
// was no port here at all, so the question went to the fallback that always
|
|
37
48
|
// answers no: in a real terminal the command printed "not written" and never
|
|
@@ -3,7 +3,7 @@ import { loadSessionBinding, storeNativeAttempt } from "@agents-can-communicate/
|
|
|
3
3
|
import { createCoordinationService } from "@agents-can-communicate/core";
|
|
4
4
|
import { establishNativeBinding } from "@agents-can-communicate/hook-runner/native-binding";
|
|
5
5
|
import { withSessionLifecycle } from "@agents-can-communicate/hook-runner/session-lifecycle";
|
|
6
|
-
import { readInstalledLivePolicyState } from "@agents-can-communicate/installer";
|
|
6
|
+
import { readInstalledLivePolicyState } from "@agents-can-communicate/installer/live-policy";
|
|
7
7
|
import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
|
|
8
8
|
|
|
9
9
|
// No hook runs when the agent starts a relay mid-turn, and the next
|
package/docs/STATUS_INDICATOR.md
CHANGED
|
@@ -17,7 +17,7 @@ stdout is a pipe. Existing installed commands need no new arguments. `--json` an
|
|
|
17
17
|
| `ACC ●` | ACC is active. This chat has an automatic delivery binding with no newer recorded failure. A busy client can queue a request. |
|
|
18
18
|
| `ACC ● · turn` | ACC is active. Messages arrive on the next normal turn. |
|
|
19
19
|
| `ACC ● · inbox` | ACC is active. The agent reads messages through `acc inbox`. This does not imply a polling schedule. |
|
|
20
|
-
| `ACC …` | The Claude indicator is reading state or waiting for startup hooks
|
|
20
|
+
| `ACC …` | The Claude indicator is reading state or waiting up to five seconds for startup hooks. Failed reads keep it until a read answers or three reads in a row fail. |
|
|
21
21
|
| `ACC ! · turn` or `ACC ! · inbox` | ACC observed a failure. The qualifier names the remaining reception path. |
|
|
22
22
|
| `ACC !` | The chat is not registered, or the indicator cannot read its state. |
|
|
23
23
|
|
|
@@ -60,7 +60,10 @@ indicator and keeps status-line settings that the user changed.
|
|
|
60
60
|
|
|
61
61
|
Claude prints one diagnostic when a new problem appears. `/acc-status` shows the current
|
|
62
62
|
explanation and recovery step without a model request. Healthy refreshes do not print
|
|
63
|
-
messages.
|
|
63
|
+
messages. A read that fails or does not answer describes the indicator, not the chat.
|
|
64
|
+
The badge keeps the last observed state until three reads in a row fail; only then
|
|
65
|
+
does it show `!` and print the diagnostic. A slow read says so and does not suggest
|
|
66
|
+
reinstalling, because a reinstall cannot make a busy machine answer sooner. Antigravity's command surface has no equivalent notification: only a problem
|
|
64
67
|
adds `acc doctor` to the compact line. Run that command in the project for diagnostics.
|
|
65
68
|
|
|
66
69
|
The Antigravity wrapper runs an existing enabled command with a 700 ms limit and a 64 KiB
|
|
@@ -125,4 +128,6 @@ and immutable history remain authoritative. An unavailable diagnostic cannot fai
|
|
|
125
128
|
|
|
126
129
|
An unreadable state produces a closed error and a recovery command. It never renders raw
|
|
127
130
|
file content. A command stops waiting after 1.5 seconds. Claude refreshes every five seconds,
|
|
128
|
-
with one-second checks during its bounded startup period.
|
|
131
|
+
with one-second checks during its bounded startup period. Claude allows each read five
|
|
132
|
+
seconds: on a loaded machine the command's own start took up to 1.3 seconds inside
|
|
133
|
+
Claude, before its 1.5-second wait begins.
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -212,8 +212,8 @@ per-session lines in `acc doctor`, or `nativeDelivery.sessions[].lastAttempt` in
|
|
|
212
212
|
|
|
213
213
|
- An absent or `off` policy means the installation record has no live-delivery consent
|
|
214
214
|
for that client. Run `acc install --adapter <adapter> --delivery actionable` (or `all`).
|
|
215
|
-
- `client_process_unknown` means ACC could not identify the client process
|
|
216
|
-
|
|
215
|
+
- `client_process_unknown` means ACC could not identify the client process, for example
|
|
216
|
+
when a loaded machine slowed the lookup at session start. Each later prompt repeats it.
|
|
217
217
|
- For Claude Code, `native_endpoint_unavailable` means the hook environment had no
|
|
218
218
|
`CLAUDE_CODE_MESSAGING_SOCKET`, or the socket failed ACC's safety checks.
|
|
219
219
|
`native_session_unavailable` means the session registry is missing or names another
|
|
@@ -2,41 +2,57 @@
|
|
|
2
2
|
// transcript access, model turns, ACC writes or transport probes are needed.
|
|
3
3
|
const NODE = __ACC_NODE__;
|
|
4
4
|
const INDICATOR = __ACC_INDICATOR__;
|
|
5
|
+
// The reader answers at the latest 1.5 s after its modules load. Inside a loaded
|
|
6
|
+
// Claude the child's start alone took up to 1.3 s, so a shorter bound killed
|
|
7
|
+
// reads that would have answered and printed a false problem.
|
|
8
|
+
const READ_TIMEOUT_MS = 5000;
|
|
9
|
+
// A read that did not happen says nothing about this chat. Only a run of them
|
|
10
|
+
// replaces the last observed state.
|
|
11
|
+
const READ_FAILURES = ["indicator_unavailable", "state_unreadable"];
|
|
12
|
+
const FAILED_READS_SHOWN = 3;
|
|
5
13
|
|
|
6
14
|
let report = { label: "ACC …", health: "starting", detail: "Reading ACC state." };
|
|
7
|
-
let busy = false, sessionId = null, startedAt = 0, lastRead = 0, announced = null;
|
|
15
|
+
let busy = false, sessionId = null, startedAt = 0, lastRead = 0, announced = null, failedReads = 0;
|
|
16
|
+
|
|
17
|
+
const failedRead = slow => ({ label: "ACC !", health: "problem", reasonCode: "indicator_unavailable",
|
|
18
|
+
...slow ? {
|
|
19
|
+
detail: "The ACC indicator did not answer within five seconds.",
|
|
20
|
+
action: "Run /acc-status to read it again; a busy machine can delay the answer.",
|
|
21
|
+
} : {
|
|
22
|
+
detail: "The ACC indicator could not read this session's state.",
|
|
23
|
+
action: "Run acc install --adapter claude_code --indicator on, then /reload-plugins.",
|
|
24
|
+
} });
|
|
8
25
|
|
|
9
26
|
async function refresh($) {
|
|
10
27
|
if (busy) return;
|
|
11
28
|
const id = await $.session.id();
|
|
12
29
|
const now = await $.clock.now();
|
|
13
30
|
if (id !== sessionId) {
|
|
14
|
-
sessionId = id; startedAt = now; lastRead = 0; announced = null;
|
|
31
|
+
sessionId = id; startedAt = now; lastRead = 0; announced = null; failedReads = 0;
|
|
15
32
|
report = { label: "ACC …", health: "starting", detail: "Reading ACC state." };
|
|
16
33
|
}
|
|
17
34
|
if (now - lastRead < (now - startedAt < 5000 ? 1000 : 5000)) return;
|
|
18
35
|
lastRead = now; busy = true;
|
|
36
|
+
let next;
|
|
19
37
|
try {
|
|
20
38
|
const value = await $.process.run([NODE, INDICATOR, "--adapter", "claude_code",
|
|
21
|
-
"--native-session", id, "--json"], { timeoutMs:
|
|
22
|
-
|
|
23
|
-
if (await $.session.id() !== id) return;
|
|
24
|
-
if (next.reasonCode === "not_registered" && now - startedAt < 5000) {
|
|
25
|
-
report = { label: "ACC …", health: "starting",
|
|
26
|
-
detail: "Waiting up to five seconds for the session hooks to register ACC." };
|
|
27
|
-
} else report = next;
|
|
39
|
+
"--native-session", id, "--json"], { timeoutMs: READ_TIMEOUT_MS });
|
|
40
|
+
next = JSON.parse(value.stdout);
|
|
28
41
|
} catch {
|
|
29
|
-
|
|
30
|
-
detail: "The ACC indicator could not read this session's state.",
|
|
31
|
-
action: "Run acc install --adapter claude_code --indicator on, then /reload-plugins." };
|
|
42
|
+
next = failedRead(await $.clock.now() - now >= READ_TIMEOUT_MS);
|
|
32
43
|
} finally { busy = false; }
|
|
44
|
+
if (await $.session.id() !== id) return;
|
|
45
|
+
failedReads = READ_FAILURES.includes(next.reasonCode) ? failedReads + 1 : 0;
|
|
46
|
+
if (next.reasonCode === "not_registered" && now - startedAt < 5000) {
|
|
47
|
+
report = { label: "ACC …", health: "starting",
|
|
48
|
+
detail: "Waiting up to five seconds for the session hooks to register ACC." };
|
|
49
|
+
} else if (failedReads === 0 || failedReads >= FAILED_READS_SHOWN) report = next;
|
|
33
50
|
if (report.health === "problem" && report.reasonCode !== announced) {
|
|
34
51
|
await $.ui.log([report.detail, report.action, "Details: /acc-status"].filter(Boolean).join(" "));
|
|
35
52
|
announced = report.reasonCode;
|
|
36
53
|
} else if (report.health === "ready") announced = null;
|
|
37
54
|
$.ui.invalidate("ui.render");
|
|
38
55
|
}
|
|
39
|
-
|
|
40
56
|
export function register(on) {
|
|
41
57
|
on("session.start", async ($, e, next) => {
|
|
42
58
|
$.clock.every(1000, async () => { await refresh($); });
|
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { parentPort, workerData } from "node:worker_threads";
|
|
5
5
|
|
|
6
6
|
import { assertPortableId } from "@agents-can-communicate/protocol";
|
|
7
|
-
import { withWriterMutex } from "@agents-can-communicate/storage-filesystem";
|
|
7
|
+
import { withWriterMutex } from "@agents-can-communicate/storage-filesystem/writer-mutex";
|
|
8
8
|
import { loadNativeAttempt, nativeAttemptFrom } from "./native-attempt.mjs";
|
|
9
9
|
|
|
10
10
|
const keyFor = harnessSessionId => createHash("sha256").update(String(harnessSessionId))
|
|
@@ -12,33 +12,45 @@ const DEFAULT_TIMEOUT_MS = 1_000;
|
|
|
12
12
|
const LINE = /^\s*(\d+)\s+(\d+)\s+(.+?)\s*$/;
|
|
13
13
|
// A pid, then the whole rest of the line: a command line has spaces anywhere.
|
|
14
14
|
const ARGS_LINE = /^\s*(\d+)\s+(.+?)\s*$/;
|
|
15
|
-
//
|
|
16
|
-
//
|
|
15
|
+
// One command line can reach ARG_MAX, 1 MiB on macOS and execFile's default
|
|
16
|
+
// buffer; Windows reads several in one answer.
|
|
17
17
|
const ARGS_BUFFER = 16 * 1024 * 1024;
|
|
18
18
|
|
|
19
19
|
// Deep enough for a client that wraps its hook in a shell and a launcher.
|
|
20
20
|
const WINDOWS_HOPS = 8;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
22
|
+
// Deep enough for a client that wraps its hook in a shell and a launcher.
|
|
23
|
+
const POSIX_HOPS = 8;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The chain from `from` upward, one `ps -p` per process. Reading every
|
|
27
|
+
* process's command line took 830-1430 ms at load average 69 on a Mac with
|
|
28
|
+
* 1,900 processes (2026-10-12), past the hook's limit; one process took 7 ms.
|
|
29
|
+
* The limit bounds the whole walk. `until` ends it at the process the caller
|
|
30
|
+
* is looking for, so nothing above the client is read.
|
|
31
|
+
*/
|
|
32
|
+
async function posixChain({ from, run, timeoutMs, until }) {
|
|
33
|
+
const deadline = Date.now() + timeoutMs;
|
|
35
34
|
const table = new Map();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
let next = Number(from);
|
|
36
|
+
for (let hop = 0; hop < POSIX_HOPS && next > 1 && !table.has(next); hop += 1) {
|
|
37
|
+
const timeout = deadline - Date.now();
|
|
38
|
+
if (timeout <= 0) break;
|
|
39
|
+
const options = { timeout, killSignal: "SIGKILL", windowsHide: true };
|
|
40
|
+
const pid = String(next);
|
|
41
|
+
const [tree, commandLine] = await Promise.all([
|
|
42
|
+
run("ps", ["-o", "pid=,ppid=,comm=", "-p", pid], options).then(({ stdout }) => String(stdout), () => ""),
|
|
43
|
+
run("ps", ["-o", "pid=,args=", "-p", pid], { ...options, maxBuffer: ARGS_BUFFER })
|
|
44
|
+
.then(({ stdout }) => String(stdout), () => ""),
|
|
45
|
+
]);
|
|
46
|
+
const match = LINE.exec(tree.trim());
|
|
47
|
+
if (match === null || Number(match[1]) !== next) break;
|
|
48
|
+
const args = ARGS_LINE.exec(commandLine.trim());
|
|
49
|
+
const entry = { ppid: Number(match[2]), comm: match[3],
|
|
50
|
+
...(args !== null && Number(args[1]) === next ? { args: args[2] } : {}) };
|
|
51
|
+
table.set(next, entry);
|
|
52
|
+
if (typeof until === "function" && until(entry)) break;
|
|
53
|
+
next = entry.ppid;
|
|
42
54
|
}
|
|
43
55
|
return table;
|
|
44
56
|
}
|
|
@@ -154,15 +166,16 @@ async function windowsTable({ from, run, timeoutMs, env }) {
|
|
|
154
166
|
* Processes on this machine, as pid -> parent, executable and, when it could be
|
|
155
167
|
* read, command line.
|
|
156
168
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
169
|
+
* Both platforms read the chain from `from` upward - the only part a hook asks
|
|
170
|
+
* about. POSIX asks `ps` one process at a time and stops early where `until`
|
|
171
|
+
* says; Windows reads up to eight hops in one query, with a creation time for
|
|
172
|
+
* each. A failed read is an empty map, never an error: an empty table resolves
|
|
173
|
+
* no client, which is the "nobody knows" answer every caller already handles.
|
|
161
174
|
*/
|
|
162
175
|
export async function readProcessTable({ platform = process.platform, from = process.pid,
|
|
163
|
-
run = execFileAsync, timeoutMs = DEFAULT_TIMEOUT_MS, env = process.env } = {}) {
|
|
176
|
+
run = execFileAsync, timeoutMs = DEFAULT_TIMEOUT_MS, env = process.env, until } = {}) {
|
|
164
177
|
if (platform === "win32") return windowsTable({ from, run, timeoutMs, env });
|
|
165
|
-
return
|
|
178
|
+
return posixChain({ from, run, timeoutMs, until });
|
|
166
179
|
}
|
|
167
180
|
|
|
168
181
|
/**
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agents-can-communicate/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.mjs",
|
|
8
|
+
"./adapters": "./src/adapter-catalog.mjs",
|
|
9
|
+
"./confirm": "./src/confirm.mjs",
|
|
10
|
+
"./git-probe": "./src/git-probe.mjs",
|
|
11
|
+
"./main": "./src/main.mjs",
|
|
12
|
+
"./runtime-paths": "./src/runtime-paths.mjs",
|
|
13
|
+
"./workspace-discovery": "./src/workspace-discovery.mjs",
|
|
8
14
|
"./hook-support": "./src/hook-support.mjs",
|
|
9
15
|
"./indicator": "./src/indicator-command.mjs",
|
|
10
16
|
"./managed-entry": "./src/managed-runtime/entry.mjs",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Every client adapter by id, loaded only by the command or hook that serves
|
|
2
|
+
// it. Importing all six cost every `acc version`, indicator read and hook their
|
|
3
|
+
// module graphs (#270), so this module imports none of them until asked.
|
|
4
|
+
const LOADERS = Object.freeze({
|
|
5
|
+
// Registered like any other client. Its hook commands carry an extra
|
|
6
|
+
// argument - the event name - because this client's payload has none.
|
|
7
|
+
antigravity: () => import("@agents-can-communicate/adapter-antigravity")
|
|
8
|
+
.then(module => module.createAntigravityAdapter()),
|
|
9
|
+
claude_code: () => import("@agents-can-communicate/adapter-claude-code")
|
|
10
|
+
.then(module => module.createClaudeCodeAdapter()),
|
|
11
|
+
codex: () => import("@agents-can-communicate/adapter-codex")
|
|
12
|
+
.then(module => module.createCodexAdapter()),
|
|
13
|
+
gemini_cli: () => import("@agents-can-communicate/adapter-gemini-cli")
|
|
14
|
+
.then(module => module.createGeminiCliAdapter()),
|
|
15
|
+
grok: () => import("@agents-can-communicate/adapter-grok")
|
|
16
|
+
.then(module => module.createGrokAdapter()),
|
|
17
|
+
kimi: () => import("@agents-can-communicate/adapter-kimi")
|
|
18
|
+
.then(module => module.createKimiAdapter()),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const ADAPTER_IDS = Object.freeze(Object.keys(LOADERS));
|
|
22
|
+
|
|
23
|
+
/** The adapter for one id, or null for an id no adapter has. */
|
|
24
|
+
export async function loadAdapter(adapterId) {
|
|
25
|
+
return Object.hasOwn(LOADERS, adapterId) ? LOADERS[adapterId]() : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The adapters for these ids, keyed by id; unknown ids are left out. */
|
|
29
|
+
export async function loadAdapters(adapterIds) {
|
|
30
|
+
const known = [...new Set(adapterIds)].filter(adapterId => Object.hasOwn(LOADERS, adapterId));
|
|
31
|
+
return Object.fromEntries(await Promise.all(known.map(async adapterId =>
|
|
32
|
+
[adapterId, await LOADERS[adapterId]()])));
|
|
33
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { CLAIM_ENFORCEMENTS, CLAIM_MODES, GENERIC_MESSAGE_KINDS, HANDOFF_STATUSES,
|
|
2
2
|
INTENT_MODES, INTENT_STATES, MESSAGE_KINDS, OBLIGATIONS } from "@agents-can-communicate/protocol";
|
|
3
|
-
import { LIVE_POLICIES } from "@agents-can-communicate/installer";
|
|
3
|
+
import { LIVE_POLICIES } from "@agents-can-communicate/installer/live-policy";
|
|
4
4
|
|
|
5
5
|
import { COMMANDS, commandSpec } from "./args.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { ADAPTER_IDS } from "./adapter-catalog.mjs";
|
|
7
7
|
import { needsOwner } from "./session-owner.mjs";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -65,7 +65,6 @@ export function describeCommands() {
|
|
|
65
65
|
const DOCS = "https://github.com/automatis-tools/agents-can-communicate"
|
|
66
66
|
+ "/blob/main/docs/CLI.md";
|
|
67
67
|
|
|
68
|
-
const ADAPTER_IDS = Object.freeze(ALL_ADAPTERS().map(adapter => adapter.id));
|
|
69
68
|
const CHOICES = Object.freeze({
|
|
70
69
|
work: { mode: INTENT_MODES, state: INTENT_STATES },
|
|
71
70
|
claim: { mode: CLAIM_MODES, enforcement: CLAIM_ENFORCEMENTS },
|
|
@@ -2,7 +2,7 @@ import { readTomlIndicatorSettings } from "@agents-can-communicate/adapter-sdk";
|
|
|
2
2
|
import { previousOutput } from "./indicator-previous-command.mjs";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { readIndicator, indicatorFailure } from "./indicator.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { loadAdapter } from "./adapter-catalog.mjs";
|
|
6
6
|
|
|
7
7
|
function output(report, options, context) {
|
|
8
8
|
if (options.json) return JSON.stringify(report);
|
|
@@ -40,7 +40,7 @@ export async function runIndicator({ argv = process.argv.slice(2), stdin = proce
|
|
|
40
40
|
&& typeof argv[i + 1] === "string") options[flag.slice(2)] = argv[++i];
|
|
41
41
|
else throw new Error("invalid indicator option");
|
|
42
42
|
}
|
|
43
|
-
context.extension =
|
|
43
|
+
context.extension = (await loadAdapter(options.adapter))?.statusIndicator;
|
|
44
44
|
const plain = options.json || options.details;
|
|
45
45
|
timeout = setTimeout(() => {
|
|
46
46
|
expired = true;
|
|
@@ -4,8 +4,8 @@ import { assertPortableId } from "@agents-can-communicate/protocol";
|
|
|
4
4
|
import { effectiveCapabilities, loadNativeAttempt, loadSessionBinding, loadOfferObservation }
|
|
5
5
|
from "@agents-can-communicate/adapter-sdk";
|
|
6
6
|
import { readDeliveryBindingRecord, readSessionRecord } from "@agents-can-communicate/storage-filesystem";
|
|
7
|
-
import { readInstalledLivePolicyState } from "@agents-can-communicate/installer";
|
|
8
|
-
import {
|
|
7
|
+
import { readInstalledLivePolicyState } from "@agents-can-communicate/installer/live-policy";
|
|
8
|
+
import { loadAdapter } from "./adapter-catalog.mjs";
|
|
9
9
|
import { canonicalManagerRoot, managedDirectory, readManagedJson } from "./managed-runtime/state.mjs";
|
|
10
10
|
import { platformDataHome, runtimePaths } from "./runtime-paths.mjs";
|
|
11
11
|
|
|
@@ -34,7 +34,7 @@ export const indicatorFailure = (reasonCode = "state_unreadable") => result({
|
|
|
34
34
|
|
|
35
35
|
const actions = {
|
|
36
36
|
client_session_embedded: "This session has no shared delivery service. Run acc doctor to see the launch option and how to open a supported session.",
|
|
37
|
-
client_process_unknown: "
|
|
37
|
+
client_process_unknown: "Send a prompt: each prompt looks for the client process again. If this stays, run acc doctor.",
|
|
38
38
|
transport_permission_denied: "Allow the sending command to access the local delivery channel; acc doctor shows the affected session.",
|
|
39
39
|
handshake_version_mismatch: "Update the client's delivery service to a supported version; acc doctor shows the version in use.",
|
|
40
40
|
native_policy_off: "Automatic delivery is disabled in ACC settings. Run acc install --delivery actionable to enable it.",
|
|
@@ -47,7 +47,7 @@ const actions = {
|
|
|
47
47
|
export async function readIndicator({ adapterId, nativeSessionId,
|
|
48
48
|
dataHome = platformDataHome(), now = new Date().toISOString() }) {
|
|
49
49
|
try {
|
|
50
|
-
const adapter =
|
|
50
|
+
const adapter = await loadAdapter(adapterId);
|
|
51
51
|
if (!adapter || typeof nativeSessionId !== "string" || !nativeSessionId
|
|
52
52
|
|| nativeSessionId.length > 4096) return indicatorFailure("not_registered");
|
|
53
53
|
const key = createHash("sha256").update(JSON.stringify([adapterId, nativeSessionId])).digest("hex");
|