agents-can-communicate 0.6.3 → 0.7.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 +3 -1
- package/bin/entrypoints/hook-output.mjs +12 -0
- package/docs/ARCHITECTURE.md +5 -0
- package/docs/CAPABILITIES.md +1 -1
- package/docs/CLI.md +50 -2
- package/docs/CONCEPTS.md +7 -0
- package/docs/GETTING_STARTED.md +2 -2
- package/docs/PROTOCOL.md +31 -0
- package/docs/TROUBLESHOOTING.md +4 -3
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +8 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +2 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/native-delivery.mjs +57 -11
- 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 +8 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +8 -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 +8 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- 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 +8 -0
- 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 +8 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +15 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt-writer.mjs +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-attempt.mjs +3 -1
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/args.mjs +6 -0
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +13 -1
- package/node_modules/@agents-can-communicate/cli/src/help.mjs +2 -1
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +33 -8
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/attention.mjs +7 -2
- package/node_modules/@agents-can-communicate/core/src/conversations.mjs +2 -2
- package/node_modules/@agents-can-communicate/core/src/intents.mjs +16 -0
- package/node_modules/@agents-can-communicate/core/src/next-turn-delivery.mjs +21 -4
- package/node_modules/@agents-can-communicate/core/src/prune.mjs +241 -0
- package/node_modules/@agents-can-communicate/core/src/receipts.mjs +66 -10
- package/node_modules/@agents-can-communicate/core/src/service.mjs +3 -0
- package/node_modules/@agents-can-communicate/core/src/status.mjs +25 -1
- package/node_modules/@agents-can-communicate/core/src/sync.mjs +15 -2
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/native-attempt.mjs +60 -7
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +62 -27
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +5 -3
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +26 -7
- package/node_modules/@agents-can-communicate/storage-filesystem/src/doomed-directory.mjs +98 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/event-floor.mjs +52 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/reclaim.mjs +272 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/recovery.mjs +54 -7
- package/node_modules/@agents-can-communicate/storage-filesystem/src/stage-sweep.mjs +174 -0
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +97 -5
- package/package.json +1 -1
|
@@ -16,6 +16,9 @@ import { assertEventBinding, assertStateBinding, eventPath, stateEnvelope, state
|
|
|
16
16
|
import { ephemeralIsDeleted, markEphemeral, stateDeletionPublication,
|
|
17
17
|
stateGenerationIsDeleted } from "./retention.mjs";
|
|
18
18
|
import { ensureManagedDirectory } from "./safe-directory.mjs";
|
|
19
|
+
import { readEventFloor } from "./event-floor.mjs";
|
|
20
|
+
import { reclaimStateRecords, trimEventLog } from "./reclaim.mjs";
|
|
21
|
+
import { sweepIfDue } from "./stage-sweep.mjs";
|
|
19
22
|
import { withWriterMutex } from "./writer-mutex.mjs";
|
|
20
23
|
|
|
21
24
|
// Kept cohesive above 300 lines because durable transactions and ephemeral
|
|
@@ -29,7 +32,15 @@ export const ZERO_CURSOR = "0".repeat(SEQUENCE_WIDTH);
|
|
|
29
32
|
// corrupt record, so nothing ever had a reason to put one aside. An empty
|
|
30
33
|
// directory that reads as a feature is the same mistake as an attention kind
|
|
31
34
|
// with no rule behind it. If quarantining is ever built, it comes back with it.
|
|
32
|
-
|
|
35
|
+
//
|
|
36
|
+
// `stage` is the counter-example that keeps that rule rather than breaking it:
|
|
37
|
+
// every immutable publication fills it and stage-sweep.mjs empties it. Holding
|
|
38
|
+
// accepted stages apart from the partials in `tmp` is what lets the sweep
|
|
39
|
+
// remove a whole directory instead of deciding file by file from a filename
|
|
40
|
+
// suffix. A `stage` ever found empty by design rather than by sweeping is the
|
|
41
|
+
// mistake described above, and should go.
|
|
42
|
+
const DIRECTORIES = ["state", "events", "journal", "locks", "ephemeral", "retained", "tmp",
|
|
43
|
+
"stage"];
|
|
33
44
|
|
|
34
45
|
const pad = value => String(value).padStart(SEQUENCE_WIDTH, "0");
|
|
35
46
|
|
|
@@ -68,7 +79,24 @@ async function loadAllState(paths, root, wanted = null) {
|
|
|
68
79
|
|
|
69
80
|
async function nextSequence(paths, root) {
|
|
70
81
|
const last = (await listJsonFiles(paths.events, { root })).at(-1);
|
|
71
|
-
|
|
82
|
+
// Only an empty directory consults the floor, so the path every transaction
|
|
83
|
+
// runs pays nothing for retention. Trimming removes the oldest events, so
|
|
84
|
+
// while any file remains the newest one still answers this. A log trimmed
|
|
85
|
+
// away entirely is the case that would otherwise restart at 1 and hand out a
|
|
86
|
+
// sequence a peer already holds a cursor for.
|
|
87
|
+
const highest = last === undefined
|
|
88
|
+
? await readEventFloor(paths, root)
|
|
89
|
+
: path.basename(last, ".json");
|
|
90
|
+
if (highest === null) return 1;
|
|
91
|
+
// Counted as a BigInt whichever side it came from. A 16-digit sequence
|
|
92
|
+
// reaches past Number.MAX_SAFE_INTEGER, where adding one stops changing the
|
|
93
|
+
// value, so a Number here would eventually hand out a sequence twice.
|
|
94
|
+
// Refusing is the only honest answer left at that point.
|
|
95
|
+
const next = BigInt(highest) + 1n;
|
|
96
|
+
if (next > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
97
|
+
throw new AccError(EXIT.DATA, "event sequence is exhausted", { highest });
|
|
98
|
+
}
|
|
99
|
+
return Number(next);
|
|
72
100
|
}
|
|
73
101
|
|
|
74
102
|
export async function openFilesystemStore({ root, clock, ids, workspaceId, failAt,
|
|
@@ -93,6 +121,18 @@ export async function openFilesystemStore({ root, clock, ids, workspaceId, failA
|
|
|
93
121
|
// must not roll the same journal forward concurrently.
|
|
94
122
|
await recoverOpenJournals();
|
|
95
123
|
|
|
124
|
+
// Sweeping is a write, so it holds the same mutex recovery does and no
|
|
125
|
+
// publisher is in flight while the stage directory is detached. It is bounded
|
|
126
|
+
// per pass: a store carrying a large accumulation drains over several opens
|
|
127
|
+
// rather than spending one hook's whole budget on it. The mutex is taken only
|
|
128
|
+
// on the opens that actually sweep - see sweepIfDue, which decides before it
|
|
129
|
+
// locks, because this runs inside every hook.
|
|
130
|
+
// Maintenance never decides whether a store can be opened: sweepIfDue answers
|
|
131
|
+
// a lost lock and a lapsed budget itself, and raises only real store faults.
|
|
132
|
+
await sweepIfDue(paths, { root, clock, deadlineAt: storeDeadline,
|
|
133
|
+
withLock: operation => withWriterMutex(paths,
|
|
134
|
+
{ root, tmpDir: paths.tmp, clock, deadlineAt: storeDeadline }, operation) });
|
|
135
|
+
|
|
96
136
|
async function recoverOpenJournals() {
|
|
97
137
|
const open = await readOpenJournals(paths, root);
|
|
98
138
|
if (open.length === 0) return [];
|
|
@@ -253,7 +293,11 @@ export async function openFilesystemStore({ root, clock, ids, workspaceId, failA
|
|
|
253
293
|
events.push(event);
|
|
254
294
|
if (events.length === limit) break;
|
|
255
295
|
}
|
|
256
|
-
|
|
296
|
+
// A caller whose cursor precedes the boundary is served what is left and
|
|
297
|
+
// told where the log now starts. A short page that reads like a complete
|
|
298
|
+
// one is the failure trimming must not introduce.
|
|
299
|
+
return { cursor: events.at(-1)?.sequence ?? after, events,
|
|
300
|
+
trimmedThrough: await readEventFloor(paths, root) };
|
|
257
301
|
}
|
|
258
302
|
|
|
259
303
|
/**
|
|
@@ -365,6 +409,54 @@ export async function openFilesystemStore({ root, clock, ids, workspaceId, failA
|
|
|
365
409
|
},
|
|
366
410
|
});
|
|
367
411
|
|
|
368
|
-
|
|
369
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Every live state record with the generation that identifies it.
|
|
414
|
+
*
|
|
415
|
+
* `snapshot` deliberately hands back records alone, because that is all any
|
|
416
|
+
* reader of coordination state needs. Reclaiming is the one caller that needs
|
|
417
|
+
* the envelope generation too: it decides what is eligible from a reading
|
|
418
|
+
* taken outside the writer mutex and applies it inside, and the generation is
|
|
419
|
+
* what proves the record did not change in between.
|
|
420
|
+
*/
|
|
421
|
+
async function stateEnvelopes(workspace, { kinds } = {}) {
|
|
422
|
+
const wanted = kinds === undefined ? null : new Set(kinds);
|
|
423
|
+
return [...(await loadAllState(paths, root, wanted)).values()]
|
|
424
|
+
.filter(envelope => envelope.record.workspaceId === workspace);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Remove named state records and the retention markers they own.
|
|
429
|
+
*
|
|
430
|
+
* Under the writer mutex, because it competes with every transaction; the
|
|
431
|
+
* store deadline still bounds it, so an operator waiting on a busy store is
|
|
432
|
+
* told rather than blocked forever.
|
|
433
|
+
*/
|
|
434
|
+
async function reclaimRecords(plan, { limit, deadlineAt } = {}) {
|
|
435
|
+
const bounded = Math.min(deadlineAt ?? Infinity, storeDeadline ?? Infinity);
|
|
436
|
+
return withWriterMutex(paths, { ...publishOptions, deadlineAt: bounded }, async () => {
|
|
437
|
+
// A function is decided here, holding the mutex, because eligibility is a
|
|
438
|
+
// statement about relationships between records and not only about each
|
|
439
|
+
// record. The generation on an entry proves that record did not change;
|
|
440
|
+
// it says nothing about a session opening for a participant this was
|
|
441
|
+
// about to remove. Deciding inside the lock is what closes that.
|
|
442
|
+
const entries = typeof plan === "function" ? await plan() : plan;
|
|
443
|
+
return reclaimStateRecords(paths, entries, { root, limit, deadlineAt: bounded });
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Trim the event log to a boundary and report where it now starts.
|
|
449
|
+
*
|
|
450
|
+
* Under the writer mutex, because it competes with the sequence every
|
|
451
|
+
* transaction allocates.
|
|
452
|
+
*/
|
|
453
|
+
async function trimHistory(boundary, { limit, deadlineAt } = {}) {
|
|
454
|
+
const bounded = Math.min(deadlineAt ?? Infinity, storeDeadline ?? Infinity);
|
|
455
|
+
const publish = { ...publishOptions, deadlineAt: bounded };
|
|
456
|
+
return withWriterMutex(paths, publish,
|
|
457
|
+
() => trimEventLog(paths, boundary, { root, publish, limit, deadlineAt: bounded }));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return Object.freeze({ transaction, eventsSince, snapshot, stateRecord, stateEnvelopes,
|
|
461
|
+
reclaimRecords, trimHistory, ephemeral, paths, root, workspaceId });
|
|
370
462
|
}
|