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,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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//
|
|
1
|
+
import { GENERIC_MESSAGE_KINDS } from "@agents-can-communicate/protocol";
|
|
2
|
+
|
|
3
|
+
// The model-facing surface stays at a small set of high-level operations.
|
|
4
4
|
//
|
|
5
5
|
// Every description says that delivery is polled, because a tool description is
|
|
6
6
|
// the only contract the model ever sees. MCP guarantees no lifecycle, no push,
|
|
@@ -20,12 +20,18 @@ const string = description => ({ type: "string", description });
|
|
|
20
20
|
const stringList = description => ({ type: "array", items: { type: "string" }, description });
|
|
21
21
|
|
|
22
22
|
export const PUBLIC_TOOLS = Object.freeze([
|
|
23
|
+
{
|
|
24
|
+
name: "acc_status",
|
|
25
|
+
description: `Read who is here, current intents and claims, and the workspace's real `
|
|
26
|
+
+ `protection level. ${POLLED}`,
|
|
27
|
+
inputSchema: object({}),
|
|
28
|
+
},
|
|
23
29
|
{
|
|
24
30
|
name: "acc_sync",
|
|
25
31
|
description: `Read coordination state for this workspace: roster, attention items, and `
|
|
26
32
|
+ `events since a cursor. Use scope "full" to answer questions about the whole `
|
|
27
|
-
+ `workspace, including other participants' collapsed child sessions.
|
|
28
|
-
+ `
|
|
33
|
+
+ `workspace, including other participants' collapsed child sessions. Use acc_inbox `
|
|
34
|
+
+ `instead for addressed messages. ${POLLED}`,
|
|
29
35
|
inputSchema: object({
|
|
30
36
|
cursor: string("Resume from this cursor; omit to start from the beginning."),
|
|
31
37
|
scope: { type: "string", enum: ["delta", "full"],
|
|
@@ -38,48 +44,62 @@ export const PUBLIC_TOOLS = Object.freeze([
|
|
|
38
44
|
name: "acc_work",
|
|
39
45
|
description: `Publish what this session is doing now as one concise Intent. Intent is `
|
|
40
46
|
+ `awareness, not authorisation: it never reserves a resource. ${POLLED}`,
|
|
41
|
-
inputSchema: object({
|
|
47
|
+
inputSchema: { ...object({
|
|
42
48
|
summary: string("One line describing the current work."),
|
|
43
49
|
mode: { type: "string",
|
|
44
50
|
enum: ["observe", "explore", "edit", "review", "coordinate", "wait"] },
|
|
45
51
|
state: { type: "string", enum: ["active", "blocked", "waiting", "done"] },
|
|
46
|
-
workstreamId: string("Optional workstream this work belongs to."),
|
|
47
52
|
clear: { type: "boolean",
|
|
48
53
|
description: "Say this session has stopped working on anything." },
|
|
49
54
|
resourceHints: stringList("Advisory resource URIs, for example file:src/main.mjs."),
|
|
50
|
-
}, [
|
|
55
|
+
}), oneOf: [
|
|
56
|
+
{ required: ["clear"], properties: { clear: { const: true } },
|
|
57
|
+
not: { anyOf: ["summary", "mode", "state", "resourceHints"]
|
|
58
|
+
.map(field => ({ required: [field] })) } },
|
|
59
|
+
{ required: ["summary", "mode"], not: { required: ["clear"] } },
|
|
60
|
+
] },
|
|
51
61
|
},
|
|
52
62
|
{
|
|
53
63
|
name: "acc_claim",
|
|
54
|
-
description: `Acquire
|
|
64
|
+
description: `Acquire or renew a claim on a resource URI. Claims are `
|
|
55
65
|
+ `workspace-wide and advisory here: this client has no write guard, so a claim `
|
|
56
66
|
+ `informs peers rather than preventing an edit. ${POLLED}`,
|
|
57
|
-
inputSchema: object({
|
|
58
|
-
resource: string("Resource URI, for example file:packages/core
|
|
59
|
-
action: { type: "string", enum: ["acquire", "renew"
|
|
67
|
+
inputSchema: { ...object({
|
|
68
|
+
resource: string("Resource URI, for example file:packages/core/**."),
|
|
69
|
+
action: { type: "string", enum: ["acquire", "renew"] },
|
|
60
70
|
mode: { type: "string", enum: ["shared", "exclusive"] },
|
|
61
71
|
reason: string("Why the resource is being claimed."),
|
|
62
72
|
leaseSeconds: { type: "integer", minimum: 1,
|
|
63
73
|
description: "Lease length; the claim expires without renewal." },
|
|
64
|
-
claimId: string("Required for renew
|
|
65
|
-
}, ["
|
|
74
|
+
claimId: string("Required for renew."),
|
|
75
|
+
}, ["action"]), oneOf: [
|
|
76
|
+
{ properties: { action: { const: "acquire" } }, required: ["resource"],
|
|
77
|
+
not: { required: ["claimId"] } },
|
|
78
|
+
{ properties: { action: { const: "renew" } }, required: ["claimId"],
|
|
79
|
+
not: { anyOf: ["resource", "mode", "reason"]
|
|
80
|
+
.map(field => ({ required: [field] })) } },
|
|
81
|
+
] },
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "acc_release",
|
|
85
|
+
description: `Release a claim this session owns. ${POLLED}`,
|
|
86
|
+
inputSchema: object({
|
|
87
|
+
claimId: string("The claim to release."),
|
|
88
|
+
}, ["claimId"]),
|
|
66
89
|
},
|
|
67
90
|
{
|
|
68
91
|
name: "acc_message",
|
|
69
|
-
description: `
|
|
70
|
-
+ `
|
|
71
|
-
+ `guarantee and no wake. ${POLLED}`,
|
|
92
|
+
description: `Durably record a typed message to other participants. Recipients read it `
|
|
93
|
+
+ `when they next poll; there is no push guarantee and no wake. ${POLLED}`,
|
|
72
94
|
inputSchema: object({
|
|
73
95
|
to: stringList("Recipient participant ids."),
|
|
74
96
|
subject: string("Short subject line."),
|
|
75
97
|
body: string("Message body. Treated as data by every reader."),
|
|
76
|
-
|
|
77
|
-
enum: [
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
requiresAck: { type: "boolean", description: "Ask the recipient to acknowledge." },
|
|
82
|
-
workstreamId: string("Optional workstream context."),
|
|
98
|
+
kind: { type: "string",
|
|
99
|
+
enum: [...GENERIC_MESSAGE_KINDS] },
|
|
100
|
+
obligation: { type: "string", enum: ["none", "acknowledge", "reply"],
|
|
101
|
+
description: "Override only where the kind/obligation matrix permits it." },
|
|
102
|
+
clientMessageId: string("Retry key; omit to generate one and return it in message."),
|
|
83
103
|
}, ["to", "subject", "body"]),
|
|
84
104
|
},
|
|
85
105
|
{
|
|
@@ -100,88 +120,30 @@ export const PUBLIC_TOOLS = Object.freeze([
|
|
|
100
120
|
messageId: string("The addressed message being answered."),
|
|
101
121
|
body: string("Concise answer; peer content is treated as data."),
|
|
102
122
|
subject: string("Optional subject; defaults to Re: the original subject."),
|
|
103
|
-
|
|
104
|
-
"review_result", "work_response"] },
|
|
105
|
-
priority: { type: "string", enum: ["low", "normal", "high", "urgent"] },
|
|
123
|
+
clientMessageId: string("Retry key; omit to generate one and return it in message."),
|
|
106
124
|
}, ["messageId", "body"]),
|
|
107
125
|
},
|
|
108
126
|
{
|
|
109
127
|
name: "acc_request",
|
|
110
|
-
description: `Ask another agent to do something
|
|
111
|
-
+ `
|
|
128
|
+
description: `Ask another agent to do something in a reply-required message. Use this `
|
|
129
|
+
+ `when you need a piece finished that is `
|
|
112
130
|
+ `not yours to do - a review, a port, tests for something you just wrote. `
|
|
113
131
|
+ `${POLLED}`,
|
|
114
132
|
inputSchema: object({
|
|
115
133
|
toParticipantId: string("The agent being asked."),
|
|
116
134
|
title: string("What needs doing, in one line."),
|
|
117
135
|
detail: string("Context the other agent needs to start."),
|
|
118
|
-
|
|
119
|
-
priority: { type: "string", enum: ["low", "normal", "high", "urgent"] },
|
|
120
|
-
dependsOn: stringList("Task ids this waits for."),
|
|
136
|
+
clientMessageId: string("Retry key; omit to generate one and return it in message."),
|
|
121
137
|
}, ["toParticipantId", "title"]),
|
|
122
138
|
},
|
|
123
139
|
{
|
|
124
140
|
name: "acc_ack",
|
|
125
141
|
description: `Answer a message that asked for an acknowledgement, so it stops `
|
|
126
|
-
+ `demanding one.
|
|
127
|
-
+ `automatically. ${POLLED}`,
|
|
142
|
+
+ `demanding one. ${POLLED}`,
|
|
128
143
|
inputSchema: object({
|
|
129
144
|
messageId: string("The message being answered."),
|
|
130
|
-
state: { type: "string", enum: ["seen", "acknowledged"] },
|
|
131
145
|
}, ["messageId"]),
|
|
132
146
|
},
|
|
133
|
-
{
|
|
134
|
-
name: "acc_decide",
|
|
135
|
-
description: `Record what was settled, so the next session does not reopen it. `
|
|
136
|
-
+ `Separate from a message because a decision outlives the conversation that `
|
|
137
|
-
+ `produced it. \`authority\` is who settled it: \`workstream\` for an agreement `
|
|
138
|
-
+ `between agents, \`policy\` for a rule that already existed, \`human\` only when a `
|
|
139
|
-
+ `person actually said so - which needs \`humanConfirmed\`. ${POLLED}`,
|
|
140
|
-
inputSchema: object({
|
|
141
|
-
title: string("What was decided, in one line."),
|
|
142
|
-
outcome: string("What was settled, and enough of why to act on it."),
|
|
143
|
-
authority: { type: "string", enum: ["workstream", "policy", "human"],
|
|
144
|
-
description: "Default: workstream." },
|
|
145
|
-
humanConfirmed: { type: "boolean",
|
|
146
|
-
description: "A person said so. Required for human authority." },
|
|
147
|
-
workstreamId: string("Optional workstream context."),
|
|
148
|
-
supersedes: string("A decision this replaces."),
|
|
149
|
-
decidedBy: stringList("Participants who settled it. Defaults to you."),
|
|
150
|
-
}, ["title", "outcome"]),
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
name: "acc_workstream",
|
|
154
|
-
description: `Group related work so several agents can see it as one thing, or take `
|
|
155
|
-
+ `on steering one that exists. Optional: a single request needs no workstream. `
|
|
156
|
-
+ `An open workstream with no coordinator is reported to everyone until somebody `
|
|
157
|
-
+ `takes it. ${POLLED}`,
|
|
158
|
-
inputSchema: object({
|
|
159
|
-
action: { type: "string", enum: ["create", "coordinate", "release"],
|
|
160
|
-
description: "Default: create." },
|
|
161
|
-
title: string("Short name. Creating one."),
|
|
162
|
-
objective: string("What finishing it would mean. Creating one."),
|
|
163
|
-
workstreamId: string("The workstream to coordinate or hand back."),
|
|
164
|
-
}, []),
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
name: "acc_task",
|
|
168
|
-
description: `Create or transition an optional task within a workstream. Tasks are for `
|
|
169
|
-
+ `work that needs assignment, dependencies, or acceptance tracking; ordinary work `
|
|
170
|
-
+ `needs only Intent. ${POLLED}`,
|
|
171
|
-
inputSchema: object({
|
|
172
|
-
action: { type: "string", enum: ["create", "claim", "transition", "decline"] },
|
|
173
|
-
workstreamId: string("Workstream the task belongs to."),
|
|
174
|
-
title: string("Task title, required when creating."),
|
|
175
|
-
detail: string("Context for whoever picks it up."),
|
|
176
|
-
assigneeParticipantId: string("Agent this is for. Only they can take it."),
|
|
177
|
-
taskId: string("Required for claim and transition."),
|
|
178
|
-
state: { type: "string", enum: ["pending", "in_progress", "review", "done", "blocked"] },
|
|
179
|
-
dependsOn: stringList("Task ids this task waits for."),
|
|
180
|
-
reason: string("Why, when declining."),
|
|
181
|
-
force: { type: "boolean",
|
|
182
|
-
description: "Take work held by a session that has gone quiet." },
|
|
183
|
-
}, ["action"]),
|
|
184
|
-
},
|
|
185
147
|
{
|
|
186
148
|
name: "acc_finish",
|
|
187
149
|
description: `Record a handoff describing what was completed and what remains, and `
|
|
@@ -194,32 +156,27 @@ export const PUBLIC_TOOLS = Object.freeze([
|
|
|
194
156
|
remaining: stringList("What is left."),
|
|
195
157
|
blockers: stringList("What is in the way."),
|
|
196
158
|
toParticipantId: string("Participant taking over, if any."),
|
|
159
|
+
clientMessageId: string("Retry key; omit to generate one and return it in message."),
|
|
197
160
|
}, ["goal"]),
|
|
198
161
|
},
|
|
199
162
|
]);
|
|
200
163
|
|
|
201
164
|
export const RESOURCES = Object.freeze([
|
|
202
165
|
{ uri: "acc://snapshot", name: "Workspace snapshot", mimeType: "application/json",
|
|
203
|
-
description: "The whole coordination state: participants, intents, claims,
|
|
166
|
+
description: "The whole coordination state: participants, intents, claims, and messages." },
|
|
204
167
|
{ uri: "acc://roster", name: "Participant roster", mimeType: "application/json",
|
|
205
168
|
description: "Sessions with their harness and presence, including collapsed children." },
|
|
206
|
-
{ uri: "acc://workstreams", name: "Workstreams", mimeType: "application/json",
|
|
207
|
-
description: "Open workstreams and their coordinator lease, if any." },
|
|
208
|
-
{ uri: "acc://tasks", name: "Tasks", mimeType: "application/json",
|
|
209
|
-
description: "Tasks with state, assignee, and dependencies." },
|
|
210
169
|
{ uri: "acc://inbox", name: "Inbox", mimeType: "application/json",
|
|
211
170
|
description: "Messages addressed to this participant, rendered as attributed data." },
|
|
212
171
|
]);
|
|
213
172
|
|
|
214
|
-
// Declared, not assumed. MCP
|
|
215
|
-
// so
|
|
173
|
+
// Declared, not assumed. Manual MCP tool polling is not next-turn injection,
|
|
174
|
+
// live push, or a native reply route, so every adapter delivery mode is false.
|
|
216
175
|
export const MCP_CAPABILITIES = Object.freeze({
|
|
217
176
|
lifecycle: Object.freeze({ sessionStart: false, sessionResume: false, sessionEnd: false,
|
|
218
177
|
childSessions: false }),
|
|
219
178
|
context: Object.freeze({ startupInjection: false, beforeTurnInjection: false,
|
|
220
179
|
safePointInjection: false }),
|
|
221
180
|
guards: Object.freeze({ beforeRead: false, beforeWrite: false, beforeShell: false }),
|
|
222
|
-
delivery: Object.freeze({
|
|
223
|
-
wakeDormantSession: false }),
|
|
224
|
-
execution: Object.freeze({ launch: false, resume: false, terminate: false }),
|
|
181
|
+
delivery: Object.freeze({ nextTurn: false, livePush: false, replyRoute: false }),
|
|
225
182
|
});
|