agents-can-communicate 0.1.18 → 0.3.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 +87 -69
- package/SECURITY.md +31 -0
- package/bin/acc-bootstrap.mjs +56 -0
- package/bin/acc-claude-channel.mjs +177 -0
- package/bin/acc-hook.mjs +94 -12
- package/bin/acc-mcp.mjs +6 -2
- package/bin/acc.mjs +13 -3
- package/docs/ADAPTER_AUTHORING.md +204 -0
- package/docs/ARCHITECTURE.md +131 -0
- package/docs/CAPABILITIES.md +117 -214
- package/docs/CLI.md +164 -0
- package/docs/CONCEPTS.md +134 -0
- package/docs/CONFIGURATION.md +147 -0
- package/docs/DESIGN_DECISIONS.md +89 -0
- package/docs/GETTING_STARTED.md +145 -0
- package/docs/GLOSSARY.md +26 -0
- package/docs/HOW_IT_WORKS.md +277 -0
- package/docs/MCP.md +94 -0
- package/docs/PROTOCOL.md +200 -0
- package/docs/RELEASING.md +115 -0
- package/docs/SECURITY_MODEL.md +131 -0
- package/docs/TROUBLESHOOTING.md +108 -0
- package/docs/WHY_ACC.md +61 -0
- package/docs/index.md +44 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/certification.json +228 -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 +269 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.252.json +21 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.258.json +23 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/fixtures/delivery/claude-code-2.1.260.json +23 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +13 -2
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/.mcp.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +22 -22
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +45 -5
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/channel.mjs +377 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +27 -7
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/native-delivery.mjs +229 -0
- package/node_modules/@agents-can-communicate/adapter-codex/certification.json +150 -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 +199 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.0.json +21 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-remote-workspace.json +25 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +11 -2
- 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 +22 -22
- package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +54 -12
- package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +121 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +151 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +192 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/certification.json +68 -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 +22 -22
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/BeforeAgent-0.57.0.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/BeforeTool-0.57.0.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/BeforeTool-shell-0.57.0.json +12 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/SessionEnd-0.57.0.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/SessionStart-0.57.0.json +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/fixtures/certification-provenance.json +293 -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 +31 -13
- 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 +22 -22
- package/node_modules/@agents-can-communicate/adapter-grok/src/adapter.mjs +11 -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 +22 -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 +52 -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 +9 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +7 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-activation.mjs +76 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +202 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +101 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +28 -4
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +12 -31
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +70 -5
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +2 -5
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +111 -12
- 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 +131 -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 +21 -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 +131 -0
- package/node_modules/@agents-can-communicate/hook-runner/package.json +4 -2
- package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +20 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +90 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +190 -105
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +70 -10
- package/node_modules/@agents-can-communicate/installer/src/bootstrap-runtime.mjs +144 -0
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +89 -5
- package/node_modules/@agents-can-communicate/installer/src/index.mjs +10 -2
- package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +161 -0
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +112 -12
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +54 -2
- package/node_modules/@agents-can-communicate/installer/src/shell-bootstrap.mjs +210 -0
- 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/fields.mjs +17 -0
- package/node_modules/@agents-can-communicate/protocol/src/index.mjs +4 -1
- package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +64 -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 +20 -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,210 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, readdir, readFile, rename, rm, rmdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { AccError, EXIT, assertPortableId } from "@agents-can-communicate/protocol";
|
|
6
|
+
|
|
7
|
+
// The reversible shell half of native delivery: one marked PATH block in the
|
|
8
|
+
// user's zsh rc and one per-command shim in an ACC-owned directory. The shim
|
|
9
|
+
// keeps the user's command name, runs the bounded bootstrap check, and then
|
|
10
|
+
// replaces itself with the real vendor executable through `exec`; ACC is never
|
|
11
|
+
// the parent of a model session. Every byte ACC writes is recorded with its
|
|
12
|
+
// hash so uninstall removes only what is still ACC's and refuses a block or
|
|
13
|
+
// shim someone has edited.
|
|
14
|
+
|
|
15
|
+
export const BLOCK_BEGIN = "# >>> agents-can-communicate native delivery >>>";
|
|
16
|
+
export const BLOCK_END = "# <<< agents-can-communicate native delivery <<<";
|
|
17
|
+
export const SHIM_MARKER = "# agents-can-communicate native delivery shim";
|
|
18
|
+
export const SUPPORTED_SHELLS = Object.freeze(["zsh"]);
|
|
19
|
+
export const SHIM_POLICIES = Object.freeze(["actionable", "all"]);
|
|
20
|
+
const COMMAND_NAME = /^[a-z][a-z0-9_.-]*$/;
|
|
21
|
+
|
|
22
|
+
const usage = (message, details = {}) => { throw new AccError(EXIT.USAGE, message, details); };
|
|
23
|
+
const sha256 = text => createHash("sha256").update(text).digest("hex");
|
|
24
|
+
// Single-quoted POSIX literal: the only escaping is the quote itself, so no
|
|
25
|
+
// byte of a path or argument is ever interpreted by the shell.
|
|
26
|
+
export const shellLiteral = value => `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
27
|
+
const isAbsolute = value => typeof value === "string" && path.isAbsolute(value);
|
|
28
|
+
const inside = (root, candidate) => {
|
|
29
|
+
const relative = path.relative(root, candidate);
|
|
30
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function renderPathBlock(shimDir) {
|
|
34
|
+
return `${BLOCK_BEGIN}\nexport PATH=${shellLiteral(shimDir)}:"$PATH"\n${BLOCK_END}\n`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function validateShimEntry(entry) {
|
|
38
|
+
if (entry === null || typeof entry !== "object") usage("a shim entry must be an object");
|
|
39
|
+
for (const key of Object.keys(entry)) {
|
|
40
|
+
if (!["adapterId", "command", "realExecutable", "prefixArgs", "livePolicy"].includes(key)) {
|
|
41
|
+
usage(`unknown shim entry field ${key}`, { key });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
assertPortableId(entry.adapterId, "shim adapter id");
|
|
45
|
+
if (typeof entry.command !== "string" || !COMMAND_NAME.test(entry.command)) {
|
|
46
|
+
usage("a shim command must be a bare command name", { command: entry.command });
|
|
47
|
+
}
|
|
48
|
+
if (!isAbsolute(entry.realExecutable)) usage("a shim realExecutable must be an absolute path");
|
|
49
|
+
if (!Array.isArray(entry.prefixArgs) || entry.prefixArgs.some(arg => typeof arg !== "string"
|
|
50
|
+
|| arg === "" || /[\0\n]/.test(arg))) {
|
|
51
|
+
usage("shim prefixArgs must be non-empty argument strings without NUL or newline");
|
|
52
|
+
}
|
|
53
|
+
if (!SHIM_POLICIES.includes(entry.livePolicy)) {
|
|
54
|
+
usage("a shim livePolicy must be actionable or all; off means no shim", { livePolicy: entry.livePolicy });
|
|
55
|
+
}
|
|
56
|
+
return Object.freeze({ ...entry, prefixArgs: Object.freeze([...entry.prefixArgs]) });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function renderCommandShim({ node, bootstrap, dataHome, entry }) {
|
|
60
|
+
const shim = validateShimEntry(entry);
|
|
61
|
+
for (const [name, value] of Object.entries({ node, bootstrap, dataHome })) {
|
|
62
|
+
if (!isAbsolute(value)) usage(`shim ${name} must be an absolute path`, { [name]: value });
|
|
63
|
+
}
|
|
64
|
+
const real = shellLiteral(shim.realExecutable);
|
|
65
|
+
const check = [node, bootstrap, "--adapter", shim.adapterId, "--real-executable",
|
|
66
|
+
shim.realExecutable, "--data-home", dataHome].map(shellLiteral).join(" ");
|
|
67
|
+
const prefix = shim.prefixArgs.map(shellLiteral).join(" ");
|
|
68
|
+
return [
|
|
69
|
+
"#!/bin/sh",
|
|
70
|
+
`${SHIM_MARKER} for ${shellLiteral(shim.command)}. Generated; do not edit.`,
|
|
71
|
+
"# ACC_BYPASS=1 runs the vendor command untouched. A failed or missing check",
|
|
72
|
+
"# does the same: the vendor command is never blocked on ACC.",
|
|
73
|
+
'if [ "${ACC_BYPASS-}" = "1" ]; then',
|
|
74
|
+
" unset ACC_NATIVE_DELIVERY_POLICY",
|
|
75
|
+
` exec ${real} "$@"`,
|
|
76
|
+
"fi",
|
|
77
|
+
`if ${check} </dev/null >/dev/null 2>&1; then`,
|
|
78
|
+
` ACC_NATIVE_DELIVERY_POLICY=${shellLiteral(shim.livePolicy)}`,
|
|
79
|
+
" export ACC_NATIVE_DELIVERY_POLICY",
|
|
80
|
+
` exec ${real}${prefix === "" ? "" : ` ${prefix}`} "$@"`,
|
|
81
|
+
"fi",
|
|
82
|
+
"unset ACC_NATIVE_DELIVERY_POLICY",
|
|
83
|
+
`exec ${real} "$@"`,
|
|
84
|
+
"",
|
|
85
|
+
].join("\n");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function planShellBootstrap({ shell, rcFile, shimDir, entries = [], runtime = null }) {
|
|
89
|
+
if (!SUPPORTED_SHELLS.includes(shell)) {
|
|
90
|
+
return Object.freeze({ eligible: false, reasonCode: "unsupported_shell", shell: shell ?? null,
|
|
91
|
+
rcFile: null, shimDir: null, block: null, shims: Object.freeze([]), runtime: null });
|
|
92
|
+
}
|
|
93
|
+
if (!isAbsolute(rcFile) || !isAbsolute(shimDir)) usage("rcFile and shimDir must be absolute");
|
|
94
|
+
const validated = entries.map(validateShimEntry);
|
|
95
|
+
const commands = new Set();
|
|
96
|
+
for (const entry of validated) {
|
|
97
|
+
if (commands.has(entry.command)) usage(`duplicate shim command ${entry.command}`);
|
|
98
|
+
commands.add(entry.command);
|
|
99
|
+
// A shim that resolved to itself would loop forever; the real executable
|
|
100
|
+
// is resolved before the shim directory is ever on PATH.
|
|
101
|
+
if (inside(shimDir, entry.realExecutable)) {
|
|
102
|
+
usage("a shim realExecutable cannot live inside the shim directory",
|
|
103
|
+
{ realExecutable: entry.realExecutable });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Object.freeze({ eligible: true, reasonCode: null, shell, rcFile, shimDir,
|
|
107
|
+
block: renderPathBlock(shimDir), runtime,
|
|
108
|
+
shims: Object.freeze(validated.map(entry => Object.freeze({
|
|
109
|
+
path: path.join(shimDir, entry.command), entry }))) });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const defaultIo = Object.freeze({ readFile, writeFile, mkdir, chmod, rename, rm, rmdir, readdir });
|
|
113
|
+
|
|
114
|
+
async function readText(io, file) {
|
|
115
|
+
try {
|
|
116
|
+
return await io.readFile(file, "utf8");
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error.code === "ENOENT") return null;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function writeAtomic(io, file, content, mode) {
|
|
124
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
125
|
+
await io.writeFile(temporary, content, { mode });
|
|
126
|
+
await io.rename(temporary, file);
|
|
127
|
+
await io.chmod(file, mode);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// The block's exact span inside the rc text, or null. The block is matched by
|
|
131
|
+
// its two sentinel lines so a user's other lines are never touched.
|
|
132
|
+
export function locateBlock(text) {
|
|
133
|
+
if (typeof text !== "string") return null;
|
|
134
|
+
const begin = text.indexOf(BLOCK_BEGIN);
|
|
135
|
+
if (begin === -1 || (begin > 0 && text[begin - 1] !== "\n")) return null;
|
|
136
|
+
const endLine = text.indexOf(BLOCK_END, begin);
|
|
137
|
+
if (endLine === -1) return null;
|
|
138
|
+
const end = text.indexOf("\n", endLine);
|
|
139
|
+
return { start: begin, end: end === -1 ? text.length : end + 1 };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function installShellBootstrap({ plan, io = defaultIo }) {
|
|
143
|
+
if (!plan?.eligible) return { ok: false, reasonCode: plan?.reasonCode ?? "unsupported_shell" };
|
|
144
|
+
const { node, bootstrap, dataHome } = plan.runtime ?? {};
|
|
145
|
+
const current = await readText(io, plan.rcFile);
|
|
146
|
+
const span = locateBlock(current);
|
|
147
|
+
const existing = span === null ? null : current.slice(span.start, span.end);
|
|
148
|
+
if (existing !== null && existing !== plan.block) {
|
|
149
|
+
return { ok: false, reasonCode: "rc_block_modified", rcFile: plan.rcFile };
|
|
150
|
+
}
|
|
151
|
+
await io.mkdir(plan.shimDir, { recursive: true, mode: 0o700 });
|
|
152
|
+
await io.chmod(plan.shimDir, 0o700);
|
|
153
|
+
const shims = [];
|
|
154
|
+
for (const shim of plan.shims) {
|
|
155
|
+
const content = renderCommandShim({ node, bootstrap, dataHome, entry: shim.entry });
|
|
156
|
+
await writeAtomic(io, shim.path, content, 0o700);
|
|
157
|
+
shims.push({ path: shim.path, command: shim.entry.command, sha256: sha256(content) });
|
|
158
|
+
}
|
|
159
|
+
let appended = false;
|
|
160
|
+
if (existing === null) {
|
|
161
|
+
const base = current ?? "";
|
|
162
|
+
const separator = base === "" || base.endsWith("\n") ? "" : "\n";
|
|
163
|
+
await writeAtomic(io, plan.rcFile, `${base}${separator}${plan.block}`, 0o600);
|
|
164
|
+
appended = true;
|
|
165
|
+
}
|
|
166
|
+
return { ok: true, reasonCode: null, shell: plan.shell, shimDir: plan.shimDir,
|
|
167
|
+
rcFile: { path: plan.rcFile, blockSha256: sha256(plan.block), appended },
|
|
168
|
+
shims };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function uninstallShellBootstrap({ ownership, io = defaultIo }) {
|
|
172
|
+
const result = { ok: true, reasonCode: null, removedShims: [], keptShims: [],
|
|
173
|
+
missingShims: [], rcBlock: "absent" };
|
|
174
|
+
for (const shim of ownership?.shims ?? []) {
|
|
175
|
+
const content = await readText(io, shim.path);
|
|
176
|
+
if (content === null) { result.missingShims.push(shim.path); continue; }
|
|
177
|
+
if (sha256(content) !== shim.sha256) { result.keptShims.push(shim.path); continue; }
|
|
178
|
+
await io.rm(shim.path, { force: true });
|
|
179
|
+
result.removedShims.push(shim.path);
|
|
180
|
+
}
|
|
181
|
+
const shimDir = ownership?.shimDir;
|
|
182
|
+
let remaining = [];
|
|
183
|
+
if (typeof shimDir === "string") {
|
|
184
|
+
const names = await io.readdir(shimDir).catch(() => []);
|
|
185
|
+
for (const name of names) {
|
|
186
|
+
const content = await readText(io, path.join(shimDir, name));
|
|
187
|
+
if (content !== null && content.includes(SHIM_MARKER)) remaining.push(name);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const rcFile = ownership?.rcFile;
|
|
191
|
+
const text = typeof rcFile?.path === "string" ? await readText(io, rcFile.path) : null;
|
|
192
|
+
const span = locateBlock(text);
|
|
193
|
+
if (span !== null) {
|
|
194
|
+
const block = text.slice(span.start, span.end);
|
|
195
|
+
if (sha256(block) !== rcFile.blockSha256) {
|
|
196
|
+
result.rcBlock = "modified";
|
|
197
|
+
result.ok = false;
|
|
198
|
+
result.reasonCode = "rc_block_modified";
|
|
199
|
+
} else if (remaining.length > 0) {
|
|
200
|
+
result.rcBlock = "kept";
|
|
201
|
+
} else {
|
|
202
|
+
await writeAtomic(io, rcFile.path, text.slice(0, span.start) + text.slice(span.end), 0o600);
|
|
203
|
+
result.rcBlock = "removed";
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (typeof shimDir === "string" && remaining.length === 0 && result.keptShims.length === 0) {
|
|
207
|
+
await io.rmdir(shimDir).catch(() => null);
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
class InputValidationError extends Error {}
|
|
2
|
+
|
|
3
|
+
const fail = (path, message) => {
|
|
4
|
+
throw new InputValidationError(`${path} ${message}`);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const isObject = value => value !== null && typeof value === "object"
|
|
8
|
+
&& !Array.isArray(value);
|
|
9
|
+
|
|
10
|
+
function matches(schema, value, path) {
|
|
11
|
+
try {
|
|
12
|
+
validateSchema(schema, value, path);
|
|
13
|
+
return true;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (!(error instanceof InputValidationError)) throw error;
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function validateSchema(schema, value, path) {
|
|
21
|
+
if (schema.const !== undefined && !Object.is(value, schema.const)) {
|
|
22
|
+
fail(path, `must equal ${JSON.stringify(schema.const)}`);
|
|
23
|
+
}
|
|
24
|
+
if (schema.enum !== undefined && !schema.enum.some(item => Object.is(item, value))) {
|
|
25
|
+
fail(path, `must be one of ${schema.enum.map(item => JSON.stringify(item)).join(", ")}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const objectKeywords = schema.type === "object" || schema.properties !== undefined
|
|
29
|
+
|| schema.required !== undefined || schema.additionalProperties !== undefined;
|
|
30
|
+
if (objectKeywords) {
|
|
31
|
+
if (!isObject(value)) fail(path, "must be an object");
|
|
32
|
+
const properties = schema.properties ?? {};
|
|
33
|
+
for (const key of schema.required ?? []) {
|
|
34
|
+
if (!Object.hasOwn(value, key)) fail(`${path}.${key}`, "is required");
|
|
35
|
+
}
|
|
36
|
+
if (schema.additionalProperties === false) {
|
|
37
|
+
for (const key of Object.keys(value)) {
|
|
38
|
+
if (!Object.hasOwn(properties, key)) fail(`${path}.${key}`, "is not a known field");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
42
|
+
if (Object.hasOwn(value, key)) validateSchema(child, value[key], `${path}.${key}`);
|
|
43
|
+
}
|
|
44
|
+
} else if (schema.type === "array") {
|
|
45
|
+
if (!Array.isArray(value)) fail(path, "must be an array");
|
|
46
|
+
if (schema.items !== undefined) {
|
|
47
|
+
value.forEach((item, index) => validateSchema(schema.items, item, `${path}[${index}]`));
|
|
48
|
+
}
|
|
49
|
+
} else if (schema.type === "string" && typeof value !== "string") {
|
|
50
|
+
fail(path, "must be a string");
|
|
51
|
+
} else if (schema.type === "boolean" && typeof value !== "boolean") {
|
|
52
|
+
fail(path, "must be a boolean");
|
|
53
|
+
} else if (schema.type === "integer" && !Number.isInteger(value)) {
|
|
54
|
+
fail(path, "must be an integer");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
58
|
+
fail(path, `must be at least ${schema.minimum}`);
|
|
59
|
+
}
|
|
60
|
+
if (schema.maximum !== undefined && value > schema.maximum) {
|
|
61
|
+
fail(path, `must be at most ${schema.maximum}`);
|
|
62
|
+
}
|
|
63
|
+
if (schema.anyOf !== undefined
|
|
64
|
+
&& !schema.anyOf.some(branch => matches(branch, value, path))) {
|
|
65
|
+
fail(path, "must match an accepted shape");
|
|
66
|
+
}
|
|
67
|
+
if (schema.oneOf !== undefined
|
|
68
|
+
&& schema.oneOf.filter(branch => matches(branch, value, path)).length !== 1) {
|
|
69
|
+
fail(path, "must match exactly one accepted shape");
|
|
70
|
+
}
|
|
71
|
+
if (schema.not !== undefined && matches(schema.not, value, path)) {
|
|
72
|
+
fail(path, "contains fields that cannot be combined");
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function validateToolInput(schema, value) {
|
|
78
|
+
return validateSchema(schema, value, "arguments");
|
|
79
|
+
}
|
|
@@ -15,42 +15,37 @@ function escapeText(value) {
|
|
|
15
15
|
return result;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function escapePeerValue(value) {
|
|
19
|
+
if (typeof value === "string") return escapeText(value);
|
|
20
|
+
if (Array.isArray(value)) return value.map(escapePeerValue);
|
|
21
|
+
if (value !== null && typeof value === "object") {
|
|
22
|
+
return Object.fromEntries(Object.entries(value)
|
|
23
|
+
.map(([key, child]) => [key, escapePeerValue(child)]));
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
const attributedMessage = message => ({
|
|
19
|
-
|
|
20
|
-
from: message.fromSessionId,
|
|
21
|
-
type: message.type,
|
|
22
|
-
priority: message.priority,
|
|
23
|
-
requiresAck: message.requiresAck,
|
|
24
|
-
sentAt: message.sentAt,
|
|
29
|
+
...escapePeerValue(message),
|
|
25
30
|
trust: "untrusted peer content",
|
|
26
|
-
subject: escapeText(message.subject),
|
|
27
|
-
body: escapeText(message.body),
|
|
28
31
|
});
|
|
29
32
|
|
|
30
|
-
export async function readResource(uri, { service, participantId, workspaceId }) {
|
|
31
|
-
const snapshot = await service.store.snapshot(workspaceId);
|
|
33
|
+
export async function readResource(uri, { service, participantId, workspaceId, session }) {
|
|
32
34
|
switch (uri) {
|
|
33
|
-
case "acc://snapshot":
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
case "acc://snapshot": {
|
|
36
|
+
const { snapshot } = await service.sync({ workspaceId, scope: "full" });
|
|
37
|
+
return { ...snapshot, messages: snapshot.messages.map(attributedMessage) };
|
|
38
|
+
}
|
|
36
39
|
case "acc://roster":
|
|
37
40
|
return (await service.sync({ workspaceId })).roster;
|
|
38
|
-
case "acc://workstreams":
|
|
39
|
-
return snapshot.workstreams;
|
|
40
|
-
case "acc://tasks":
|
|
41
|
-
return snapshot.tasks;
|
|
42
41
|
case "acc://inbox": {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|| message.toParticipantIds.includes(participantId)
|
|
51
|
-
|| snapshot.sessions.some(session => session.sessionId === message.fromSessionId
|
|
52
|
-
&& session.participantId === participantId))
|
|
53
|
-
.map(attributedMessage);
|
|
42
|
+
if (session === undefined) {
|
|
43
|
+
throw new AccError(EXIT.DATA, "the inbox resource requires a resolved session",
|
|
44
|
+
{ participantId });
|
|
45
|
+
}
|
|
46
|
+
const inbox = await service.readInbox({ workspaceId, sessionId: session.sessionId,
|
|
47
|
+
generation: session.generation });
|
|
48
|
+
return inbox.map(item => attributedMessage(item.message));
|
|
54
49
|
}
|
|
55
50
|
default:
|
|
56
51
|
throw new AccError(EXIT.DATA, `unknown resource: ${uri}`, { uri });
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
import { AccError, EXIT, GENERIC_MESSAGE_KINDS, VALID_OBLIGATIONS }
|
|
4
|
+
from "@agents-can-communicate/protocol";
|
|
3
5
|
import { clearSessionBinding, loadSessionBinding, storeSessionBinding }
|
|
4
6
|
from "@agents-can-communicate/adapter-sdk";
|
|
5
7
|
|
|
6
8
|
import { readResource } from "./resources.mjs";
|
|
9
|
+
import { validateToolInput } from "./input-validator.mjs";
|
|
7
10
|
import { MCP_CAPABILITIES, PUBLIC_TOOLS, RESOURCES } from "./tools.mjs";
|
|
8
11
|
|
|
9
12
|
export const PROTOCOL_VERSION = "2026-07-28";
|
|
10
13
|
export const SUPPORTED_VERSIONS = Object.freeze([PROTOCOL_VERSION]);
|
|
11
|
-
const
|
|
14
|
+
const PACKAGE_VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
15
|
+
const SERVER_INFO = Object.freeze({ name: "agents-can-communicate", version: PACKAGE_VERSION });
|
|
12
16
|
|
|
13
17
|
const META = "io.modelcontextprotocol";
|
|
14
18
|
const HEARTBEAT_CADENCE_MS = 60_000;
|
|
@@ -72,20 +76,40 @@ async function resolveSession(context) {
|
|
|
72
76
|
return session;
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
export async function recordAndOffer({ record, router, selectMessage = value => value }) {
|
|
80
|
+
const recorded = await record();
|
|
81
|
+
const message = selectMessage(recorded);
|
|
82
|
+
if (router === null || router === undefined
|
|
83
|
+
|| !Array.isArray(message?.toParticipantIds) || message.toParticipantIds.length === 0) {
|
|
84
|
+
return { recorded, delivery: [] };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
return { recorded, delivery: await router.offer(message) };
|
|
88
|
+
} catch {
|
|
89
|
+
return { recorded, delivery: message.toParticipantIds.map(recipientParticipantId => ({
|
|
90
|
+
recipientParticipantId, outcome: "queued", transport: "durable",
|
|
91
|
+
errorCode: "transport_error",
|
|
92
|
+
})) };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const clientMessageId = (args, service) =>
|
|
97
|
+
args.clientMessageId ?? service.ids.next("client");
|
|
98
|
+
|
|
99
|
+
function obligationFor(kind, explicit, addressed) {
|
|
100
|
+
if (!GENERIC_MESSAGE_KINDS.includes(kind)) {
|
|
101
|
+
const command = kind === "answer"
|
|
102
|
+
? "acc_reply" : kind === "handoff" ? "acc_finish" : null;
|
|
103
|
+
throw new AccError(EXIT.USAGE, command === null
|
|
104
|
+
? `unknown message kind: ${kind}` : `${kind} messages require ${command}`);
|
|
105
|
+
}
|
|
106
|
+
const obligation = explicit ?? VALID_OBLIGATIONS[kind][0];
|
|
107
|
+
if (!VALID_OBLIGATIONS[kind].includes(obligation)
|
|
108
|
+
|| (!addressed && obligation !== "none")) {
|
|
109
|
+
throw new AccError(EXIT.USAGE,
|
|
110
|
+
`message obligation ${obligation} is invalid for ${kind}`);
|
|
87
111
|
}
|
|
88
|
-
return
|
|
112
|
+
return obligation;
|
|
89
113
|
}
|
|
90
114
|
|
|
91
115
|
/**
|
|
@@ -93,7 +117,7 @@ async function syncWithMail(service, owner, context, args) {
|
|
|
93
117
|
*
|
|
94
118
|
* The hook runtime hands a session its pending messages when it builds a turn,
|
|
95
119
|
* and marks them delivered. An MCP client has no turn and no hook, and no tool
|
|
96
|
-
* ever handed it anything: it saw a `
|
|
120
|
+
* ever handed it anything: it saw a `reply_required` line carrying a subject and
|
|
97
121
|
* an id, and to read what a peer had actually said it had to ask for the whole
|
|
98
122
|
* snapshot and search every message in the workspace for its own name.
|
|
99
123
|
*
|
|
@@ -102,7 +126,7 @@ async function syncWithMail(service, owner, context, args) {
|
|
|
102
126
|
* that had answered it.
|
|
103
127
|
*
|
|
104
128
|
* Returning them here is delivery, in the same sense and with the same honesty
|
|
105
|
-
* as the turn: what is handed over is marked `
|
|
129
|
+
* as the turn: what is handed over is marked `retrieved`, and nothing else is.
|
|
106
130
|
* Acknowledgement stays a separate act, because being shown something is not
|
|
107
131
|
* agreeing to it.
|
|
108
132
|
*/
|
|
@@ -113,72 +137,66 @@ async function callTool(name, args, context) {
|
|
|
113
137
|
const service = context.service;
|
|
114
138
|
|
|
115
139
|
switch (name) {
|
|
140
|
+
case "acc_status":
|
|
141
|
+
return service.collectStatus({});
|
|
116
142
|
case "acc_sync":
|
|
117
|
-
return
|
|
143
|
+
return service.sync({ ...owner, cursor: args.cursor ?? null,
|
|
144
|
+
scope: args.scope, limit: args.limit });
|
|
118
145
|
case "acc_work":
|
|
119
|
-
if (args.clear === true)
|
|
146
|
+
if (args.clear === true) {
|
|
147
|
+
await service.clearIntent({ ...owner });
|
|
148
|
+
return { cleared: true };
|
|
149
|
+
}
|
|
120
150
|
return service.setIntent({ ...owner, summary: args.summary, mode: args.mode,
|
|
121
|
-
state: args.state,
|
|
122
|
-
resourceHints: args.resourceHints ?? [] });
|
|
151
|
+
state: args.state, resourceHints: args.resourceHints ?? [] });
|
|
123
152
|
case "acc_claim":
|
|
124
|
-
if (args.action === "release") return service.releaseClaim({ ...owner,
|
|
125
|
-
claimId: args.claimId }) ?? { released: args.claimId };
|
|
126
153
|
if (args.action === "renew") return service.renewClaim({ ...owner,
|
|
127
154
|
claimId: args.claimId, leaseSeconds: args.leaseSeconds });
|
|
128
155
|
return service.acquireClaim({ ...owner, resource: args.resource,
|
|
129
156
|
mode: args.mode ?? "exclusive", enforcement: "advisory",
|
|
130
157
|
reason: args.reason ?? "unspecified", leaseSeconds: args.leaseSeconds });
|
|
158
|
+
case "acc_release":
|
|
159
|
+
await service.releaseClaim({ ...owner, claimId: args.claimId });
|
|
160
|
+
return { released: args.claimId };
|
|
131
161
|
case "acc_message": {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
return
|
|
162
|
+
const kind = args.kind ?? "note";
|
|
163
|
+
const toParticipantIds = args.to ?? [];
|
|
164
|
+
const routed = await recordAndOffer({ router: context.deliveryRouter, record: () =>
|
|
165
|
+
service.sendMessage({ ...owner, clientMessageId: clientMessageId(args, service),
|
|
166
|
+
toParticipantIds, subject: args.subject, body: args.body, kind,
|
|
167
|
+
obligation: obligationFor(kind, args.obligation, toParticipantIds.length > 0) }) });
|
|
168
|
+
const message = routed.recorded;
|
|
169
|
+
return { message, delivery: routed.delivery };
|
|
140
170
|
}
|
|
141
171
|
case "acc_inbox":
|
|
142
172
|
return service.readInbox({ ...owner, messageId: args.messageId });
|
|
143
|
-
case "acc_reply":
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
title: args.title, detail: args.detail, workstreamId: args.workstreamId,
|
|
160
|
-
priority: args.priority, dependsOn: args.dependsOn ?? [] });
|
|
173
|
+
case "acc_reply": {
|
|
174
|
+
const routed = await recordAndOffer({ router: context.deliveryRouter,
|
|
175
|
+
selectMessage: value => value.reply,
|
|
176
|
+
record: () => service.replyToMessage({ ...owner, messageId: args.messageId,
|
|
177
|
+
body: args.body, subject: args.subject,
|
|
178
|
+
clientMessageId: clientMessageId(args, service) }) });
|
|
179
|
+
return { message: routed.recorded.reply, delivery: routed.delivery };
|
|
180
|
+
}
|
|
181
|
+
case "acc_request": {
|
|
182
|
+
const routed = await recordAndOffer({ router: context.deliveryRouter,
|
|
183
|
+
record: () => service.sendMessage({ ...owner,
|
|
184
|
+
clientMessageId: clientMessageId(args, service),
|
|
185
|
+
toParticipantIds: [args.toParticipantId], kind: "request", obligation: "reply",
|
|
186
|
+
subject: args.title, body: args.detail ?? args.title }) });
|
|
187
|
+
return { message: routed.recorded, delivery: routed.delivery };
|
|
188
|
+
}
|
|
161
189
|
case "acc_ack":
|
|
162
|
-
return service.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
humanConfirmed: args.humanConfirmed === true });
|
|
169
|
-
case "acc_workstream":
|
|
170
|
-
if (args.action === "coordinate") {
|
|
171
|
-
return service.acquireCoordinator({ ...owner, workstreamId: args.workstreamId });
|
|
172
|
-
}
|
|
173
|
-
if (args.action === "release") {
|
|
174
|
-
return service.releaseCoordinator({ ...owner, workstreamId: args.workstreamId });
|
|
175
|
-
}
|
|
176
|
-
return service.createWorkstream({ ...owner, title: args.title,
|
|
177
|
-
objective: args.objective });
|
|
178
|
-
case "acc_finish":
|
|
179
|
-
return service.finishSession({ ...owner, goal: args.goal, status: args.status,
|
|
190
|
+
return service.acknowledgeMessage({ ...owner, messageId: args.messageId });
|
|
191
|
+
case "acc_finish": {
|
|
192
|
+
const routed = await recordAndOffer({ router: context.deliveryRouter,
|
|
193
|
+
selectMessage: value => value.message,
|
|
194
|
+
record: () => service.finishSession({ ...owner,
|
|
195
|
+
clientMessageId: clientMessageId(args, service), goal: args.goal, status: args.status,
|
|
180
196
|
completed: args.completed ?? [], remaining: args.remaining ?? [],
|
|
181
|
-
blockers: args.blockers ?? [], toParticipantId: args.toParticipantId
|
|
197
|
+
blockers: args.blockers ?? [], toParticipantId: args.toParticipantId }) });
|
|
198
|
+
return { message: routed.recorded.message, delivery: routed.delivery };
|
|
199
|
+
}
|
|
182
200
|
default:
|
|
183
201
|
throw new AccError(EXIT.USAGE, `unknown tool: ${name}`, { name });
|
|
184
202
|
}
|
|
@@ -198,15 +216,27 @@ async function handle(message, context) {
|
|
|
198
216
|
case "resources/list":
|
|
199
217
|
return complete({ resources: [...RESOURCES] });
|
|
200
218
|
case "resources/read": {
|
|
201
|
-
|
|
219
|
+
// Snapshot and roster are observation-only. Inbox is a delivery boundary:
|
|
220
|
+
// resolve this configured participant's durable session and let the core
|
|
221
|
+
// inbox service record that the returned bodies were retrieved.
|
|
222
|
+
const resourceContext = params.uri === "acc://inbox"
|
|
223
|
+
? { ...context, session: await resolveSession(context) }
|
|
224
|
+
: context;
|
|
225
|
+
const value = await readResource(params.uri, resourceContext);
|
|
202
226
|
return complete({ contents: [{ uri: params.uri, mimeType: "application/json",
|
|
203
227
|
text: JSON.stringify(value, null, 2) }] });
|
|
204
228
|
}
|
|
205
229
|
case "tools/call": {
|
|
206
230
|
try {
|
|
207
|
-
const
|
|
231
|
+
const args = params.arguments === undefined ? {} : params.arguments;
|
|
232
|
+
const tool = PUBLIC_TOOLS.find(candidate => candidate.name === params.name);
|
|
233
|
+
if (tool === undefined) {
|
|
234
|
+
throw new AccError(EXIT.USAGE, `unknown tool: ${params.name}`, { name: params.name });
|
|
235
|
+
}
|
|
236
|
+
validateToolInput(tool.inputSchema, args);
|
|
237
|
+
const value = await callTool(params.name, args, context);
|
|
208
238
|
return complete({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
209
|
-
structuredContent:
|
|
239
|
+
structuredContent: value });
|
|
210
240
|
} catch (error) {
|
|
211
241
|
// A failing operation is a tool result, not a transport failure: the
|
|
212
242
|
// model must see it and be able to react.
|