agents-can-communicate 0.3.1 → 0.4.1
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 +95 -52
- package/SECURITY.md +1 -1
- package/bin/acc-bootstrap.mjs +5 -54
- package/bin/acc-claude-channel.mjs +8 -175
- package/bin/acc-hook.mjs +4 -133
- package/bin/acc-mcp.mjs +4 -59
- package/bin/acc-update-worker.mjs +8 -0
- package/bin/acc.mjs +5 -39
- package/bin/entrypoints/acc-bootstrap.mjs +47 -0
- package/bin/entrypoints/acc-claude-channel.mjs +149 -0
- package/bin/entrypoints/acc-hook.mjs +58 -0
- package/bin/entrypoints/acc-mcp.mjs +70 -0
- package/bin/entrypoints/acc.mjs +51 -0
- package/bin/entrypoints/claude-channel-stdio.mjs +29 -0
- package/bin/entrypoints/hook-output.mjs +77 -0
- package/docs/ADAPTER_AUTHORING.md +52 -8
- package/docs/ARCHITECTURE.md +64 -11
- package/docs/CAPABILITIES.md +41 -37
- package/docs/CLI.md +145 -25
- package/docs/CONCEPTS.md +13 -6
- package/docs/CONFIGURATION.md +37 -22
- package/docs/DESIGN_DECISIONS.md +16 -11
- package/docs/GETTING_STARTED.md +41 -7
- package/docs/GLOSSARY.md +9 -3
- package/docs/HOW_IT_WORKS.md +32 -11
- package/docs/MCP.md +91 -24
- package/docs/PROTOCOL.md +69 -12
- package/docs/RELEASING.md +28 -10
- package/docs/SECURITY_MODEL.md +47 -13
- package/docs/TROUBLESHOOTING.md +102 -20
- package/docs/UPGRADING.md +154 -0
- package/docs/WHY_ACC.md +5 -4
- package/docs/index.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +106 -5
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +6 -6
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/native-delivery.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-codex/certification.json +38 -13
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/certification-provenance.json +220 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-current-binding-product-evidence.json +1549 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-current-binding-product.json +27 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-product-evidence.json +1549 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-product.json +27 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-transport-evidence.json +259 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.152.1-local-daemon-transport.json +21 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-current-binding-product-evidence.json +1549 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-current-binding-product.json +27 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-product-evidence.json +1549 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-product.json +27 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-transport-evidence.json +259 -0
- package/node_modules/@agents-can-communicate/adapter-codex/fixtures/delivery/codex-cli-0.153.4-local-daemon-transport.json +21 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +14 -2
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +106 -5
- package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +26 -44
- package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +70 -17
- package/node_modules/@agents-can-communicate/adapter-codex/src/config-block.mjs +231 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/hooks.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +71 -63
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +107 -103
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +96 -0
- package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +3 -2
- 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 +106 -5
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +2 -2
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/plugin/skills/acc/SKILL.md +122 -8
- package/node_modules/@agents-can-communicate/adapter-grok/src/adapter.mjs +5 -3
- package/node_modules/@agents-can-communicate/adapter-grok/src/hooks.mjs +9 -0
- package/node_modules/@agents-can-communicate/adapter-grok/src/install.mjs +2 -2
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +106 -5
- package/node_modules/@agents-can-communicate/adapter-kimi/src/hooks.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +2 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +5 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +26 -4
- package/node_modules/@agents-can-communicate/adapter-sdk/src/decision-text.mjs +19 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +5 -3
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +9 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +2 -6
- package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +29 -9
- package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-block.mjs +8 -6
- package/node_modules/@agents-can-communicate/cli/package.json +4 -2
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +49 -22
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +11 -4
- package/node_modules/@agents-can-communicate/cli/src/git-probe.mjs +9 -3
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +109 -14
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +24 -5
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +35 -12
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/activation.mjs +110 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/command.mjs +64 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/download.mjs +71 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/entry.mjs +110 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/generation.mjs +167 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/install.mjs +78 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/launchers.mjs +60 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/leases.mjs +60 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/location.mjs +55 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/mutex.mjs +145 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/policy.mjs +4 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/refresh.mjs +40 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/schedule.mjs +21 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/state.mjs +123 -0
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/worker.mjs +65 -0
- package/node_modules/@agents-can-communicate/cli/src/runtime-paths.mjs +1 -1
- package/node_modules/@agents-can-communicate/cli/src/session-owner.mjs +33 -108
- package/node_modules/@agents-can-communicate/cli/src/update-command.mjs +25 -7
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/attention.mjs +3 -0
- package/node_modules/@agents-can-communicate/core/src/claims.mjs +13 -8
- package/node_modules/@agents-can-communicate/core/src/conversations.mjs +9 -25
- package/node_modules/@agents-can-communicate/core/src/decision-changes.mjs +36 -0
- package/node_modules/@agents-can-communicate/core/src/decision-state.mjs +42 -0
- package/node_modules/@agents-can-communicate/core/src/delivery-bindings.mjs +11 -4
- package/node_modules/@agents-can-communicate/core/src/inbox.mjs +48 -17
- package/node_modules/@agents-can-communicate/core/src/intents.mjs +35 -15
- package/node_modules/@agents-can-communicate/core/src/materialisation.mjs +7 -5
- package/node_modules/@agents-can-communicate/core/src/message-pages.mjs +75 -0
- package/node_modules/@agents-can-communicate/core/src/next-turn-delivery.mjs +31 -0
- package/node_modules/@agents-can-communicate/core/src/session-opening.mjs +128 -0
- package/node_modules/@agents-can-communicate/core/src/sessions.mjs +58 -132
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +31 -2
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/refresh-binding.mjs +38 -0
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +86 -10
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-version.mjs +1 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +6 -4
- package/node_modules/@agents-can-communicate/hook-runner/src/native-retirement.mjs +48 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/owner-context.mjs +30 -0
- package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +172 -67
- package/node_modules/@agents-can-communicate/hook-runner/src/session-lifecycle.mjs +13 -0
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/apply.mjs +17 -2
- package/node_modules/@agents-can-communicate/installer/src/bootstrap-runtime.mjs +11 -5
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +1 -1
- package/node_modules/@agents-can-communicate/installer/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/installer/src/live-policy.mjs +21 -0
- package/node_modules/@agents-can-communicate/installer/src/native-activation.mjs +20 -6
- package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +10 -4
- package/node_modules/@agents-can-communicate/installer/src/plan.mjs +3 -3
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/src/protocol.mjs +85 -0
- package/node_modules/@agents-can-communicate/mcp-server/src/resources.mjs +3 -2
- package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +59 -57
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +47 -33
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/src/conversations.mjs +3 -0
- package/node_modules/@agents-can-communicate/protocol/src/decision-changes.mjs +15 -0
- package/node_modules/@agents-can-communicate/protocol/src/index.mjs +3 -1
- package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +14 -5
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +5 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/deadline.mjs +7 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +2 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/session-read.mjs +25 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +28 -25
- package/package.json +2 -1
|
@@ -21,6 +21,9 @@ const data = (message, details) => {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export function assertMessageSemantics(message) {
|
|
24
|
+
if (message.decisionChange !== undefined && message.kind !== "decision") {
|
|
25
|
+
data("only a decision can replace or withdraw decisions", { kind: message.kind });
|
|
26
|
+
}
|
|
24
27
|
const room = message.toParticipantIds.length === 0;
|
|
25
28
|
if (room && !["note", "decision", "handoff"].includes(message.kind)) {
|
|
26
29
|
data(`a room message cannot have kind ${message.kind}`, { kind: message.kind });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { id, invalid, listOf, oneOf, plainObject } from "./fields.mjs";
|
|
2
|
+
|
|
3
|
+
export function assertDecisionChange(value, field = "decisionChange") {
|
|
4
|
+
plainObject(value, field);
|
|
5
|
+
for (const key of Object.keys(value)) {
|
|
6
|
+
if (!["action", "messageIds"].includes(key)) invalid(`${field}.${key}`, "is not a known field", value[key]);
|
|
7
|
+
}
|
|
8
|
+
oneOf("replace", "withdraw")(value.action, `${field}.action`);
|
|
9
|
+
listOf(id)(value.messageIds, `${field}.messageIds`);
|
|
10
|
+
if (value.messageIds.length < 1 || value.messageIds.length > 16
|
|
11
|
+
|| new Set(value.messageIds).size !== value.messageIds.length) {
|
|
12
|
+
invalid(`${field}.messageIds`, "requires 1 to 16 unique decision message ids", value.messageIds);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
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
|
-
export {
|
|
5
|
+
export { CLAIM_ENFORCEMENTS, CLAIM_MODES, HANDOFF_STATUSES, INTENT_MODES,
|
|
6
|
+
INTENT_STATES, RECORD_KINDS, SCHEMA_VERSION, validateRecord } from "./schema.mjs";
|
|
6
7
|
export { GENERIC_MESSAGE_KINDS, MESSAGE_KINDS, OBLIGATIONS, VALID_OBLIGATIONS,
|
|
7
8
|
assertMessageSemantics }
|
|
8
9
|
from "./conversations.mjs";
|
|
@@ -10,3 +11,4 @@ export { CONFIG_FILENAME, CONFIG_SCHEMA_VERSION, RUNTIME_KEYS, defaultProjectCon
|
|
|
10
11
|
validateProjectConfig } from "./config.mjs";
|
|
11
12
|
export { RECEIPT_STATES, advanceReceipt } from "./states.mjs";
|
|
12
13
|
export { assertMatchableResource, normaliseResource } from "./resources.mjs";
|
|
14
|
+
export { assertDecisionChange } from "./decision-changes.mjs";
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { assertMessageSemantics, MESSAGE_KINDS, OBLIGATIONS } from "./conversations.mjs";
|
|
2
|
+
import { assertDecisionChange } from "./decision-changes.mjs";
|
|
2
3
|
import { AccError, EXIT } from "./errors.mjs";
|
|
3
4
|
import { id, invalid, listOf, nullable, oneOf, optional, plainObject, positiveInteger,
|
|
4
5
|
resourceUri, sequence, text, timestamp } from "./fields.mjs";
|
|
5
6
|
|
|
6
7
|
export const SCHEMA_VERSION = 3;
|
|
7
8
|
|
|
9
|
+
// Shared with CLI help so advertised choices are the ones durable records accept.
|
|
10
|
+
export const INTENT_MODES = Object.freeze(["observe", "explore", "edit", "review",
|
|
11
|
+
"coordinate", "wait"]);
|
|
12
|
+
export const INTENT_STATES = Object.freeze(["active", "blocked", "waiting", "done"]);
|
|
13
|
+
export const CLAIM_MODES = Object.freeze(["shared", "exclusive"]);
|
|
14
|
+
export const CLAIM_ENFORCEMENTS = Object.freeze(["advisory", "guarded"]);
|
|
15
|
+
export const HANDOFF_STATUSES = Object.freeze(["complete", "partial", "blocked"]);
|
|
16
|
+
|
|
8
17
|
const line = text();
|
|
9
18
|
const prose = text({ max: 4000, multiline: true });
|
|
10
19
|
const summary = text({ max: 280 });
|
|
@@ -47,7 +56,7 @@ const artifactRef = (value, field) => {
|
|
|
47
56
|
};
|
|
48
57
|
|
|
49
58
|
const handoffPayload = (value, field) => closedObject(value, field, {
|
|
50
|
-
status: oneOf(
|
|
59
|
+
status: oneOf(...HANDOFF_STATUSES),
|
|
51
60
|
completed: listOf(line),
|
|
52
61
|
remaining: listOf(line),
|
|
53
62
|
blockers: listOf(line),
|
|
@@ -90,19 +99,19 @@ const DURABLE_RECORDS = Object.freeze({
|
|
|
90
99
|
heartbeatCadenceMs: positiveInteger, startedAt: timestamp, heartbeatAt: timestamp },
|
|
91
100
|
|
|
92
101
|
intent: { sessionId: id, workspaceId: id, summary,
|
|
93
|
-
mode: oneOf(
|
|
102
|
+
mode: oneOf(...INTENT_MODES),
|
|
94
103
|
resourceHints: listOf(resourceUri),
|
|
95
|
-
state: oneOf(
|
|
104
|
+
state: oneOf(...INTENT_STATES), updatedAt: timestamp },
|
|
96
105
|
|
|
97
106
|
claim: { claimId: id, workspaceId: id, ownerSessionId: id, resource: resourceUri,
|
|
98
|
-
mode: oneOf(
|
|
107
|
+
mode: oneOf(...CLAIM_MODES), enforcement: oneOf(...CLAIM_ENFORCEMENTS),
|
|
99
108
|
reason: line, acquiredAt: timestamp, expiresAt: timestamp, generation: id },
|
|
100
109
|
|
|
101
110
|
message: { messageId: id, threadId: id, clientMessageId: id, workspaceId: id,
|
|
102
111
|
fromParticipantId: id, fromSessionId: id, toParticipantIds: listOf(id),
|
|
103
112
|
kind: oneOf(...MESSAGE_KINDS), obligation: oneOf(...OBLIGATIONS),
|
|
104
113
|
subject: line, body: prose, inReplyTo: nullable(id), artifacts: listOf(artifactRef),
|
|
105
|
-
handoff: nullable(handoffPayload), sentAt: timestamp },
|
|
114
|
+
handoff: nullable(handoffPayload), decisionChange: optional(assertDecisionChange), sentAt: timestamp },
|
|
106
115
|
|
|
107
116
|
receipt: { messageId: id, workspaceId: id, recipientParticipantId: id,
|
|
108
117
|
state: receiptState, updatedAt: timestamp },
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
6
6
|
|
|
7
|
+
import { assertPublicationDeadline } from "./deadline.mjs";
|
|
7
8
|
import { assertManagedDirectory, ensureManagedDirectory } from "./safe-directory.mjs";
|
|
8
9
|
import { readRegularNoFollow } from "./safe-file.mjs";
|
|
9
10
|
|
|
@@ -62,7 +63,8 @@ async function replaceHandleBytes(handle, bytes) {
|
|
|
62
63
|
*
|
|
63
64
|
* @returns {Promise<"published" | "already_published">}
|
|
64
65
|
*/
|
|
65
|
-
export async function publishAtomic(destination, bytes, { root, tmpDir, replace = false }) {
|
|
66
|
+
export async function publishAtomic(destination, bytes, { root, tmpDir, replace = false, deadlineAt }) {
|
|
67
|
+
assertPublicationDeadline(deadlineAt);
|
|
66
68
|
const destinationDir = path.dirname(destination);
|
|
67
69
|
await Promise.all([
|
|
68
70
|
ensureManagedDirectory(root, tmpDir),
|
|
@@ -87,12 +89,14 @@ export async function publishAtomic(destination, bytes, { root, tmpDir, replace
|
|
|
87
89
|
if (replace) {
|
|
88
90
|
await handle.close();
|
|
89
91
|
handle = null;
|
|
92
|
+
assertPublicationDeadline(deadlineAt);
|
|
90
93
|
await rename(temporary, destination);
|
|
91
94
|
await syncDirectory(destinationDir);
|
|
92
95
|
return "published";
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
try {
|
|
99
|
+
assertPublicationDeadline(deadlineAt);
|
|
96
100
|
await link(temporary, destination);
|
|
97
101
|
stageAcceptedBytes = true;
|
|
98
102
|
await syncDirectory(destinationDir);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AccError, EXIT } from "@agents-can-communicate/protocol";
|
|
2
|
+
|
|
3
|
+
export function assertPublicationDeadline(deadlineAt) {
|
|
4
|
+
if (deadlineAt !== undefined && Date.now() >= deadlineAt) {
|
|
5
|
+
throw new AccError(EXIT.CONFLICT, "operation deadline expired before publication", {});
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -2,3 +2,5 @@
|
|
|
2
2
|
export { openFilesystemStore, storePaths, ZERO_CURSOR } from "./store.mjs";
|
|
3
3
|
export { diagnoseFilesystemStore, repairFilesystemStore } from "./recovery.mjs";
|
|
4
4
|
export { readStoreIdentity, requireStoreIdentity, STORE_VERSION } from "./identity.mjs";
|
|
5
|
+
export { withWriterMutex } from "./writer-mutex.mjs";
|
|
6
|
+
export { readSessionRecord } from "./session-read.mjs";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { assertPortableId, validateRecord } from "@agents-can-communicate/protocol";
|
|
3
|
+
import { readJsonIfPresent } from "./atomic-json.mjs";
|
|
4
|
+
import { requireStoreIdentity } from "./identity.mjs";
|
|
5
|
+
import { assertStateBinding, statePath } from "./record-id.mjs";
|
|
6
|
+
import { ephemeralIsDeleted, stateGenerationIsDeleted } from "./retention.mjs";
|
|
7
|
+
import { storePaths } from "./store.mjs";
|
|
8
|
+
|
|
9
|
+
/** Inspect ownership without opening, creating, recovering, or locking the store. */
|
|
10
|
+
export async function readSessionRecord({ root, workspaceId, sessionId }) {
|
|
11
|
+
assertPortableId(sessionId, "session id");
|
|
12
|
+
const paths = storePaths(root);
|
|
13
|
+
await requireStoreIdentity(paths, { workspaceId, create: false });
|
|
14
|
+
const file = statePath(paths, "session", sessionId);
|
|
15
|
+
const durable = await readJsonIfPresent(file, root);
|
|
16
|
+
if (durable !== null) {
|
|
17
|
+
const envelope = assertStateBinding(durable.value, "session", sessionId, file);
|
|
18
|
+
if (!await stateGenerationIsDeleted(paths, root, "session", sessionId, envelope.generation)) {
|
|
19
|
+
return validateRecord("session", envelope.record);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const ephemeral = await readJsonIfPresent(path.join(paths.ephemeral, "session", `${sessionId}.json`), root);
|
|
23
|
+
if (ephemeral === null || await ephemeralIsDeleted(paths, root, "session", sessionId)) return null;
|
|
24
|
+
return validateRecord("session", ephemeral.value);
|
|
25
|
+
}
|
|
@@ -7,6 +7,7 @@ import { AccError, EXIT, assertPortableId, validateRecord }
|
|
|
7
7
|
import { encode, listDirectoryEntries, listJsonFiles, publishAtomic, readJsonIfPresent,
|
|
8
8
|
retainFile } from "./atomic-json.mjs";
|
|
9
9
|
import { initialiseActiveJournal } from "./active-journal.mjs";
|
|
10
|
+
import { assertPublicationDeadline } from "./deadline.mjs";
|
|
10
11
|
import { requireStoreIdentity } from "./identity.mjs";
|
|
11
12
|
import { journalEntry, readJournalCeiling, readOpenJournals, rollForward, writeJournalEntry }
|
|
12
13
|
from "./journal.mjs";
|
|
@@ -32,13 +33,6 @@ const DIRECTORIES = ["state", "events", "journal", "locks", "ephemeral", "retain
|
|
|
32
33
|
|
|
33
34
|
const pad = value => String(value).padStart(SEQUENCE_WIDTH, "0");
|
|
34
35
|
|
|
35
|
-
function assertBeforePublication(deadlineAt) {
|
|
36
|
-
if (deadlineAt !== undefined && Date.now() >= deadlineAt) {
|
|
37
|
-
throw new AccError(EXIT.CONFLICT,
|
|
38
|
-
"transaction deadline expired before durable publication", {});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
36
|
export function storePaths(root) {
|
|
43
37
|
return Object.freeze(Object.fromEntries([["root", root],
|
|
44
38
|
...DIRECTORIES.map(name => [name, path.join(root, name)])]));
|
|
@@ -77,7 +71,9 @@ async function nextSequence(paths, root) {
|
|
|
77
71
|
return last === undefined ? 1 : Number(path.basename(last, ".json")) + 1;
|
|
78
72
|
}
|
|
79
73
|
|
|
80
|
-
export async function openFilesystemStore({ root, clock, ids, workspaceId, failAt
|
|
74
|
+
export async function openFilesystemStore({ root, clock, ids, workspaceId, failAt,
|
|
75
|
+
deadlineAt: storeDeadline }) {
|
|
76
|
+
assertPublicationDeadline(storeDeadline);
|
|
81
77
|
const paths = storePaths(root);
|
|
82
78
|
// The caller owns the root path, so its ancestors are created here. Inside the
|
|
83
79
|
// root, containment rules apply and each level is created individually so a
|
|
@@ -100,7 +96,7 @@ export async function openFilesystemStore({ root, clock, ids, workspaceId, failA
|
|
|
100
96
|
async function recoverOpenJournals() {
|
|
101
97
|
const open = await readOpenJournals(paths, root);
|
|
102
98
|
if (open.length === 0) return [];
|
|
103
|
-
return withWriterMutex(paths, { root, tmpDir: paths.tmp, clock }, async () => {
|
|
99
|
+
return withWriterMutex(paths, { root, tmpDir: paths.tmp, clock, deadlineAt: storeDeadline }, async () => {
|
|
104
100
|
const completed = [];
|
|
105
101
|
for (const entry of await readOpenJournals(paths, root)) {
|
|
106
102
|
completed.push(...await rollForward(paths, { root, tmpDir: paths.tmp, clock }, entry));
|
|
@@ -125,6 +121,8 @@ export async function openFilesystemStore({ root, clock, ids, workspaceId, failA
|
|
|
125
121
|
* Declaring nothing reads everything, which is what this always did.
|
|
126
122
|
*/
|
|
127
123
|
async function transaction(callback, { kinds, deadlineAt } = {}) {
|
|
124
|
+
// A caller may tighten this invocation's budget, never extend it.
|
|
125
|
+
deadlineAt = Math.min(deadlineAt ?? Infinity, storeDeadline ?? Infinity);
|
|
128
126
|
const wanted = kinds === undefined ? null : new Set(kinds);
|
|
129
127
|
const declared = kind => {
|
|
130
128
|
if (wanted !== null && !wanted.has(kind)) {
|
|
@@ -135,7 +133,7 @@ export async function openFilesystemStore({ root, clock, ids, workspaceId, failA
|
|
|
135
133
|
return kind;
|
|
136
134
|
};
|
|
137
135
|
return withWriterMutex(paths, { ...publishOptions, deadlineAt }, async () => {
|
|
138
|
-
|
|
136
|
+
assertPublicationDeadline(deadlineAt);
|
|
139
137
|
// Reads are loaded once per transaction so get, list, and the generation
|
|
140
138
|
// that put() compares against all describe the same instant.
|
|
141
139
|
const loaded = await loadAllState(paths, root, wanted);
|
|
@@ -224,14 +222,13 @@ export async function openFilesystemStore({ root, clock, ids, workspaceId, failA
|
|
|
224
222
|
];
|
|
225
223
|
if (publications.length === 0) return result;
|
|
226
224
|
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
assertBeforePublication(deadlineAt);
|
|
225
|
+
// Preparation is still cancellable. The active journal's atomic
|
|
226
|
+
// publication decides the write; roll-forward must then finish even if
|
|
227
|
+
// this invocation expires, so its publication options carry no deadline.
|
|
228
|
+
assertPublicationDeadline(deadlineAt);
|
|
232
229
|
const entry = journalEntry(ids.next("transaction"), firstSequence, publications,
|
|
233
230
|
clock.now());
|
|
234
|
-
await writeJournalEntry(paths, publishOptions, entry);
|
|
231
|
+
await writeJournalEntry(paths, { ...publishOptions, deadlineAt }, entry);
|
|
235
232
|
await failAt?.("after-journal");
|
|
236
233
|
await rollForward(paths, publishOptions, entry);
|
|
237
234
|
return result;
|
|
@@ -308,29 +305,35 @@ export async function openFilesystemStore({ root, clock, ids, workspaceId, failA
|
|
|
308
305
|
},
|
|
309
306
|
async put(kind, id, record) {
|
|
310
307
|
validateRecord(kind, record);
|
|
311
|
-
return withWriterMutex(paths, publishOptions, async () => {
|
|
308
|
+
return withWriterMutex(paths, { ...publishOptions, deadlineAt: storeDeadline }, async () => {
|
|
312
309
|
await publishAtomic(ephemeralPath(kind, id), encode(record),
|
|
313
|
-
{ root, tmpDir: paths.tmp, replace: true });
|
|
310
|
+
{ root, tmpDir: paths.tmp, replace: true, deadlineAt: storeDeadline });
|
|
311
|
+
// Once record bytes are accepted, finish the same logical publication.
|
|
314
312
|
await markEphemeral(paths, publishOptions, kind, id, "present");
|
|
315
313
|
return record;
|
|
316
314
|
});
|
|
317
315
|
},
|
|
318
|
-
async update(kind, id, updater) {
|
|
319
|
-
|
|
316
|
+
async update(kind, id, updater, { deadlineAt = storeDeadline } = {}) {
|
|
317
|
+
deadlineAt = Math.min(deadlineAt ?? Infinity, storeDeadline ?? Infinity);
|
|
318
|
+
return withWriterMutex(paths, { ...publishOptions, deadlineAt }, async () => {
|
|
320
319
|
const next = await updater(await readEphemeral(kind, id));
|
|
321
320
|
if (next === null) return null;
|
|
322
321
|
validateRecord(kind, next);
|
|
322
|
+
assertPublicationDeadline(deadlineAt);
|
|
323
323
|
await publishAtomic(ephemeralPath(kind, id), encode(next),
|
|
324
|
-
{ root, tmpDir: paths.tmp, replace: true });
|
|
324
|
+
{ root, tmpDir: paths.tmp, replace: true, deadlineAt });
|
|
325
325
|
await markEphemeral(paths, publishOptions, kind, id, "present");
|
|
326
326
|
return next;
|
|
327
327
|
});
|
|
328
328
|
},
|
|
329
|
-
async delete(kind, id) {
|
|
330
|
-
return withWriterMutex(paths, publishOptions, async () => {
|
|
331
|
-
|
|
329
|
+
async delete(kind, id, guard = () => true) {
|
|
330
|
+
return withWriterMutex(paths, { ...publishOptions, deadlineAt: storeDeadline }, async () => {
|
|
331
|
+
// Decide and delete under the same lock; a caller may be retiring an
|
|
332
|
+
// old generation while its replacement is waiting to write this id.
|
|
333
|
+
const current = await readEphemeral(kind, id);
|
|
334
|
+
if (current === null || !await guard(current)) return null;
|
|
332
335
|
await retainFile(ephemeralPath(kind, id), { root });
|
|
333
|
-
await markEphemeral(paths, publishOptions, kind, id, "deleted");
|
|
336
|
+
await markEphemeral(paths, { ...publishOptions, deadlineAt: storeDeadline }, kind, id, "deleted");
|
|
334
337
|
return null;
|
|
335
338
|
});
|
|
336
339
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-can-communicate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local-first coordination for independently opened AI agent sessions.",
|
|
6
6
|
"keywords": [
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"docs/RELEASING.md",
|
|
61
61
|
"docs/SECURITY_MODEL.md",
|
|
62
62
|
"docs/TROUBLESHOOTING.md",
|
|
63
|
+
"docs/UPGRADING.md",
|
|
63
64
|
"docs/WHY_ACC.md",
|
|
64
65
|
"docs/index.md",
|
|
65
66
|
"SECURITY.md",
|