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
|
@@ -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
|
});
|
|
@@ -12,7 +12,7 @@ export const CONFIG_FILENAME = "acc.workspace.json";
|
|
|
12
12
|
// the wrong place for them, and a config carrying them is either a mistake or
|
|
13
13
|
// an attempt to hand a peer state it would otherwise have to earn.
|
|
14
14
|
export const RUNTIME_KEYS = Object.freeze(["sessions", "participants", "messages",
|
|
15
|
-
"claims", "receipts", "intents", "events", "tokens", "credentials"]);
|
|
15
|
+
"claims", "receipts", "intents", "events", "deliveryBindings", "tokens", "credentials"]);
|
|
16
16
|
|
|
17
17
|
const KNOWN_KEYS = Object.freeze(["schemaVersion", "workspaceId", "displayName",
|
|
18
18
|
"roots", "policy", "requiredAdapters", "extensions"]);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { AccError, EXIT } from "./errors.mjs";
|
|
2
|
+
|
|
3
|
+
export const VALID_OBLIGATIONS = Object.freeze({
|
|
4
|
+
note: Object.freeze(["none"]),
|
|
5
|
+
question: Object.freeze(["reply"]),
|
|
6
|
+
request: Object.freeze(["reply"]),
|
|
7
|
+
answer: Object.freeze(["none"]),
|
|
8
|
+
decision: Object.freeze(["none", "acknowledge"]),
|
|
9
|
+
handoff: Object.freeze(["none", "acknowledge"]),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const MESSAGE_KINDS = Object.freeze(Object.keys(VALID_OBLIGATIONS));
|
|
13
|
+
// The generic send surface cannot construct reply links or structured handoff
|
|
14
|
+
// payloads. Those two complete shapes belong to reply and finish respectively.
|
|
15
|
+
export const GENERIC_MESSAGE_KINDS = Object.freeze(
|
|
16
|
+
["note", "question", "request", "decision"]);
|
|
17
|
+
export const OBLIGATIONS = Object.freeze(["none", "acknowledge", "reply"]);
|
|
18
|
+
|
|
19
|
+
const data = (message, details) => {
|
|
20
|
+
throw new AccError(EXIT.DATA, message, details);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function assertMessageSemantics(message) {
|
|
24
|
+
const room = message.toParticipantIds.length === 0;
|
|
25
|
+
if (room && !["note", "decision", "handoff"].includes(message.kind)) {
|
|
26
|
+
data(`a room message cannot have kind ${message.kind}`, { kind: message.kind });
|
|
27
|
+
}
|
|
28
|
+
if (room && message.obligation !== "none") {
|
|
29
|
+
data("a room message must have obligation none", { obligation: message.obligation });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const valid = VALID_OBLIGATIONS[message.kind] ?? [];
|
|
33
|
+
if (!valid.includes(message.obligation)) {
|
|
34
|
+
data(`message obligation ${message.obligation} is invalid for ${message.kind}`,
|
|
35
|
+
{ kind: message.kind, obligation: message.obligation });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const root = message.threadId === message.messageId;
|
|
39
|
+
if (message.kind === "answer" && message.inReplyTo === null) {
|
|
40
|
+
data("an answer requires inReplyTo", { messageId: message.messageId });
|
|
41
|
+
}
|
|
42
|
+
if (message.kind === "answer" && root) {
|
|
43
|
+
data("an answer cannot be a thread root", { messageId: message.messageId });
|
|
44
|
+
}
|
|
45
|
+
if (message.inReplyTo === null && !root) {
|
|
46
|
+
data("a thread root requires threadId to equal messageId",
|
|
47
|
+
{ threadId: message.threadId, messageId: message.messageId });
|
|
48
|
+
}
|
|
49
|
+
if (message.inReplyTo !== null && root) {
|
|
50
|
+
data("a thread root must have inReplyTo null", { inReplyTo: message.inReplyTo });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (message.kind === "handoff" && message.handoff === null) {
|
|
54
|
+
data("a handoff message requires a handoff payload", { messageId: message.messageId });
|
|
55
|
+
}
|
|
56
|
+
if (!room && message.kind === "handoff" && message.obligation !== "acknowledge") {
|
|
57
|
+
data("an addressed handoff requires acknowledgement",
|
|
58
|
+
{ messageId: message.messageId, obligation: message.obligation });
|
|
59
|
+
}
|
|
60
|
+
if (message.kind !== "handoff" && message.handoff !== null) {
|
|
61
|
+
data(`a ${message.kind} message must have handoff null`, { kind: message.kind });
|
|
62
|
+
}
|
|
63
|
+
return message;
|
|
64
|
+
}
|
|
@@ -74,6 +74,23 @@ export const listOf = inner => (value, field) => {
|
|
|
74
74
|
export const nullable = inner => (value, field) =>
|
|
75
75
|
(value === null ? null : inner(value, field));
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* A field that may be absent entirely.
|
|
79
|
+
*
|
|
80
|
+
* For a fact that either happened or did not, absence is the honest encoding of
|
|
81
|
+
* "not yet" - and it is what a record written before the field existed already
|
|
82
|
+
* says. Every record carries a schemaVersion, but adding a required field to a
|
|
83
|
+
* kind that outlives an upgrade turns a stored record into an unreadable one:
|
|
84
|
+
* measured, one binding written by the previous build made `acc status` fail
|
|
85
|
+
* outright with "deliveryBinding requires retiredAt". A coordination tool must
|
|
86
|
+
* not refuse to read its own store because it was upgraded.
|
|
87
|
+
*/
|
|
88
|
+
export const optional = inner => {
|
|
89
|
+
const check = (value, field) => (value === undefined ? undefined : inner(value, field));
|
|
90
|
+
check.optional = true;
|
|
91
|
+
return check;
|
|
92
|
+
};
|
|
93
|
+
|
|
77
94
|
export const positiveInteger = (value, field) => {
|
|
78
95
|
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
79
96
|
invalid(field, "must be a positive integer", value);
|
|
@@ -3,7 +3,10 @@ export { AccError, EXIT, isAccError } from "./errors.mjs";
|
|
|
3
3
|
export { assertPortableId, createId } from "./ids.mjs";
|
|
4
4
|
export { ENVELOPE_VERSION, failure, ok } from "./envelopes.mjs";
|
|
5
5
|
export { RECORD_KINDS, SCHEMA_VERSION, validateRecord } from "./schema.mjs";
|
|
6
|
+
export { GENERIC_MESSAGE_KINDS, MESSAGE_KINDS, OBLIGATIONS, VALID_OBLIGATIONS,
|
|
7
|
+
assertMessageSemantics }
|
|
8
|
+
from "./conversations.mjs";
|
|
6
9
|
export { CONFIG_FILENAME, CONFIG_SCHEMA_VERSION, RUNTIME_KEYS, defaultProjectConfig,
|
|
7
10
|
validateProjectConfig } from "./config.mjs";
|
|
8
|
-
export {
|
|
11
|
+
export { RECEIPT_STATES, advanceReceipt } from "./states.mjs";
|
|
9
12
|
export { assertMatchableResource, normaliseResource } from "./resources.mjs";
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { assertMessageSemantics, MESSAGE_KINDS, OBLIGATIONS } from "./conversations.mjs";
|
|
1
2
|
import { AccError, EXIT } from "./errors.mjs";
|
|
2
|
-
import {
|
|
3
|
+
import { id, invalid, listOf, nullable, oneOf, optional, plainObject, positiveInteger,
|
|
3
4
|
resourceUri, sequence, text, timestamp } from "./fields.mjs";
|
|
4
5
|
|
|
5
|
-
export const SCHEMA_VERSION =
|
|
6
|
+
export const SCHEMA_VERSION = 3;
|
|
6
7
|
|
|
7
8
|
const line = text();
|
|
8
9
|
const prose = text({ max: 4000, multiline: true });
|
|
@@ -14,10 +15,22 @@ const artifactKind = oneOf("file", "git", "url", "report", "image", "data");
|
|
|
14
15
|
/** @typedef {{ cursor: string, events: AccEvent[] }} EventPage */
|
|
15
16
|
/** @typedef {{ kind: string, priority: number, sourceId: string, summary: string }} AttentionItem */
|
|
16
17
|
/** @typedef {{ workspace: object, participants: object[], sessions: object[],
|
|
17
|
-
* intents: object[],
|
|
18
|
+
* intents: object[], claims: object[], messages: object[], receipts: object[] }} WorkspaceSnapshot */
|
|
19
|
+
|
|
20
|
+
function closedObject(value, field, fields) {
|
|
21
|
+
plainObject(value, field);
|
|
22
|
+
for (const key of Object.keys(value)) {
|
|
23
|
+
if (!Object.hasOwn(fields, key)) invalid(`${field}.${key}`, "is not a known field", value[key]);
|
|
24
|
+
}
|
|
25
|
+
for (const [key, check] of Object.entries(fields)) {
|
|
26
|
+
if (!Object.hasOwn(value, key)) {
|
|
27
|
+
throw new AccError(EXIT.DATA, `${field} requires ${key}`, { field, key });
|
|
28
|
+
}
|
|
29
|
+
check(value[key], `${field}.${key}`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
18
33
|
|
|
19
|
-
// Field names stay mappable to the A2A Agent Card, Task, Message, and Artifact
|
|
20
|
-
// concepts (spec section 11) without importing any A2A transport.
|
|
21
34
|
const artifactRef = (value, field) => {
|
|
22
35
|
plainObject(value, field);
|
|
23
36
|
const known = new Set(["kind", "uri", "description", "sha256"]);
|
|
@@ -33,120 +46,84 @@ const artifactRef = (value, field) => {
|
|
|
33
46
|
return value;
|
|
34
47
|
};
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
const handoffPayload = (value, field) => closedObject(value, field, {
|
|
50
|
+
status: oneOf("complete", "partial", "blocked"),
|
|
51
|
+
completed: listOf(line),
|
|
52
|
+
remaining: listOf(line),
|
|
53
|
+
blockers: listOf(line),
|
|
54
|
+
verification: listOf(artifactRef),
|
|
55
|
+
});
|
|
56
|
+
|
|
41
57
|
const EVENT_TYPES = Object.freeze([
|
|
42
58
|
"workspace.materialised",
|
|
43
59
|
"session.opened", "session.closed",
|
|
44
60
|
"intent.published", "intent.cleared",
|
|
45
|
-
"workstream.created", "workstream.coordinator_acquired",
|
|
46
|
-
"workstream.coordinator_released",
|
|
47
|
-
"task.created", "task.claimed", "task.transitioned", "task.unblocked",
|
|
48
|
-
"task.declined", "task.released",
|
|
49
61
|
"claim.acquired", "claim.released", "claim.renewed", "claim.force_released",
|
|
50
|
-
"message.
|
|
51
|
-
|
|
52
|
-
// so the set has to carry each one they can produce.
|
|
53
|
-
...["recorded", "queued", "injected", "seen", "acknowledged", "failed"]
|
|
54
|
-
.map(state => `message.${state}`),
|
|
55
|
-
...["accepted", "declined", "review", "done", "released"]
|
|
56
|
-
.map(outcome => `work.${outcome}`),
|
|
57
|
-
"work.requested",
|
|
62
|
+
"message.recorded", "message.offered", "message.retrieved", "message.acknowledged",
|
|
63
|
+
"message.offer_succeeded", "message.offer_failed",
|
|
58
64
|
]);
|
|
59
65
|
|
|
60
66
|
const eventType = oneOf(...EVENT_TYPES);
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
// nextTurn is the durable hook projection; the other four are what a native
|
|
69
|
+
// transport proved for one session generation. A mode listed twice would let a
|
|
70
|
+
// reader count capabilities it does not have.
|
|
71
|
+
export const DELIVERY_MODES = Object.freeze(["nextTurn", "livePush", "idleWake", "busyQueue",
|
|
72
|
+
"replyRoute"]);
|
|
73
|
+
const uniqueListOf = check => (value, field) => {
|
|
74
|
+
listOf(check)(value, field);
|
|
75
|
+
if (new Set(value).size !== value.length) invalid(field, "must not repeat entries", value);
|
|
76
|
+
};
|
|
77
|
+
const receiptState = oneOf("queued", "offered", "retrieved", "acknowledged");
|
|
78
|
+
|
|
79
|
+
const DURABLE_RECORDS = Object.freeze({
|
|
63
80
|
workspace: { workspaceId: id, displayName: line, source: oneOf("config", "git", "directory"),
|
|
64
81
|
roots: listOf(line), createdAt: timestamp },
|
|
65
82
|
|
|
66
83
|
participant: { participantId: id, workspaceId: id, displayName: line,
|
|
67
84
|
kind: oneOf("agent", "human"), createdAt: timestamp },
|
|
68
85
|
|
|
69
|
-
// `enforcement` and `lifecycle` are what this session's harness can actually
|
|
70
|
-
// do, declared at attach. The harness name does not imply them: the same
|
|
71
|
-
// client guards or does not depending on its model and its approval mode, and
|
|
72
|
-
// a peer deciding whether to rely on a claim needs the answer, not the brand.
|
|
73
|
-
// A workspace spans every worktree of one repository, so the workspace id
|
|
74
|
-
// cannot say which checkout a session is sitting in. Recorded at attach from
|
|
75
|
-
// what discovery already resolved: without it nobody can tell which worktrees
|
|
76
|
-
// have an owner, and asking cannot answer for the agents that are not running
|
|
77
|
-
// - which are exactly the ones a clean-up is looking for.
|
|
78
86
|
session: { sessionId: id, participantId: id, workspaceId: id, generation: id,
|
|
79
87
|
harness: line, state: oneOf("open", "closed"), parentSessionId: nullable(id),
|
|
80
|
-
checkoutRoot: nullable(line), branch: nullable(line),
|
|
81
|
-
// The process behind this session, when it can be named. Null means nobody
|
|
82
|
-
// knows - no process table, or an ancestry that did not resolve - and is
|
|
83
|
-
// read as "judge this one by age alone", never as "dead".
|
|
84
|
-
pid: nullable(positiveInteger),
|
|
88
|
+
checkoutRoot: nullable(line), branch: nullable(line), pid: nullable(positiveInteger),
|
|
85
89
|
enforcement: oneOf("guarded", "advisory"), lifecycle: oneOf("managed", "manual"),
|
|
86
90
|
heartbeatCadenceMs: positiveInteger, startedAt: timestamp, heartbeatAt: timestamp },
|
|
87
91
|
|
|
88
92
|
intent: { sessionId: id, workspaceId: id, summary,
|
|
89
93
|
mode: oneOf("observe", "explore", "edit", "review", "coordinate", "wait"),
|
|
90
|
-
resourceHints: listOf(resourceUri),
|
|
94
|
+
resourceHints: listOf(resourceUri),
|
|
91
95
|
state: oneOf("active", "blocked", "waiting", "done"), updatedAt: timestamp },
|
|
92
96
|
|
|
93
|
-
workstream: { workstreamId: id, workspaceId: id, title: line, objective: prose,
|
|
94
|
-
coordinatorSessionId: nullable(id),
|
|
95
|
-
state: oneOf("open", "paused", "complete", "cancelled"), createdAt: timestamp },
|
|
96
|
-
|
|
97
|
-
// Two assignees, deliberately. `assigneeParticipantId` is who the work is
|
|
98
|
-
// for and survives that agent restarting; `assigneeSessionId` is the exact
|
|
99
|
-
// session doing it right now and dies with the process. Asking one field to
|
|
100
|
-
// be both would either lose the request when a terminal closes or claim a
|
|
101
|
-
// dead session is still working.
|
|
102
|
-
task: { taskId: id, workstreamId: nullable(id), workspaceId: id, title: line,
|
|
103
|
-
state: oneOf("pending", "in_progress", "review", "done", "blocked"),
|
|
104
|
-
assigneeParticipantId: nullable(id), assigneeSessionId: nullable(id),
|
|
105
|
-
// Who asked. Without it nothing could tell the requester that their work
|
|
106
|
-
// was accepted, declined or finished - the task knew who it was for and
|
|
107
|
-
// had no idea who was waiting on it.
|
|
108
|
-
requestedByParticipantId: nullable(id),
|
|
109
|
-
dependsOn: listOf(id), acceptance: listOf(line), detail: nullable(prose),
|
|
110
|
-
createdAt: timestamp },
|
|
111
|
-
|
|
112
97
|
claim: { claimId: id, workspaceId: id, ownerSessionId: id, resource: resourceUri,
|
|
113
98
|
mode: oneOf("shared", "exclusive"), enforcement: oneOf("advisory", "guarded"),
|
|
114
99
|
reason: line, acquiredAt: timestamp, expiresAt: timestamp, generation: id },
|
|
115
100
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
fromParticipantId: id, toParticipantIds: listOf(id),
|
|
122
|
-
type: oneOf("note", "question", "answer", "contract_request", "contract_response",
|
|
123
|
-
"decision_proposal", "decision_result", "blocker", "review_request",
|
|
124
|
-
"review_result", "handoff", "work_request", "work_response"),
|
|
125
|
-
subject: line, body: prose, priority: oneOf("low", "normal", "high", "urgent"),
|
|
126
|
-
workstreamId: nullable(id), taskId: nullable(id), inReplyTo: nullable(id),
|
|
127
|
-
requiresAck: flag, artifacts: listOf(artifactRef), sentAt: timestamp },
|
|
101
|
+
message: { messageId: id, threadId: id, clientMessageId: id, workspaceId: id,
|
|
102
|
+
fromParticipantId: id, fromSessionId: id, toParticipantIds: listOf(id),
|
|
103
|
+
kind: oneOf(...MESSAGE_KINDS), obligation: oneOf(...OBLIGATIONS),
|
|
104
|
+
subject: line, body: prose, inReplyTo: nullable(id), artifacts: listOf(artifactRef),
|
|
105
|
+
handoff: nullable(handoffPayload), sentAt: timestamp },
|
|
128
106
|
|
|
129
107
|
receipt: { messageId: id, workspaceId: id, recipientParticipantId: id,
|
|
130
|
-
state:
|
|
131
|
-
updatedAt: timestamp },
|
|
132
|
-
|
|
133
|
-
decision: { decisionId: id, workspaceId: id, workstreamId: nullable(id), title: line,
|
|
134
|
-
outcome: prose, authority: oneOf("human", "workstream", "policy"),
|
|
135
|
-
decidedBy: listOf(id), evidence: listOf(artifactRef), supersedes: nullable(id),
|
|
136
|
-
decidedAt: timestamp },
|
|
137
|
-
|
|
138
|
-
artifact: { kind: artifactKind, uri: resourceUri, description: line },
|
|
139
|
-
|
|
140
|
-
handoff: { handoffId: id, workspaceId: id, fromSessionId: id, toParticipantId: nullable(id),
|
|
141
|
-
goal: line, status: oneOf("complete", "partial", "blocked"), completed: listOf(line),
|
|
142
|
-
remaining: listOf(line), blockers: listOf(line), claimsToRelease: listOf(resourceUri),
|
|
143
|
-
verification: listOf(artifactRef), artifacts: listOf(artifactRef), createdAt: timestamp },
|
|
108
|
+
state: receiptState, updatedAt: timestamp },
|
|
144
109
|
|
|
145
110
|
event: { sequence, eventId: id, workspaceId: id, actorSessionId: id, type: eventType,
|
|
146
111
|
occurredAt: timestamp, payload: plainObject },
|
|
147
112
|
});
|
|
148
113
|
|
|
149
|
-
|
|
114
|
+
const RECORDS = Object.freeze({
|
|
115
|
+
...DURABLE_RECORDS,
|
|
116
|
+
deliveryBinding: { sessionId: id, generation: id, adapterId: id, clientVersion: line,
|
|
117
|
+
availableModes: uniqueListOf(oneOf(...DELIVERY_MODES)),
|
|
118
|
+
livePolicy: oneOf("off", "actionable", "all"), opaqueEndpointRef: prose,
|
|
119
|
+
// The lease says how long the endpoint's owner has vouched for it; the
|
|
120
|
+
// retirement says the session gave it up. They were once the same field -
|
|
121
|
+
// clearing expired the lease - which made a renewal by a channel that had
|
|
122
|
+
// not yet noticed indistinguishable from a legitimate extension.
|
|
123
|
+
leaseUntil: timestamp, retiredAt: optional(nullable(timestamp)) },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export const RECORD_KINDS = Object.freeze(Object.keys(DURABLE_RECORDS));
|
|
150
127
|
|
|
151
128
|
export function validateRecord(kind, value) {
|
|
152
129
|
const fields = RECORDS[kind];
|
|
@@ -161,19 +138,16 @@ export function validateRecord(kind, value) {
|
|
|
161
138
|
}
|
|
162
139
|
for (const key of Object.keys(value)) {
|
|
163
140
|
if (key === "schemaVersion" || key === "extensions") continue;
|
|
164
|
-
if (!Object.hasOwn(fields, key)) {
|
|
165
|
-
invalid(`${kind}.${key}`, "is not a known field", value[key]);
|
|
166
|
-
}
|
|
141
|
+
if (!Object.hasOwn(fields, key)) invalid(`${kind}.${key}`, "is not a known field", value[key]);
|
|
167
142
|
}
|
|
168
143
|
for (const [field, check] of Object.entries(fields)) {
|
|
169
144
|
if (!Object.hasOwn(value, field)) {
|
|
145
|
+
if (check.optional === true) continue;
|
|
170
146
|
throw new AccError(EXIT.DATA, `${kind} requires ${field}`, { kind, field });
|
|
171
147
|
}
|
|
172
148
|
check(value[field], field);
|
|
173
149
|
}
|
|
174
|
-
// Forward-compatible metadata is tolerated only inside a named container, so
|
|
175
|
-
// an older reader can round-trip a newer writer's record without guessing
|
|
176
|
-
// which unknown top-level keys are safe.
|
|
177
150
|
if (Object.hasOwn(value, "extensions")) plainObject(value.extensions, "extensions");
|
|
151
|
+
if (kind === "message") assertMessageSemantics(value);
|
|
178
152
|
return value;
|
|
179
153
|
}
|