agents-can-communicate 0.1.18 → 0.2.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 +78 -70
- package/SECURITY.md +31 -0
- package/bin/acc-hook.mjs +94 -12
- package/bin/acc-mcp.mjs +6 -2
- package/bin/acc.mjs +6 -1
- package/docs/ADAPTER_AUTHORING.md +172 -0
- package/docs/ARCHITECTURE.md +131 -0
- package/docs/CAPABILITIES.md +102 -214
- package/docs/CLI.md +157 -0
- package/docs/CONCEPTS.md +134 -0
- package/docs/CONFIGURATION.md +143 -0
- package/docs/DESIGN_DECISIONS.md +89 -0
- package/docs/GETTING_STARTED.md +145 -0
- package/docs/GLOSSARY.md +26 -0
- package/docs/MCP.md +94 -0
- package/docs/PROTOCOL.md +200 -0
- package/docs/RELEASING.md +109 -0
- package/docs/SECURITY_MODEL.md +131 -0
- package/docs/TROUBLESHOOTING.md +102 -0
- package/docs/WHY_ACC.md +61 -0
- package/docs/index.md +42 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/certification.json +78 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/PreToolUse-Edit.json +19 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/PreToolUse.json +17 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/SessionEnd.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/SessionStart.json +7 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/UserPromptSubmit.json +9 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/certification-provenance.json +77 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.252.json +19 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +9 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +20 -22
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +12 -4
- package/node_modules/@agents-can-communicate/adapter-codex/certification.json +117 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/PreToolUse.json +14 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/SessionEnd.json +7 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/SessionStart.json +9 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/UserPromptSubmit.json +10 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/certification-provenance.json +66 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.0.json +19 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +8 -1
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +20 -22
- package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +18 -11
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/certification.json +52 -0
- 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 +20 -22
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/BeforeAgent.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/BeforeTool-shell.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/BeforeTool.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/SessionEnd.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/SessionStart.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/certification-provenance.json +66 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +8 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/adapter.mjs +7 -3
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +4 -2
- package/node_modules/@agents-can-communicate/adapter-grok/certification.json +3 -0
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +2 -1
- package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +20 -22
- package/node_modules/@agents-can-communicate/adapter-grok/src/adapter.mjs +9 -9
- package/node_modules/@agents-can-communicate/adapter-kimi/certification.json +52 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/PreToolUse-Bash.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/PreToolUse-Write.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/SessionHeartbeat.json +7 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/SessionStart.json +9 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/UserPromptSubmit.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/fixtures/certification-provenance.json +66 -0
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +8 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +20 -22
- package/node_modules/@agents-can-communicate/adapter-kimi/src/adapter.mjs +7 -3
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +34 -18
- package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +158 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +36 -17
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +7 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +13 -4
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +11 -30
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +3 -0
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +2 -5
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +9 -2
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +100 -121
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/attention.mjs +106 -0
- package/node_modules/@agents-can-communicate/core/src/conversations.mjs +276 -0
- package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +81 -0
- package/node_modules/@agents-can-communicate/core/src/finish-retries.mjs +97 -0
- package/node_modules/@agents-can-communicate/core/src/inbox.mjs +91 -107
- package/node_modules/@agents-can-communicate/core/src/index.mjs +3 -3
- package/node_modules/@agents-can-communicate/core/src/intents.mjs +0 -1
- package/node_modules/@agents-can-communicate/core/src/ports.mjs +2 -1
- package/node_modules/@agents-can-communicate/core/src/receipts.mjs +109 -0
- package/node_modules/@agents-can-communicate/core/src/service.mjs +11 -10
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +22 -20
- package/node_modules/@agents-can-communicate/core/src/status.mjs +11 -9
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +3 -294
- package/node_modules/@agents-can-communicate/delivery-router/package.json +12 -0
- package/node_modules/@agents-can-communicate/delivery-router/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +117 -0
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +20 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +115 -63
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +23 -7
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +20 -5
- package/node_modules/@agents-can-communicate/installer/src/index.mjs +3 -2
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +108 -12
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +19 -2
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/src/input-validator.mjs +79 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/resources.mjs +23 -28
- package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +102 -72
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +54 -97
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/config.mjs +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/conversations.mjs +64 -0
- package/node_modules/@agents-can-communicate/protocol/src/index.mjs +4 -1
- package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +49 -90
- package/node_modules/@agents-can-communicate/protocol/src/states.mjs +13 -40
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/active-journal.mjs +230 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +77 -28
- package/node_modules/@agents-can-communicate/storage-filesystem/src/identity.mjs +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/journal.mjs +83 -35
- package/node_modules/@agents-can-communicate/storage-filesystem/src/retention.mjs +112 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-file.mjs +18 -8
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +68 -26
- package/node_modules/@agents-can-communicate/storage-filesystem/src/writer-mutex.mjs +113 -35
- package/package.json +19 -1
- package/node_modules/@agents-can-communicate/core/src/communication.mjs +0 -334
- package/node_modules/@agents-can-communicate/core/src/message-signals.mjs +0 -41
- package/node_modules/@agents-can-communicate/core/src/notify.mjs +0 -95
- package/node_modules/@agents-can-communicate/core/src/tasks.mjs +0 -244
- package/node_modules/@agents-can-communicate/core/src/workstreams.mjs +0 -109
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
4
|
+
|
|
5
|
+
export const CAPABILITY_SHAPE = Object.freeze({
|
|
6
|
+
lifecycle: Object.freeze(["sessionStart", "sessionResume", "sessionEnd", "heartbeat",
|
|
7
|
+
"childSessions"]),
|
|
8
|
+
context: Object.freeze(["startupInjection", "beforeTurnInjection", "safePointInjection"]),
|
|
9
|
+
guards: Object.freeze(["beforeRead", "beforeWrite", "beforeShell"]),
|
|
10
|
+
delivery: Object.freeze(["nextTurn", "livePush", "replyRoute"]),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const REQUIRED_TEXT = Object.freeze(["client", "version", "platform", "observedAt",
|
|
14
|
+
"capability", "fixture", "provenance", "provenanceId", "idleBehavior", "busyBehavior",
|
|
15
|
+
"authorityLevel"]);
|
|
16
|
+
const MANIFEST_KEYS = new Set(["evidence"]);
|
|
17
|
+
const EVIDENCE_KEYS = new Set([...REQUIRED_TEXT, "limitations", "result"]);
|
|
18
|
+
const RESULTS = new Set(["pass", "fail"]);
|
|
19
|
+
const VERSION = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
20
|
+
const PLATFORM = /^(?:darwin|linux|win32)-(?:arm64|x64)$/;
|
|
21
|
+
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
22
|
+
const TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
|
|
23
|
+
|
|
24
|
+
function usage(message, details = {}) {
|
|
25
|
+
throw new AccError(EXIT.USAGE, message, details);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertText(entry, key, index) {
|
|
29
|
+
if (typeof entry[key] !== "string" || entry[key].trim() === ""
|
|
30
|
+
|| entry[key] !== entry[key].trim()) {
|
|
31
|
+
usage(`certification evidence ${index} must declare ${key}`, { index, key });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function validatePackageJson(value, index, key) {
|
|
36
|
+
if (path.isAbsolute(value) || value.includes("\\") || value.split("/").includes("..")
|
|
37
|
+
|| !value.startsWith("fixtures/") || !value.endsWith(".json")) {
|
|
38
|
+
usage(`certification evidence ${index} ${key} must be package-local JSON`,
|
|
39
|
+
{ index, [key]: value });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateObservedAt(value, index) {
|
|
44
|
+
const parsed = new Date(value);
|
|
45
|
+
if (!Number.isFinite(parsed.valueOf())) {
|
|
46
|
+
usage(`certification evidence ${index} observedAt must be an exact ISO date or timestamp`,
|
|
47
|
+
{ index, observedAt: value });
|
|
48
|
+
}
|
|
49
|
+
const validDate = DATE.test(value)
|
|
50
|
+
&& parsed.toISOString().slice(0, 10) === value;
|
|
51
|
+
const validTimestamp = TIMESTAMP.test(value)
|
|
52
|
+
&& [parsed.toISOString(), parsed.toISOString().replace(".000Z", "Z")].includes(value);
|
|
53
|
+
if (!validDate && !validTimestamp) {
|
|
54
|
+
usage(`certification evidence ${index} observedAt must be an exact ISO date or timestamp`,
|
|
55
|
+
{ index, observedAt: value });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function rejectUnknownKeys(value, known, label, index = null) {
|
|
60
|
+
const unknown = Object.keys(value).find(key => !known.has(key));
|
|
61
|
+
if (unknown !== undefined) {
|
|
62
|
+
usage(`unknown ${label} field ${unknown}`,
|
|
63
|
+
index === null ? { key: unknown } : { index, key: unknown });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function validateCertification(manifest) {
|
|
68
|
+
if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
69
|
+
usage("an adapter must declare certification evidence");
|
|
70
|
+
}
|
|
71
|
+
rejectUnknownKeys(manifest, MANIFEST_KEYS, "certification");
|
|
72
|
+
if (!Array.isArray(manifest.evidence)) {
|
|
73
|
+
usage("certification.evidence must be an array");
|
|
74
|
+
}
|
|
75
|
+
const evidence = manifest.evidence.map((entry, index) => {
|
|
76
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
77
|
+
usage(`certification evidence ${index} must be an object`, { index });
|
|
78
|
+
}
|
|
79
|
+
rejectUnknownKeys(entry, EVIDENCE_KEYS, "certification evidence", index);
|
|
80
|
+
for (const key of REQUIRED_TEXT) assertText(entry, key, index);
|
|
81
|
+
validatePackageJson(entry.fixture, index, "fixture");
|
|
82
|
+
validatePackageJson(entry.provenance, index, "provenance");
|
|
83
|
+
if (!VERSION.test(entry.version) || entry.version.toLowerCase() === "unknown") {
|
|
84
|
+
usage(`certification evidence ${index} version must be an exact client version`,
|
|
85
|
+
{ index, version: entry.version });
|
|
86
|
+
}
|
|
87
|
+
if (!PLATFORM.test(entry.platform) || entry.platform.toLowerCase() === "unknown") {
|
|
88
|
+
usage(`certification evidence ${index} platform must be an exact supported platform`,
|
|
89
|
+
{ index, platform: entry.platform });
|
|
90
|
+
}
|
|
91
|
+
validateObservedAt(entry.observedAt, index);
|
|
92
|
+
if (!RESULTS.has(entry.result)) {
|
|
93
|
+
usage(`certification evidence ${index} result must be pass or fail`,
|
|
94
|
+
{ index, result: entry.result });
|
|
95
|
+
}
|
|
96
|
+
if (!Array.isArray(entry.limitations)
|
|
97
|
+
|| entry.limitations.some(item => typeof item !== "string" || item.trim() === "")) {
|
|
98
|
+
usage(`certification evidence ${index} limitations must be an array of text`, { index });
|
|
99
|
+
}
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
client: entry.client,
|
|
102
|
+
version: entry.version,
|
|
103
|
+
platform: entry.platform,
|
|
104
|
+
observedAt: entry.observedAt,
|
|
105
|
+
capability: entry.capability,
|
|
106
|
+
fixture: entry.fixture,
|
|
107
|
+
provenance: entry.provenance,
|
|
108
|
+
provenanceId: entry.provenanceId,
|
|
109
|
+
idleBehavior: entry.idleBehavior,
|
|
110
|
+
busyBehavior: entry.busyBehavior,
|
|
111
|
+
authorityLevel: entry.authorityLevel,
|
|
112
|
+
limitations: Object.freeze([...entry.limitations]),
|
|
113
|
+
result: entry.result,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
const tuples = new Set();
|
|
117
|
+
for (const [index, entry] of evidence.entries()) {
|
|
118
|
+
const tuple = JSON.stringify([entry.client, entry.version, entry.platform,
|
|
119
|
+
entry.capability]);
|
|
120
|
+
if (tuples.has(tuple)) {
|
|
121
|
+
usage(`duplicate certification tuple at evidence ${index}`,
|
|
122
|
+
{ index, client: entry.client, version: entry.version,
|
|
123
|
+
platform: entry.platform, capability: entry.capability });
|
|
124
|
+
}
|
|
125
|
+
tuples.add(tuple);
|
|
126
|
+
}
|
|
127
|
+
return Object.freeze({ evidence: Object.freeze(evidence) });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function falseCapabilities() {
|
|
131
|
+
return Object.fromEntries(Object.entries(CAPABILITY_SHAPE).map(([group, names]) => [group,
|
|
132
|
+
Object.fromEntries(names.map(name => [name, false]))]));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function effectiveCapabilities(adapter, { clientVersion, platform } = {}) {
|
|
136
|
+
const resolved = falseCapabilities();
|
|
137
|
+
if (typeof clientVersion !== "string" || !VERSION.test(clientVersion)
|
|
138
|
+
|| clientVersion.toLowerCase() === "unknown"
|
|
139
|
+
|| typeof platform !== "string" || !PLATFORM.test(platform)
|
|
140
|
+
|| platform.toLowerCase() === "unknown") return freezeCapabilities(resolved);
|
|
141
|
+
const client = adapter.client?.certificationName ?? adapter.client?.command;
|
|
142
|
+
const passing = new Set((adapter.certification?.evidence ?? [])
|
|
143
|
+
.filter(item => item.result === "pass" && item.client === client
|
|
144
|
+
&& item.version === clientVersion && item.platform === platform)
|
|
145
|
+
.map(item => item.capability));
|
|
146
|
+
for (const [group, names] of Object.entries(CAPABILITY_SHAPE)) {
|
|
147
|
+
for (const name of names) {
|
|
148
|
+
resolved[group][name] = adapter.capabilities?.[group]?.[name] === true
|
|
149
|
+
&& passing.has(`${group}.${name}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return freezeCapabilities(resolved);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function freezeCapabilities(capabilities) {
|
|
156
|
+
for (const group of Object.keys(CAPABILITY_SHAPE)) Object.freeze(capabilities[group]);
|
|
157
|
+
return Object.freeze(capabilities);
|
|
158
|
+
}
|
|
@@ -28,15 +28,27 @@ function truncate(line, limit) {
|
|
|
28
28
|
return cut === "" ? "" : `${cut}…`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function attentionGroups(attention, { truncatable }) {
|
|
32
|
-
return attention.map(item =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
function attentionGroups(attention, { truncatable, liveOfferedMessageIds = new Set() }) {
|
|
32
|
+
return attention.map(item => {
|
|
33
|
+
const breadcrumb = offeredBreadcrumb(item, liveOfferedMessageIds);
|
|
34
|
+
return {
|
|
35
|
+
lines: [breadcrumb ?? (typeof item.sourceId === "string" && item.sourceId !== ""
|
|
36
|
+
? `- [${item.kind}] ${item.sourceId} ${item.summary}`
|
|
37
|
+
: `- [${item.kind}] ${item.summary}`)],
|
|
38
|
+
kind: "attention",
|
|
39
|
+
sourceId: item.sourceId ?? null,
|
|
40
|
+
truncatable: breadcrumb === null && truncatable,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function offeredBreadcrumb(item, liveOfferedMessageIds) {
|
|
46
|
+
if (!liveOfferedMessageIds.has(item.sourceId)) return null;
|
|
47
|
+
const noun = item.kind === "reply_required" ? "question"
|
|
48
|
+
: item.kind === "acknowledgement_required" ? "message" : null;
|
|
49
|
+
if (noun === null) return null;
|
|
50
|
+
return `- [${item.kind}] ${item.sourceId} live-offered peer ${noun} remains unresolved; `
|
|
51
|
+
+ `\`acc inbox --message ${item.sourceId}\``;
|
|
40
52
|
}
|
|
41
53
|
|
|
42
54
|
function messageGroups(messages) {
|
|
@@ -45,8 +57,12 @@ function messageGroups(messages) {
|
|
|
45
57
|
kind: "message",
|
|
46
58
|
lines: [
|
|
47
59
|
`${FENCE}${BLOCK}`,
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
"untrusted peer message",
|
|
61
|
+
`kind: ${message.kind}`,
|
|
62
|
+
`threadId: ${message.threadId}`,
|
|
63
|
+
`messageId: ${message.messageId}`,
|
|
64
|
+
`sender: ${message.fromParticipantId} (session ${message.fromSessionId})`,
|
|
65
|
+
`obligation: ${message.obligation}`,
|
|
50
66
|
`subject: ${oneLine(escapePeerText(message.subject))}`,
|
|
51
67
|
"body:",
|
|
52
68
|
escapePeerText(message.body),
|
|
@@ -88,16 +104,19 @@ export function projectContextResult(sync, { budgetBytes = DEFAULT_BUDGET_BYTES
|
|
|
88
104
|
const attention = [...(sync.attention ?? [])]
|
|
89
105
|
.sort((left, right) => left.priority - right.priority
|
|
90
106
|
|| (left.sourceId ?? "").localeCompare(right.sourceId ?? ""));
|
|
91
|
-
const
|
|
92
|
-
const
|
|
107
|
+
const leadsPeerBodies = item => item.priority <= 2 || item.kind === "claim_conflict";
|
|
108
|
+
const urgent = attention.filter(leadsPeerBodies);
|
|
109
|
+
const informational = attention.filter(item => !leadsPeerBodies(item));
|
|
110
|
+
const messages = sync.messages ?? [];
|
|
111
|
+
const liveOfferedMessageIds = new Set(sync.liveOfferedMessageIds ?? []);
|
|
93
112
|
const groups = [
|
|
94
|
-
...attentionGroups(urgent, { truncatable: true }),
|
|
95
|
-
...messageGroups(
|
|
113
|
+
...attentionGroups(urgent, { truncatable: true, liveOfferedMessageIds }),
|
|
114
|
+
...messageGroups(messages),
|
|
96
115
|
...attentionGroups(informational, { truncatable: false }),
|
|
97
116
|
];
|
|
98
117
|
const peers = peerCount(sync);
|
|
99
118
|
if (groups.length === 0 && (sync.solo === true || peers === 0)) {
|
|
100
|
-
return { text: "",
|
|
119
|
+
return { text: "", offeredMessageIds: [], includedAttentionIds: [] };
|
|
101
120
|
}
|
|
102
121
|
|
|
103
122
|
const fullHeader = groups.length === 0 ? ambientHeader(peers) : "ACC (load the acc skill):";
|
|
@@ -159,7 +178,7 @@ export function projectContextResult(sync, { budgetBytes = DEFAULT_BUDGET_BYTES
|
|
|
159
178
|
|
|
160
179
|
return {
|
|
161
180
|
text: lines.join("\n"),
|
|
162
|
-
|
|
181
|
+
offeredMessageIds: included
|
|
163
182
|
.filter(item => item.group.kind === "message")
|
|
164
183
|
.map(item => item.group.messageId),
|
|
165
184
|
includedAttentionIds: included
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, chmod, mkdir, readFile, readdir, rm, writeFile }
|
|
1
|
+
import { access, chmod, lstat, mkdir, readFile, readdir, rm, writeFile }
|
|
2
2
|
from "node:fs/promises";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
@@ -147,6 +147,12 @@ export async function writeHookShim({ dir, adapterId, runner = defaultRunner(),
|
|
|
147
147
|
*/
|
|
148
148
|
export async function removeInstalledTree(target, keep = []) {
|
|
149
149
|
if (keep.includes(target)) return false;
|
|
150
|
+
try {
|
|
151
|
+
await lstat(target);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error.code === "ENOENT") return false;
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
150
156
|
await rm(target, { recursive: true, force: true });
|
|
151
157
|
return true;
|
|
152
158
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Capability contract, context projection, config ownership, and the binding
|
|
2
2
|
// that survives between two ephemeral hook processes.
|
|
3
3
|
export { CAPABILITY_SHAPE, assertCapabilities, defineAdapter } from "./capabilities.mjs";
|
|
4
|
+
export { effectiveCapabilities, validateCertification } from "./certification.mjs";
|
|
4
5
|
export { EVENT_KINDS, NORMALIZED_EVENT_KEYS, normalizedEvent } from "./events.mjs";
|
|
5
6
|
export { assertRunner, bakeSkillCommand, defaultCli, defaultRunner, removeInstalledTree,
|
|
6
7
|
runnerExists, writeHookShim }
|
|
@@ -18,13 +18,16 @@ const fileFor = (runtimeDir, harnessSessionId) => path.join(runtimeDir, "binding
|
|
|
18
18
|
* detach the exact generation instead of opening a second session.
|
|
19
19
|
*
|
|
20
20
|
* It lives in the runtime directory, never the project, and carries identity
|
|
21
|
-
*
|
|
21
|
+
* plus the exact client facts observed at attach time - no prompt, transcript,
|
|
22
|
+
* or harness state.
|
|
22
23
|
*/
|
|
23
24
|
export async function storeSessionBinding({ runtimeDir, harnessSessionId, accSessionId,
|
|
24
|
-
generation }) {
|
|
25
|
+
generation, clientVersion, platform }) {
|
|
25
26
|
const file = fileFor(runtimeDir, harnessSessionId);
|
|
26
27
|
await mkdir(path.dirname(file), { recursive: true });
|
|
27
28
|
const record = { schemaVersion: SCHEMA_VERSION, harnessSessionId, accSessionId, generation };
|
|
29
|
+
if (typeof clientVersion === "string" && clientVersion !== "") record.clientVersion = clientVersion;
|
|
30
|
+
if (typeof platform === "string" && platform !== "") record.platform = platform;
|
|
28
31
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
29
32
|
await writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
30
33
|
// Replace rather than append: re-attaching supersedes the old generation, and
|
|
@@ -54,7 +57,10 @@ export async function loadSessionBinding({ runtimeDir, harnessSessionId }) {
|
|
|
54
57
|
throw new AccError(EXIT.DATA, "unknown session binding schemaVersion",
|
|
55
58
|
{ file, schemaVersion: record?.schemaVersion });
|
|
56
59
|
}
|
|
57
|
-
|
|
60
|
+
const binding = { accSessionId: record.accSessionId, generation: record.generation };
|
|
61
|
+
if (typeof record.clientVersion === "string") binding.clientVersion = record.clientVersion;
|
|
62
|
+
if (typeof record.platform === "string") binding.platform = record.platform;
|
|
63
|
+
return binding;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export async function clearSessionBinding({ runtimeDir, harnessSessionId }) {
|
|
@@ -89,7 +95,10 @@ export async function listSessionBindings({ runtimeDir }) {
|
|
|
89
95
|
.then(JSON.parse).catch(() => null);
|
|
90
96
|
if (record?.schemaVersion !== SCHEMA_VERSION) continue;
|
|
91
97
|
bindings.push({ harnessSessionId: record.harnessSessionId,
|
|
92
|
-
accSessionId: record.accSessionId, generation: record.generation
|
|
98
|
+
accSessionId: record.accSessionId, generation: record.generation,
|
|
99
|
+
...(typeof record.clientVersion === "string"
|
|
100
|
+
? { clientVersion: record.clientVersion } : {}),
|
|
101
|
+
...(typeof record.platform === "string" ? { platform: record.platform } : {}) });
|
|
93
102
|
}
|
|
94
103
|
return bindings;
|
|
95
104
|
}
|
|
@@ -14,7 +14,7 @@ export const COMMANDS = Object.freeze({
|
|
|
14
14
|
detach: { required: ["session", "generation"], optional: [] },
|
|
15
15
|
sync: { required: [], optional: ["session", "cursor", "limit", "scope"] },
|
|
16
16
|
work: { required: [], optional: ["session", "generation", "summary", "mode",
|
|
17
|
-
"state"
|
|
17
|
+
"state"], repeated: ["hint"], flags: ["clear"] },
|
|
18
18
|
claim: { required: ["resource"],
|
|
19
19
|
optional: ["session", "generation", "mode", "enforcement", "reason", "lease"] },
|
|
20
20
|
// Neither is required on its own, because either one names the claim: an id
|
|
@@ -24,37 +24,17 @@ export const COMMANDS = Object.freeze({
|
|
|
24
24
|
release: { required: [],
|
|
25
25
|
optional: ["claim", "resource", "session", "generation", "authority", "reason"] },
|
|
26
26
|
message: { required: ["subject", "body"],
|
|
27
|
-
optional: ["session", "generation", "type", "
|
|
28
|
-
repeated: ["to"]
|
|
27
|
+
optional: ["session", "generation", "type", "obligation", "client-message-id"],
|
|
28
|
+
repeated: ["to"] },
|
|
29
29
|
inbox: { required: [], optional: ["session", "generation", "message"] },
|
|
30
30
|
reply: { required: ["message", "body"],
|
|
31
|
-
optional: ["session", "generation", "subject", "
|
|
32
|
-
// Asking another agent to do something
|
|
33
|
-
// told about and a message pointing at no task are each useless.
|
|
31
|
+
optional: ["session", "generation", "subject", "client-message-id"] },
|
|
32
|
+
// Asking another agent to do something as a message with a reply obligation.
|
|
34
33
|
request: { required: ["to", "title"],
|
|
35
|
-
optional: ["session", "generation", "detail", "
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
task: { required: [], optional: ["session", "generation", "workstream", "title",
|
|
40
|
-
"detail", "assignee", "state", "task", "reason"],
|
|
41
|
-
repeated: ["depends-on"], flags: ["take", "decline", "force"] },
|
|
42
|
-
// Create one, or take and hand back the coordination of one that exists.
|
|
43
|
-
// Creating a workstream raised `coordinator_missing` on every turn from then
|
|
44
|
-
// on, and nothing could answer it: the two core operations that do had no
|
|
45
|
-
// surface at all.
|
|
46
|
-
workstream: { required: [], optional: ["session", "generation", "title", "objective",
|
|
47
|
-
"workstream"], flags: ["take", "release"] },
|
|
48
|
-
// Messages not tied to a task need a way to be answered too. Without one a
|
|
49
|
-
// `requiresAck` message raised an attention item nothing could ever clear.
|
|
50
|
-
ack: { required: ["message"], optional: ["session", "generation", "state"] },
|
|
51
|
-
// Recording what was settled, so the next session does not reopen it. The
|
|
52
|
-
// protocol has described this object from the start and nothing could make
|
|
53
|
-
// one: no command, no tool.
|
|
54
|
-
decide: { required: ["title", "outcome"],
|
|
55
|
-
optional: ["session", "generation", "authority", "workstream", "supersedes"],
|
|
56
|
-
repeated: ["decided-by"], flags: ["human"] },
|
|
57
|
-
finish: { required: ["goal"], optional: ["session", "generation", "status", "to"],
|
|
34
|
+
optional: ["session", "generation", "detail", "client-message-id"] },
|
|
35
|
+
ack: { required: ["message"], optional: ["session", "generation"] },
|
|
36
|
+
finish: { required: ["goal"], optional: ["session", "generation", "status", "to",
|
|
37
|
+
"client-message-id"],
|
|
58
38
|
repeated: ["completed", "remaining", "blocker"] },
|
|
59
39
|
status: { required: [], optional: ["participant"], flags: ["all"] },
|
|
60
40
|
doctor: { required: [], optional: ["home"], flags: ["repair"] },
|
|
@@ -69,7 +49,8 @@ export const COMMANDS = Object.freeze({
|
|
|
69
49
|
// `--downgrade` because an older acc first on PATH will otherwise rewire every
|
|
70
50
|
// client to itself, and the only symptom is a guard behaving like the version
|
|
71
51
|
// it came from.
|
|
72
|
-
install: { required: [], optional: ["adapter", "home"
|
|
52
|
+
install: { required: [], optional: ["adapter", "home", "delivery"],
|
|
53
|
+
flags: ["dry-run", "downgrade"] },
|
|
73
54
|
// `--dry-run` on both, because the preview was computed for either action and
|
|
74
55
|
// only `install` could ask for it. Removal is the side that reaches into a
|
|
75
56
|
// client's configuration - including a client that has left the machine.
|
|
@@ -238,6 +238,9 @@ export async function runDoctor({ options, context, runtime }) {
|
|
|
238
238
|
// the store was healthy and nothing else.
|
|
239
239
|
const text = [`store healthy; ${describePresence(status.counts)}; `
|
|
240
240
|
+ `protection ${status.protection}; ${installed} of ${adapters.length} adapter(s) installed`,
|
|
241
|
+
...adapters.filter(adapter => (adapter.present || adapter.installed)
|
|
242
|
+
&& typeof adapter.deliveryDiagnostic === "string")
|
|
243
|
+
.map(adapter => ` ${adapter.deliveryDiagnostic}`),
|
|
241
244
|
...data.remediation.map(line => ` ${line}`),
|
|
242
245
|
// `0 of 4` is a true line that reads as a broken machine, and on an
|
|
243
246
|
// MCP-only one it would read that way on every run forever. The server needs
|
|
@@ -14,7 +14,7 @@ import { COMMANDS } from "./args.mjs";
|
|
|
14
14
|
const GROUPS = Object.freeze([
|
|
15
15
|
["Set up", ["install", "uninstall", "doctor", "config"]],
|
|
16
16
|
["In a session", ["status", "sync", "work", "claim", "release", "inbox", "reply",
|
|
17
|
-
"ack", "message", "request", "
|
|
17
|
+
"ack", "message", "request", "finish"]],
|
|
18
18
|
["Driven by adapters, not by people", ["attach", "heartbeat", "detach"]],
|
|
19
19
|
["About acc", ["help", "version", "update"]],
|
|
20
20
|
]);
|
|
@@ -33,10 +33,7 @@ const SUMMARY = Object.freeze({
|
|
|
33
33
|
message: "send a typed message to named participants",
|
|
34
34
|
inbox: "read only unresolved messages addressed to this participant",
|
|
35
35
|
reply: "reply to one message and acknowledge it in the same operation",
|
|
36
|
-
request: "ask another agent to do something
|
|
37
|
-
task: "create work, --take it, or move its --state along",
|
|
38
|
-
workstream: "group related work, and steer it with --take / --release",
|
|
39
|
-
decide: "record what was settled, so the next session does not reopen it",
|
|
36
|
+
request: "ask another agent to do something in a reply-required message",
|
|
40
37
|
finish: "write the handoff and release what this session held",
|
|
41
38
|
attach: "open a session; an adapter calls this, not a person",
|
|
42
39
|
heartbeat: "say the session is still alive",
|
|
@@ -110,6 +110,8 @@ export function describeChanges(operation, home) {
|
|
|
110
110
|
}
|
|
111
111
|
return [
|
|
112
112
|
...(operation.removed ?? []).map(file => ` removed ${shorten(file, home)}`),
|
|
113
|
+
...(operation.removedDirectories ?? [])
|
|
114
|
+
.map(file => ` removed ${shorten(file, home)}`),
|
|
113
115
|
...edited,
|
|
114
116
|
...(operation.kept ?? [])
|
|
115
117
|
.map(file => ` kept ${shorten(file, home)} - changed since ACC wrote it`),
|
|
@@ -122,6 +124,9 @@ export function describeOutcome({ action, acted, failed = [], skipped = [],
|
|
|
122
124
|
+ (failed.length > 0 ? `; ${failed.length} failed` : ""),
|
|
123
125
|
...operations.filter(operation => operation.applied)
|
|
124
126
|
.flatMap(operation => describeChanges(operation, home)),
|
|
127
|
+
...operations.filter(operation => operation.applied
|
|
128
|
+
&& typeof operation.deliveryDiagnostic === "string")
|
|
129
|
+
.map(operation => ` ${operation.deliveryDiagnostic}`),
|
|
125
130
|
...skipped.map(entry => ` skip ${entry.adapterId}: ${entry.reason}`),
|
|
126
131
|
...failed.map(entry => ` ${entry.adapterId}: ${entry.error}`),
|
|
127
132
|
// Said once, where it is needed: the reader has just been shown a list of
|
|
@@ -165,7 +170,8 @@ export function failureOf({ action, acted, failed = [] }) {
|
|
|
165
170
|
export function actedOn(result) {
|
|
166
171
|
return result.operations.filter(operation => operation.applied
|
|
167
172
|
&& (result.action === "install"
|
|
168
|
-
|| (operation.removed?.length ?? 0) + (operation.
|
|
173
|
+
|| (operation.removed?.length ?? 0) + (operation.removedDirectories?.length ?? 0)
|
|
174
|
+
+ (operation.changes?.length ?? 0) > 0)).length;
|
|
169
175
|
}
|
|
170
176
|
|
|
171
177
|
export async function runInstallCommand({ options, runtime, action = "install" }) {
|
|
@@ -200,7 +206,8 @@ export async function runInstallCommand({ options, runtime, action = "install" }
|
|
|
200
206
|
? []
|
|
201
207
|
: (Array.isArray(options.adapter) ? options.adapter : [options.adapter]);
|
|
202
208
|
const plan = planInstallation({ adapters, detected, context, action, recorded,
|
|
203
|
-
accVersion, allowDowngrade: options.downgrade === true, requested
|
|
209
|
+
accVersion, allowDowngrade: options.downgrade === true, requested,
|
|
210
|
+
delivery: options.delivery ?? "off" });
|
|
204
211
|
const result = await applyPlan({ plan, adapters, context, dataHome, dryRun, accVersion });
|
|
205
212
|
|
|
206
213
|
const acted = actedOn(result);
|