agents-can-communicate 0.10.3 → 0.11.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 +29 -21
- package/bin/acc-indicator.mjs +5 -0
- package/docs/CAPABILITIES.md +3 -0
- package/docs/CLI.md +34 -7
- package/docs/MCP.md +42 -2
- package/docs/PROTOCOL.md +19 -0
- package/docs/STATUS_INDICATOR.md +128 -0
- package/docs/TROUBLESHOOTING.md +10 -0
- package/docs/UPGRADING.md +16 -0
- package/docs/WHY_ACC.md +14 -8
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +29 -5
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +3 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/indicator-install.mjs +80 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/indicator-render.mjs +8 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +7 -4
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/hooks/indicator.mjs +67 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +29 -5
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +1 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +16 -5
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +48 -5
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +9 -6
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +16 -5
- 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 +29 -5
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +29 -5
- package/node_modules/@agents-can-communicate/adapter-grok/src/adapter.mjs +4 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/indicator-install.mjs +30 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/indicator.mjs +23 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +9 -5
- 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 +29 -5
- package/node_modules/@agents-can-communicate/adapter-kimi/src/adapter.mjs +4 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/src/indicator-install.mjs +30 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/src/indicator.mjs +17 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +12 -6
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +1 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +5 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/indicator-command.mjs +14 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/indicator-text.mjs +13 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +2 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/offer-observation-order.mjs +12 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/offer-observation-writer.mjs +37 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/offer-observation.mjs +47 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-indicator.mjs +141 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-scan.mjs +99 -0
- package/node_modules/@agents-can-communicate/cli/package.json +2 -1
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +5 -3
- package/node_modules/@agents-can-communicate/cli/src/claim-command.mjs +64 -0
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +15 -8
- package/node_modules/@agents-can-communicate/cli/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/cli/src/indicator-command.mjs +65 -0
- package/node_modules/@agents-can-communicate/cli/src/indicator-previous-command.mjs +84 -0
- package/node_modules/@agents-can-communicate/cli/src/indicator-previous-windows.mjs +8 -0
- package/node_modules/@agents-can-communicate/cli/src/indicator.mjs +112 -0
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +4 -0
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +8 -54
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +19 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +5 -3
- package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +2 -0
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/claim-selectors.mjs +62 -0
- package/node_modules/@agents-can-communicate/core/src/claims.mjs +88 -81
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/refresh-binding.mjs +9 -6
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +19 -5
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +3 -0
- package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +1 -0
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +2 -1
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +11 -2
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/src/claim-tools.mjs +56 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/input-validator.mjs +9 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +7 -8
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +27 -9
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/session-read.mjs +13 -0
- package/package.json +3 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { defaultIndicator, shellQuote, shortPath, windowsHookCommand, writeForeignJson }
|
|
4
|
+
from "@agents-can-communicate/adapter-sdk";
|
|
5
|
+
import { agySettingsPath } from "./allow-rule.mjs";
|
|
6
|
+
|
|
7
|
+
const io = { readFile, writeFile, mkdir };
|
|
8
|
+
const load = async file => {
|
|
9
|
+
try { return JSON.parse(await readFile(file, "utf8")); }
|
|
10
|
+
catch (error) { if (error.code === "ENOENT") return null; throw error; }
|
|
11
|
+
};
|
|
12
|
+
const markerPath = ({ dataHome, home }) => path.join(dataHome ?? path.join(home, ".gemini", "config"),
|
|
13
|
+
"acc", "adapter-antigravity", "indicator.json");
|
|
14
|
+
|
|
15
|
+
/** Own only the wrapper command and the fields ACC actually changed. A later
|
|
16
|
+
* user edit wins; the original status line is restored only where still ours. */
|
|
17
|
+
export async function configureIndicator(context, enabled) {
|
|
18
|
+
const file = agySettingsPath(context.home), marker = markerPath(context);
|
|
19
|
+
const saved = await load(marker);
|
|
20
|
+
if (!enabled && !saved) return [];
|
|
21
|
+
const existing = await load(file);
|
|
22
|
+
if (saved && (saved.schemaVersion !== 1 || typeof saved.installed?.command !== "string"
|
|
23
|
+
|| !Array.isArray(saved.owned) || !saved.owned.every(key => typeof key === "string"))) {
|
|
24
|
+
throw new Error("ACC status-line ownership is invalid; keep the user's settings and run acc doctor");
|
|
25
|
+
}
|
|
26
|
+
if (!enabled) {
|
|
27
|
+
if (!saved) return [];
|
|
28
|
+
if (existing?.statusLine?.command === saved.installed.command) {
|
|
29
|
+
const current = { ...existing.statusLine };
|
|
30
|
+
for (const key of saved.owned) {
|
|
31
|
+
const value = saved.installed[key];
|
|
32
|
+
if (JSON.stringify(current[key]) !== JSON.stringify(value)) continue;
|
|
33
|
+
if (Object.hasOwn(saved.previous ?? {}, key)) current[key] = saved.previous[key];
|
|
34
|
+
else delete current[key];
|
|
35
|
+
}
|
|
36
|
+
const next = { ...existing };
|
|
37
|
+
if (Object.keys(current).length) next.statusLine = current;
|
|
38
|
+
else if (saved.previous !== null) next.statusLine = saved.previous;
|
|
39
|
+
else delete next.statusLine;
|
|
40
|
+
if (saved.createdFile && Object.keys(next).length === 0) await rm(file, { force: true });
|
|
41
|
+
else await writeForeignJson(file, next, io);
|
|
42
|
+
}
|
|
43
|
+
await rm(marker, { force: true });
|
|
44
|
+
return [file];
|
|
45
|
+
}
|
|
46
|
+
const ours = saved && existing?.statusLine?.command === saved.installed.command;
|
|
47
|
+
// Keep the marker after a user replacement or removal, so later updates do
|
|
48
|
+
// not mistake it for a first install. Explicit disable clears this history.
|
|
49
|
+
if (saved && !ours) return [];
|
|
50
|
+
const previous = ours ? saved.previous : existing?.statusLine ?? null;
|
|
51
|
+
if (previous !== null && (previous.type !== "command" || typeof previous.command !== "string")) {
|
|
52
|
+
throw new Error("ACC cannot compose this statusLine type; keep it and configure the indicator manually");
|
|
53
|
+
}
|
|
54
|
+
const node = context.node ?? process.execPath;
|
|
55
|
+
const indicator = context.indicatorRunner ?? defaultIndicator();
|
|
56
|
+
// Each path is one shell argument. The user's old shell program stays in a
|
|
57
|
+
// JSON record; interpolating it into this command would change its quoting.
|
|
58
|
+
let command;
|
|
59
|
+
if ((context.hostPlatform ?? process.platform) === "win32") {
|
|
60
|
+
const shim = path.join(context.home, ".gemini", "config", "acc", "indicator.mjs");
|
|
61
|
+
await mkdir(path.dirname(shim), { recursive: true });
|
|
62
|
+
await writeFile(shim, `import { spawnSync } from "node:child_process";\n`
|
|
63
|
+
+ `const child = spawnSync(${JSON.stringify(node)}, ${JSON.stringify([indicator,
|
|
64
|
+
"--adapter", "antigravity", "--previous", marker])}, {stdio:"inherit",windowsHide:true});\n`
|
|
65
|
+
+ `process.exitCode = child.status ?? 0;\n`);
|
|
66
|
+
command = windowsHookCommand("unquoted", { node, shim: await shortPath(shim), args: [] }).trimEnd();
|
|
67
|
+
} else command = [node, indicator, "--adapter", "antigravity", "--previous", marker]
|
|
68
|
+
.map(shellQuote).join(" ");
|
|
69
|
+
const installed = ours ? { ...existing.statusLine, command }
|
|
70
|
+
: { ...previous, type: "command", command, enabled: true,
|
|
71
|
+
stack_with_default: previous?.stack_with_default ?? true };
|
|
72
|
+
const owned = ours ? saved.owned.filter(key =>
|
|
73
|
+
JSON.stringify(existing.statusLine[key]) === JSON.stringify(saved.installed[key]))
|
|
74
|
+
: Object.keys(installed).filter(key => JSON.stringify(installed[key]) !== JSON.stringify(previous?.[key]));
|
|
75
|
+
await mkdir(path.dirname(marker), { recursive: true });
|
|
76
|
+
await writeFile(marker, JSON.stringify({ schemaVersion: 1, previous, installed, owned,
|
|
77
|
+
createdFile: saved?.createdFile ?? existing === null }) + "\n");
|
|
78
|
+
await writeForeignJson(file, { ...existing, statusLine: installed }, io);
|
|
79
|
+
return [file];
|
|
80
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Antigravity consumes ANSI from a command whose stdout is a pipe, not a TTY.
|
|
2
|
+
// Foreground and intensity affect only the glyph. Never set a background.
|
|
3
|
+
export function renderAntigravityIndicator(report) {
|
|
4
|
+
const rgb = report.health === "ready" ? "44;122;57"
|
|
5
|
+
: report.health === "problem" ? "150;108;30" : "102;102;102";
|
|
6
|
+
return report.label.replace(/^ACC ([●!…])/u,
|
|
7
|
+
(_, glyph) => `ACC \x1b[22;1;38;2;${rgb}m${glyph}\x1b[22;39m`);
|
|
8
|
+
}
|
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
|
|
8
|
-
import { bakeSkillCommand, blankJson, defaultAntigravityRelay, isShellWord, mergeEnv, ownVersion,
|
|
8
|
+
import { bakeSkillCommand, blankJson, compareVersionOrder, versionOrder, defaultAntigravityRelay, isShellWord, mergeEnv, ownVersion,
|
|
9
9
|
removeIfEmpty, shellQuote, shortPath, stampPluginVersion, windowsHookCommand, writeCliShim,
|
|
10
10
|
writeForeignJson, writeHookShim, writeNodeShim }
|
|
11
11
|
from "@agents-can-communicate/adapter-sdk";
|
|
@@ -15,6 +15,7 @@ import { agySettingsPath, cliWrapperPath, ensureAllowRule, inspectAllowRule, ins
|
|
|
15
15
|
withdrawAllowRule } from "./allow-rule.mjs";
|
|
16
16
|
import { ensureDesktopRule, inspectDesktopRule, withdrawDesktopRule } from "./desktop-allow-rule.mjs";
|
|
17
17
|
import { relayShimName, relayStartCommand, runningRelays, stopRelays } from "./relays.mjs";
|
|
18
|
+
import { configureIndicator } from "./indicator-install.mjs";
|
|
18
19
|
|
|
19
20
|
const run = promisify(execFile);
|
|
20
21
|
|
|
@@ -425,8 +426,10 @@ export async function installAntigravity(context) {
|
|
|
425
426
|
const allow = await ensureAllowRule(context);
|
|
426
427
|
// Antigravity 2.0 keeps grants of its own, read only where it has run.
|
|
427
428
|
const desktop = await ensureDesktopRule(context);
|
|
429
|
+
const indicatorChanges = await configureIndicator(context, context.indicator === "on"
|
|
430
|
+
&& compareVersionOrder(versionOrder(context.clientVersion), versionOrder("1.0.6")) >= 0);
|
|
428
431
|
const changes = [shimDir(home), file, pluginInstallPath(home), vendorManifestPath(home),
|
|
429
|
-
...allow.changes, ...desktop.changes];
|
|
432
|
+
...allow.changes, ...desktop.changes, ...indicatorChanges];
|
|
430
433
|
|
|
431
434
|
const diagnostics = [...allow.diagnostics, ...desktop.diagnostics];
|
|
432
435
|
const needsAction = [...allow.needsAction, ...desktop.needsAction];
|
|
@@ -495,7 +498,7 @@ export async function installAntigravity(context) {
|
|
|
495
498
|
*/
|
|
496
499
|
export async function uninstallAntigravity(context) {
|
|
497
500
|
const { home, keep = [] } = context;
|
|
498
|
-
const changes =
|
|
501
|
+
const changes = await configureIndicator(context, false);
|
|
499
502
|
const diagnostics = [];
|
|
500
503
|
// Nothing may keep running once ACC is gone. A relay holds a live session
|
|
501
504
|
// endpoint in memory, so it is stopped here rather than left to find out.
|
|
@@ -744,7 +747,7 @@ export function planAntigravityInstall(context) {
|
|
|
744
747
|
// match: a wrapper path that needs quotes gets none.
|
|
745
748
|
const plugin = [{ path: pluginInstallPath(context.home), kind: "merge" },
|
|
746
749
|
{ path: vendorManifestPath(context.home), kind: "merge" },
|
|
747
|
-
...(isShellWord(cliWrapperPath(context.home, context.hostPlatform))
|
|
750
|
+
...(context.indicator === "on" || isShellWord(cliWrapperPath(context.home, context.hostPlatform))
|
|
748
751
|
? [{ path: agySettingsPath(context.home), kind: "merge" }] : [])];
|
|
749
752
|
if (locationChoice(context) !== null) {
|
|
750
753
|
return [shim, ...plugin,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Interpreter and read-only entrypoint are baked by the installer. No prompts,
|
|
2
|
+
// transcript access, model turns, ACC writes or transport probes are needed.
|
|
3
|
+
const NODE = __ACC_NODE__;
|
|
4
|
+
const INDICATOR = __ACC_INDICATOR__;
|
|
5
|
+
|
|
6
|
+
let report = { label: "ACC …", health: "starting", detail: "Reading ACC state." };
|
|
7
|
+
let busy = false, sessionId = null, startedAt = 0, lastRead = 0, announced = null;
|
|
8
|
+
|
|
9
|
+
async function refresh($) {
|
|
10
|
+
if (busy) return;
|
|
11
|
+
const id = await $.session.id();
|
|
12
|
+
const now = await $.clock.now();
|
|
13
|
+
if (id !== sessionId) {
|
|
14
|
+
sessionId = id; startedAt = now; lastRead = 0; announced = null;
|
|
15
|
+
report = { label: "ACC …", health: "starting", detail: "Reading ACC state." };
|
|
16
|
+
}
|
|
17
|
+
if (now - lastRead < (now - startedAt < 5000 ? 1000 : 5000)) return;
|
|
18
|
+
lastRead = now; busy = true;
|
|
19
|
+
try {
|
|
20
|
+
const value = await $.process.run([NODE, INDICATOR, "--adapter", "claude_code",
|
|
21
|
+
"--native-session", id, "--json"], { timeoutMs: 1200 });
|
|
22
|
+
const next = JSON.parse(value.stdout);
|
|
23
|
+
if (await $.session.id() !== id) return;
|
|
24
|
+
if (next.reasonCode === "not_registered" && now - startedAt < 5000) {
|
|
25
|
+
report = { label: "ACC …", health: "starting",
|
|
26
|
+
detail: "Waiting up to five seconds for the session hooks to register ACC." };
|
|
27
|
+
} else report = next;
|
|
28
|
+
} catch {
|
|
29
|
+
report = { label: "ACC !", health: "problem", reasonCode: "indicator_unavailable",
|
|
30
|
+
detail: "The ACC indicator could not read this session's state.",
|
|
31
|
+
action: "Run acc install --adapter claude_code --indicator on, then /reload-plugins." };
|
|
32
|
+
} finally { busy = false; }
|
|
33
|
+
if (report.health === "problem" && report.reasonCode !== announced) {
|
|
34
|
+
await $.ui.log([report.detail, report.action, "Details: /acc-status"].filter(Boolean).join(" "));
|
|
35
|
+
announced = report.reasonCode;
|
|
36
|
+
} else if (report.health === "ready") announced = null;
|
|
37
|
+
$.ui.invalidate("ui.render");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function register(on) {
|
|
41
|
+
on("session.start", async ($, e, next) => {
|
|
42
|
+
$.clock.every(1000, async () => { await refresh($); });
|
|
43
|
+
await refresh($);
|
|
44
|
+
await $.command.register({ name: "acc-status", immediate: true,
|
|
45
|
+
description: "Explain this chat's ACC indicator and its next recovery step" });
|
|
46
|
+
return next(e);
|
|
47
|
+
});
|
|
48
|
+
on("command.run", { command: "acc-status" }, async ($) => {
|
|
49
|
+
lastRead = 0;
|
|
50
|
+
await refresh($);
|
|
51
|
+
return { text: [report.label, report.detail, report.action].filter(Boolean).join("\n") };
|
|
52
|
+
});
|
|
53
|
+
on("ui.render", { component: "SessionMode" }, async ($, e, next) => {
|
|
54
|
+
const { Box, Text } = $.ui.resolve(e);
|
|
55
|
+
const symbol = report.health === "ready" ? "●" : report.health === "problem" ? "!" : "…";
|
|
56
|
+
const color = report.health === "ready" ? "#2c7a39" : report.health === "problem" ? "warning" : "inactive";
|
|
57
|
+
const qualifier = ["turn", "inbox"].includes(report.reception) ? ` · ${report.reception}` : "";
|
|
58
|
+
// Native mode strings are all dimmed. Keep their rendering intact and put
|
|
59
|
+
// only ACC's glyph in its own non-dim Text. Match Antigravity's ready green
|
|
60
|
+
// exactly instead of allowing Claude's success theme color to desaturate it.
|
|
61
|
+
return Box({ flexDirection: "row", children: [await next(e),
|
|
62
|
+
Text({ color: "inactive", children: [" ACC "] }),
|
|
63
|
+
Text({ color, bold: true, dimColor: false, children: [symbol] }),
|
|
64
|
+
...(qualifier ? [Text({ color: "inactive", children: [qualifier] })] : []),
|
|
65
|
+
] });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -57,19 +57,43 @@ Do this once, not every turn. Update it only when the scope or mode materially
|
|
|
57
57
|
changes. `--hint` is important: it lets ACC match your plan against a peer's
|
|
58
58
|
claim. Intent is awareness, not permission.
|
|
59
59
|
|
|
60
|
-
Before changing shared files, claim the smallest useful
|
|
60
|
+
Before changing shared files, claim the smallest useful resources in one operation:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
{{ACC}} claim --resource 'file:packages/core/**'
|
|
63
|
+
{{ACC}} claim --resource 'file:packages/core/**' \
|
|
64
|
+
--resource 'file:packages/protocol/**' --reason "porting the claim model"
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
Repeat `--resource` for an atomic batch, or supply it once for one claim.
|
|
68
|
+
Shared mode, enforcement, reason, and lease apply to every resource. Duplicate resources after
|
|
69
|
+
normalization are rejected. Exit 5 means a conflict: no claim is created or
|
|
70
|
+
renewed. Batch `details.conflicts` names blocked resources and their holders.
|
|
71
|
+
Narrow your scope, contact the owner, or ask the human. Repeating a successful
|
|
72
|
+
claim command renews your existing claims on those resources.
|
|
73
|
+
|
|
74
|
+
Release selected claims when useful:
|
|
68
75
|
|
|
69
76
|
```bash
|
|
70
|
-
{{ACC}} release --resource 'file:packages/core/**'
|
|
77
|
+
{{ACC}} release --resource 'file:packages/core/**' \
|
|
78
|
+
--resource 'file:packages/protocol/**'
|
|
71
79
|
```
|
|
72
80
|
|
|
81
|
+
Choose exactly one selector family: repeated `--resource`, repeated `--claim`,
|
|
82
|
+
or `--all`. Duplicate selectors after normalization are rejected. A missing or
|
|
83
|
+
foreign explicit target fails without releasing any claims. Repeating an explicit release after its targets were removed is an error.
|
|
84
|
+
Batch release accepts no force-release authority.
|
|
85
|
+
|
|
86
|
+
To release all your claims while keeping your session and intent open:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
{{ACC}} release --all
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This selects only your session's current claims in its workspace. An empty set
|
|
93
|
+
succeeds. Each retry selects the current set and can release newly acquired claims.
|
|
94
|
+
Use `work --clear` to clear intent, `ack` to acknowledge a message, and `finish`
|
|
95
|
+
to record a handoff and close your ACC presence.
|
|
96
|
+
|
|
73
97
|
## Communicate only when it changes another agent's work
|
|
74
98
|
|
|
75
99
|
Send a message for a dependency, conflict, direct question, decision, or
|
|
@@ -34,6 +34,7 @@ export function createClaudeCodeAdapter() {
|
|
|
34
34
|
return defineAdapter({
|
|
35
35
|
id: "claude_code",
|
|
36
36
|
displayName: "Claude Code",
|
|
37
|
+
statusIndicator: { minimumVersion: "2.1.287", reload: "run /reload-plugins in the open session" },
|
|
37
38
|
// The binary this client actually installs. Probed for a version to
|
|
38
39
|
// decide whether the client is on this machine, so it has to be the
|
|
39
40
|
// real command rather than the adapter id: `2.1.233 (Claude Code)`.
|
|
@@ -7,7 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
|
|
10
|
-
import { acccreatedFile, bakeSkillCommand, blankJson,
|
|
10
|
+
import { acccreatedFile, bakeSkillCommand, blankJson, defaultIndicator, compareVersionOrder, versionOrder,
|
|
11
11
|
mergeOwnedEntries, ownedEntries,
|
|
12
12
|
keepVersions, ownVersion, stampPluginVersion,
|
|
13
13
|
removeIfEmpty,
|
|
@@ -181,7 +181,7 @@ async function execFormHooks(file, node) {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/** A plugin tree with the shim written and the skill's command baked in. */
|
|
184
|
-
async function layOutPlugin(target, { runner, node, cli, hostPlatform }) {
|
|
184
|
+
async function layOutPlugin(target, { runner, node, cli, hostPlatform, indicator, indicatorRunner, clientVersion }) {
|
|
185
185
|
await rm(target, { recursive: true, force: true });
|
|
186
186
|
await cp(bundle, target, { recursive: true });
|
|
187
187
|
// The skill ships with a placeholder where the command belongs: `acc` is not
|
|
@@ -194,6 +194,15 @@ async function layOutPlugin(target, { runner, node, cli, hostPlatform }) {
|
|
|
194
194
|
await writeHookShim({ dir: path.join(target, "hooks"), adapterId: "claude_code",
|
|
195
195
|
runner, node, platform: hostPlatform });
|
|
196
196
|
if (hostPlatform === "win32") await execFormHooks(path.join(target, "hooks", "hooks.json"), node);
|
|
197
|
+
if (indicator === "on" && compareVersionOrder(versionOrder(clientVersion), versionOrder("2.1.287")) >= 0) {
|
|
198
|
+
const hooksFile = path.join(target, "hooks", "hooks.json");
|
|
199
|
+
const hooks = await readJson(hooksFile, {});
|
|
200
|
+
await writeJson(hooksFile, { ...hooks, modules: ["./indicator.mjs"] });
|
|
201
|
+
const moduleFile = path.join(target, "hooks", "indicator.mjs");
|
|
202
|
+
const source = await readFile(moduleFile, "utf8");
|
|
203
|
+
await writeFile(moduleFile, source.replace("__ACC_NODE__", JSON.stringify(node))
|
|
204
|
+
.replace("__ACC_INDICATOR__", JSON.stringify(indicatorRunner ?? defaultIndicator())));
|
|
205
|
+
} else await rm(path.join(target, "hooks", "indicator.mjs"), { force: true });
|
|
197
206
|
// The copy the client reads says which ACC wrote it. The shipped manifest
|
|
198
207
|
// carries no version, so there is nothing in the repository to fall out of
|
|
199
208
|
// step - which is how every client came to report 0.1.6 while running 0.1.9.
|
|
@@ -202,7 +211,8 @@ async function layOutPlugin(target, { runner, node, cli, hostPlatform }) {
|
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
export async function installClaudePlugin({ configDir, runner, cli, keepPreviousVersion = null,
|
|
205
|
-
node = process.execPath, now = new Date(), hostPlatform = process.platform
|
|
214
|
+
node = process.execPath, now = new Date(), hostPlatform = process.platform,
|
|
215
|
+
indicator = "off", indicatorRunner, clientVersion }) {
|
|
206
216
|
// Everything this will merge into, read before a byte is written. A settings
|
|
207
217
|
// file that will not parse used to be discovered after the plugin tree was
|
|
208
218
|
// already on disk, and the install then failed with nineteen files left
|
|
@@ -219,12 +229,13 @@ export async function installClaudePlugin({ configDir, runner, cli, keepPrevious
|
|
|
219
229
|
const source = sourceDir(configDir);
|
|
220
230
|
const cached = cachePath(configDir, version);
|
|
221
231
|
|
|
222
|
-
|
|
232
|
+
const layout = { runner, node, cli, hostPlatform, indicator, indicatorRunner, clientVersion };
|
|
233
|
+
await layOutPlugin(source, layout);
|
|
223
234
|
await writeJson(marketplaceFile(configDir), marketplaceManifest());
|
|
224
235
|
// The copy the client runs from. Written here rather than asking the user to
|
|
225
236
|
// run `claude plugin install`, exactly as the Codex adapter does, because the
|
|
226
237
|
// command's only effect is this copy plus the two registry entries below.
|
|
227
|
-
await layOutPlugin(cached,
|
|
238
|
+
await layOutPlugin(cached, layout);
|
|
228
239
|
// The copy just written, plus the one an upgrade moved off. A client caches a
|
|
229
240
|
// plugin under its version, so every upgrade would otherwise leave the previous
|
|
230
241
|
// release's tree beside this one - invisible while the version never moved,
|
|
@@ -16,6 +16,25 @@ Use this skill when hook context reports peers or actionable attention, or
|
|
|
16
16
|
the user asks for coordination between sessions. An `ACC CLI (append):` header
|
|
17
17
|
by itself supplies identity for later use; continue the user's ordinary work.
|
|
18
18
|
|
|
19
|
+
### Send through Codex's approved command path
|
|
20
|
+
|
|
21
|
+
Run `message`, `request`, `reply`, and `finish` with `exec_command` and
|
|
22
|
+
`sandbox_permissions: "require_escalated"`. Put the installed `{{ACC}}` wrapper
|
|
23
|
+
first in the command. ACC's installed prefix rule can then authorize that exact
|
|
24
|
+
local command. Do not combine it with other shell commands. These sends stay
|
|
25
|
+
on this machine and reach the user's own agent sessions.
|
|
26
|
+
|
|
27
|
+
Codex can deny its daemon socket inside the sandbox even when the ACC profile
|
|
28
|
+
allows the socket. The approved command path preserves that sandbox boundary.
|
|
29
|
+
An initial execution refusal records no message. If a retry is refused, the
|
|
30
|
+
earlier recorded message remains durable. Report which case occurred.
|
|
31
|
+
|
|
32
|
+
Choose a distinct `--client-message-id` for each new send. If delivery reports
|
|
33
|
+
`transport_permission_denied`, retry the same command through approved execution
|
|
34
|
+
with the same key and content. The CLI prints the original key when it generated
|
|
35
|
+
one. A retry uses the recorded message and offers it again only while queued.
|
|
36
|
+
Use normal sandboxed execution for local reads and claims.
|
|
37
|
+
|
|
19
38
|
## Use your own CLI credentials
|
|
20
39
|
|
|
21
40
|
When this turn's ACC hook supplies `ACC CLI (append):`, append those exact
|
|
@@ -57,19 +76,43 @@ Do this once, not every turn. Update it only when the scope or mode materially
|
|
|
57
76
|
changes. `--hint` is important: it lets ACC match your plan against a peer's
|
|
58
77
|
claim. Intent is awareness, not permission.
|
|
59
78
|
|
|
60
|
-
Before changing shared files, claim the smallest useful
|
|
79
|
+
Before changing shared files, claim the smallest useful resources in one operation:
|
|
61
80
|
|
|
62
81
|
```bash
|
|
63
|
-
{{ACC}} claim --resource 'file:packages/core/**'
|
|
82
|
+
{{ACC}} claim --resource 'file:packages/core/**' \
|
|
83
|
+
--resource 'file:packages/protocol/**' --reason "porting the claim model"
|
|
64
84
|
```
|
|
65
85
|
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
Repeat `--resource` for an atomic batch, or supply it once for one claim.
|
|
87
|
+
Shared mode, enforcement, reason, and lease apply to every resource. Duplicate resources after
|
|
88
|
+
normalization are rejected. Exit 5 means a conflict: no claim is created or
|
|
89
|
+
renewed. Batch `details.conflicts` names blocked resources and their holders.
|
|
90
|
+
Narrow your scope, contact the owner, or ask the human. Repeating a successful
|
|
91
|
+
claim command renews your existing claims on those resources.
|
|
92
|
+
|
|
93
|
+
Release selected claims when useful:
|
|
68
94
|
|
|
69
95
|
```bash
|
|
70
|
-
{{ACC}} release --resource 'file:packages/core/**'
|
|
96
|
+
{{ACC}} release --resource 'file:packages/core/**' \
|
|
97
|
+
--resource 'file:packages/protocol/**'
|
|
71
98
|
```
|
|
72
99
|
|
|
100
|
+
Choose exactly one selector family: repeated `--resource`, repeated `--claim`,
|
|
101
|
+
or `--all`. Duplicate selectors after normalization are rejected. A missing or
|
|
102
|
+
foreign explicit target fails without releasing any claims. Repeating an explicit release after its targets were removed is an error.
|
|
103
|
+
Batch release accepts no force-release authority.
|
|
104
|
+
|
|
105
|
+
To release all your claims while keeping your session and intent open:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
{{ACC}} release --all
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This selects only your session's current claims in its workspace. An empty set
|
|
112
|
+
succeeds. Each retry selects the current set and can release newly acquired claims.
|
|
113
|
+
Use `work --clear` to clear intent, `ack` to acknowledge a message, and `finish`
|
|
114
|
+
to record a handoff and close your ACC presence.
|
|
115
|
+
|
|
73
116
|
## Communicate only when it changes another agent's work
|
|
74
117
|
|
|
75
118
|
Send a message for a dependency, conflict, direct question, decision, or
|
|
@@ -168,22 +168,25 @@ async function bindAttempt({ event, clientPid, clientVersion, runtimeDir,
|
|
|
168
168
|
// what `codex --version` printed, and a daemon that had updated under its CLI
|
|
169
169
|
// was refused here on that difference alone.
|
|
170
170
|
async function receiverFor(binding, runtimeDir, platform = process.platform) {
|
|
171
|
-
const endpoint = await readNativeEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef,
|
|
172
|
-
|
|
171
|
+
const endpoint = await readNativeEndpoint({ runtimeDir, endpointId: binding?.opaqueEndpointRef,
|
|
172
|
+
platform, throwPermissionErrors: true });
|
|
173
|
+
return endpoint !== null && await socketIsReady(endpoint.socketPath,
|
|
174
|
+
{ platform, throwPermissionErrors: true }) ? endpoint : null;
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
export async function refreshNativeSession({ binding, runtimeDir, timeoutMs = 750,
|
|
176
178
|
now = Date.now, open = openCodexAppServer, platform = process.platform } = {}) {
|
|
177
179
|
const rejected = reason => closed(binding?.clientVersion, reason);
|
|
178
|
-
const endpoint = await receiverFor(binding, runtimeDir, platform);
|
|
179
|
-
if (endpoint === null) return rejected("handshake_failed");
|
|
180
180
|
try {
|
|
181
|
+
const endpoint = await receiverFor(binding, runtimeDir, platform);
|
|
182
|
+
if (endpoint === null) return rejected("handshake_failed");
|
|
181
183
|
return await usingPeer(endpoint.socketPath, timeoutMs, open, async peer => {
|
|
182
184
|
const { reasonCode, servingVersion } = await verifyReceiver(peer, endpoint);
|
|
183
185
|
return reasonCode === null
|
|
184
186
|
? handshake({ ...endpoint, clientVersion: servingVersion }, now) : rejected(reasonCode);
|
|
185
187
|
});
|
|
186
188
|
} catch (error) {
|
|
189
|
+
if (["EPERM", "EACCES"].includes(error?.code)) return rejected("transport_permission_denied");
|
|
187
190
|
return rejected(error?.code === "ETIMEDOUT" ? "handshake_timeout" : "handshake_failed");
|
|
188
191
|
}
|
|
189
192
|
}
|
|
@@ -192,9 +195,9 @@ export async function offerMessage({ binding, message, runtimeDir, timeoutMs = 5
|
|
|
192
195
|
open = openCodexAppServer, platform = process.platform } = {}) {
|
|
193
196
|
const rejected = safeErrorCode => ({ accepted: false, transport: "codex-app-server",
|
|
194
197
|
clientVersion: binding?.clientVersion ?? null, safeErrorCode });
|
|
195
|
-
const endpoint = await receiverFor(binding, runtimeDir, platform);
|
|
196
|
-
if (endpoint === null) return rejected("recipient_unavailable");
|
|
197
198
|
try {
|
|
199
|
+
const endpoint = await receiverFor(binding, runtimeDir, platform);
|
|
200
|
+
if (endpoint === null) return rejected("recipient_unavailable");
|
|
198
201
|
return await usingPeer(endpoint.socketPath, timeoutMs, open, async peer => {
|
|
199
202
|
const { reasonCode, servingVersion } = await verifyReceiver(peer, endpoint);
|
|
200
203
|
if (reasonCode !== null) return rejected(reasonCode === "handshake_timeout" ? "transport_error"
|
|
@@ -50,18 +50,25 @@ async function directory(runtimeDir, create = false, platform = process.platform
|
|
|
50
50
|
* 0.159.3), so there the socket counts while its directory lists it; Codex keeps
|
|
51
51
|
* that directory to its user.
|
|
52
52
|
*/
|
|
53
|
-
export async function socketIsReady(socketPath, { platform = process.platform
|
|
53
|
+
export async function socketIsReady(socketPath, { platform = process.platform,
|
|
54
|
+
throwPermissionErrors = false } = {}) {
|
|
54
55
|
if (isWindowsPlatform(platform)) {
|
|
55
56
|
if (!absolute(socketPath)) return false;
|
|
56
57
|
const name = path.basename(socketPath).toLowerCase();
|
|
57
|
-
const names = await readdir(path.dirname(socketPath)).catch(
|
|
58
|
+
const names = await readdir(path.dirname(socketPath)).catch(error => {
|
|
59
|
+
if (throwPermissionErrors && ["EPERM", "EACCES"].includes(error.code)) throw error;
|
|
60
|
+
return [];
|
|
61
|
+
});
|
|
58
62
|
return names.some(entry => entry.toLowerCase() === name);
|
|
59
63
|
}
|
|
60
64
|
if (!absolute(socketPath)) return false;
|
|
61
65
|
try {
|
|
62
66
|
const info = await lstat(socketPath);
|
|
63
67
|
return info.isSocket() && !info.isSymbolicLink() && own(info);
|
|
64
|
-
} catch {
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (throwPermissionErrors && ["EPERM", "EACCES"].includes(error.code)) throw error;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
/** The socket a control socket path leads to, or null. Codex 0.157.1 keeps a
|
|
@@ -104,7 +111,8 @@ export async function writeNativeEndpoint({ runtimeDir, record, platform = proce
|
|
|
104
111
|
|
|
105
112
|
// Expired records are metadata for revalidation, never proof of reachability.
|
|
106
113
|
// Refuse links and oversized files before reading; no foreign error text escapes.
|
|
107
|
-
export async function readNativeEndpoint({ runtimeDir, endpointId, platform = process.platform
|
|
114
|
+
export async function readNativeEndpoint({ runtimeDir, endpointId, platform = process.platform,
|
|
115
|
+
throwPermissionErrors = false }) {
|
|
108
116
|
if (typeof endpointId !== "string" || !ENDPOINT.test(endpointId)) return null;
|
|
109
117
|
let handle;
|
|
110
118
|
try {
|
|
@@ -116,7 +124,10 @@ export async function readNativeEndpoint({ runtimeDir, endpointId, platform = pr
|
|
|
116
124
|
}
|
|
117
125
|
const record = JSON.parse(await handle.readFile("utf8"));
|
|
118
126
|
return valid(record) && record.endpointId === endpointId ? record : null;
|
|
119
|
-
} catch {
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (throwPermissionErrors && ["EPERM", "EACCES"].includes(error.code)) throw error;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
120
131
|
finally { await handle?.close().catch(() => null); }
|
|
121
132
|
}
|
|
122
133
|
|
package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md
CHANGED
|
@@ -57,19 +57,43 @@ Do this once, not every turn. Update it only when the scope or mode materially
|
|
|
57
57
|
changes. `--hint` is important: it lets ACC match your plan against a peer's
|
|
58
58
|
claim. Intent is awareness, not permission.
|
|
59
59
|
|
|
60
|
-
Before changing shared files, claim the smallest useful
|
|
60
|
+
Before changing shared files, claim the smallest useful resources in one operation:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
{{ACC}} claim --resource 'file:packages/core/**'
|
|
63
|
+
{{ACC}} claim --resource 'file:packages/core/**' \
|
|
64
|
+
--resource 'file:packages/protocol/**' --reason "porting the claim model"
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
Repeat `--resource` for an atomic batch, or supply it once for one claim.
|
|
68
|
+
Shared mode, enforcement, reason, and lease apply to every resource. Duplicate resources after
|
|
69
|
+
normalization are rejected. Exit 5 means a conflict: no claim is created or
|
|
70
|
+
renewed. Batch `details.conflicts` names blocked resources and their holders.
|
|
71
|
+
Narrow your scope, contact the owner, or ask the human. Repeating a successful
|
|
72
|
+
claim command renews your existing claims on those resources.
|
|
73
|
+
|
|
74
|
+
Release selected claims when useful:
|
|
68
75
|
|
|
69
76
|
```bash
|
|
70
|
-
{{ACC}} release --resource 'file:packages/core/**'
|
|
77
|
+
{{ACC}} release --resource 'file:packages/core/**' \
|
|
78
|
+
--resource 'file:packages/protocol/**'
|
|
71
79
|
```
|
|
72
80
|
|
|
81
|
+
Choose exactly one selector family: repeated `--resource`, repeated `--claim`,
|
|
82
|
+
or `--all`. Duplicate selectors after normalization are rejected. A missing or
|
|
83
|
+
foreign explicit target fails without releasing any claims. Repeating an explicit release after its targets were removed is an error.
|
|
84
|
+
Batch release accepts no force-release authority.
|
|
85
|
+
|
|
86
|
+
To release all your claims while keeping your session and intent open:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
{{ACC}} release --all
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This selects only your session's current claims in its workspace. An empty set
|
|
93
|
+
succeeds. Each retry selects the current set and can release newly acquired claims.
|
|
94
|
+
Use `work --clear` to clear intent, `ack` to acknowledge a message, and `finish`
|
|
95
|
+
to record a handoff and close your ACC presence.
|
|
96
|
+
|
|
73
97
|
## Communicate only when it changes another agent's work
|
|
74
98
|
|
|
75
99
|
Send a message for a dependency, conflict, direct question, decision, or
|
|
@@ -73,19 +73,43 @@ Do this once, not every turn. Update it only when the scope or mode materially
|
|
|
73
73
|
changes. `--hint` is important: it lets ACC match your plan against a peer's
|
|
74
74
|
claim. Intent is awareness, not permission.
|
|
75
75
|
|
|
76
|
-
Before changing shared files, claim the smallest useful
|
|
76
|
+
Before changing shared files, claim the smallest useful resources in one operation:
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
{{ACC}} claim --resource 'file:packages/core/**'
|
|
79
|
+
{{ACC}} claim --resource 'file:packages/core/**' \
|
|
80
|
+
--resource 'file:packages/protocol/**' --reason "porting the claim model"
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
Repeat `--resource` for an atomic batch, or supply it once for one claim.
|
|
84
|
+
Shared mode, enforcement, reason, and lease apply to every resource. Duplicate resources after
|
|
85
|
+
normalization are rejected. Exit 5 means a conflict: no claim is created or
|
|
86
|
+
renewed. Batch `details.conflicts` names blocked resources and their holders.
|
|
87
|
+
Narrow your scope, contact the owner, or ask the human. Repeating a successful
|
|
88
|
+
claim command renews your existing claims on those resources.
|
|
89
|
+
|
|
90
|
+
Release selected claims when useful:
|
|
84
91
|
|
|
85
92
|
```bash
|
|
86
|
-
{{ACC}} release --resource 'file:packages/core/**'
|
|
93
|
+
{{ACC}} release --resource 'file:packages/core/**' \
|
|
94
|
+
--resource 'file:packages/protocol/**'
|
|
87
95
|
```
|
|
88
96
|
|
|
97
|
+
Choose exactly one selector family: repeated `--resource`, repeated `--claim`,
|
|
98
|
+
or `--all`. Duplicate selectors after normalization are rejected. A missing or
|
|
99
|
+
foreign explicit target fails without releasing any claims. Repeating an explicit release after its targets were removed is an error.
|
|
100
|
+
Batch release accepts no force-release authority.
|
|
101
|
+
|
|
102
|
+
To release all your claims while keeping your session and intent open:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
{{ACC}} release --all
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This selects only your session's current claims in its workspace. An empty set
|
|
109
|
+
succeeds. Each retry selects the current set and can release newly acquired claims.
|
|
110
|
+
Use `work --clear` to clear intent, `ack` to acknowledge a message, and `finish`
|
|
111
|
+
to record a handoff and close your ACC presence.
|
|
112
|
+
|
|
89
113
|
## Communicate only when it changes another agent's work
|
|
90
114
|
|
|
91
115
|
Send a message for a dependency, conflict, direct question, decision, or
|