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
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
5
|
+
import { AccError, EXIT, assertPortableId } from "@agents-can-communicate/protocol";
|
|
6
6
|
|
|
7
7
|
const SCHEMA_VERSION = 1;
|
|
8
8
|
|
|
@@ -23,11 +23,32 @@ const fileFor = (runtimeDir, harnessSessionId) => path.join(runtimeDir, "binding
|
|
|
23
23
|
*/
|
|
24
24
|
const isPid = value => Number.isInteger(value) && value > 0;
|
|
25
25
|
|
|
26
|
+
function assertIdentity(record, harnessSessionId) {
|
|
27
|
+
if (record?.schemaVersion !== SCHEMA_VERSION) {
|
|
28
|
+
throw new AccError(EXIT.DATA, "unknown session binding schemaVersion",
|
|
29
|
+
{ schemaVersion: record?.schemaVersion });
|
|
30
|
+
}
|
|
31
|
+
assertPortableId(record.accSessionId, "binding session id");
|
|
32
|
+
assertPortableId(record.generation, "binding generation");
|
|
33
|
+
if (typeof record.harnessSessionId !== "string" || record.harnessSessionId === ""
|
|
34
|
+
|| harnessSessionId !== undefined && record.harnessSessionId !== harnessSessionId) {
|
|
35
|
+
throw new AccError(EXIT.DATA, "session binding names a different or invalid harness session", {});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assertBindingDeadline(deadlineAt) {
|
|
40
|
+
if (deadlineAt !== undefined && Date.now() >= deadlineAt) {
|
|
41
|
+
throw new AccError(EXIT.CONFLICT, "hook deadline expired before binding publication", {});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
26
45
|
export async function storeSessionBinding({ runtimeDir, harnessSessionId, accSessionId,
|
|
27
|
-
generation, clientVersion, platform, clientPid }) {
|
|
46
|
+
generation, clientVersion, platform, clientPid, deadlineAt }) {
|
|
47
|
+
assertBindingDeadline(deadlineAt);
|
|
28
48
|
const file = fileFor(runtimeDir, harnessSessionId);
|
|
29
|
-
await mkdir(path.dirname(file), { recursive: true });
|
|
30
49
|
const record = { schemaVersion: SCHEMA_VERSION, harnessSessionId, accSessionId, generation };
|
|
50
|
+
assertIdentity(record);
|
|
51
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
31
52
|
if (typeof clientVersion === "string" && clientVersion !== "") record.clientVersion = clientVersion;
|
|
32
53
|
if (typeof platform === "string" && platform !== "") record.platform = platform;
|
|
33
54
|
// The vendor process this session runs in, resolved once at SessionStart.
|
|
@@ -45,6 +66,7 @@ export async function storeSessionBinding({ runtimeDir, harnessSessionId, accSes
|
|
|
45
66
|
await writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
46
67
|
// Replace rather than append: re-attaching supersedes the old generation, and
|
|
47
68
|
// two live bindings for one harness session would be worse than none.
|
|
69
|
+
assertBindingDeadline(deadlineAt);
|
|
48
70
|
await rename(temporary, file);
|
|
49
71
|
}
|
|
50
72
|
|
|
@@ -66,10 +88,7 @@ export async function loadSessionBinding({ runtimeDir, harnessSessionId }) {
|
|
|
66
88
|
throw new AccError(EXIT.DATA, "session binding is not valid JSON",
|
|
67
89
|
{ file, cause: error.message });
|
|
68
90
|
}
|
|
69
|
-
|
|
70
|
-
throw new AccError(EXIT.DATA, "unknown session binding schemaVersion",
|
|
71
|
-
{ file, schemaVersion: record?.schemaVersion });
|
|
72
|
-
}
|
|
91
|
+
assertIdentity(record, harnessSessionId);
|
|
73
92
|
const binding = { accSessionId: record.accSessionId, generation: record.generation };
|
|
74
93
|
if (typeof record.clientVersion === "string") binding.clientVersion = record.clientVersion;
|
|
75
94
|
if (typeof record.platform === "string") binding.platform = record.platform;
|
|
@@ -77,7 +96,8 @@ export async function loadSessionBinding({ runtimeDir, harnessSessionId }) {
|
|
|
77
96
|
return binding;
|
|
78
97
|
}
|
|
79
98
|
|
|
80
|
-
export async function clearSessionBinding({ runtimeDir, harnessSessionId }) {
|
|
99
|
+
export async function clearSessionBinding({ runtimeDir, harnessSessionId, deadlineAt }) {
|
|
100
|
+
assertBindingDeadline(deadlineAt);
|
|
81
101
|
await rm(fileFor(runtimeDir, harnessSessionId), { force: true });
|
|
82
102
|
}
|
|
83
103
|
|
|
@@ -107,7 +127,7 @@ export async function listSessionBindings({ runtimeDir }) {
|
|
|
107
127
|
if (!name.endsWith(".json")) continue;
|
|
108
128
|
const record = await readFile(path.join(dir, name), "utf8")
|
|
109
129
|
.then(JSON.parse).catch(() => null);
|
|
110
|
-
|
|
130
|
+
try { assertIdentity(record); } catch { continue; }
|
|
111
131
|
bindings.push({ harnessSessionId: record.harnessSessionId,
|
|
112
132
|
accSessionId: record.accSessionId, generation: record.generation,
|
|
113
133
|
...(typeof record.clientVersion === "string"
|
|
@@ -4,8 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
// Two clients keep configuration ACC must add to in TOML, and ACC ships without
|
|
5
5
|
// dependencies. Parsing and re-emitting the file would mean writing a TOML
|
|
6
6
|
// round-tripper and losing the user's comments and formatting to it. Instead ACC
|
|
7
|
-
//
|
|
8
|
-
//
|
|
7
|
+
// offers a delimited-region default. Clients that relocate comments must pass
|
|
8
|
+
// an ownership-aware cleanup callback; a comment boundary alone cannot establish
|
|
9
|
+
// ownership after the client has rewritten its configuration.
|
|
9
10
|
export const BEGIN = "# >>> agents-can-communicate (managed; edits here are overwritten)";
|
|
10
11
|
export const END = "# <<< agents-can-communicate";
|
|
11
12
|
|
|
@@ -40,17 +41,18 @@ export const tomlString = value =>
|
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* Replace ACC's region in a TOML file, creating the file if needed.
|
|
44
|
+
* Clients that rewrite comments must supply their own ownership-aware cleanup.
|
|
43
45
|
*
|
|
44
46
|
* The block goes at the end because a table header there closes whatever table
|
|
45
47
|
* preceded it: appended anywhere else, the user's last section would swallow
|
|
46
48
|
* ACC's keys.
|
|
47
49
|
*/
|
|
48
|
-
export async function writeTomlBlock(file, body) {
|
|
50
|
+
export async function writeTomlBlock(file, body, strip = stripBlock) {
|
|
49
51
|
const existing = await readFile(file, "utf8").catch(error => {
|
|
50
52
|
if (error.code === "ENOENT") return "";
|
|
51
53
|
throw error;
|
|
52
54
|
});
|
|
53
|
-
const withoutOurs =
|
|
55
|
+
const withoutOurs = strip(existing);
|
|
54
56
|
const separator = withoutOurs === "" || withoutOurs.endsWith("\n") ? "" : "\n";
|
|
55
57
|
await mkdir(path.dirname(file), { recursive: true });
|
|
56
58
|
await writeFile(file, `${withoutOurs}${separator}${renderBlock(body)}\n`);
|
|
@@ -58,13 +60,13 @@ export async function writeTomlBlock(file, body) {
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/** Remove ACC's region, reporting whether anything was there. */
|
|
61
|
-
export async function removeTomlBlock(file) {
|
|
63
|
+
export async function removeTomlBlock(file, strip = stripBlock) {
|
|
62
64
|
const existing = await readFile(file, "utf8").catch(error => {
|
|
63
65
|
if (error.code === "ENOENT") return null;
|
|
64
66
|
throw error;
|
|
65
67
|
});
|
|
66
68
|
if (existing === null) return false;
|
|
67
|
-
const stripped =
|
|
69
|
+
const stripped = strip(existing);
|
|
68
70
|
if (stripped === existing) return false;
|
|
69
71
|
await writeFile(file, stripped);
|
|
70
72
|
return true;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agents-can-communicate/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
|
-
".": "./src/index.mjs"
|
|
7
|
+
".": "./src/index.mjs",
|
|
8
|
+
"./managed-entry": "./src/managed-runtime/entry.mjs",
|
|
9
|
+
"./managed-worker": "./src/managed-runtime/worker.mjs"
|
|
8
10
|
},
|
|
9
11
|
"files": [
|
|
10
12
|
"src/"
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// Native adapters manage session lifecycle automatically. Manual CLI sessions
|
|
4
|
+
// use attach, heartbeat, and detach; both paths are described in command help.
|
|
5
5
|
//
|
|
6
6
|
// `--session` and `--generation` are optional on every agent-facing command:
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// attach
|
|
7
|
+
// an operator may instead configure the explicit owner pair in the environment
|
|
8
|
+
// (see session-owner.mjs). Public status/sync also work without an owner pair.
|
|
9
|
+
// A public session id alone proves nothing. Heartbeat and detach always require
|
|
10
|
+
// both arguments; attach creates and returns the pair.
|
|
11
11
|
export const COMMANDS = Object.freeze({
|
|
12
12
|
attach: { required: ["participant"], optional: ["harness", "cadence", "parent", "session"] },
|
|
13
13
|
heartbeat: { required: ["session", "generation"], optional: [] },
|
|
14
14
|
detach: { required: ["session", "generation"], optional: [] },
|
|
15
|
-
sync: { required: [], optional: ["session", "cursor", "limit", "scope"
|
|
15
|
+
sync: { required: [], optional: ["session", "generation", "cursor", "limit", "scope",
|
|
16
|
+
"message", "type"], flags: ["current"] },
|
|
16
17
|
work: { required: [], optional: ["session", "generation", "summary", "mode",
|
|
17
18
|
"state"], repeated: ["hint"], flags: ["clear"] },
|
|
18
19
|
claim: { required: ["resource"],
|
|
@@ -25,8 +26,8 @@ export const COMMANDS = Object.freeze({
|
|
|
25
26
|
optional: ["claim", "resource", "session", "generation", "authority", "reason"] },
|
|
26
27
|
message: { required: ["subject", "body"],
|
|
27
28
|
optional: ["session", "generation", "type", "obligation", "client-message-id"],
|
|
28
|
-
repeated: ["to"] },
|
|
29
|
-
inbox: { required: [], optional: ["session", "generation", "message"] },
|
|
29
|
+
repeated: ["to", "supersedes", "withdraws"] },
|
|
30
|
+
inbox: { required: [], optional: ["session", "generation", "message", "cursor", "limit"] },
|
|
30
31
|
reply: { required: ["message", "body"],
|
|
31
32
|
optional: ["session", "generation", "subject", "client-message-id"] },
|
|
32
33
|
// Asking another agent to do something as a message with a reply obligation.
|
|
@@ -36,13 +37,13 @@ export const COMMANDS = Object.freeze({
|
|
|
36
37
|
finish: { required: ["goal"], optional: ["session", "generation", "status", "to",
|
|
37
38
|
"client-message-id"],
|
|
38
39
|
repeated: ["completed", "remaining", "blocker"] },
|
|
39
|
-
status: { required: [], optional: ["participant"], flags: ["all"] },
|
|
40
|
+
status: { required: [], optional: ["session", "generation", "participant"], flags: ["all"] },
|
|
40
41
|
doctor: { required: [], optional: ["home"], flags: ["repair"] },
|
|
41
42
|
// The one command with a subcommand. Kept as an explicit list rather than a
|
|
42
43
|
// free positional: `acc config delete` should fail at the parser, not deep
|
|
43
44
|
// inside a handler that has already decided what to do.
|
|
44
|
-
config: { required: [], optional: [],
|
|
45
|
-
|
|
45
|
+
config: { required: [], optional: [], subcommands: ["init", "validate"],
|
|
46
|
+
subcommandOptions: { init: { flags: ["yes", "force"] }, validate: { flags: [] } } },
|
|
46
47
|
// No `--yes`: neither of these ever asked, so the flag agreed to nothing. It
|
|
47
48
|
// was accepted and read by nobody, which is a promise that a confirmation
|
|
48
49
|
// exists to be skipped.
|
|
@@ -57,7 +58,7 @@ export const COMMANDS = Object.freeze({
|
|
|
57
58
|
uninstall: { required: [], optional: ["home"], repeated: ["adapter"], flags: ["dry-run"] },
|
|
58
59
|
// Asking npm whether there is a newer ACC. The one command that touches the
|
|
59
60
|
// network, and never on the hook path.
|
|
60
|
-
update: { required: [], optional: [], flags: ["apply"] },
|
|
61
|
+
update: { required: [], optional: ["auto", "pin"], flags: ["check", "apply"] },
|
|
61
62
|
// The two things a person types first after installing from a registry. The
|
|
62
63
|
// CLI answered neither: `acc --version` and `acc --help` were both "unknown
|
|
63
64
|
// command", and `acc` on its own asked for a command without naming one.
|
|
@@ -65,10 +66,8 @@ export const COMMANDS = Object.freeze({
|
|
|
65
66
|
version: { required: [], optional: [] },
|
|
66
67
|
});
|
|
67
68
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
// so reading them anywhere in the argv would make `acc message --body "--help"`
|
|
71
|
-
// print the help instead of sending it.
|
|
69
|
+
// Leading aliases select a command. Command-local help is recognized while
|
|
70
|
+
// consuming options below, never by searching inside their values.
|
|
72
71
|
const ALIASES = Object.freeze({ "--help": "help", "-h": "help",
|
|
73
72
|
"--version": "version", "-v": "version", "-V": "version" });
|
|
74
73
|
|
|
@@ -80,26 +79,39 @@ function usage(message, details = {}) {
|
|
|
80
79
|
|
|
81
80
|
const camel = name => name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
82
81
|
|
|
82
|
+
export function commandSpec(name, subcommand) {
|
|
83
|
+
const spec = COMMANDS[name];
|
|
84
|
+
return { ...spec, ...spec.subcommandOptions?.[subcommand] };
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
export function parseArgs(argv) {
|
|
84
88
|
if (!Array.isArray(argv) || argv.length === 0) {
|
|
85
89
|
usage("a command is required - `acc help` lists them");
|
|
86
90
|
}
|
|
87
91
|
const [first, ...rest] = argv;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
92
|
+
let command = Object.hasOwn(ALIASES, first) ? ALIASES[first] : first;
|
|
93
|
+
let helpRequested = false;
|
|
94
|
+
if (command === "help" && rest[0] !== undefined && !rest[0].startsWith("-")) {
|
|
95
|
+
command = rest.shift();
|
|
96
|
+
helpRequested = true;
|
|
97
|
+
}
|
|
98
|
+
let spec = COMMANDS[command];
|
|
99
|
+
if (!Object.hasOwn(COMMANDS, command)) {
|
|
91
100
|
usage(`unknown command: ${command} - \`acc help\` lists them`, { command });
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
let tokens = rest;
|
|
95
104
|
let subcommand;
|
|
96
105
|
if (spec.subcommands !== undefined) {
|
|
97
|
-
[
|
|
98
|
-
|
|
106
|
+
if (rest[0] !== undefined && !rest[0].startsWith("-")) {
|
|
107
|
+
[subcommand, ...tokens] = rest;
|
|
108
|
+
}
|
|
109
|
+
if (subcommand !== undefined && !spec.subcommands.includes(subcommand)) {
|
|
99
110
|
usage(`${command} requires one of: ${spec.subcommands.join(", ")}`,
|
|
100
111
|
{ command, subcommand: subcommand ?? null });
|
|
101
112
|
}
|
|
102
113
|
}
|
|
114
|
+
spec = commandSpec(command, subcommand);
|
|
103
115
|
|
|
104
116
|
const repeated = new Set(spec.repeated ?? []);
|
|
105
117
|
const flags = new Set([...(spec.flags ?? []), "json"]);
|
|
@@ -117,6 +129,12 @@ export function parseArgs(argv) {
|
|
|
117
129
|
|
|
118
130
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
119
131
|
const token = tokens[index];
|
|
132
|
+
// A preceding value-taking option has already consumed its value. In
|
|
133
|
+
// particular, `--body --help` still sends the literal text "--help".
|
|
134
|
+
if (token === "--help" || token === "-h") {
|
|
135
|
+
helpRequested = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
120
138
|
if (!token.startsWith("--") || token.length === 2) usage(`unexpected argument: ${token}`);
|
|
121
139
|
const separator = token.indexOf("=");
|
|
122
140
|
const name = separator === -1 ? token.slice(2) : token.slice(2, separator);
|
|
@@ -146,6 +164,15 @@ export function parseArgs(argv) {
|
|
|
146
164
|
else options[key] = value;
|
|
147
165
|
}
|
|
148
166
|
|
|
167
|
+
if (helpRequested) {
|
|
168
|
+
return { command: "help", options: { helpCommand: command,
|
|
169
|
+
...(subcommand === undefined ? {} : { subcommand }),
|
|
170
|
+
...(options.json === true ? { json: true } : {}) } };
|
|
171
|
+
}
|
|
172
|
+
if (spec.subcommands !== undefined && subcommand === undefined) {
|
|
173
|
+
usage(`${command} requires one of: ${spec.subcommands.join(", ")}`,
|
|
174
|
+
{ command, subcommand: null });
|
|
175
|
+
}
|
|
149
176
|
for (const name of spec.required ?? []) {
|
|
150
177
|
if (!Object.hasOwn(options, camel(name))) {
|
|
151
178
|
usage(`${command} requires --${name}`, { command, option: name });
|
|
@@ -10,6 +10,7 @@ import { ALL_ADAPTERS, clientContext, probeTimeout } from "./install-command.mjs
|
|
|
10
10
|
import { describePresence } from "./main.mjs";
|
|
11
11
|
import { platformPaths } from "./platform-paths.mjs";
|
|
12
12
|
import { noticeUpdate } from "./update-check.mjs";
|
|
13
|
+
import { readControl } from "./managed-runtime/state.mjs";
|
|
13
14
|
import { diagnoseFilesystemStore, repairFilesystemStore }
|
|
14
15
|
from "@agents-can-communicate/storage-filesystem";
|
|
15
16
|
|
|
@@ -229,9 +230,13 @@ export async function runDoctor({ options, context, runtime }) {
|
|
|
229
230
|
const running = typeof runtime?.version === "function"
|
|
230
231
|
? await runtime.version().catch(() => null)
|
|
231
232
|
: null;
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
const manager = runtime?.managerRoot ? await readControl(runtime.managerRoot) : null;
|
|
234
|
+
const update = manager === null
|
|
235
|
+
? await noticeUpdate({ dataHome, running, env: runtime?.env ?? {},
|
|
236
|
+
now: Date.parse(clock.now()), get: runtime?.fetch, io: { readFile, writeFile, mkdir } })
|
|
237
|
+
: { checked: false, running, latest: manager.pending?.version ?? null,
|
|
238
|
+
newer: manager.pending !== null, auto: manager.auto, pin: manager.pin,
|
|
239
|
+
pending: manager.pending?.version ?? null, phase: manager.phase, notice: manager.notice };
|
|
235
240
|
|
|
236
241
|
// Before the store is read for anything else. `collectStatus` reads every
|
|
237
242
|
// record, so on the store this command exists to describe it threw first and
|
|
@@ -285,7 +290,7 @@ export async function runDoctor({ options, context, runtime }) {
|
|
|
285
290
|
remediation: [...adapters.flatMap(adapter => adapter.remediation),
|
|
286
291
|
// Said here rather than in its own line of prose, because this list is
|
|
287
292
|
// what a reader acts on and an upgrade is one more thing to run.
|
|
288
|
-
...(update.newer ? [`acc update
|
|
293
|
+
...(update.newer ? [`acc update # ${update.latest} is on npm, `
|
|
289
294
|
+ `you have ${running}`] : [])],
|
|
290
295
|
};
|
|
291
296
|
const installed = adapters.filter(adapter => adapter.installed).length;
|
|
@@ -303,6 +308,8 @@ export async function runDoctor({ options, context, runtime }) {
|
|
|
303
308
|
// claims that "active" means a model read anything.
|
|
304
309
|
...adapters.filter(adapter => adapter.present)
|
|
305
310
|
.map(adapter => ` ${adapter.displayName} native delivery: ${describeNative(adapter.nativeDelivery)}`),
|
|
311
|
+
...(manager === null ? [] : [` automatic updates ${manager.auto ? "on" : "off"}; ACC ${manager.active.version}`
|
|
312
|
+
+ (manager.pin ? `; pinned to ${manager.pin}` : ""), ...(manager.notice ? [` ${manager.notice}`] : [])]),
|
|
306
313
|
...data.remediation.map(line => ` ${line}`),
|
|
307
314
|
// `0 of 4` is a true line that reads as a broken machine, and on an
|
|
308
315
|
// MCP-only one it would read that way on every run forever. The server needs
|
|
@@ -16,9 +16,15 @@ export function hermeticEnv(env = process.env) {
|
|
|
16
16
|
return Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_")));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function createGitProbe({ run } = {}) {
|
|
20
|
-
const exec =
|
|
21
|
-
|
|
19
|
+
export function createGitProbe({ run, deadlineAt } = {}) {
|
|
20
|
+
const exec = async (cwd, args) => {
|
|
21
|
+
const remaining = deadlineAt === undefined ? 0 : deadlineAt - Date.now();
|
|
22
|
+
if (deadlineAt !== undefined && remaining <= 0) throw new Error("Git probe deadline expired");
|
|
23
|
+
return run === undefined
|
|
24
|
+
? (await execFileAsync("git", args, { cwd, env: hermeticEnv(), timeout: remaining,
|
|
25
|
+
killSignal: "SIGKILL" })).stdout.trim()
|
|
26
|
+
: run(cwd, args);
|
|
27
|
+
};
|
|
22
28
|
|
|
23
29
|
return async function gitProbe({ cwd }) {
|
|
24
30
|
const commonDir = await exec(cwd, ["rev-parse", "--path-format=absolute",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CLAIM_ENFORCEMENTS, CLAIM_MODES, GENERIC_MESSAGE_KINDS, HANDOFF_STATUSES,
|
|
2
|
+
INTENT_MODES, INTENT_STATES, MESSAGE_KINDS, OBLIGATIONS } from "@agents-can-communicate/protocol";
|
|
3
|
+
import { LIVE_POLICIES } from "@agents-can-communicate/installer";
|
|
4
|
+
|
|
5
|
+
import { COMMANDS, commandSpec } from "./args.mjs";
|
|
6
|
+
import { ALL_ADAPTERS } from "./install-command.mjs";
|
|
7
|
+
import { needsOwner } from "./session-owner.mjs";
|
|
2
8
|
|
|
3
9
|
/**
|
|
4
10
|
* What `acc` says to a person who has just installed it.
|
|
@@ -7,15 +13,14 @@ import { COMMANDS } from "./args.mjs";
|
|
|
7
13
|
* is always listed. Only the heading a command sits under and its one line are
|
|
8
14
|
* written here, and the tests prove both cover every command in the table.
|
|
9
15
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* answer at the moment it is actually needed.
|
|
16
|
+
* Per-command help also reads the parser's option table, and exposes the
|
|
17
|
+
* protocol's enum choices before a caller has to attempt a mutation.
|
|
13
18
|
*/
|
|
14
19
|
const GROUPS = Object.freeze([
|
|
15
20
|
["Set up", ["install", "uninstall", "doctor", "config"]],
|
|
16
21
|
["In a session", ["status", "sync", "work", "claim", "release", "inbox", "reply",
|
|
17
22
|
"ack", "message", "request", "finish"]],
|
|
18
|
-
["
|
|
23
|
+
["Session lifecycle", ["attach", "heartbeat", "detach"]],
|
|
19
24
|
["About acc", ["help", "version", "update"]],
|
|
20
25
|
]);
|
|
21
26
|
|
|
@@ -25,22 +30,22 @@ const SUMMARY = Object.freeze({
|
|
|
25
30
|
doctor: "clients, versions, install health, and what to run next",
|
|
26
31
|
config: "write or check acc.workspace.json (init | validate)",
|
|
27
32
|
status: "who else is here, what they hold, how protected this workspace is",
|
|
28
|
-
sync: "
|
|
33
|
+
sync: "events since a cursor, or bounded message history",
|
|
29
34
|
work: "publish what this session is doing, or --clear when it has stopped",
|
|
30
35
|
claim: "reserve a resource; exit 5 when someone else already holds it",
|
|
31
36
|
release: "give a claim back",
|
|
32
37
|
ack: "answer a message that asked for one, so it stops asking",
|
|
33
38
|
message: "send a typed message to named participants",
|
|
34
|
-
inbox: "
|
|
39
|
+
inbox: "list pending message headers; --message reads one complete message",
|
|
35
40
|
reply: "reply to one message and acknowledge it in the same operation",
|
|
36
41
|
request: "ask another agent to do something in a reply-required message",
|
|
37
42
|
finish: "write the handoff and release what this session held",
|
|
38
|
-
attach: "open a session
|
|
43
|
+
attach: "open a manual session and return its session/generation pair",
|
|
39
44
|
heartbeat: "say the session is still alive",
|
|
40
45
|
detach: "close a session",
|
|
41
|
-
help: "
|
|
46
|
+
help: "list commands, or inspect one with acc help <command>",
|
|
42
47
|
version: "print the version that is installed",
|
|
43
|
-
update: "
|
|
48
|
+
update: "install a newer acc and refresh clients; --check only reports",
|
|
44
49
|
});
|
|
45
50
|
|
|
46
51
|
/** The same list `acc help --json` returns, so a tool can read it too. */
|
|
@@ -59,6 +64,96 @@ export function describeCommands() {
|
|
|
59
64
|
const DOCS = "https://github.com/automatis-tools/agents-can-communicate"
|
|
60
65
|
+ "/blob/main/docs/CLI.md";
|
|
61
66
|
|
|
67
|
+
const ADAPTER_IDS = Object.freeze(ALL_ADAPTERS().map(adapter => adapter.id));
|
|
68
|
+
const CHOICES = Object.freeze({
|
|
69
|
+
work: { mode: INTENT_MODES, state: INTENT_STATES },
|
|
70
|
+
claim: { mode: CLAIM_MODES, enforcement: CLAIM_ENFORCEMENTS },
|
|
71
|
+
finish: { status: HANDOFF_STATUSES },
|
|
72
|
+
message: { type: GENERIC_MESSAGE_KINDS, obligation: OBLIGATIONS },
|
|
73
|
+
sync: { scope: ["delta", "full", "history"], type: MESSAGE_KINDS },
|
|
74
|
+
install: { delivery: LIVE_POLICIES, adapter: ADAPTER_IDS },
|
|
75
|
+
uninstall: { adapter: ADAPTER_IDS },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const NOTES = Object.freeze({
|
|
79
|
+
attach: ["Native hooks already manage their own session. Attach only for a manual CLI session.",
|
|
80
|
+
"Keep the returned session and generation together for subsequent calls.",
|
|
81
|
+
"--cadence is a positive interval in milliseconds."],
|
|
82
|
+
work: ["Provide --summary unless using --clear. Default mode: edit; default state: active."],
|
|
83
|
+
claim: ["--lease is in seconds. Default mode: exclusive; default enforcement: advisory.",
|
|
84
|
+
"For files, use file:src/a.mjs or file:src/**. A claim may conflict with a peer."],
|
|
85
|
+
release: ["Provide --claim or --resource. Releasing another owner's claim requires human or policy authority."],
|
|
86
|
+
message: ["Default type: note. question/request require --to and obligation reply; note uses none.",
|
|
87
|
+
"decision permits none, or acknowledge when addressed. Use reply for answers and finish for handoffs.",
|
|
88
|
+
"For decisions, repeat --supersedes or --withdraws with 1..16 prior decision IDs; never combine the two.",
|
|
89
|
+
"Changes inherit target recipients and authors, including offline participants. Any peer may record a change; competing branches remain visible."],
|
|
90
|
+
inbox: ["Without --message, returns read-only {items, nextCursor} summary pages, newest first.",
|
|
91
|
+
"Default: 20 items, at most 12000 bytes of formatted page JSON. --limit accepts 1..500.",
|
|
92
|
+
"Continue with --cursor <nextCursor>; omit it to see new arrivals. Listing never retrieves bodies.",
|
|
93
|
+
"Replaced decisions leave the pending list without changing receipts; exact inbox and history retain them.",
|
|
94
|
+
"--message reads one complete addressed message, including acknowledged mail; do not combine with cursor or limit.",
|
|
95
|
+
"Exact reads return a one-item array. Acknowledged inspection leaves the receipt unchanged."],
|
|
96
|
+
sync: ["Default delta uses 16-digit event cursors. full adds an unbounded diagnostic snapshot.",
|
|
97
|
+
"history lists read-only message summaries, newest first: default 20 items, at most 12000 bytes of formatted page JSON.",
|
|
98
|
+
"With history, --type filters before paging; --cursor takes the complete message ID in nextCursor.",
|
|
99
|
+
"history --type decision --current lists terminal decisions and withdrawals, including conflicts. Current means no recorded successor, not consensus.",
|
|
100
|
+
"history --message reads one complete historical message without changing receipts; do not combine with cursor, limit, or type."],
|
|
101
|
+
reply: ["Returns the outgoing message/delivery plus the original acknowledged receipt."],
|
|
102
|
+
finish: ["Default status: partial. Records a handoff, releases claims, and closes this ACC session."],
|
|
103
|
+
config: ["init writes optional workspace configuration; validate only checks it.",
|
|
104
|
+
"Use acc config <subcommand> --help for its options."],
|
|
105
|
+
update: ["Run acc update to install the latest release and refresh client integrations.",
|
|
106
|
+
"Use acc update --check to only report whether a newer release exists.",
|
|
107
|
+
"Automatic updates are on after acc install; activation waits for active clients and ACC processes.",
|
|
108
|
+
"--auto on|off changes automatic updates; --pin VERSION holds an exact stable version; --pin none follows stable releases.",
|
|
109
|
+
"--apply remains accepted as a compatibility alias for acc update."],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const CONFIG_HELP = Object.freeze({
|
|
113
|
+
init: { summary: "write optional workspace configuration",
|
|
114
|
+
notes: ["init requires confirmation or --yes. --force bypasses the active-session check; an existing config is never overwritten."] },
|
|
115
|
+
validate: { summary: "check optional workspace configuration",
|
|
116
|
+
notes: ["Checks acc.workspace.json in the selected directory without writing it."] },
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export function describeCommand(name, subcommand) {
|
|
120
|
+
const spec = commandSpec(name, subcommand);
|
|
121
|
+
const detail = name === "config" ? CONFIG_HELP[subcommand] : undefined;
|
|
122
|
+
return { name, summary: detail?.summary ?? SUMMARY[name],
|
|
123
|
+
...(subcommand === undefined ? {} : { subcommand }),
|
|
124
|
+
required: spec.required ?? [], optional: spec.optional ?? [],
|
|
125
|
+
repeated: spec.repeated ?? [], flags: spec.flags ?? [],
|
|
126
|
+
subcommands: spec.subcommands ?? [], choices: CHOICES[name] ?? {},
|
|
127
|
+
notes: [...(detail?.notes ?? NOTES[name] ?? []), ...(needsOwner(name)
|
|
128
|
+
? ["Use your own --session and --generation from the ACC hook or manual attach; never a peer's pair."]
|
|
129
|
+
: [])] };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function commandHelpText(command) {
|
|
133
|
+
const { name, summary, subcommand, subcommands, choices, notes } = command;
|
|
134
|
+
const suffix = subcommand !== undefined ? ` ${subcommand}`
|
|
135
|
+
: subcommands.length > 0 ? ` <${subcommands.join("|")}>` : "";
|
|
136
|
+
const lines = [`acc ${name} - ${summary}`, "", `Usage: acc ${name}${suffix} [options]`, ""];
|
|
137
|
+
for (const [key, label] of [["required", "Required"], ["optional", "Optional"],
|
|
138
|
+
["repeated", "Repeatable"], ["flags", "Flags"]]) {
|
|
139
|
+
if (command[key].length === 0) continue;
|
|
140
|
+
lines.push(`${label}:`);
|
|
141
|
+
for (const option of command[key]) {
|
|
142
|
+
const value = key === "flags" ? "" : ` <${choices[option]?.join("|") ?? "value"}>`;
|
|
143
|
+
lines.push(` --${option}${value}`);
|
|
144
|
+
}
|
|
145
|
+
lines.push("");
|
|
146
|
+
}
|
|
147
|
+
const globals = ["--json", "--help", "-h"];
|
|
148
|
+
if (!["install", "uninstall", "update", "help", "version"].includes(name)) {
|
|
149
|
+
globals.push("--cwd <path>");
|
|
150
|
+
if (name !== "config" || subcommand !== "validate") globals.push("--workspace <config>");
|
|
151
|
+
}
|
|
152
|
+
lines.push(`Global: ${globals.join(", ")}`, "", ...notes,
|
|
153
|
+
`Full reference: ${DOCS}`);
|
|
154
|
+
return lines.join("\n");
|
|
155
|
+
}
|
|
156
|
+
|
|
62
157
|
export function helpText() {
|
|
63
158
|
const width = Math.max(...Object.keys(COMMANDS).map(name => name.length)) + 4;
|
|
64
159
|
const lines = ["acc - coordinate the agent sessions you already opened", ""];
|
|
@@ -69,10 +164,10 @@ export function helpText() {
|
|
|
69
164
|
}
|
|
70
165
|
lines.push("");
|
|
71
166
|
}
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
167
|
+
// The longer reference is linked to the repository named in the manifest;
|
|
168
|
+
// installed command help supplies local syntax without needing that link.
|
|
169
|
+
lines.push("Every command takes --json for machine output.",
|
|
170
|
+
"Use acc <command> --help or acc help <command> for options and accepted values.",
|
|
76
171
|
`Full reference: ${DOCS}`);
|
|
77
172
|
return lines.join("\n");
|
|
78
173
|
}
|
|
@@ -12,6 +12,11 @@ import { LIVE_POLICIES, applyPlan, detectInstallation, livePolicyOf,
|
|
|
12
12
|
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
13
13
|
|
|
14
14
|
import { platformPaths } from "./platform-paths.mjs";
|
|
15
|
+
import { installManaged, uninstallManaged } from "./managed-runtime/install.mjs";
|
|
16
|
+
|
|
17
|
+
// Kept cohesive above 300 lines because this is the install command boundary:
|
|
18
|
+
// detection, consent, planning, application, and reporting share one context
|
|
19
|
+
// and result contract that would otherwise be duplicated across modules.
|
|
15
20
|
|
|
16
21
|
// Where each client keeps its own configuration. All of it derives from one
|
|
17
22
|
// home, so a test - or an operator with a second account - can point the whole
|
|
@@ -19,11 +24,13 @@ import { platformPaths } from "./platform-paths.mjs";
|
|
|
19
24
|
// Each client keeps its own directory under the user's home, and an adapter
|
|
20
25
|
// pointed at the home itself writes beside them rather than inside them. That
|
|
21
26
|
// install reports success and the client never reads a byte of it.
|
|
22
|
-
export const clientContext = (home, stateRoot, { shell = null, env = {} } = {}) => ({
|
|
27
|
+
export const clientContext = (home, stateRoot, { shell = null, env = {}, dataHome } = {}) => ({
|
|
23
28
|
home,
|
|
29
|
+
...(dataHome === undefined ? {} : { dataHome }),
|
|
24
30
|
configDir: path.join(home, ".claude"),
|
|
25
31
|
agentsHome: home,
|
|
26
|
-
codexHome:
|
|
32
|
+
codexHome: typeof env.CODEX_HOME === "string" && env.CODEX_HOME !== ""
|
|
33
|
+
? env.CODEX_HOME : path.join(home, ".codex"),
|
|
27
34
|
kimiHome: path.join(home, ".kimi-code"),
|
|
28
35
|
grokHome: path.join(home, ".grok"),
|
|
29
36
|
// The user's login shell and PATH, for the optional native shell bootstrap:
|
|
@@ -142,6 +149,9 @@ export function describeOutcome({ action, acted, failed = [], skipped = [],
|
|
|
142
149
|
...operations.filter(operation => operation.applied
|
|
143
150
|
&& typeof operation.deliveryDiagnostic === "string")
|
|
144
151
|
.map(operation => ` ${operation.deliveryDiagnostic}`),
|
|
152
|
+
...operations.filter(operation => operation.applied)
|
|
153
|
+
.flatMap(operation => (operation.needsAction ?? [])
|
|
154
|
+
.map(step => ` ${operation.adapterId}: ${step}`)),
|
|
145
155
|
...skipped.map(entry => ` skip ${entry.adapterId}: ${entry.reason}`),
|
|
146
156
|
...failed.map(entry => ` ${entry.adapterId}: ${entry.error}`),
|
|
147
157
|
// Said once, where it is needed: the reader has just been shown a list of
|
|
@@ -293,7 +303,7 @@ export async function runInstallCommand({ options, runtime, action = "install" }
|
|
|
293
303
|
const { data: dataHome } = platformPaths({ platform: runtime.platform,
|
|
294
304
|
env: runtime.env ?? {} });
|
|
295
305
|
const context = clientContext(home, path.join(dataHome, "acc"),
|
|
296
|
-
{ shell: shellOf(runtime.env), env: runtime.env ?? {} });
|
|
306
|
+
{ shell: shellOf(runtime.env), env: runtime.env ?? {}, dataHome });
|
|
297
307
|
|
|
298
308
|
const detected = await detectInstallation({ adapters, context,
|
|
299
309
|
probeTimeoutMs: probeTimeout(runtime.env) });
|
|
@@ -327,7 +337,15 @@ export async function runInstallCommand({ options, runtime, action = "install" }
|
|
|
327
337
|
const plan = planInstallation({ adapters, detected, context, action, recorded,
|
|
328
338
|
accVersion, allowDowngrade: options.downgrade === true, requested,
|
|
329
339
|
deliveryByAdapter: decided.deliveryByAdapter });
|
|
330
|
-
const
|
|
340
|
+
const apply = (paths = {}) => applyPlan({ plan, adapters, context: { ...context, ...paths },
|
|
341
|
+
dataHome, dryRun, accVersion, activation: { bootstrap: paths.bootstrap } });
|
|
342
|
+
const result = action === "install" && !dryRun && runtime.packageRoot
|
|
343
|
+
? await installManaged({ packageRoot: runtime.packageRoot,
|
|
344
|
+
managerRoot: path.join(dataHome, "acc", "runtime"), dataHome, home, cwd: options.cwd ?? runtime.cwd,
|
|
345
|
+
targets: plan.operations.map(operation => operation.adapterId), env: runtime.env, apply })
|
|
346
|
+
: action === "uninstall" && !dryRun && runtime.managerRoot
|
|
347
|
+
? await uninstallManaged({ managerRoot: runtime.managerRoot, apply })
|
|
348
|
+
: await apply();
|
|
331
349
|
|
|
332
350
|
const acted = actedOn(result);
|
|
333
351
|
if (dryRun) {
|