agents-can-communicate 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -7
- package/SECURITY.md +1 -1
- package/bin/acc-bootstrap.mjs +5 -54
- package/bin/acc-claude-channel.mjs +8 -175
- package/bin/acc-hook.mjs +4 -133
- package/bin/acc-mcp.mjs +4 -59
- package/bin/acc-update-worker.mjs +8 -0
- package/bin/acc.mjs +5 -39
- package/bin/entrypoints/acc-bootstrap.mjs +47 -0
- package/bin/entrypoints/acc-claude-channel.mjs +149 -0
- package/bin/entrypoints/acc-hook.mjs +58 -0
- package/bin/entrypoints/acc-mcp.mjs +70 -0
- package/bin/entrypoints/acc.mjs +51 -0
- package/bin/entrypoints/claude-channel-stdio.mjs +29 -0
- package/bin/entrypoints/hook-output.mjs +73 -0
- package/docs/ADAPTER_AUTHORING.md +41 -7
- package/docs/ARCHITECTURE.md +62 -11
- package/docs/CAPABILITIES.md +40 -36
- package/docs/CLI.md +143 -25
- package/docs/CONCEPTS.md +13 -6
- package/docs/CONFIGURATION.md +37 -22
- package/docs/DESIGN_DECISIONS.md +16 -11
- package/docs/GETTING_STARTED.md +37 -7
- package/docs/GLOSSARY.md +9 -3
- package/docs/HOW_IT_WORKS.md +32 -11
- package/docs/MCP.md +91 -24
- package/docs/PROTOCOL.md +69 -12
- package/docs/RELEASING.md +28 -10
- package/docs/SECURITY_MODEL.md +45 -13
- package/docs/TROUBLESHOOTING.md +76 -18
- package/docs/UPGRADING.md +119 -0
- package/docs/WHY_ACC.md +5 -4
- package/docs/index.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +106 -5
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +6 -6
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/native-delivery.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-codex/certification.json +38 -13
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/certification-provenance.json +220 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-current-binding-product-evidence.json +1549 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-current-binding-product.json +27 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-product-evidence.json +1549 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-product.json +27 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-transport-evidence.json +259 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-transport.json +21 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-current-binding-product-evidence.json +1549 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-current-binding-product.json +27 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-product-evidence.json +1549 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-product.json +27 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-transport-evidence.json +259 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-transport.json +21 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +14 -2
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +106 -5
- package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +26 -44
- package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +70 -17
- package/node_modules/@agents-can-communicate/adapter-codex/src/config-block.mjs +231 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/hooks.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +71 -63
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +107 -103
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +96 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +3 -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/extension/skills/acc/SKILL.md +106 -5
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +2 -2
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +106 -5
- package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +2 -2
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +106 -5
- package/node_modules/@agents-can-communicate/adapter-kimi/src/hooks.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +2 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +5 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +26 -4
- package/node_modules/@agents-can-communicate/adapter-sdk/src/decision-text.mjs +19 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +5 -3
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +9 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +2 -6
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +29 -9
- package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-block.mjs +8 -6
- package/node_modules/@agents-can-communicate/cli/package.json +4 -2
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +49 -22
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +11 -4
- package/node_modules/@agents-can-communicate/cli/src/git-probe.mjs +9 -3
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +109 -14
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +22 -4
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +35 -12
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/activation.mjs +110 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/command.mjs +63 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/download.mjs +71 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +110 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +167 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/install.mjs +73 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +60 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/leases.mjs +60 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/location.mjs +55 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/mutex.mjs +145 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/policy.mjs +4 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +40 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/schedule.mjs +21 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +122 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/worker.mjs +65 -0
- package/node_modules/@agents-can-communicate/cli/src/session-owner.mjs +33 -108
- package/node_modules/@agents-can-communicate/cli/src/update-command.mjs +25 -7
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/attention.mjs +3 -0
- package/node_modules/@agents-can-communicate/core/src/claims.mjs +13 -8
- package/node_modules/@agents-can-communicate/core/src/conversations.mjs +9 -25
- package/node_modules/@agents-can-communicate/core/src/decision-changes.mjs +36 -0
- package/node_modules/@agents-can-communicate/core/src/decision-state.mjs +42 -0
- package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +11 -4
- package/node_modules/@agents-can-communicate/core/src/inbox.mjs +48 -17
- package/node_modules/@agents-can-communicate/core/src/intents.mjs +35 -15
- package/node_modules/@agents-can-communicate/core/src/materialisation.mjs +7 -5
- package/node_modules/@agents-can-communicate/core/src/message-pages.mjs +75 -0
- package/node_modules/@agents-can-communicate/core/src/next-turn-delivery.mjs +31 -0
- package/node_modules/@agents-can-communicate/core/src/session-opening.mjs +128 -0
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +58 -132
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +31 -2
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/refresh-binding.mjs +38 -0
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +86 -10
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +1 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +6 -4
- package/node_modules/@agents-can-communicate/hook-runner/src/native-retirement.mjs +48 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +175 -67
- package/node_modules/@agents-can-communicate/hook-runner/src/session-lifecycle.mjs +13 -0
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +17 -2
- package/node_modules/@agents-can-communicate/installer/src/bootstrap-runtime.mjs +11 -5
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +1 -1
- package/node_modules/@agents-can-communicate/installer/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/installer/src/live-policy.mjs +21 -0
- package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +20 -6
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +10 -4
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +3 -3
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/src/protocol.mjs +85 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/resources.mjs +3 -2
- package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +59 -57
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +47 -33
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/conversations.mjs +3 -0
- package/node_modules/@agents-can-communicate/protocol/src/decision-changes.mjs +15 -0
- package/node_modules/@agents-can-communicate/protocol/src/index.mjs +3 -1
- package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +14 -5
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +5 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/deadline.mjs +7 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +2 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/session-read.mjs +25 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +28 -25
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -12,6 +12,11 @@ Think of the coordination you expect from subagents, extended across the indepen
|
|
|
12
12
|
sessions you open yourself — with different clients and models. You keep giving them
|
|
13
13
|
ordinary tasks; they handle the conversations around their work.
|
|
14
14
|
|
|
15
|
+
Active hooks supply each agent's own CLI arguments in coordination context; the installed
|
|
16
|
+
skill tells the agent to use them. This path was observed in Claude Code 2.1.263 and
|
|
17
|
+
Codex 0.153.4. Hooks must be enabled and trusted by the client. Without hook context, owner
|
|
18
|
+
operations require an explicit pair or session-bound ACC MCP tools. See [CLI ownership](docs/CLI.md#coordinate-from-a-session).
|
|
19
|
+
|
|
15
20
|
[Try it](#try-it) · [Supported clients](#when-messages-arrive) · [Documentation](docs/index.md)
|
|
16
21
|
|
|
17
22
|
[](https://github.com/automatis-tools/agents-can-communicate/actions/workflows/ci.yml)
|
|
@@ -40,6 +45,14 @@ Claude → Codex I'll use that shape and keep my changes in the UI files.
|
|
|
40
45
|
The agents identify who to talk to and what to agree on. Your prompts stay focused on
|
|
41
46
|
the feature you want to build.
|
|
42
47
|
|
|
48
|
+
The same setup supports a review and handoff. Ask one session to implement a change and
|
|
49
|
+
request review of an identified revision: a commit when Git is available, or named files
|
|
50
|
+
and version otherwise. Ask the other to return blocking defects or approval. The author
|
|
51
|
+
can address the verdict before finishing, or leave a durable handoff so a later session
|
|
52
|
+
can recover what is complete and what remains. See the
|
|
53
|
+
[review request](docs/CLI.md#messages-and-requests) and [handoff](docs/CLI.md#handoff)
|
|
54
|
+
commands behind that flow.
|
|
55
|
+
|
|
43
56
|
Each session keeps its own conversation and instructions. Response timing depends on the
|
|
44
57
|
receiving client: see [when messages arrive](#when-messages-arrive).
|
|
45
58
|
|
|
@@ -55,6 +68,9 @@ acc install
|
|
|
55
68
|
|
|
56
69
|
The installer connects the clients it finds. Open a new terminal and restart your AI
|
|
57
70
|
clients to load the integration. If you use Codex, accept its plugin trust prompt.
|
|
71
|
+
ACC then keeps its runtime and skills updated in the background, waiting for active clients
|
|
72
|
+
to exit before switching versions. [Update controls](docs/UPGRADING.md#automatic-updates-after-installation)
|
|
73
|
+
include manual update, opt-out, and version pinning.
|
|
58
74
|
|
|
59
75
|
Open two sessions in your project and give them ordinary tasks, as in the example above.
|
|
60
76
|
On supported clients, ACC introduces peer awareness through the client's own integration;
|
|
@@ -79,13 +95,17 @@ session can pick them up later. Delivery depends on the client:
|
|
|
79
95
|
| Codex CLI, Claude Code, Gemini CLI, Kimi Code | At the next normal turn on verified versions and platforms; otherwise by checking its ACC inbox. |
|
|
80
96
|
| Grok or another client connected through [MCP](docs/MCP.md) | By checking its ACC inbox. |
|
|
81
97
|
|
|
82
|
-
**Claude Code can also reply while idle.**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
**Codex and Claude Code can also reply while idle.** These optional modes are
|
|
99
|
+
experimental, off by default, and can spend model tokens. On Apple Silicon Macs,
|
|
100
|
+
Codex 0.152.1 or newer requires an already-running LocalDaemon and an exact session
|
|
101
|
+
check; ACC preserves your ordinary launch command. Claude Code 2.1.258 or newer
|
|
102
|
+
requires zsh and its development-channel warning at startup. Messages arriving
|
|
103
|
+
mid-turn wait for it to finish.
|
|
86
104
|
|
|
87
|
-
Each direction follows the receiving client's
|
|
88
|
-
|
|
105
|
+
Each direction follows the receiving client's policy and current reachability.
|
|
106
|
+
A Codex thread retained by LocalDaemon can receive opted-in messages after its
|
|
107
|
+
terminal exits. Turn ACC delivery off to prevent new native offers; already accepted
|
|
108
|
+
queue entries remain with the client.
|
|
89
109
|
|
|
90
110
|
[Full compatibility details](docs/CAPABILITIES.md).
|
|
91
111
|
|
|
@@ -96,7 +116,8 @@ Codex turn. Codex can pick it up on a supported next turn or by checking its inb
|
|
|
96
116
|
- **Separate checkouts, one project.** Git worktrees share an ACC workspace.
|
|
97
117
|
Plain project folders work too; Git is optional.
|
|
98
118
|
- **Agree before editing.** Agents can reserve files and identify overlapping work.
|
|
99
|
-
|
|
119
|
+
CLI reservations default to advisory. `--enforcement guarded` requests enforcement,
|
|
120
|
+
which still requires certified guards from every live participant.
|
|
100
121
|
[How reservations work](docs/CONCEPTS.md#intent-is-awareness-a-claim-commits).
|
|
101
122
|
- **Local coordination.** Messages live in app data outside your project. ACC doesn't
|
|
102
123
|
collect or share raw transcripts. Your clients use their usual model providers.
|
package/SECURITY.md
CHANGED
|
@@ -19,7 +19,7 @@ version. A reproduction against a throwaway `ACC_DATA_HOME` is ideal.
|
|
|
19
19
|
| Peer text escaping its quoted block | An attacker who already has write access to your data home |
|
|
20
20
|
| Any path escaping the managed root | A model choosing to obey persuasive peer text |
|
|
21
21
|
| Uninstall deleting files ACC did not write | Vulnerabilities in Codex, Claude Code, Gemini, Grok, or Kimi themselves |
|
|
22
|
-
| ACC
|
|
22
|
+
| Unexpected ACC runtime/state or installer artifacts in a repository (excluding user-requested `acc.workspace.json`) | Denial of service by a trusted peer |
|
|
23
23
|
| Session impersonation across MCP | |
|
|
24
24
|
|
|
25
25
|
Reasoning behind each: [docs/SECURITY_MODEL.md](docs/SECURITY_MODEL.md).
|
package/bin/acc-bootstrap.mjs
CHANGED
|
@@ -1,56 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// shim exports its owned policy before `exec`; any other exit - including a
|
|
5
|
-
// crash, a missing adapter, or a damaged install - means the shim launches the
|
|
6
|
-
// vendor command untouched. Nothing is ever written to stdout, and stderr stays
|
|
7
|
-
// empty unless ACC_BOOTSTRAP_DEBUG=1 asks for one safe line.
|
|
8
|
-
import { checkNativeBootstrap } from "@agents-can-communicate/installer";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { invokedDirectly, runEntry } from "@agents-can-communicate/cli/managed-entry";
|
|
9
4
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import { createGrokAdapter } from "@agents-can-communicate/adapter-grok";
|
|
14
|
-
import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
|
|
15
|
-
|
|
16
|
-
const FLAGS = new Map([["--adapter", "adapter"], ["--real-executable", "realExecutable"],
|
|
17
|
-
["--data-home", "dataHome"]]);
|
|
18
|
-
|
|
19
|
-
function debug(line) {
|
|
20
|
-
if (process.env.ACC_BOOTSTRAP_DEBUG === "1") process.stderr.write(`acc-bootstrap: ${line}\n`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function parse(args) {
|
|
24
|
-
const options = {};
|
|
25
|
-
for (let index = 0; index < args.length; index += 2) {
|
|
26
|
-
const key = FLAGS.get(args[index]);
|
|
27
|
-
const value = args[index + 1];
|
|
28
|
-
if (key === undefined || typeof value !== "string" || value === "" || key in options) return null;
|
|
29
|
-
options[key] = value;
|
|
30
|
-
}
|
|
31
|
-
return FLAGS.values().every(key => key in options) ? options : null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const options = parse(process.argv.slice(2));
|
|
36
|
-
if (options === null) {
|
|
37
|
-
debug("usage: --adapter <id> --real-executable <path> --data-home <path>");
|
|
38
|
-
process.exit(2);
|
|
39
|
-
}
|
|
40
|
-
const registry = {
|
|
41
|
-
claude_code: createClaudeCodeAdapter, codex: createCodexAdapter,
|
|
42
|
-
gemini_cli: createGeminiCliAdapter, grok: createGrokAdapter, kimi: createKimiAdapter,
|
|
43
|
-
};
|
|
44
|
-
const adapter = registry[options.adapter]?.();
|
|
45
|
-
if (adapter === undefined) {
|
|
46
|
-
debug(`${options.adapter}: unknown adapter`);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
const result = await checkNativeBootstrap({ adapter, realExecutable: options.realExecutable,
|
|
50
|
-
platform: `${process.platform}-${process.arch}`, dataHome: options.dataHome, timeoutMs: 750 });
|
|
51
|
-
debug(`${options.adapter}: ${result.supported ? "supported" : result.reasonCode}`);
|
|
52
|
-
process.exit(result.supported ? 0 : 1);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
debug(`failed: ${String(error?.code ?? error?.name ?? "error")}`);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
5
|
+
if (invokedDirectly(import.meta.url)) await runEntry({
|
|
6
|
+
kind: "acc-bootstrap", packageRoot: fileURLToPath(new URL("..", import.meta.url)),
|
|
7
|
+
});
|
|
@@ -1,177 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// The ACC Channel binary Claude Code spawns as a stdio MCP child. It composes
|
|
3
|
-
// the same workspace discovery, session binding, core service, and local
|
|
4
|
-
// data-home rules as acc-hook, opens a session-scoped endpoint for the delivery
|
|
5
|
-
// router, and turns the model's explicit acc_reply / acc_ack into real ACC
|
|
6
|
-
// records. It never collects a transcript or runs a model, and it fails open:
|
|
7
|
-
// any composition error leaves Claude a plain MCP server that simply offers no
|
|
8
|
-
// tools rather than crashing the session.
|
|
9
|
-
import { randomBytes } from "node:crypto";
|
|
10
|
-
import { realpathSync } from "node:fs";
|
|
11
|
-
import path from "node:path";
|
|
12
2
|
import { fileURLToPath } from "node:url";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
import { createGitProbe, discoverWorkspace, platformDataHome, runtimePaths }
|
|
22
|
-
from "@agents-can-communicate/cli";
|
|
23
|
-
|
|
24
|
-
import { createAccChannel, createInertChannel, endpointDir, routeAck, routeReply }
|
|
25
|
-
from "@agents-can-communicate/adapter-claude-code/channel";
|
|
26
|
-
|
|
27
|
-
const clock = { now: () => new Date().toISOString() };
|
|
28
|
-
const ids = { next: kind => createId(kind, randomBytes) };
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The ACC session this Claude process opened - decided by which client process
|
|
32
|
-
* this Channel is running under, never by how many sessions happen to be
|
|
33
|
-
* visible.
|
|
34
|
-
*
|
|
35
|
-
* There used to be a fallback here: if exactly one session was live, it was
|
|
36
|
-
* taken to be this one. On a single-session machine that looked safe, and with
|
|
37
|
-
* two sessions it silently handed one client the other's identity. The Channel
|
|
38
|
-
* starts while its own SessionStart hook is still writing its binding, so the
|
|
39
|
-
* only binding a second client can see at that moment is the *first* client's -
|
|
40
|
-
* and the wait that was added to survive that race then locks the wrong answer
|
|
41
|
-
* in on the first attempt, because it is a complete, live, plausible binding.
|
|
42
|
-
*
|
|
43
|
-
* Measured on two real 2.1.259 sessions: both Channels registered an endpoint
|
|
44
|
-
* under the first client's pid. `bindNativeSession` refuses two registrations
|
|
45
|
-
* for one pid, so the session that had been receiving live messages dropped
|
|
46
|
-
* back to the durable inbox - and an `acc_reply` from the second window would
|
|
47
|
-
* have been recorded as the first session's answer, which is a false record
|
|
48
|
-
* rather than a lost message.
|
|
49
|
-
*
|
|
50
|
-
* This process was spawned by its client, so its own ancestry names that
|
|
51
|
-
* client, and no race can change that. Ownership is decided by it.
|
|
52
|
-
*/
|
|
53
|
-
export async function resolveSession({ runtimeDir, service, env, ownClientPid,
|
|
54
|
-
listBindings = listSessionBindings }) {
|
|
55
|
-
if (!Number.isInteger(ownClientPid)) return null;
|
|
56
|
-
const bindings = await listBindings({ runtimeDir });
|
|
57
|
-
if (bindings.length === 0) return null;
|
|
58
|
-
const status = await service.collectStatus({});
|
|
59
|
-
const live = new Set(status.participants
|
|
60
|
-
.filter(participant => participant.presence !== "offline")
|
|
61
|
-
.map(participant => participant.sessionId));
|
|
62
|
-
const mine = bindings.filter(binding => live.has(binding.accSessionId)
|
|
63
|
-
&& binding.clientPid === ownClientPid);
|
|
64
|
-
// One client process can outlive a session and open another, so the pid alone
|
|
65
|
-
// can still name two. The harness session id the client exports separates
|
|
66
|
-
// them; with nothing to separate them the answer is nobody, as before.
|
|
67
|
-
const exported = new Set(Object.values(env ?? {}).filter(value => typeof value === "string"));
|
|
68
|
-
const matched = mine.filter(binding => exported.has(binding.harnessSessionId));
|
|
69
|
-
const chosen = mine.length === 1 ? mine[0] : matched.length === 1 ? matched[0] : null;
|
|
70
|
-
return chosen === null ? null
|
|
71
|
-
: { sessionId: chosen.accSessionId, generation: chosen.generation, clientPid: chosen.clientPid };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** The client process this Channel was spawned by, or null when nobody knows. */
|
|
75
|
-
export async function ownClient({ table = null, from = process.pid,
|
|
76
|
-
command = createClaudeCodeAdapter().client.command } = {}) {
|
|
77
|
-
return resolveClientPid({ table: table ?? await readProcessTable(), from, command });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// How long the Channel will wait for the hook to publish this session's
|
|
81
|
-
// binding. Claude spawns this child while SessionStart is still running - both
|
|
82
|
-
// landed in the same second on a real 2.1.259 launch - and a single lookup
|
|
83
|
-
// loses that race often enough to make native delivery activate or not per
|
|
84
|
-
// launch. Bounded, because a session with no ACC binding must still be answered.
|
|
85
|
-
const BINDING_WAIT_MS = 5_000;
|
|
86
|
-
const BINDING_POLL_MS = 100;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* The session binding, once it exists - or null when the wait runs out.
|
|
90
|
-
*
|
|
91
|
-
* A binding with no `clientPid` names no process, and the Channel binds an
|
|
92
|
-
* exact one, so it does not count as resolved: the hook writes the identity
|
|
93
|
-
* first and the certified facts after, and reading in between must keep waiting
|
|
94
|
-
* rather than settle for the half-written answer.
|
|
95
|
-
*/
|
|
96
|
-
export async function resolveWithin({ resolve, deadline, intervalMs = BINDING_POLL_MS,
|
|
97
|
-
now = () => Date.now(), sleep = ms => new Promise(resolve_ => setTimeout(resolve_, ms)) }) {
|
|
98
|
-
for (;;) {
|
|
99
|
-
const session = await resolve();
|
|
100
|
-
if (session !== null && Number.isInteger(session?.clientPid)) return session;
|
|
101
|
-
if (now() >= deadline) return null;
|
|
102
|
-
await sleep(intervalMs);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function main() {
|
|
107
|
-
const descriptor = await discoverWorkspace({ cwd: process.cwd(), env: process.env,
|
|
108
|
-
gitProbe: createGitProbe() });
|
|
109
|
-
const paths = runtimePaths({ dataHome: platformDataHome({ platform: process.platform,
|
|
110
|
-
env: process.env }), workspaceId: descriptor.id, workspaceRoots: descriptor.roots });
|
|
111
|
-
const store = await openFilesystemStore({ root: paths.root, clock, ids,
|
|
112
|
-
workspaceId: descriptor.id });
|
|
113
|
-
const service = createCoordinationService({ store, clock, ids });
|
|
114
|
-
const write = payload => process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
115
|
-
// Read once: the ancestry that decides ownership cannot change while this
|
|
116
|
-
// process runs, and re-reading it on every poll would spawn `ps` in a loop.
|
|
117
|
-
const clientPid = await ownClient();
|
|
118
|
-
const session = await resolveWithin({
|
|
119
|
-
resolve: () => resolveSession({ runtimeDir: paths.root, service, env: process.env,
|
|
120
|
-
ownClientPid: clientPid }),
|
|
121
|
-
deadline: Date.now() + BINDING_WAIT_MS });
|
|
122
|
-
// No binding to serve - but Claude is already speaking MCP to this child, and
|
|
123
|
-
// returning here left the event loop empty: the process exited in 75ms
|
|
124
|
-
// without answering `initialize`, and Claude reported the server as failed to
|
|
125
|
-
// connect on every session that enables the plugin without ACC's shim.
|
|
126
|
-
if (session === null || !Number.isInteger(session.clientPid)) {
|
|
127
|
-
const inert = createInertChannel({ write });
|
|
128
|
-
pump(inert.handleLine, () => process.exit(0));
|
|
129
|
-
return inert;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const channel = createAccChannel({
|
|
133
|
-
endpointDir: endpointDir(paths.root),
|
|
134
|
-
clientPid: session.clientPid,
|
|
135
|
-
write,
|
|
136
|
-
routeReply: ({ messageId, body }) => routeReply({ service, session, messageId, body }),
|
|
137
|
-
routeAck: ({ messageId }) => routeAck({ service, session, messageId }),
|
|
138
|
-
// This process is the only party that knows the endpoint is still being
|
|
139
|
-
// served, and Claude publishes no heartbeat, so nothing else would move
|
|
140
|
-
// ACC's lease while the session sits idle - which is when live delivery is
|
|
141
|
-
// worth having.
|
|
142
|
-
refreshBinding: leaseUntil => service.refreshDeliveryBinding({
|
|
143
|
-
sessionId: session.sessionId, generation: session.generation, leaseUntil }),
|
|
144
|
-
});
|
|
145
|
-
await channel.listen();
|
|
146
|
-
pump(channel.handleLine, () => { channel.close(); process.exit(0); });
|
|
147
|
-
return channel;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** One line-delimited JSON pump, for the bound server and the unbound one alike. */
|
|
151
|
-
function pump(handleLine, shutdown) {
|
|
152
|
-
let buffer = "";
|
|
153
|
-
process.stdin.setEncoding("utf8");
|
|
154
|
-
process.stdin.on("data", async chunk => {
|
|
155
|
-
buffer += chunk;
|
|
156
|
-
let newline = buffer.indexOf("\n");
|
|
157
|
-
while (newline !== -1) {
|
|
158
|
-
const line = buffer.slice(0, newline).trim();
|
|
159
|
-
buffer = buffer.slice(newline + 1);
|
|
160
|
-
newline = buffer.indexOf("\n");
|
|
161
|
-
if (line !== "") await handleLine(line);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
process.stdin.on("end", shutdown);
|
|
165
|
-
process.once("SIGTERM", shutdown);
|
|
166
|
-
process.once("SIGINT", shutdown);
|
|
167
|
-
// Keeps the child answerable for as long as Claude holds the transport open.
|
|
168
|
-
process.stdin.resume();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const invokedDirectly = typeof process.argv[1] === "string"
|
|
172
|
-
&& realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
173
|
-
if (invokedDirectly) {
|
|
174
|
-
// Fail open: a Channel that cannot compose is simply absent, never a crash in
|
|
175
|
-
// front of the user's session.
|
|
176
|
-
await main().catch(() => { process.stdin.resume(); });
|
|
177
|
-
}
|
|
3
|
+
import { invokedDirectly, runEntry } from "@agents-can-communicate/cli/managed-entry";
|
|
4
|
+
export const resolveSession = async options => (await import("./entrypoints/acc-claude-channel.mjs")).resolveSession(options);
|
|
5
|
+
export const ownClient = async options => (await import("./entrypoints/acc-claude-channel.mjs")).ownClient(options);
|
|
6
|
+
export const resolveWithin = async options => (await import("./entrypoints/acc-claude-channel.mjs")).resolveWithin(options);
|
|
7
|
+
|
|
8
|
+
if (invokedDirectly(import.meta.url)) await runEntry({
|
|
9
|
+
kind: "acc-claude-channel", packageRoot: fileURLToPath(new URL("..", import.meta.url)),
|
|
10
|
+
});
|
package/bin/acc-hook.mjs
CHANGED
|
@@ -1,137 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Hook entry point: `acc-hook <adapter-id> <kind>`, payload on stdin.
|
|
3
|
-
//
|
|
4
|
-
// Every harness runs this as a short-lived child process in front of the user's
|
|
5
|
-
// turn. The one rule that overrides all others here: never be the reason
|
|
6
|
-
// someone's session stops working. Unknown adapter, malformed payload, broken
|
|
7
|
-
// store, missing binding - all of them end in "allow, exit 0".
|
|
8
|
-
import { randomBytes } from "node:crypto";
|
|
9
|
-
import { realpathSync } from "node:fs";
|
|
10
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { invokedDirectly, runEntry } from "@agents-can-communicate/cli/managed-entry";
|
|
4
|
+
export { writeOutput, completeHookOutput } from "./entrypoints/hook-output.mjs";
|
|
11
5
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
|
|
16
|
-
import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
|
|
17
|
-
import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
|
|
18
|
-
import { createGrokAdapter } from "@agents-can-communicate/adapter-grok";
|
|
19
|
-
import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
|
|
20
|
-
|
|
21
|
-
const adapters = {
|
|
22
|
-
claude_code: createClaudeCodeAdapter(),
|
|
23
|
-
codex: createCodexAdapter(),
|
|
24
|
-
gemini_cli: createGeminiCliAdapter(),
|
|
25
|
-
grok: createGrokAdapter(),
|
|
26
|
-
kimi: createKimiAdapter(),
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const readStdin = () => new Promise(resolve => {
|
|
30
|
-
// A hook is always given its payload on stdin, but a client that closes it
|
|
31
|
-
// without writing must not leave this process hanging in front of a turn.
|
|
32
|
-
if (process.stdin.isTTY) return resolve("");
|
|
33
|
-
let raw = "";
|
|
34
|
-
process.stdin.setEncoding("utf8");
|
|
35
|
-
process.stdin.on("data", chunk => { raw += chunk; });
|
|
36
|
-
process.stdin.on("end", () => resolve(raw));
|
|
37
|
-
process.stdin.on("error", () => resolve(""));
|
|
38
|
-
return undefined;
|
|
6
|
+
if (invokedDirectly(import.meta.url)) await runEntry({
|
|
7
|
+
kind: "acc-hook", packageRoot: fileURLToPath(new URL("..", import.meta.url)),
|
|
39
8
|
});
|
|
40
|
-
|
|
41
|
-
export const writeOutput = (stream, output, { deadlineAt } = {}) => {
|
|
42
|
-
if (output === "") return Promise.resolve();
|
|
43
|
-
return new Promise((resolve, reject) => {
|
|
44
|
-
let settled = false;
|
|
45
|
-
let timer;
|
|
46
|
-
const finish = error => {
|
|
47
|
-
if (settled) return;
|
|
48
|
-
settled = true;
|
|
49
|
-
if (timer !== undefined) clearTimeout(timer);
|
|
50
|
-
if (error === undefined || error === null) resolve();
|
|
51
|
-
else reject(error);
|
|
52
|
-
};
|
|
53
|
-
if (deadlineAt !== undefined) {
|
|
54
|
-
const remaining = deadlineAt - Date.now();
|
|
55
|
-
if (remaining <= 0) {
|
|
56
|
-
finish(new Error("hook budget exhausted before stdout write"));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
timer = setTimeout(() => finish(
|
|
60
|
-
new Error("hook budget exhausted waiting for stdout callback")), remaining);
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
stream.write(output, finish);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
finish(error);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const DIAGNOSTIC_BYTES = 512;
|
|
71
|
-
|
|
72
|
-
function boundedDiagnostic(label, error) {
|
|
73
|
-
const detail = String(error?.message ?? error).replace(/[\u0000-\u001f\u007f]/g, " ");
|
|
74
|
-
let line = `acc: ${label}: ${detail}`;
|
|
75
|
-
while (Buffer.byteLength(`${line}\n`, "utf8") > DIAGNOSTIC_BYTES && line.length > 0) {
|
|
76
|
-
line = line.slice(0, -1);
|
|
77
|
-
}
|
|
78
|
-
return `${line}\n`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function tryWrite(stream, output) {
|
|
82
|
-
if (output === "") return;
|
|
83
|
-
try {
|
|
84
|
-
stream.write(output, () => {});
|
|
85
|
-
} catch {
|
|
86
|
-
// A broken diagnostic stream must not turn a failed-open hook into a crash.
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export async function completeHookOutput(result,
|
|
91
|
-
{ stdout = process.stdout, stderr = process.stderr } = {}) {
|
|
92
|
-
try {
|
|
93
|
-
await writeOutput(stdout, result.stdout ?? "", { deadlineAt: result.deadlineAt });
|
|
94
|
-
} catch (error) {
|
|
95
|
-
tryWrite(stderr, boundedDiagnostic("stdout write failed", error));
|
|
96
|
-
return { exitCode: 0, wroteStdout: false, committedOffers: false };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
let committedOffers = true;
|
|
100
|
-
try {
|
|
101
|
-
await result.commitOffers?.();
|
|
102
|
-
} catch (error) {
|
|
103
|
-
committedOffers = false;
|
|
104
|
-
tryWrite(stderr, boundedDiagnostic("offer commit failed", error));
|
|
105
|
-
}
|
|
106
|
-
if (result.stderr) tryWrite(stderr, `${result.stderr}\n`);
|
|
107
|
-
return { exitCode: 0, wroteStdout: true, committedOffers };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function main() {
|
|
111
|
-
const [adapterId] = process.argv.slice(2);
|
|
112
|
-
|
|
113
|
-
let payload = null;
|
|
114
|
-
try {
|
|
115
|
-
payload = JSON.parse(await readStdin());
|
|
116
|
-
} catch {
|
|
117
|
-
payload = null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const result = await runHook({ adapterId, payload, adapters,
|
|
121
|
-
runtime: { clock: { now: () => new Date().toISOString() },
|
|
122
|
-
ids: { next: kind => createId(kind, randomBytes) } },
|
|
123
|
-
env: process.env });
|
|
124
|
-
|
|
125
|
-
const completed = await completeHookOutput(result);
|
|
126
|
-
process.exitCode = result.exitCode ?? completed.exitCode;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
let isMain = false;
|
|
130
|
-
try {
|
|
131
|
-
isMain = process.argv[1] !== undefined
|
|
132
|
-
&& realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
133
|
-
} catch {
|
|
134
|
-
isMain = false;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (isMain) await main();
|
package/bin/acc-mcp.mjs
CHANGED
|
@@ -1,62 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { invokedDirectly, runEntry } from "@agents-can-communicate/cli/managed-entry";
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { createDeliveryRouter } from "@agents-can-communicate/delivery-router";
|
|
7
|
-
import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
|
|
8
|
-
import { ALL_ADAPTERS, createGitProbe, discoverWorkspace, platformDataHome, runtimePaths }
|
|
9
|
-
from "@agents-can-communicate/cli";
|
|
10
|
-
import { serve } from "@agents-can-communicate/mcp-server";
|
|
11
|
-
|
|
12
|
-
// The composition root is the only place allowed to reach for ambient time and
|
|
13
|
-
// randomness. The participant name comes from configuration, never from the
|
|
14
|
-
// client: the protocol says clientInfo is self-reported and must not drive
|
|
15
|
-
// behaviour, and the session is derived from this configuration alone.
|
|
16
|
-
// Nothing is read from the command line, so nothing may be passed on it. It
|
|
17
|
-
// used to accept and ignore anything: writing `acc-mcp --cwd <project>` - the
|
|
18
|
-
// habit `acc` teaches - started a server rooted wherever the client happened to
|
|
19
|
-
// launch it, alone in a workspace nobody else was in, with no warning at all.
|
|
20
|
-
if (process.argv.length > 2) {
|
|
21
|
-
process.stderr.write("acc-mcp takes no arguments. It is configured by environment:\n"
|
|
22
|
-
+ " ACC_MCP_PARTICIPANT who this server takes part as (default: mcp)\n"
|
|
23
|
-
+ " ACC_MCP_WORKSPACE the project it joins (default: the working directory)\n"
|
|
24
|
-
+ `refusing: ${process.argv.slice(2).join(" ")}\n`);
|
|
25
|
-
process.exit(EXIT.USAGE);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const participantId = process.env.ACC_MCP_PARTICIPANT ?? "mcp";
|
|
29
|
-
const clock = { now: () => new Date().toISOString() };
|
|
30
|
-
const ids = { next: kind => createId(kind, randomBytes) };
|
|
31
|
-
|
|
32
|
-
const descriptor = await discoverWorkspace({
|
|
33
|
-
cwd: process.env.ACC_MCP_WORKSPACE ?? process.cwd(),
|
|
34
|
-
env: process.env,
|
|
35
|
-
gitProbe: createGitProbe(),
|
|
36
|
-
});
|
|
37
|
-
const paths = runtimePaths({
|
|
38
|
-
dataHome: platformDataHome({ platform: process.platform, env: process.env }),
|
|
39
|
-
workspaceId: descriptor.id,
|
|
40
|
-
workspaceRoots: descriptor.roots,
|
|
5
|
+
if (invokedDirectly(import.meta.url)) await runEntry({
|
|
6
|
+
kind: "acc-mcp", packageRoot: fileURLToPath(new URL("..", import.meta.url)),
|
|
41
7
|
});
|
|
42
|
-
const store = await openFilesystemStore({ root: paths.root, clock, ids,
|
|
43
|
-
workspaceId: descriptor.id });
|
|
44
|
-
const service = createCoordinationService({ store, clock, ids });
|
|
45
|
-
const adapters = Object.fromEntries(ALL_ADAPTERS().map(adapter => [adapter.id, adapter]));
|
|
46
|
-
|
|
47
|
-
await serve({
|
|
48
|
-
input: process.stdin,
|
|
49
|
-
output: process.stdout,
|
|
50
|
-
log: message => process.stderr.write(`acc-mcp: ${message}\n`),
|
|
51
|
-
context: {
|
|
52
|
-
service,
|
|
53
|
-
deliveryRouter: createDeliveryRouter({ service, adapters, clock }),
|
|
54
|
-
workspaceId: descriptor.id,
|
|
55
|
-
participantId,
|
|
56
|
-
descriptor,
|
|
57
|
-
runtimeDir: paths.root,
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Exiting on stdin EOF is the only portable graceful shutdown in this binding.
|
|
62
|
-
process.exitCode = 0;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Private detached updater. It never opens workspace state or runs a model.
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { runWorker, recordWorkerFailure } from "@agents-can-communicate/cli/managed-worker";
|
|
5
|
+
const [root, ...extra] = process.argv.slice(2);
|
|
6
|
+
if (typeof root === "string" && path.isAbsolute(root) && extra.length === 0) {
|
|
7
|
+
await runWorker(root, { wait: true }).catch(error => recordWorkerFailure(root, error));
|
|
8
|
+
}
|
package/bin/acc.mjs
CHANGED
|
@@ -1,41 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { invokedDirectly, runEntry } from "@agents-can-communicate/cli/managed-entry";
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const adapters = Object.fromEntries(ALL_ADAPTERS().map(adapter => [adapter.id, adapter]));
|
|
10
|
-
|
|
11
|
-
// The composition root is the only place allowed to reach for ambient time and
|
|
12
|
-
// randomness; everything below it receives them as ports.
|
|
13
|
-
const runtime = {
|
|
14
|
-
cwd: process.cwd(),
|
|
15
|
-
env: process.env,
|
|
16
|
-
platform: process.platform,
|
|
17
|
-
stdout: process.stdout,
|
|
18
|
-
stderr: process.stderr,
|
|
19
|
-
clock: { now: () => new Date().toISOString() },
|
|
20
|
-
ids: { next: kind => createId(kind, randomBytes) },
|
|
21
|
-
createDeliveryRouter: ({ service, clock }) =>
|
|
22
|
-
createDeliveryRouter({ service, adapters, clock }),
|
|
23
|
-
// Asked only by `acc config init`, and only when stdout is a terminal. There
|
|
24
|
-
// was no port here at all, so the question went to the fallback that always
|
|
25
|
-
// answers no: in a real terminal the command printed "not written" and never
|
|
26
|
-
// said why, and `--yes` - the flag documented for runs with nobody to ask -
|
|
27
|
-
// was the only way to write the file.
|
|
28
|
-
confirm: (question, io = { input: process.stdin, output: process.stdout }) =>
|
|
29
|
-
askConfirmation(question, io),
|
|
30
|
-
// The two streams and the fact that a person is at both ends, for the
|
|
31
|
-
// per-client native delivery question `acc install` asks only interactively.
|
|
32
|
-
input: process.stdin,
|
|
33
|
-
output: process.stdout,
|
|
34
|
-
isInteractive: () => process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
35
|
-
// Asked for only by `acc version`, so a package missing its own manifest
|
|
36
|
-
// fails that one command rather than every command.
|
|
37
|
-
version: async () => JSON.parse(
|
|
38
|
-
await readFile(new URL("../package.json", import.meta.url), "utf8")).version,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
process.exitCode = await main(process.argv.slice(2), runtime);
|
|
5
|
+
if (invokedDirectly(import.meta.url)) await runEntry({
|
|
6
|
+
kind: "acc", packageRoot: fileURLToPath(new URL("..", import.meta.url)),
|
|
7
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Internal entry point behind an ACC-owned shell shim: is this exact vendor
|
|
3
|
+
// executable eligible for native delivery right now? Exit 0 means yes and the
|
|
4
|
+
// shim exports its owned policy before `exec`; any other exit - including a
|
|
5
|
+
// crash, a missing adapter, or a damaged install - means the shim launches the
|
|
6
|
+
// vendor command untouched. Nothing is ever written to stdout, and stderr stays
|
|
7
|
+
// empty unless ACC_BOOTSTRAP_DEBUG=1 asks for one safe line.
|
|
8
|
+
import { checkNativeBootstrap } from "@agents-can-communicate/installer";
|
|
9
|
+
|
|
10
|
+
import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
|
|
11
|
+
import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
|
|
12
|
+
import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
|
|
13
|
+
import { createGrokAdapter } from "@agents-can-communicate/adapter-grok";
|
|
14
|
+
import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
|
|
15
|
+
|
|
16
|
+
import { parseBootstrapOptions } from "@agents-can-communicate/cli/managed-entry";
|
|
17
|
+
|
|
18
|
+
function debug(line) {
|
|
19
|
+
if (process.env.ACC_BOOTSTRAP_DEBUG === "1") process.stderr.write(`acc-bootstrap: ${line}\n`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function main() {
|
|
23
|
+
try {
|
|
24
|
+
const options = parseBootstrapOptions(process.argv.slice(2));
|
|
25
|
+
if (options === null) {
|
|
26
|
+
debug("usage: --adapter <id> --real-executable <path> --data-home <path>");
|
|
27
|
+
process.exit(2);
|
|
28
|
+
}
|
|
29
|
+
const registry = {
|
|
30
|
+
claude_code: createClaudeCodeAdapter, codex: createCodexAdapter,
|
|
31
|
+
gemini_cli: createGeminiCliAdapter, grok: createGrokAdapter, kimi: createKimiAdapter,
|
|
32
|
+
};
|
|
33
|
+
const adapter = registry[options.adapter]?.();
|
|
34
|
+
if (adapter === undefined) {
|
|
35
|
+
debug(`${options.adapter}: unknown adapter`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const result = await checkNativeBootstrap({ adapter, realExecutable: options.realExecutable,
|
|
39
|
+
platform: `${process.platform}-${process.arch}`, dataHome: options.dataHome, timeoutMs: 750 });
|
|
40
|
+
debug(`${options.adapter}: ${result.supported ? "supported" : result.reasonCode}`);
|
|
41
|
+
process.exit(result.supported ? 0 : 1);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
debug(`failed: ${String(error?.code ?? error?.name ?? "error")}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|