agents-can-communicate 0.3.1 → 0.4.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/README.md +95 -52
- 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 +77 -0
- package/docs/ADAPTER_AUTHORING.md +52 -8
- package/docs/ARCHITECTURE.md +64 -11
- package/docs/CAPABILITIES.md +41 -37
- package/docs/CLI.md +145 -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 +41 -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 +47 -13
- package/docs/TROUBLESHOOTING.md +102 -20
- package/docs/UPGRADING.md +154 -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 +122 -8
- package/node_modules/@agents-can-communicate/adapter-grok/src/adapter.mjs +5 -3
- package/node_modules/@agents-can-communicate/adapter-grok/src/hooks.mjs +9 -0
- 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 +24 -5
- 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 +64 -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 +78 -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 +123 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/worker.mjs +65 -0
- package/node_modules/@agents-can-communicate/cli/src/runtime-paths.mjs +1 -1
- 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/owner-context.mjs +30 -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 +172 -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
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# agents-can-communicate
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Independent AI sessions. Shared work context.**
|
|
4
4
|
|
|
5
|
-
Give Codex the backend. Ask Claude Code to build the interface. Keep
|
|
5
|
+
Give Codex the backend. Ask Claude Code to build the interface. Keep using the clients
|
|
6
|
+
and models you prefer.
|
|
6
7
|
|
|
7
|
-
ACC
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
ACC is a local coordination layer for AI sessions you open yourself. It gives them peer
|
|
9
|
+
presence, work intent, file claims, messages, review requests, and durable handoffs.
|
|
10
|
+
Each session keeps its own conversation, permissions, and task. The agents decide when
|
|
11
|
+
coordination helps their work.
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
ordinary tasks; they handle the conversations around their work.
|
|
13
|
+
Sessions can use different clients or multiple instances of the same one. They meet in
|
|
14
|
+
the same workspace on the same machine and operating-system user; Git is optional.
|
|
14
15
|
|
|
15
|
-
[Try it](#try-it) · [
|
|
16
|
+
[Try it](#try-it) · [Client support](#when-messages-arrive) · [Update ACC](#update-or-remove-acc) · [Documentation](docs/index.md)
|
|
16
17
|
|
|
17
18
|
[](https://github.com/automatis-tools/agents-can-communicate/actions/workflows/ci.yml)
|
|
18
19
|
[](LICENSE)
|
|
@@ -27,9 +28,9 @@ Open two sessions in your project and give each its task:
|
|
|
27
28
|
| Codex | Build the backend for account registration. |
|
|
28
29
|
| Claude Code | Build the registration screen. |
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Supported integrations introduce peer awareness and teach agents how to find out what
|
|
32
|
+
others are doing. As they work, they can notice a dependency and coordinate. An
|
|
33
|
+
illustrative exchange:
|
|
33
34
|
|
|
34
35
|
```text
|
|
35
36
|
Claude → Codex I'm building the registration screen. What will your endpoint accept?
|
|
@@ -37,71 +38,111 @@ Codex → Claude Email and password. I'll return the new user and handle valid
|
|
|
37
38
|
Claude → Codex I'll use that shape and keep my changes in the UI files.
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
Your prompts stay focused on the feature. Each agent decides which peers and messages
|
|
42
|
+
matter to its task; installing ACC does not guarantee that a model will coordinate on
|
|
43
|
+
every task. [Client support](#when-messages-arrive) determines how automatically that
|
|
44
|
+
awareness reaches it.
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
The same setup supports review and recovery. An author can request review of an
|
|
47
|
+
identified revision, receive defects or approval, and leave a handoff describing completed
|
|
48
|
+
work, remaining work, and blockers. A later session can look up that history. Git commits
|
|
49
|
+
can identify a revision; named files and versions work when Git is unavailable.
|
|
50
|
+
See [requests and replies](docs/CLI.md#messages-and-requests) and
|
|
51
|
+
[handoffs](docs/CLI.md#handoff).
|
|
45
52
|
|
|
46
53
|
## Try it
|
|
47
54
|
|
|
48
55
|
You'll need **macOS or Linux, Node.js 24 or newer**, and two AI sessions in the same
|
|
49
|
-
project.
|
|
56
|
+
project. Install once on the machine:
|
|
50
57
|
|
|
51
58
|
```bash
|
|
52
59
|
npm install -g agents-can-communicate
|
|
53
60
|
acc install
|
|
54
61
|
```
|
|
55
62
|
|
|
56
|
-
The installer connects the clients it finds.
|
|
57
|
-
clients
|
|
63
|
+
The installer connects the supported clients it finds. Follow its activation instructions,
|
|
64
|
+
then restart your clients from the project directory. In Codex, check `/plugins` and
|
|
65
|
+
review the current ACC definitions in `/hooks`; changed hooks may need fresh trust.
|
|
66
|
+
[Getting started](docs/GETTING_STARTED.md) covers activation and preserved sandbox settings.
|
|
58
67
|
|
|
59
|
-
Open two sessions
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
Open two sessions and give them ordinary tasks, as above. Look for an agent discovering a
|
|
69
|
+
peer, checking who is changing a file, asking about a shared dependency, or replying to a
|
|
70
|
+
review request.
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
coordinate on every task. [Client support](#when-messages-arrive) determines how
|
|
67
|
-
automatically peers and messages reach it.
|
|
72
|
+
Run `acc doctor` from the project if a peer is missing. A directory containing ACC's own
|
|
73
|
+
state, commonly your home directory, cannot be used as a workspace; start the client in a
|
|
74
|
+
project directory. See [Troubleshooting](docs/TROUBLESHOOTING.md).
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
Already using ACC? Follow the [upgrade guide](docs/UPGRADING.md), including the 0.4.0 →
|
|
77
|
+
0.4.1 update and the data-format boundary when moving from 0.3.1.
|
|
71
78
|
|
|
72
79
|
## When messages arrive
|
|
73
80
|
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
Messages are saved locally before delivery is attempted. A durable inbox remains
|
|
82
|
+
available when a faster route cannot be used.
|
|
76
83
|
|
|
77
84
|
| Client | How the agent receives a message |
|
|
78
85
|
|---|---|
|
|
79
|
-
| Codex CLI, Claude Code, Gemini CLI, Kimi Code | At the next normal turn on verified versions and platforms; otherwise
|
|
80
|
-
| Grok
|
|
86
|
+
| Codex CLI, Claude Code, Gemini CLI, Kimi Code | At the next normal turn on the exact verified versions and platforms; otherwise through explicit ACC inbox reads. |
|
|
87
|
+
| Grok | Through explicit ACC inbox reads, using its installed hooks and skill. CLI ownership was verified on Grok 1.0.24. |
|
|
88
|
+
| Other clients connected through [MCP](docs/MCP.md) | Through ACC tools and inbox reads. Generic MCP requires its own client configuration and coordination instructions. |
|
|
89
|
+
|
|
90
|
+
Grok's updated skill first runs public status through the terminal. The ACC hook reminder
|
|
91
|
+
after that result supplies the session's own CLI arguments for subsequent inbox reads and
|
|
92
|
+
mutations. It adds no automatic peer-message injection or idle delivery. A relocated
|
|
93
|
+
`GROK_HOME` is respected by install, doctor, and uninstall.
|
|
94
|
+
|
|
95
|
+
**Optional live delivery can start a turn in an idle Codex or Claude Code session.** It is
|
|
96
|
+
experimental, off by default, and can spend model tokens. On Apple Silicon Macs, Codex
|
|
97
|
+
0.152.1 or newer requires an already-running LocalDaemon and a verified session;
|
|
98
|
+
Claude Code 2.1.258 or newer requires zsh and shows its development-channel warning at
|
|
99
|
+
startup. Messages arriving mid-turn wait for the turn to finish. The receiving session's
|
|
100
|
+
opt-in policy and current reachability determine whether delivery can proceed.
|
|
101
|
+
|
|
102
|
+
A Codex thread retained by LocalDaemon can receive opted-in messages after its terminal
|
|
103
|
+
exits. Turning ACC delivery off prevents new native offers; already accepted queue entries
|
|
104
|
+
remain with the client. [Compatibility and delivery controls](docs/CAPABILITIES.md) describe
|
|
105
|
+
the exact evidence, versions, platforms, and fallback paths.
|
|
106
|
+
|
|
107
|
+
A recorded message is send success. An offer is not proof of reading, and an
|
|
108
|
+
acknowledgement or reply is not proof that the requested work is complete.
|
|
81
109
|
|
|
82
|
-
|
|
83
|
-
default. It supports Apple Silicon Macs with zsh and Claude Code 2.1.258 or newer, subject
|
|
84
|
-
to compatibility checks. It can spend model tokens and requires accepting Claude's
|
|
85
|
-
development-channel warning at startup. Messages arriving mid-turn wait for it to finish.
|
|
110
|
+
## Keep the workflow you like
|
|
86
111
|
|
|
87
|
-
|
|
88
|
-
|
|
112
|
+
- **Your usual tools.** Start clients with their normal commands. You choose each agent's
|
|
113
|
+
task, model, and permissions.
|
|
114
|
+
- **Separate checkouts, one project.** Git worktrees share an ACC workspace. Plain folders
|
|
115
|
+
work too; optional workspace configuration can supply a shared identity and roots.
|
|
116
|
+
- **Agree before editing.** Agents can claim files and identify overlapping work. CLI claims
|
|
117
|
+
default to advisory. Guarded claims require certified guards from every live participant.
|
|
118
|
+
[How claims work](docs/CONCEPTS.md#intent-is-awareness-a-claim-commits).
|
|
119
|
+
- **Focused context and durable history.** Normal turn context is bounded. Inbox and history
|
|
120
|
+
return summary pages, with message bodies fetched by id. Agents can supersede or withdraw
|
|
121
|
+
old decisions and recover prior handoffs when needed.
|
|
122
|
+
- **Local coordination.** State lives in app data outside your project. ACC never collects
|
|
123
|
+
or shares raw transcripts; peer messages are untrusted input. Your clients keep using
|
|
124
|
+
their usual model providers.
|
|
89
125
|
|
|
90
|
-
|
|
126
|
+
One npm package. ACC needs no separate account, model API key, or hosted service.
|
|
91
127
|
|
|
92
|
-
##
|
|
128
|
+
## Update or remove ACC
|
|
129
|
+
|
|
130
|
+
Initial installation enables automatic updates. ACC downloads stable releases in the
|
|
131
|
+
background and waits for active clients and ACC processes to exit before switching the
|
|
132
|
+
runtime and refreshing integrations. Restart clients afterward and complete any requested
|
|
133
|
+
hook or plugin trust review.
|
|
93
134
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Reservations are advisory unless the clients support enforcement.
|
|
100
|
-
[How reservations work](docs/CONCEPTS.md#intent-is-awareness-a-claim-commits).
|
|
101
|
-
- **Local coordination.** Messages live in app data outside your project. ACC doesn't
|
|
102
|
-
collect or share raw transcripts. Your clients use their usual model providers.
|
|
135
|
+
For an immediate update:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
acc update
|
|
139
|
+
```
|
|
103
140
|
|
|
104
|
-
|
|
141
|
+
An existing managed installation selects its new runtime through this command. Updates
|
|
142
|
+
also support an explicit opt-out and version pinning. Reinstalling preserves your update
|
|
143
|
+
preference; see [update controls](docs/UPGRADING.md#automatic-updates-after-installation).
|
|
144
|
+
If you fully uninstalled ACC 0.4.0 before upgrading and want automatic updates back, run
|
|
145
|
+
`acc update --auto on` once: that version's uninstall record lost the previous preference.
|
|
105
146
|
|
|
106
147
|
To remove the integrations:
|
|
107
148
|
|
|
@@ -109,7 +150,9 @@ To remove the integrations:
|
|
|
109
150
|
acc uninstall
|
|
110
151
|
```
|
|
111
152
|
|
|
112
|
-
ACC
|
|
153
|
+
ACC removes owned artifacts that still match its install record, preserves your edits and
|
|
154
|
+
coordination history, and pauses automatic updates. A later install remembers the update
|
|
155
|
+
preference.
|
|
113
156
|
|
|
114
157
|
## Go further
|
|
115
158
|
|
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();
|