agenthub-multiagent-mcp 1.15.0 → 1.15.2
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/dist/brain/journal.d.ts +77 -0
- package/dist/brain/journal.d.ts.map +1 -0
- package/dist/brain/journal.js +276 -0
- package/dist/brain/journal.js.map +1 -0
- package/dist/brain/journal.test.d.ts +5 -0
- package/dist/brain/journal.test.d.ts.map +1 -0
- package/dist/brain/journal.test.js +151 -0
- package/dist/brain/journal.test.js.map +1 -0
- package/dist/hooks/brainCapture.d.ts +29 -2
- package/dist/hooks/brainCapture.d.ts.map +1 -1
- package/dist/hooks/brainCapture.js +89 -7
- package/dist/hooks/brainCapture.js.map +1 -1
- package/dist/index.js +55 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/commands/close-session.md +135 -0
- package/skills/commands/start-session.md +57 -34
- package/skills/manifest.json +1 -1
- package/skills/skills/close-session.md +0 -105
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Journal — crash-safe, fire-and-forget event log for real-time
|
|
3
|
+
* session durability without polluting the sealed `.mv2` brain.
|
|
4
|
+
*
|
|
5
|
+
* Location: `~/.claude/brains/journal/<sanitized-agent-id>.jsonl`
|
|
6
|
+
*
|
|
7
|
+
* Design goals:
|
|
8
|
+
* 1. **Zero LLM token cost** — filesystem-only, no network per event.
|
|
9
|
+
* 2. **Zero measurable perf impact** — async `appendFile`, fire-and-forget,
|
|
10
|
+
* never awaited by the hot path. ~50–200μs per write on SSDs.
|
|
11
|
+
* 3. **Bounded power-cut loss** — no explicit fsync (fsync would add 2–10ms
|
|
12
|
+
* per event). Worst-case loss is OS write-buffer depth, typically <5s on
|
|
13
|
+
* Windows NTFS. Accept that; `.mv2` summary at session close is the
|
|
14
|
+
* durable record.
|
|
15
|
+
* 4. **Clean `.mv2`** — journals are summarized into a single `.mv2` frame
|
|
16
|
+
* on session end. `.mv2` BM25 search quality stays high.
|
|
17
|
+
* 5. **Recoverable** — if Claude Code crashes or power dies mid-session, the
|
|
18
|
+
* JSONL survives (up to the OS flush boundary). On next startup we
|
|
19
|
+
* summarize it into `.mv2` and archive.
|
|
20
|
+
*
|
|
21
|
+
* Event schema:
|
|
22
|
+
* { "t": "2026-04-23T...", "e": "tool", "n": "save_decision",
|
|
23
|
+
* "s": "ok", "p": { ...truncated args... } }
|
|
24
|
+
*/
|
|
25
|
+
export interface JournalEvent {
|
|
26
|
+
t: string;
|
|
27
|
+
e: "tool" | "session_start" | "session_end" | "error";
|
|
28
|
+
n?: string;
|
|
29
|
+
s?: "ok" | "err";
|
|
30
|
+
p?: unknown;
|
|
31
|
+
}
|
|
32
|
+
export declare function getJournalPath(agentId: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Is this tool call worth journaling? Keep the allowlist in sync with the
|
|
35
|
+
* set of tools that represent real work worth recovering on crash.
|
|
36
|
+
*/
|
|
37
|
+
export declare function shouldJournal(toolName: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Fire-and-forget append. Never throws, never awaits on the hot path.
|
|
40
|
+
* Returns void synchronously; I/O runs on the event-loop tail, serialized
|
|
41
|
+
* per-path to avoid Windows file-sharing violations.
|
|
42
|
+
*/
|
|
43
|
+
export declare function logEvent(agentId: string, event: JournalEvent): void;
|
|
44
|
+
/**
|
|
45
|
+
* Test helper: await all pending writes for an agent. Not used in production.
|
|
46
|
+
* Exported so unit tests can assert against fully-flushed state without polling.
|
|
47
|
+
*/
|
|
48
|
+
export declare function flushPending(agentId: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Test helper: reset internal state. Not used in production.
|
|
51
|
+
*/
|
|
52
|
+
export declare function _resetForTest(): void;
|
|
53
|
+
/** Convenience: log a tool call event. */
|
|
54
|
+
export declare function logTool(agentId: string, toolName: string, args: unknown, ok: boolean): void;
|
|
55
|
+
/** Returns true if a journal file exists for this agent. */
|
|
56
|
+
export declare function hasJournal(agentId: string): Promise<boolean>;
|
|
57
|
+
/** Read all events from the journal. Returns [] if missing/corrupt. */
|
|
58
|
+
export declare function readEvents(agentId: string): Promise<JournalEvent[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Rename the journal to an archived path so a fresh session starts clean.
|
|
61
|
+
* Returns the archive path, or null if no journal existed.
|
|
62
|
+
*/
|
|
63
|
+
export declare function archive(agentId: string): Promise<string | null>;
|
|
64
|
+
/**
|
|
65
|
+
* List agents that have an active (non-archived) journal file. Used on
|
|
66
|
+
* startup to recover journals from crashed prior sessions.
|
|
67
|
+
*/
|
|
68
|
+
export declare function listAgentsWithJournal(): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Summarize a list of journal events into a single text block suitable for
|
|
71
|
+
* a `.mv2` frame body. Deterministic ordering, compact format.
|
|
72
|
+
*/
|
|
73
|
+
export declare function summarizeEvents(events: JournalEvent[]): {
|
|
74
|
+
summary: string;
|
|
75
|
+
content: string;
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=journal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../../src/brain/journal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AA6CH,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,aAAa,GAAG,OAAO,CAAC;IACtD,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,IAAI,GAAG,KAAK,CAAC;IACjB,CAAC,CAAC,EAAE,OAAO,CAAC;CACb;AAUD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD;AAsBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvD;AAWD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAkBnE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjE;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAGpC;AAED,0CAA0C;AAC1C,wBAAgB,OAAO,CACrB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,EAAE,EAAE,OAAO,GACV,IAAI,CASN;AAED,4DAA4D;AAC5D,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlE;AAED,uEAAuE;AACvE,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAmBzE;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBrE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAUhD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CA+BA"}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Journal — crash-safe, fire-and-forget event log for real-time
|
|
3
|
+
* session durability without polluting the sealed `.mv2` brain.
|
|
4
|
+
*
|
|
5
|
+
* Location: `~/.claude/brains/journal/<sanitized-agent-id>.jsonl`
|
|
6
|
+
*
|
|
7
|
+
* Design goals:
|
|
8
|
+
* 1. **Zero LLM token cost** — filesystem-only, no network per event.
|
|
9
|
+
* 2. **Zero measurable perf impact** — async `appendFile`, fire-and-forget,
|
|
10
|
+
* never awaited by the hot path. ~50–200μs per write on SSDs.
|
|
11
|
+
* 3. **Bounded power-cut loss** — no explicit fsync (fsync would add 2–10ms
|
|
12
|
+
* per event). Worst-case loss is OS write-buffer depth, typically <5s on
|
|
13
|
+
* Windows NTFS. Accept that; `.mv2` summary at session close is the
|
|
14
|
+
* durable record.
|
|
15
|
+
* 4. **Clean `.mv2`** — journals are summarized into a single `.mv2` frame
|
|
16
|
+
* on session end. `.mv2` BM25 search quality stays high.
|
|
17
|
+
* 5. **Recoverable** — if Claude Code crashes or power dies mid-session, the
|
|
18
|
+
* JSONL survives (up to the OS flush boundary). On next startup we
|
|
19
|
+
* summarize it into `.mv2` and archive.
|
|
20
|
+
*
|
|
21
|
+
* Event schema:
|
|
22
|
+
* { "t": "2026-04-23T...", "e": "tool", "n": "save_decision",
|
|
23
|
+
* "s": "ok", "p": { ...truncated args... } }
|
|
24
|
+
*/
|
|
25
|
+
import { appendFile, mkdir, readFile, rename, stat, unlink } from "fs/promises";
|
|
26
|
+
import { existsSync, readdirSync } from "fs";
|
|
27
|
+
import { join } from "path";
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
const JOURNAL_DIR_NAME = "journal";
|
|
30
|
+
const MAX_PAYLOAD_CHARS = 500;
|
|
31
|
+
/**
|
|
32
|
+
* Tools worth journaling. Everything else (heartbeats, reads, list/get,
|
|
33
|
+
* searches) is pure noise and is skipped to keep journals tight.
|
|
34
|
+
*/
|
|
35
|
+
const JOURNALED_TOOLS = new Set([
|
|
36
|
+
"save_decision",
|
|
37
|
+
"save_note",
|
|
38
|
+
"save_codebase_insight",
|
|
39
|
+
"save_blocker",
|
|
40
|
+
"clear_blocker",
|
|
41
|
+
"agent_start_work",
|
|
42
|
+
"agent_complete_task",
|
|
43
|
+
"agent_report_blocked",
|
|
44
|
+
"agent_report_failure",
|
|
45
|
+
"accept_task",
|
|
46
|
+
"decline_task",
|
|
47
|
+
"claim_ticket_task",
|
|
48
|
+
"create_task",
|
|
49
|
+
"update_ticket",
|
|
50
|
+
"add_comment",
|
|
51
|
+
"submit_plan",
|
|
52
|
+
"submit_code_review",
|
|
53
|
+
"reach_checkpoint",
|
|
54
|
+
"send_message",
|
|
55
|
+
"broadcast",
|
|
56
|
+
"send_to_channel",
|
|
57
|
+
"reply",
|
|
58
|
+
"brain_write_knowledge",
|
|
59
|
+
"openspec_publish",
|
|
60
|
+
"create_epic",
|
|
61
|
+
"create_story",
|
|
62
|
+
"sync_context",
|
|
63
|
+
"session_close_report",
|
|
64
|
+
]);
|
|
65
|
+
function sanitizeAgentId(agentId) {
|
|
66
|
+
return agentId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
67
|
+
}
|
|
68
|
+
function getJournalDir() {
|
|
69
|
+
return join(homedir(), ".claude", "brains", JOURNAL_DIR_NAME);
|
|
70
|
+
}
|
|
71
|
+
export function getJournalPath(agentId) {
|
|
72
|
+
return join(getJournalDir(), `${sanitizeAgentId(agentId)}.jsonl`);
|
|
73
|
+
}
|
|
74
|
+
async function ensureJournalDir() {
|
|
75
|
+
await mkdir(getJournalDir(), { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Truncate an arbitrary JS value to a compact JSON-safe payload.
|
|
79
|
+
* Keeps the journal bounded per event — an accidental 10MB `content` field
|
|
80
|
+
* won't balloon the file.
|
|
81
|
+
*/
|
|
82
|
+
function truncatePayload(payload) {
|
|
83
|
+
if (payload === undefined || payload === null)
|
|
84
|
+
return payload;
|
|
85
|
+
try {
|
|
86
|
+
const json = JSON.stringify(payload);
|
|
87
|
+
if (json.length <= MAX_PAYLOAD_CHARS)
|
|
88
|
+
return payload;
|
|
89
|
+
return { _truncated: true, preview: json.slice(0, MAX_PAYLOAD_CHARS) };
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { _truncated: true, preview: String(payload).slice(0, MAX_PAYLOAD_CHARS) };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Is this tool call worth journaling? Keep the allowlist in sync with the
|
|
97
|
+
* set of tools that represent real work worth recovering on crash.
|
|
98
|
+
*/
|
|
99
|
+
export function shouldJournal(toolName) {
|
|
100
|
+
return JOURNALED_TOOLS.has(toolName);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Per-path serialized write queue. Multiple concurrent appendFile calls against
|
|
104
|
+
* the same path on Windows can trip EBUSY / ERROR_SHARING_VIOLATION because
|
|
105
|
+
* each call opens its own handle. Chaining via a per-path promise serializes
|
|
106
|
+
* writes without blocking the caller — the caller still returns synchronously.
|
|
107
|
+
*/
|
|
108
|
+
const writeQueues = new Map();
|
|
109
|
+
let dirEnsured = false;
|
|
110
|
+
/**
|
|
111
|
+
* Fire-and-forget append. Never throws, never awaits on the hot path.
|
|
112
|
+
* Returns void synchronously; I/O runs on the event-loop tail, serialized
|
|
113
|
+
* per-path to avoid Windows file-sharing violations.
|
|
114
|
+
*/
|
|
115
|
+
export function logEvent(agentId, event) {
|
|
116
|
+
if (!agentId)
|
|
117
|
+
return;
|
|
118
|
+
const path = getJournalPath(agentId);
|
|
119
|
+
const line = JSON.stringify(event) + "\n";
|
|
120
|
+
const prev = writeQueues.get(path) ?? Promise.resolve();
|
|
121
|
+
const next = prev
|
|
122
|
+
.then(async () => {
|
|
123
|
+
if (!dirEnsured) {
|
|
124
|
+
await ensureJournalDir();
|
|
125
|
+
dirEnsured = true;
|
|
126
|
+
}
|
|
127
|
+
await appendFile(path, line, { encoding: "utf8" });
|
|
128
|
+
})
|
|
129
|
+
.catch(() => {
|
|
130
|
+
// Best-effort. Journal is a crash-safety net, not a correctness dependency.
|
|
131
|
+
});
|
|
132
|
+
writeQueues.set(path, next);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Test helper: await all pending writes for an agent. Not used in production.
|
|
136
|
+
* Exported so unit tests can assert against fully-flushed state without polling.
|
|
137
|
+
*/
|
|
138
|
+
export async function flushPending(agentId) {
|
|
139
|
+
const path = getJournalPath(agentId);
|
|
140
|
+
const pending = writeQueues.get(path);
|
|
141
|
+
if (pending)
|
|
142
|
+
await pending;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Test helper: reset internal state. Not used in production.
|
|
146
|
+
*/
|
|
147
|
+
export function _resetForTest() {
|
|
148
|
+
writeQueues.clear();
|
|
149
|
+
dirEnsured = false;
|
|
150
|
+
}
|
|
151
|
+
/** Convenience: log a tool call event. */
|
|
152
|
+
export function logTool(agentId, toolName, args, ok) {
|
|
153
|
+
if (!shouldJournal(toolName))
|
|
154
|
+
return;
|
|
155
|
+
logEvent(agentId, {
|
|
156
|
+
t: new Date().toISOString(),
|
|
157
|
+
e: "tool",
|
|
158
|
+
n: toolName,
|
|
159
|
+
s: ok ? "ok" : "err",
|
|
160
|
+
p: truncatePayload(args),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/** Returns true if a journal file exists for this agent. */
|
|
164
|
+
export async function hasJournal(agentId) {
|
|
165
|
+
try {
|
|
166
|
+
await stat(getJournalPath(agentId));
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Read all events from the journal. Returns [] if missing/corrupt. */
|
|
174
|
+
export async function readEvents(agentId) {
|
|
175
|
+
const path = getJournalPath(agentId);
|
|
176
|
+
let raw;
|
|
177
|
+
try {
|
|
178
|
+
raw = await readFile(path, "utf8");
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
const events = [];
|
|
184
|
+
for (const line of raw.split("\n")) {
|
|
185
|
+
const trimmed = line.trim();
|
|
186
|
+
if (!trimmed)
|
|
187
|
+
continue;
|
|
188
|
+
try {
|
|
189
|
+
events.push(JSON.parse(trimmed));
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Skip malformed lines (e.g. a torn write at the tail from a power cut).
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return events;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Rename the journal to an archived path so a fresh session starts clean.
|
|
199
|
+
* Returns the archive path, or null if no journal existed.
|
|
200
|
+
*/
|
|
201
|
+
export async function archive(agentId) {
|
|
202
|
+
const path = getJournalPath(agentId);
|
|
203
|
+
if (!existsSync(path))
|
|
204
|
+
return null;
|
|
205
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
206
|
+
const dest = `${path}.archived.${stamp}`;
|
|
207
|
+
try {
|
|
208
|
+
await rename(path, dest);
|
|
209
|
+
return dest;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// If rename fails (e.g. locked on Windows), fall back to delete.
|
|
213
|
+
try {
|
|
214
|
+
await unlink(path);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Swallow — worst case, next session re-summarizes the same content (idempotent enough).
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* List agents that have an active (non-archived) journal file. Used on
|
|
224
|
+
* startup to recover journals from crashed prior sessions.
|
|
225
|
+
*/
|
|
226
|
+
export function listAgentsWithJournal() {
|
|
227
|
+
const dir = getJournalDir();
|
|
228
|
+
if (!existsSync(dir))
|
|
229
|
+
return [];
|
|
230
|
+
const result = [];
|
|
231
|
+
for (const file of readdirSync(dir)) {
|
|
232
|
+
if (!file.endsWith(".jsonl"))
|
|
233
|
+
continue;
|
|
234
|
+
if (file.includes(".archived."))
|
|
235
|
+
continue;
|
|
236
|
+
result.push(file.slice(0, -".jsonl".length));
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Summarize a list of journal events into a single text block suitable for
|
|
242
|
+
* a `.mv2` frame body. Deterministic ordering, compact format.
|
|
243
|
+
*/
|
|
244
|
+
export function summarizeEvents(events) {
|
|
245
|
+
if (events.length === 0) {
|
|
246
|
+
return { summary: "empty journal", content: "" };
|
|
247
|
+
}
|
|
248
|
+
const counts = new Map();
|
|
249
|
+
for (const ev of events) {
|
|
250
|
+
if (ev.e !== "tool" || !ev.n)
|
|
251
|
+
continue;
|
|
252
|
+
counts.set(ev.n, (counts.get(ev.n) ?? 0) + 1);
|
|
253
|
+
}
|
|
254
|
+
const topTools = [...counts.entries()]
|
|
255
|
+
.sort((a, b) => b[1] - a[1])
|
|
256
|
+
.slice(0, 5)
|
|
257
|
+
.map(([n, c]) => `${n}×${c}`)
|
|
258
|
+
.join(", ");
|
|
259
|
+
const first = events[0];
|
|
260
|
+
const last = events[events.length - 1];
|
|
261
|
+
const summary = `journal: ${events.length} events — ${topTools}`;
|
|
262
|
+
const content = [
|
|
263
|
+
`start=${first.t}`,
|
|
264
|
+
`end=${last.t}`,
|
|
265
|
+
`events=${events.length}`,
|
|
266
|
+
`tools=${topTools}`,
|
|
267
|
+
"",
|
|
268
|
+
...events.map((ev) => {
|
|
269
|
+
const base = `${ev.t} ${ev.e}${ev.n ? " " + ev.n : ""}${ev.s ? " " + ev.s : ""}`;
|
|
270
|
+
const payload = ev.p !== undefined ? " " + JSON.stringify(ev.p) : "";
|
|
271
|
+
return base + payload;
|
|
272
|
+
}),
|
|
273
|
+
].join("\n");
|
|
274
|
+
return { summary, content };
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/brain/journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.test.d.ts","sourceRoot":"","sources":["../../src/brain/journal.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the crash-safe session journal.
|
|
3
|
+
*/
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { mkdtempSync, rmSync, writeFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from "fs";
|
|
6
|
+
import { tmpdir } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
let tempHome;
|
|
9
|
+
vi.mock("os", async () => {
|
|
10
|
+
const actual = await vi.importActual("os");
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
homedir: () => process.env.__TEST_HOME__ ?? actual.homedir(),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
async function importJournal() {
|
|
17
|
+
return await import("./journal.js");
|
|
18
|
+
}
|
|
19
|
+
describe("journal", () => {
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
tempHome = mkdtempSync(join(tmpdir(), "journal-test-"));
|
|
22
|
+
process.env.__TEST_HOME__ = tempHome;
|
|
23
|
+
vi.resetModules();
|
|
24
|
+
const j = await importJournal();
|
|
25
|
+
j._resetForTest();
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
rmSync(tempHome, { recursive: true, force: true });
|
|
29
|
+
delete process.env.__TEST_HOME__;
|
|
30
|
+
});
|
|
31
|
+
it("shouldJournal accepts allowlisted tools and rejects noise", async () => {
|
|
32
|
+
const j = await importJournal();
|
|
33
|
+
expect(j.shouldJournal("save_decision")).toBe(true);
|
|
34
|
+
expect(j.shouldJournal("agent_complete_task")).toBe(true);
|
|
35
|
+
expect(j.shouldJournal("heartbeat")).toBe(false);
|
|
36
|
+
expect(j.shouldJournal("check_inbox")).toBe(false);
|
|
37
|
+
expect(j.shouldJournal("list_agents")).toBe(false);
|
|
38
|
+
expect(j.shouldJournal("brain_search")).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it("logTool writes a JSONL line to the journal file", async () => {
|
|
41
|
+
const j = await importJournal();
|
|
42
|
+
j.logTool("agent-x", "save_decision", { what: "use bun", why: "speed" }, true);
|
|
43
|
+
// Fire-and-forget — give the event loop a tick to flush.
|
|
44
|
+
await j.flushPending("agent-x");
|
|
45
|
+
const path = j.getJournalPath("agent-x");
|
|
46
|
+
expect(existsSync(path)).toBe(true);
|
|
47
|
+
const raw = readFileSync(path, "utf8").trim();
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
expect(parsed.e).toBe("tool");
|
|
50
|
+
expect(parsed.n).toBe("save_decision");
|
|
51
|
+
expect(parsed.s).toBe("ok");
|
|
52
|
+
expect(parsed.p).toEqual({ what: "use bun", why: "speed" });
|
|
53
|
+
expect(typeof parsed.t).toBe("string");
|
|
54
|
+
});
|
|
55
|
+
it("logTool skips non-allowlisted tools silently", async () => {
|
|
56
|
+
const j = await importJournal();
|
|
57
|
+
j.logTool("agent-x", "heartbeat", {}, true);
|
|
58
|
+
await j.flushPending("agent-x");
|
|
59
|
+
expect(await j.hasJournal("agent-x")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
it("logTool truncates oversized payloads", async () => {
|
|
62
|
+
const j = await importJournal();
|
|
63
|
+
const huge = "x".repeat(5000);
|
|
64
|
+
j.logTool("agent-x", "save_note", { body: huge }, true);
|
|
65
|
+
await j.flushPending("agent-x");
|
|
66
|
+
const events = await j.readEvents("agent-x");
|
|
67
|
+
expect(events).toHaveLength(1);
|
|
68
|
+
const payload = events[0].p;
|
|
69
|
+
expect(payload._truncated).toBe(true);
|
|
70
|
+
expect(payload.preview.length).toBeLessThanOrEqual(500);
|
|
71
|
+
});
|
|
72
|
+
it("readEvents tolerates torn final line from a power-cut scenario", async () => {
|
|
73
|
+
const j = await importJournal();
|
|
74
|
+
const dir = join(tempHome, ".claude", "brains", "journal");
|
|
75
|
+
mkdirSync(dir, { recursive: true });
|
|
76
|
+
const path = j.getJournalPath("agent-x");
|
|
77
|
+
const good = JSON.stringify({ t: "2026-04-23T00:00:00Z", e: "tool", n: "save_decision", s: "ok" });
|
|
78
|
+
// Second line is truncated mid-JSON — simulates a power-cut append.
|
|
79
|
+
writeFileSync(path, good + "\n" + '{"t":"2026-04-23T00:00:01Z","e":"tool","n":"save_no', "utf8");
|
|
80
|
+
const events = await j.readEvents("agent-x");
|
|
81
|
+
expect(events).toHaveLength(1);
|
|
82
|
+
expect(events[0].n).toBe("save_decision");
|
|
83
|
+
});
|
|
84
|
+
it("archive renames the journal so the next session starts clean", async () => {
|
|
85
|
+
const j = await importJournal();
|
|
86
|
+
j.logTool("agent-x", "save_decision", { a: 1 }, true);
|
|
87
|
+
await j.flushPending("agent-x");
|
|
88
|
+
expect(await j.hasJournal("agent-x")).toBe(true);
|
|
89
|
+
const dest = await j.archive("agent-x");
|
|
90
|
+
expect(dest).not.toBeNull();
|
|
91
|
+
expect(existsSync(dest)).toBe(true);
|
|
92
|
+
expect(await j.hasJournal("agent-x")).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
it("listAgentsWithJournal returns active journals and excludes archives", async () => {
|
|
95
|
+
const j = await importJournal();
|
|
96
|
+
j.logTool("agent-a", "save_decision", {}, true);
|
|
97
|
+
j.logTool("agent-b", "save_note", {}, true);
|
|
98
|
+
await j.flushPending("agent-a");
|
|
99
|
+
await j.flushPending("agent-b");
|
|
100
|
+
await j.archive("agent-b");
|
|
101
|
+
const active = j.listAgentsWithJournal();
|
|
102
|
+
expect(active).toContain("agent-a");
|
|
103
|
+
expect(active).not.toContain("agent-b");
|
|
104
|
+
});
|
|
105
|
+
it("summarizeEvents produces counts and deterministic content", async () => {
|
|
106
|
+
const j = await importJournal();
|
|
107
|
+
const events = [
|
|
108
|
+
{ t: "2026-04-23T00:00:00Z", e: "tool", n: "save_decision", s: "ok" },
|
|
109
|
+
{ t: "2026-04-23T00:00:01Z", e: "tool", n: "save_decision", s: "ok" },
|
|
110
|
+
{ t: "2026-04-23T00:00:02Z", e: "tool", n: "save_note", s: "ok" },
|
|
111
|
+
];
|
|
112
|
+
const { summary, content } = j.summarizeEvents(events);
|
|
113
|
+
expect(summary).toContain("3 events");
|
|
114
|
+
expect(summary).toContain("save_decision×2");
|
|
115
|
+
expect(summary).toContain("save_note×1");
|
|
116
|
+
expect(content).toContain("start=2026-04-23T00:00:00Z");
|
|
117
|
+
expect(content).toContain("end=2026-04-23T00:00:02Z");
|
|
118
|
+
expect(content).toContain("events=3");
|
|
119
|
+
});
|
|
120
|
+
it("summarizeEvents handles empty input without throwing", async () => {
|
|
121
|
+
const j = await importJournal();
|
|
122
|
+
const { summary, content } = j.summarizeEvents([]);
|
|
123
|
+
expect(summary).toBe("empty journal");
|
|
124
|
+
expect(content).toBe("");
|
|
125
|
+
});
|
|
126
|
+
it("serializes 5000 concurrent appends without dropping events (Windows sharing-violation regression)", async () => {
|
|
127
|
+
const j = await importJournal();
|
|
128
|
+
const N = 5000;
|
|
129
|
+
const start = Date.now();
|
|
130
|
+
for (let i = 0; i < N; i++) {
|
|
131
|
+
j.logTool("stress-agent", "save_decision", { i }, true);
|
|
132
|
+
}
|
|
133
|
+
const syncMs = Date.now() - start;
|
|
134
|
+
await j.flushPending("stress-agent");
|
|
135
|
+
const events = await j.readEvents("stress-agent");
|
|
136
|
+
expect(events).toHaveLength(N);
|
|
137
|
+
// Synchronous call cost must stay negligible. 5000 × <0.5ms = <2.5s.
|
|
138
|
+
expect(syncMs).toBeLessThan(2500);
|
|
139
|
+
}, 30_000);
|
|
140
|
+
it("sanitizes agent ids with unsafe characters in the filename", async () => {
|
|
141
|
+
const j = await importJournal();
|
|
142
|
+
j.logTool("agent/../../etc/passwd", "save_decision", {}, true);
|
|
143
|
+
await j.flushPending("agent/../../etc/passwd");
|
|
144
|
+
const dir = join(tempHome, ".claude", "brains", "journal");
|
|
145
|
+
const files = readdirSync(dir);
|
|
146
|
+
expect(files).toHaveLength(1);
|
|
147
|
+
expect(files[0]).not.toContain("/");
|
|
148
|
+
expect(files[0]).not.toContain("..");
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
//# sourceMappingURL=journal.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.test.js","sourceRoot":"","sources":["../../src/brain/journal.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,10 +30,37 @@ interface BrainCaptureData {
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function captureOnTaskComplete(client: ApiClient, data: BrainCaptureData): Promise<void>;
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* Flush the current session to both the org brain (server-side `brain_sessions`
|
|
34
|
+
* row) and the local `.mv2` file. Does NOT delete the agent from the registry.
|
|
35
|
+
*
|
|
36
|
+
* Safe to call from process-exit signal handlers (SIGINT/SIGTERM) — the agent
|
|
37
|
+
* stays registered so the next Claude Code start can reconnect to the same
|
|
38
|
+
* agent_id. Idempotent; calling twice in quick succession produces two session
|
|
39
|
+
* rows but that's acceptable (cheap, no data loss).
|
|
40
|
+
*/
|
|
41
|
+
export declare function flushSessionToBrain(client: ApiClient, agentId: string, project?: string): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Capture a brain session summary on explicit agent_disconnect.
|
|
44
|
+
* Thin wrapper over flushSessionToBrain — the tool handler performs the
|
|
45
|
+
* actual registry delete separately.
|
|
35
46
|
*/
|
|
36
47
|
export declare function captureOnDisconnect(client: ApiClient, agentId: string, project?: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Recover journals left behind by crashed/power-cut sessions. For each orphaned
|
|
50
|
+
* journal file under `~/.claude/brains/journal/`, summarize the events into a
|
|
51
|
+
* single `.mv2` frame and archive the journal.
|
|
52
|
+
*
|
|
53
|
+
* Called on MCP server startup (before the new agent's session starts), so the
|
|
54
|
+
* prior session's work makes it into the searchable brain even if the previous
|
|
55
|
+
* process never got a chance to run its shutdown handler.
|
|
56
|
+
*
|
|
57
|
+
* Skips `excludeAgentId` (the currently-registering agent) when provided — its
|
|
58
|
+
* own journal will be summarized at session close, not at startup.
|
|
59
|
+
*/
|
|
60
|
+
export declare function recoverOrphanedJournals(excludeAgentId?: string): Promise<{
|
|
61
|
+
recovered: number;
|
|
62
|
+
failed: number;
|
|
63
|
+
}>;
|
|
37
64
|
/**
|
|
38
65
|
* Extract decisions from the local context store.
|
|
39
66
|
* Reads the decisions saved via save_decision tool during this session.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brainCapture.d.ts","sourceRoot":"","sources":["../../src/hooks/brainCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"brainCapture.d.ts","sourceRoot":"","sources":["../../src/hooks/brainCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,IAAI,CAAC,CA6Ff;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAyEf;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,uBAAuB,CAC3C,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA8BhD;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,GACd,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAUtC"}
|