hmem-mcp 2.0.3 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/cli-init.js +20 -4
- package/dist/cli-init.js.map +1 -1
- package/dist/hmem-config.d.ts +5 -32
- package/dist/hmem-config.js +5 -35
- package/dist/hmem-config.js.map +1 -1
- package/dist/hmem-store.d.ts +59 -9
- package/dist/hmem-store.js +340 -128
- package/dist/hmem-store.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +284 -76
- package/dist/mcp-server.js.map +1 -1
- package/dist/session-cache.d.ts +66 -0
- package/dist/session-cache.js +100 -0
- package/dist/session-cache.js.map +1 -0
- package/hmem_developer.hmem +0 -0
- package/package.json +3 -2
- package/skills/hmem-config/SKILL.md +15 -19
- package/skills/hmem-curate/SKILL.md +17 -0
- package/skills/hmem-read/SKILL.md +23 -10
- package/skills/hmem-self-curate/SKILL.md +113 -0
- package/skills/hmem-setup/SKILL.md +6 -16
- package/skills/hmem-write/SKILL.md +24 -5
- package/skills/hmem-save/SKILL.md +0 -128
package/dist/mcp-server.js
CHANGED
|
@@ -22,6 +22,7 @@ import path from "node:path";
|
|
|
22
22
|
import { searchMemory } from "./memory-search.js";
|
|
23
23
|
import { openAgentMemory, openCompanyMemory, resolveHmemPath, HmemStore } from "./hmem-store.js";
|
|
24
24
|
import { loadHmemConfig, formatPrefixList } from "./hmem-config.js";
|
|
25
|
+
import { SessionCache } from "./session-cache.js";
|
|
25
26
|
// ---- Environment ----
|
|
26
27
|
// HMEM_* vars are the canonical names; COUNCIL_* kept for backwards compatibility
|
|
27
28
|
const PROJECT_DIR = process.env.HMEM_PROJECT_DIR || process.env.COUNCIL_PROJECT_DIR || "";
|
|
@@ -52,11 +53,13 @@ function log(msg) {
|
|
|
52
53
|
}
|
|
53
54
|
// Load hmem config (hmem.config.json in project dir, falls back to defaults)
|
|
54
55
|
const hmemConfig = loadHmemConfig(PROJECT_DIR);
|
|
55
|
-
log(`Config: levels=[${hmemConfig.maxCharsPerLevel.join(",")}] depth=${hmemConfig.maxDepth}
|
|
56
|
+
log(`Config: levels=[${hmemConfig.maxCharsPerLevel.join(",")}] depth=${hmemConfig.maxDepth}`);
|
|
57
|
+
// Session-scoped cache — persists across tool calls within this MCP connection
|
|
58
|
+
const sessionCache = new SessionCache();
|
|
56
59
|
// ---- Server ----
|
|
57
60
|
const server = new McpServer({
|
|
58
61
|
name: "hmem",
|
|
59
|
-
version: "
|
|
62
|
+
version: "2.2.0",
|
|
60
63
|
});
|
|
61
64
|
// ---- Tool: search_memory ----
|
|
62
65
|
server.tool("search_memory", "Searches the collective memory: agent memories (lessons learned, evaluations), " +
|
|
@@ -199,7 +202,9 @@ server.tool("update_memory", "Update the text of an existing memory entry or sub
|
|
|
199
202
|
"- Mark as obsolete: FIRST write the correction, THEN update with [✓ID] reference:\n" +
|
|
200
203
|
" 1. write_memory(prefix='E', content='Correct fix is...') → E0076\n" +
|
|
201
204
|
" 2. update_memory(id='E0042', content='Wrong — see [✓E0076]', obsolete=true)\n" +
|
|
202
|
-
"- Mark as favorite: update_memory(id='D0010', content='...', favorite=true)\n
|
|
205
|
+
"- Mark as favorite: update_memory(id='D0010', content='...', favorite=true)\n" +
|
|
206
|
+
"- Mark as irrelevant: update_memory(id='L0042', content='...', irrelevant=true)\n" +
|
|
207
|
+
" No correction entry needed (unlike obsolete). Hidden from bulk reads.\n\n" +
|
|
203
208
|
"To add new child nodes, use append_memory. " +
|
|
204
209
|
"To replace the entire tree, use delete_agent_memory + write_memory (curator only).", {
|
|
205
210
|
id: z.string().describe("ID of the entry or node to update, e.g. 'L0003' or 'L0003.2'"),
|
|
@@ -207,10 +212,12 @@ server.tool("update_memory", "Update the text of an existing memory entry or sub
|
|
|
207
212
|
links: z.array(z.string()).optional().describe("Optional: update linked entry IDs (root entries only). Replaces existing links."),
|
|
208
213
|
obsolete: z.boolean().optional().describe("Mark this root entry as no longer valid (root entries only). " +
|
|
209
214
|
"Requires [✓ID] correction reference in content (e.g. 'Wrong — see [✓E0076]')."),
|
|
210
|
-
favorite: z.boolean().optional().describe("Set or clear the [♥] favorite flag on
|
|
211
|
-
"
|
|
215
|
+
favorite: z.boolean().optional().describe("Set or clear the [♥] favorite flag. Works on root entries and sub-nodes. " +
|
|
216
|
+
"Root favorites are always shown with L2 detail in bulk reads."),
|
|
217
|
+
irrelevant: z.boolean().optional().describe("Mark this root entry as irrelevant [-] (root entries only). " +
|
|
218
|
+
"No correction entry needed (unlike obsolete). Irrelevant entries are hidden from bulk reads."),
|
|
212
219
|
store: z.enum(["personal", "company"]).default("personal").describe("Target store: 'personal' or 'company'"),
|
|
213
|
-
}, async ({ id, content, links, obsolete, favorite, store: storeName }) => {
|
|
220
|
+
}, async ({ id, content, links, obsolete, favorite, irrelevant, store: storeName }) => {
|
|
214
221
|
const templateName = AGENT_ID.replace(/_\d+$/, "");
|
|
215
222
|
const agentRole = (ROLE || "worker");
|
|
216
223
|
if (storeName === "company") {
|
|
@@ -233,9 +240,9 @@ server.tool("update_memory", "Update the text of an existing memory entry or sub
|
|
|
233
240
|
isError: true,
|
|
234
241
|
};
|
|
235
242
|
}
|
|
236
|
-
const ok = hmemStore.updateNode(id, content, links, obsolete, favorite);
|
|
243
|
+
const ok = hmemStore.updateNode(id, content, links, obsolete, favorite, undefined, irrelevant);
|
|
237
244
|
const storeLabel = storeName === "company" ? "company" : (templateName || "memory");
|
|
238
|
-
log(`update_memory [${storeLabel}]: ${id} → ${ok ? "updated" : "not found"}${obsolete ? " (marked obsolete)" : ""}${favorite !== undefined ? ` (favorite=${favorite})` : ""}`);
|
|
245
|
+
log(`update_memory [${storeLabel}]: ${id} → ${ok ? "updated" : "not found"}${obsolete ? " (marked obsolete)" : ""}${irrelevant ? " (marked irrelevant)" : ""}${favorite !== undefined ? ` (favorite=${favorite})` : ""}`);
|
|
239
246
|
if (!ok) {
|
|
240
247
|
return {
|
|
241
248
|
content: [{ type: "text", text: `ERROR: Entry "${id}" not found in ${storeLabel}.` }],
|
|
@@ -247,6 +254,10 @@ server.tool("update_memory", "Update the text of an existing memory entry or sub
|
|
|
247
254
|
parts.push("links updated");
|
|
248
255
|
if (obsolete === true)
|
|
249
256
|
parts.push("marked as [!] obsolete");
|
|
257
|
+
if (irrelevant === true)
|
|
258
|
+
parts.push("marked as [-] irrelevant");
|
|
259
|
+
if (irrelevant === false)
|
|
260
|
+
parts.push("irrelevant flag cleared");
|
|
250
261
|
if (favorite === true)
|
|
251
262
|
parts.push("marked as [♥] favorite");
|
|
252
263
|
if (favorite === false)
|
|
@@ -338,7 +349,8 @@ server.tool("read_memory", "Read from your hierarchical long-term memory (.hmem)
|
|
|
338
349
|
"- By prefix: read_memory({ prefix: 'L' }) → All Lessons Learned (Level 1)\n" +
|
|
339
350
|
"- By time: read_memory({ after: '2026-02-15', before: '2026-02-17' })\n" +
|
|
340
351
|
"- Search: read_memory({ search: 'SSE' }) → Full-text search across all levels\n" +
|
|
341
|
-
"- Time-around: read_memory({ time_around: 'P0001' }) → entries near P0001's timestamp\n
|
|
352
|
+
"- Time-around: read_memory({ time_around: 'P0001' }) → entries near P0001's timestamp\n" +
|
|
353
|
+
"- Title listing: read_memory({ titles_only: true }) → compact table of contents (ID + date + title)\n\n" +
|
|
342
354
|
"Lazy loading: ID queries always return the node + its DIRECT children only.\n" +
|
|
343
355
|
"To go deeper, call read_memory(id=child_id). depth parameter is ignored for ID queries.\n\n" +
|
|
344
356
|
"Store types:\n" +
|
|
@@ -354,9 +366,20 @@ server.tool("read_memory", "Read from your hierarchical long-term memory (.hmem)
|
|
|
354
366
|
period: z.string().optional().describe("Time window: '+4h' (after), '-2h' (before), '4h' (±4h symmetric), 'both' (±2h default)"),
|
|
355
367
|
time_around: z.string().optional().describe("Reference entry ID — find entries created around the same time"),
|
|
356
368
|
show_obsolete: z.boolean().optional().describe("Include all obsolete entries (default: only top 3 most-accessed)"),
|
|
369
|
+
show_obsolete_path: z.boolean().optional().describe("When reading an obsolete entry by ID, show the full correction chain instead of just the final valid entry."),
|
|
370
|
+
titles_only: z.boolean().optional().describe("Compact title listing — shows all entries as ID + date + title, without V2 selection or children. " +
|
|
371
|
+
"Like a table of contents. Combine with prefix to filter by category."),
|
|
372
|
+
expand: z.boolean().optional().describe("Expand full tree with complete node content (ID queries only). " +
|
|
373
|
+
"Use to deep-dive into a project after a long break. " +
|
|
374
|
+
"depth controls how deep (default: 5 = full tree). " +
|
|
375
|
+
"Example: read_memory({ id: 'P0001', expand: true, depth: 3 })"),
|
|
376
|
+
mode: z.enum(["discover", "essentials"]).optional().describe("Bulk read mode. 'discover' (default for first read): newest-heavy — good for getting an overview. " +
|
|
377
|
+
"'essentials': importance-heavy (more favorites + most-accessed, fewer newest) — " +
|
|
378
|
+
"use after context compression to recover key knowledge. " +
|
|
379
|
+
"Auto-selected if omitted: first bulk read → discover, subsequent → essentials."),
|
|
357
380
|
store: z.enum(["personal", "company"]).default("personal").describe("Source store: 'personal' or 'company'"),
|
|
358
381
|
curator: z.boolean().optional().describe("Set true to show full metadata (access counts, roles, dates). For curators only."),
|
|
359
|
-
}, async ({ id, depth, prefix, after, before, search, limit: maxResults, time, period, time_around, show_obsolete, store: storeName, curator }) => {
|
|
382
|
+
}, async ({ id, depth, prefix, after, before, search, limit: maxResults, time, period, time_around, show_obsolete, show_obsolete_path, titles_only, expand, mode, store: storeName, curator }) => {
|
|
360
383
|
if (AGENT_ID === "UNKNOWN") {
|
|
361
384
|
return {
|
|
362
385
|
content: [{ type: "text", text: "ERROR: Agent-ID unknown. read_memory is only available for spawned agents." }],
|
|
@@ -374,12 +397,27 @@ server.tool("read_memory", "Read from your hierarchical long-term memory (.hmem)
|
|
|
374
397
|
? "⚠ WARNING: Memory database is corrupted! Reads may be incomplete. A backup (.corrupt) was saved.\n\n"
|
|
375
398
|
: "";
|
|
376
399
|
const effectiveDepth = depth || (id ? 2 : 1);
|
|
400
|
+
// Session cache: apply sliding window for bulk reads (personal store only)
|
|
401
|
+
const isBulkListing = !id && !search && !time_around;
|
|
402
|
+
const useCache = isBulkListing && storeName === "personal";
|
|
403
|
+
const suppressedIds = useCache ? sessionCache.getSuppressedIds() : undefined;
|
|
404
|
+
const maxNewNewest = useCache ? sessionCache.getNewestSlotCount() : undefined;
|
|
405
|
+
const maxNewAccess = useCache ? sessionCache.getAccessSlotCount() : undefined;
|
|
406
|
+
// Auto-select mode: first bulk read → discover, subsequent → essentials
|
|
407
|
+
const effectiveMode = mode ?? (useCache && sessionCache.readCount > 0 ? "essentials" : "discover");
|
|
377
408
|
const entries = hmemStore.read({
|
|
378
409
|
id, depth: effectiveDepth, prefix, after, before, search,
|
|
379
410
|
limit: maxResults,
|
|
380
411
|
agentRole: storeName === "company" ? agentRole : undefined,
|
|
381
412
|
time, period, timeAround: time_around,
|
|
382
413
|
showObsolete: show_obsolete,
|
|
414
|
+
showObsoletePath: show_obsolete_path,
|
|
415
|
+
titlesOnly: titles_only,
|
|
416
|
+
expand,
|
|
417
|
+
suppressedIds,
|
|
418
|
+
maxNewNewest,
|
|
419
|
+
maxNewAccess,
|
|
420
|
+
mode: isBulkListing ? effectiveMode : undefined,
|
|
383
421
|
});
|
|
384
422
|
if (entries.length === 0) {
|
|
385
423
|
const hint = id ? `No memory with ID "${id}".` :
|
|
@@ -388,17 +426,30 @@ server.tool("read_memory", "Read from your hierarchical long-term memory (.hmem)
|
|
|
388
426
|
"No memories found for this query.";
|
|
389
427
|
return { content: [{ type: "text", text: hint }] };
|
|
390
428
|
}
|
|
429
|
+
// Update session cache after bulk read
|
|
430
|
+
if (useCache) {
|
|
431
|
+
const allIds = entries.filter(e => !e.obsolete).map(e => e.id);
|
|
432
|
+
const promotedIds = new Set(entries.filter(e => e.promoted === "favorite" || e.promoted === "access").map(e => e.id));
|
|
433
|
+
sessionCache.registerDelivered(allIds, promotedIds);
|
|
434
|
+
}
|
|
391
435
|
// Format output
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
436
|
+
const output = titles_only
|
|
437
|
+
? formatTitlesOnly(entries, hmemConfig)
|
|
438
|
+
: isBulkListing
|
|
439
|
+
? formatGroupedOutput(hmemStore, entries, curator ?? false, hmemConfig)
|
|
440
|
+
: formatFlatOutput(entries, curator ?? false, expand ?? false);
|
|
396
441
|
const stats = hmemStore.stats();
|
|
397
442
|
const storeLabel = storeName === "company" ? "company" : templateName;
|
|
398
443
|
const visibleCount = entries.length;
|
|
444
|
+
// Cache status in header (when active)
|
|
445
|
+
const cacheInfo = useCache && sessionCache.size > 0
|
|
446
|
+
? ` | Cache: ${sessionCache.size} seen`
|
|
447
|
+
: "";
|
|
448
|
+
// Mode info in header (only for bulk reads)
|
|
449
|
+
const modeInfo = isBulkListing ? ` | Mode: ${effectiveMode}` : "";
|
|
399
450
|
const header = `## Memory: ${storeLabel} (${stats.total} total entries)\n` +
|
|
400
|
-
`Query: ${id ? `id=${id}` : ""}${prefix ? `prefix=${prefix}` : ""}${search ? `search="${search}"` : ""}${time_around ? `time_around=${time_around}` : ""}${after ? ` after=${after}` : ""}${before ? ` before=${before}` : ""}${time ? ` time=${time}` : ""} | Depth: ${effectiveDepth} | Results: ${visibleCount}\n`;
|
|
401
|
-
log(`read_memory [${storeLabel}]: ${visibleCount} results (depth=${effectiveDepth}, role=${agentRole})`);
|
|
451
|
+
`Query: ${id ? `id=${id}` : ""}${prefix ? `prefix=${prefix}` : ""}${search ? `search="${search}"` : ""}${time_around ? `time_around=${time_around}` : ""}${after ? ` after=${after}` : ""}${before ? ` before=${before}` : ""}${time ? ` time=${time}` : ""} | Depth: ${effectiveDepth} | Results: ${visibleCount}${modeInfo}${cacheInfo}\n`;
|
|
452
|
+
log(`read_memory [${storeLabel}]: ${visibleCount} results (depth=${effectiveDepth}, role=${agentRole}${cacheInfo})`);
|
|
402
453
|
return {
|
|
403
454
|
content: [{
|
|
404
455
|
type: "text",
|
|
@@ -418,6 +469,24 @@ server.tool("read_memory", "Read from your hierarchical long-term memory (.hmem)
|
|
|
418
469
|
}
|
|
419
470
|
});
|
|
420
471
|
// bump_memory removed — access_count is auto-incremented on reads, favorites cover explicit importance
|
|
472
|
+
// ---- Session Cache Reset ----
|
|
473
|
+
server.tool("reset_memory_cache", "Clear the session cache so all entries are treated as unseen again. " +
|
|
474
|
+
"The next bulk read will behave like the first read of a fresh session " +
|
|
475
|
+
"(full Fibonacci slots, no suppressed entries).\n\n" +
|
|
476
|
+
"Use when you need a clean slate — e.g., after a major topic change " +
|
|
477
|
+
"or when you suspect important entries were suppressed.", {}, async () => {
|
|
478
|
+
const before = sessionCache.size;
|
|
479
|
+
const readsBefore = sessionCache.readCount;
|
|
480
|
+
sessionCache.reset();
|
|
481
|
+
return {
|
|
482
|
+
content: [{
|
|
483
|
+
type: "text",
|
|
484
|
+
text: `Session cache reset. Cleared ${before} tracked entries, ` +
|
|
485
|
+
`bulk read counter ${readsBefore} → 0. ` +
|
|
486
|
+
`Next read_memory() will return the full first-read selection.`,
|
|
487
|
+
}],
|
|
488
|
+
};
|
|
489
|
+
});
|
|
421
490
|
// ---- Curator Tools (ceo role only) ----
|
|
422
491
|
const AUDIT_STATE_FILE = process.env.HMEM_AUDIT_STATE_PATH
|
|
423
492
|
|| path.join(PROJECT_DIR, "audit_state.json");
|
|
@@ -527,16 +596,22 @@ server.tool("read_agent_memory", "CURATOR ONLY (ceo role). Read the full memory
|
|
|
527
596
|
const date = e.created_at.substring(0, 10);
|
|
528
597
|
const role = e.min_role !== "worker" ? ` [${e.min_role}+]` : "";
|
|
529
598
|
const access = e.access_count > 0 ? ` (${e.access_count}x)` : "";
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
599
|
+
const obsoleteTag = e.obsolete ? " [⚠ OBSOLETE]" : "";
|
|
600
|
+
const irrelevantTag = e.irrelevant ? " [- IRRELEVANT]" : "";
|
|
601
|
+
const favTag = e.favorite ? " [♥]" : "";
|
|
602
|
+
lines.push(`[${e.id}] ${date}${role}${favTag}${obsoleteTag}${irrelevantTag}${access}`);
|
|
603
|
+
lines.push(` ${e.title}`);
|
|
604
|
+
if (e.level_1 !== e.title)
|
|
605
|
+
lines.push(` ${e.level_1}`);
|
|
606
|
+
if (e.children && e.children.length > 0) {
|
|
607
|
+
for (const child of e.children) {
|
|
608
|
+
const indent = " ".repeat(child.depth - 1);
|
|
609
|
+
const hint = (child.child_count ?? 0) > 0
|
|
610
|
+
? ` (${child.child_count} — use id="${child.id}" to expand)`
|
|
611
|
+
: "";
|
|
612
|
+
lines.push(`${indent}[${child.id}] ${child.title}${hint}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
540
615
|
if (e.links?.length)
|
|
541
616
|
lines.push(` Links: ${e.links.join(", ")}`);
|
|
542
617
|
lines.push("");
|
|
@@ -550,7 +625,7 @@ server.tool("read_agent_memory", "CURATOR ONLY (ceo role). Read the full memory
|
|
|
550
625
|
});
|
|
551
626
|
server.tool("fix_agent_memory", "CURATOR ONLY (ceo role). Correct a specific entry or node in any agent's memory.\n\n" +
|
|
552
627
|
"Accepts both root IDs ('L0003') and compound node IDs ('L0003.2'):\n" +
|
|
553
|
-
"- Root ID: updates L1 summary text, min_role clearance,
|
|
628
|
+
"- Root ID: updates L1 summary text, min_role clearance, obsolete/irrelevant/favorite flags\n" +
|
|
554
629
|
"- Compound node ID: updates the content of that specific node\n\n" +
|
|
555
630
|
"To fix wrong prefix: delete + re-add (prefix cannot be changed in-place).\n" +
|
|
556
631
|
"To consolidate fragmented P entries: use read_agent_memory to read them, " +
|
|
@@ -564,7 +639,8 @@ server.tool("fix_agent_memory", "CURATOR ONLY (ceo role). Correct a specific ent
|
|
|
564
639
|
obsolete: z.boolean().optional().describe("Mark or unmark as obsolete (root entries only). " +
|
|
565
640
|
"Obsolete entries stay in memory but are shown with [⚠ OBSOLETE]."),
|
|
566
641
|
favorite: z.boolean().optional().describe("Set or clear the [♥] favorite flag (root entries only)."),
|
|
567
|
-
|
|
642
|
+
irrelevant: z.boolean().optional().describe("Mark or unmark as irrelevant (root entries only). Irrelevant entries are hidden from bulk reads. No correction entry needed."),
|
|
643
|
+
}, async ({ agent_name, entry_id, content, min_role, obsolete, favorite, irrelevant }) => {
|
|
568
644
|
if (!isCurator()) {
|
|
569
645
|
return {
|
|
570
646
|
content: [{ type: "text", text: "ERROR: fix_agent_memory is only available to the ceo/curator role." }],
|
|
@@ -597,19 +673,21 @@ server.tool("fix_agent_memory", "CURATOR ONLY (ceo role). Correct a specific ent
|
|
|
597
673
|
}
|
|
598
674
|
else {
|
|
599
675
|
// Root entry — update memories table
|
|
600
|
-
if (!content && min_role === undefined && obsolete === undefined && favorite === undefined) {
|
|
676
|
+
if (!content && min_role === undefined && obsolete === undefined && favorite === undefined && irrelevant === undefined) {
|
|
601
677
|
return {
|
|
602
|
-
content: [{ type: "text", text: "ERROR: Provide at least one of: content, min_role, obsolete, favorite." }],
|
|
678
|
+
content: [{ type: "text", text: "ERROR: Provide at least one of: content, min_role, obsolete, favorite, irrelevant." }],
|
|
603
679
|
isError: true,
|
|
604
680
|
};
|
|
605
681
|
}
|
|
606
682
|
if (content) {
|
|
607
|
-
ok = store.updateNode(entry_id, content, undefined, obsolete, favorite, true /* curatorBypass
|
|
683
|
+
ok = store.updateNode(entry_id, content, undefined, obsolete, favorite, true /* curatorBypass */, irrelevant);
|
|
608
684
|
changed.push("L1");
|
|
609
685
|
if (obsolete !== undefined)
|
|
610
686
|
changed.push("obsolete");
|
|
611
687
|
if (favorite !== undefined)
|
|
612
688
|
changed.push("favorite");
|
|
689
|
+
if (irrelevant !== undefined)
|
|
690
|
+
changed.push("irrelevant");
|
|
613
691
|
}
|
|
614
692
|
else {
|
|
615
693
|
const fields = {};
|
|
@@ -619,6 +697,8 @@ server.tool("fix_agent_memory", "CURATOR ONLY (ceo role). Correct a specific ent
|
|
|
619
697
|
fields.obsolete = obsolete;
|
|
620
698
|
if (favorite !== undefined)
|
|
621
699
|
fields.favorite = favorite;
|
|
700
|
+
if (irrelevant !== undefined)
|
|
701
|
+
fields.irrelevant = irrelevant;
|
|
622
702
|
ok = store.update(entry_id, fields);
|
|
623
703
|
}
|
|
624
704
|
if (min_role !== undefined)
|
|
@@ -627,6 +707,8 @@ server.tool("fix_agent_memory", "CURATOR ONLY (ceo role). Correct a specific ent
|
|
|
627
707
|
changed.push("obsolete");
|
|
628
708
|
if (!content && favorite !== undefined)
|
|
629
709
|
changed.push("favorite");
|
|
710
|
+
if (!content && irrelevant !== undefined)
|
|
711
|
+
changed.push("irrelevant");
|
|
630
712
|
}
|
|
631
713
|
log(`fix_agent_memory [CURATOR]: ${agent_name} ${entry_id} → ${ok ? "updated" : "not found"} (${changed.join(", ")})`);
|
|
632
714
|
return {
|
|
@@ -752,6 +834,55 @@ server.tool("mark_audited", "CURATOR ONLY (ceo role). Mark an agent as audited (
|
|
|
752
834
|
* Format bulk-read output grouped by prefix with header entries.
|
|
753
835
|
* Non-curator: strips [♥], [★] markers, shortens [OBSOLETE] to [!].
|
|
754
836
|
*/
|
|
837
|
+
/**
|
|
838
|
+
* Format compact title listing — ID + date + title, grouped by prefix.
|
|
839
|
+
* V2 selection applies. Favorites/top-accessed show L2 children titles.
|
|
840
|
+
* Non-expanded entries show (N) child count indicator.
|
|
841
|
+
*/
|
|
842
|
+
function formatTitlesOnly(entries, config) {
|
|
843
|
+
const CHILD_TITLE_LEN = 50;
|
|
844
|
+
const lines = [];
|
|
845
|
+
const byPrefix = new Map();
|
|
846
|
+
for (const e of entries) {
|
|
847
|
+
const arr = byPrefix.get(e.prefix);
|
|
848
|
+
if (arr)
|
|
849
|
+
arr.push(e);
|
|
850
|
+
else
|
|
851
|
+
byPrefix.set(e.prefix, [e]);
|
|
852
|
+
}
|
|
853
|
+
for (const [prefix, prefixEntries] of byPrefix) {
|
|
854
|
+
const desc = config.prefixDescriptions[prefix] ?? config.prefixes[prefix] ?? prefix;
|
|
855
|
+
lines.push(`## ${desc} (${prefixEntries.length} total)\n`);
|
|
856
|
+
for (const e of prefixEntries) {
|
|
857
|
+
const mmdd = e.created_at.substring(5, 10);
|
|
858
|
+
const fav = e.favorite ? " [♥]" : "";
|
|
859
|
+
const obs = e.obsolete ? " [!]" : "";
|
|
860
|
+
const irr = e.irrelevant ? " [-]" : "";
|
|
861
|
+
if (e.expanded && e.children && e.children.length > 0) {
|
|
862
|
+
// Expanded entry (favorite/top-accessed): show with L2 children
|
|
863
|
+
lines.push(`${e.id} ${mmdd}${fav}${obs} ${e.title}`);
|
|
864
|
+
for (const child of e.children) {
|
|
865
|
+
const short = child.title || (child.content.length > CHILD_TITLE_LEN
|
|
866
|
+
? child.content.substring(0, CHILD_TITLE_LEN)
|
|
867
|
+
: child.content);
|
|
868
|
+
const grandchildren = (child.child_count ?? 0) > 0 ? ` (${child.child_count})` : "";
|
|
869
|
+
const cfav = child.favorite ? " [♥]" : "";
|
|
870
|
+
lines.push(` ${child.id}${cfav} ${short}${grandchildren}`);
|
|
871
|
+
}
|
|
872
|
+
if (e.hiddenChildrenCount && e.hiddenChildrenCount > 0) {
|
|
873
|
+
lines.push(` [+${e.hiddenChildrenCount} more → ${e.id}]`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
// Non-expanded: compact line with child count
|
|
878
|
+
const childHint = (e.hiddenChildrenCount ?? 0) > 0 ? ` (${e.hiddenChildrenCount})` : "";
|
|
879
|
+
lines.push(`${e.id} ${mmdd}${fav}${obs} ${e.title}${childHint}`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
lines.push("");
|
|
883
|
+
}
|
|
884
|
+
return lines.join("\n");
|
|
885
|
+
}
|
|
755
886
|
function formatGroupedOutput(store, entries, curator, config) {
|
|
756
887
|
const lines = [];
|
|
757
888
|
const headers = store.getHeaders();
|
|
@@ -787,122 +918,199 @@ function formatGroupedOutput(store, entries, curator, config) {
|
|
|
787
918
|
}
|
|
788
919
|
return lines.join("\n");
|
|
789
920
|
}
|
|
790
|
-
function formatFlatOutput(entries, curator) {
|
|
921
|
+
function formatFlatOutput(entries, curator, expand = false) {
|
|
791
922
|
const lines = [];
|
|
923
|
+
// Obsolete chain resolution note
|
|
924
|
+
if (entries.length > 0 && entries[0].obsoleteChain && entries[0].obsoleteChain.length > 1) {
|
|
925
|
+
const chain = entries[0].obsoleteChain;
|
|
926
|
+
if (entries.length === 1) {
|
|
927
|
+
const chainStr = chain.slice(0, -1).map(id => `${id} [!]`).join(" → ") + ` → ${chain[chain.length - 1]} ✓`;
|
|
928
|
+
lines.push(`[Resolved: ${chainStr}]\n`);
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
lines.push(`[Chain: ${chain.join(" → ")}]\n`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
792
934
|
for (const e of entries) {
|
|
793
|
-
renderEntryFormatted(lines, e, curator);
|
|
935
|
+
renderEntryFormatted(lines, e, curator, expand);
|
|
794
936
|
}
|
|
795
937
|
return lines.join("\n");
|
|
796
938
|
}
|
|
797
|
-
|
|
939
|
+
/** Favorite marker for child nodes. */
|
|
940
|
+
function nodeFav(node) {
|
|
941
|
+
return node.favorite ? " [♥]" : "";
|
|
942
|
+
}
|
|
943
|
+
function renderEntryFormatted(lines, e, curator, expand = false) {
|
|
798
944
|
const isNode = e.id.includes(".");
|
|
945
|
+
const hasDetail = !!(e.children?.length || e.linkedEntries?.length);
|
|
946
|
+
// Headline: use title for navigation, show full content below when drilling in
|
|
799
947
|
if (isNode) {
|
|
800
948
|
if (curator) {
|
|
801
|
-
|
|
802
|
-
lines.push(`[${e.id}] L${nodeDepth}: ${e.level_1}`);
|
|
949
|
+
lines.push(`[${e.id}] ${e.title}`);
|
|
803
950
|
}
|
|
804
951
|
else {
|
|
805
|
-
lines.push(`${e.id} ${e.
|
|
952
|
+
lines.push(`${e.id} ${e.title}`);
|
|
953
|
+
}
|
|
954
|
+
// Node drilldown: show full content below title
|
|
955
|
+
if (hasDetail && e.level_1 !== e.title) {
|
|
956
|
+
lines.push(` ${e.level_1}`);
|
|
806
957
|
}
|
|
807
958
|
}
|
|
808
959
|
else {
|
|
809
960
|
if (curator) {
|
|
810
961
|
const promotedTag = e.promoted === "favorite" ? " [♥]" : e.promoted === "access" ? " [★]" : "";
|
|
811
962
|
const obsoleteTag = e.obsolete ? " [⚠ OBSOLETE]" : "";
|
|
963
|
+
const irrelevantTag = e.irrelevant ? " [- IRRELEVANT]" : "";
|
|
812
964
|
const date = e.created_at.substring(0, 10);
|
|
813
965
|
const accessed = e.access_count > 0 ? ` (${e.access_count}x accessed)` : "";
|
|
814
966
|
const roleTag = e.min_role !== "worker" ? ` [${e.min_role}+]` : "";
|
|
815
|
-
lines.push(`[${e.id}] ${date}${roleTag}${promotedTag}${obsoleteTag}${accessed}`);
|
|
816
|
-
lines.push(`
|
|
967
|
+
lines.push(`[${e.id}] ${date}${roleTag}${promotedTag}${obsoleteTag}${irrelevantTag}${accessed}`);
|
|
968
|
+
lines.push(` ${e.title}`);
|
|
817
969
|
}
|
|
818
970
|
else {
|
|
819
|
-
// Non-curator: [♥] favorites, [★] promoted, [!] obsolete
|
|
820
971
|
const promotedTag = e.promoted === "favorite" ? " [♥]" : e.promoted === "access" ? " [★]" : "";
|
|
821
972
|
const obsoleteTag = e.obsolete ? " [!]" : "";
|
|
973
|
+
const irrelevantTag = e.irrelevant ? " [-]" : "";
|
|
822
974
|
const mmdd = e.created_at.substring(5, 10);
|
|
823
|
-
lines.push(`${e.id} ${mmdd}${promotedTag}${obsoleteTag} ${e.
|
|
975
|
+
lines.push(`${e.id} ${mmdd}${promotedTag}${obsoleteTag}${irrelevantTag} ${e.title}`);
|
|
976
|
+
}
|
|
977
|
+
// Show full level_1 content below title when entry is expanded/drilled
|
|
978
|
+
if (hasDetail && e.level_1 !== e.title) {
|
|
979
|
+
lines.push(` ${e.level_1}`);
|
|
824
980
|
}
|
|
825
981
|
}
|
|
982
|
+
// Children
|
|
826
983
|
if (e.children && e.children.length > 0) {
|
|
827
|
-
if (
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
984
|
+
if (expand) {
|
|
985
|
+
// Expand mode: full content + recursive children
|
|
986
|
+
renderChildrenExpanded(lines, e.children, curator);
|
|
987
|
+
}
|
|
988
|
+
else if (e.expanded && !expand) {
|
|
831
989
|
renderChildrenFormatted(lines, e.children, curator);
|
|
990
|
+
if (e.hiddenChildrenCount && e.hiddenChildrenCount > 0) {
|
|
991
|
+
lines.push(` [+${e.hiddenChildrenCount} more → ${e.id}]`);
|
|
992
|
+
}
|
|
832
993
|
}
|
|
833
994
|
else if (e.hiddenChildrenCount !== undefined) {
|
|
995
|
+
// Non-expanded bulk read: show only the latest child title
|
|
834
996
|
const child = e.children[0];
|
|
997
|
+
const fav = nodeFav(child);
|
|
998
|
+
const hint = (child.child_count ?? 0) > 0
|
|
999
|
+
? ` [+${child.child_count} → ${child.id}]`
|
|
1000
|
+
: "";
|
|
835
1001
|
if (curator) {
|
|
836
|
-
|
|
837
|
-
? ` (${child.child_count} — use id="${child.id}" to expand)`
|
|
838
|
-
: "";
|
|
839
|
-
lines.push(` [${child.id}] L${child.depth}: ${child.content}${hint}`);
|
|
1002
|
+
lines.push(` [${child.id}]${fav} ${child.title}${hint}`);
|
|
840
1003
|
}
|
|
841
1004
|
else {
|
|
842
|
-
|
|
843
|
-
? ` [+${child.child_count} → ${child.id}]`
|
|
844
|
-
: "";
|
|
845
|
-
lines.push(` ${child.depth}.${child.seq} ${child.content}${hint}`);
|
|
846
|
-
}
|
|
847
|
-
if (child.children && child.children.length > 0) {
|
|
848
|
-
renderChildrenFormatted(lines, child.children, curator);
|
|
1005
|
+
lines.push(` ${child.id}${fav} ${child.title}${hint}`);
|
|
849
1006
|
}
|
|
850
1007
|
if (e.hiddenChildrenCount > 0) {
|
|
851
1008
|
lines.push(` [+${e.hiddenChildrenCount} more → ${e.id}]`);
|
|
852
1009
|
}
|
|
853
1010
|
}
|
|
854
1011
|
else {
|
|
855
|
-
|
|
856
|
-
lines.push(` ${e.children.length} ${e.children.length === 1 ? "child" : "children"}:`);
|
|
857
|
-
}
|
|
1012
|
+
// ID-based read: show all direct children as titles
|
|
858
1013
|
renderChildrenFormatted(lines, e.children, curator);
|
|
859
1014
|
}
|
|
860
1015
|
}
|
|
861
|
-
|
|
862
|
-
|
|
1016
|
+
// Links
|
|
1017
|
+
if (e.links && e.links.length > 0) {
|
|
1018
|
+
const parts = [`Links: ${e.links.join(", ")}`];
|
|
1019
|
+
const hiddenParts = [];
|
|
1020
|
+
if (e.hiddenObsoleteLinks && e.hiddenObsoleteLinks > 0)
|
|
1021
|
+
hiddenParts.push(`${e.hiddenObsoleteLinks} obsolete`);
|
|
1022
|
+
if (e.hiddenIrrelevantLinks && e.hiddenIrrelevantLinks > 0)
|
|
1023
|
+
hiddenParts.push(`${e.hiddenIrrelevantLinks} irrelevant`);
|
|
1024
|
+
if (hiddenParts.length > 0)
|
|
1025
|
+
parts.push(`(+${hiddenParts.join(", ")} hidden)`);
|
|
1026
|
+
lines.push(` ${parts.join(" ")}`);
|
|
1027
|
+
}
|
|
1028
|
+
// Auto-resolved linked entries
|
|
863
1029
|
if (e.linkedEntries && e.linkedEntries.length > 0) {
|
|
864
1030
|
lines.push(` --- Linked entries ---`);
|
|
865
1031
|
for (const linked of e.linkedEntries) {
|
|
866
1032
|
const isLinkedNode = linked.id.includes(".");
|
|
867
1033
|
if (isLinkedNode) {
|
|
868
|
-
|
|
869
|
-
lines.push(` [${linked.id}] L${d}: ${linked.level_1}`);
|
|
1034
|
+
lines.push(` [${linked.id}] ${linked.title}`);
|
|
870
1035
|
}
|
|
871
1036
|
else {
|
|
872
1037
|
const ldate = linked.created_at.substring(0, 10);
|
|
873
1038
|
lines.push(` [${linked.id}] ${ldate}`);
|
|
874
|
-
lines.push(`
|
|
1039
|
+
lines.push(` ${linked.title}`);
|
|
875
1040
|
}
|
|
1041
|
+
// Linked children as titles
|
|
876
1042
|
if (linked.children && linked.children.length > 0) {
|
|
877
1043
|
for (const lchild of linked.children) {
|
|
878
|
-
const cd = (lchild.id.match(/\./g) || []).length + 1;
|
|
879
1044
|
const hint = (lchild.child_count ?? 0) > 0
|
|
880
1045
|
? ` (${lchild.child_count} ${lchild.child_count === 1 ? "child" : "children"} — use id="${lchild.id}" to expand)`
|
|
881
1046
|
: "";
|
|
882
|
-
lines.push(` [${lchild.id}]
|
|
1047
|
+
lines.push(` [${lchild.id}]${nodeFav(lchild)} ${lchild.title}${hint}`);
|
|
883
1048
|
}
|
|
884
1049
|
}
|
|
885
1050
|
}
|
|
886
1051
|
}
|
|
887
1052
|
lines.push("");
|
|
888
1053
|
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Render a list of child nodes — shows titles for navigation.
|
|
1056
|
+
* Use read_memory(id=child.id) to see full content.
|
|
1057
|
+
*/
|
|
889
1058
|
function renderChildrenFormatted(lines, children, curator) {
|
|
890
1059
|
for (const child of children) {
|
|
891
1060
|
const indent = " ".repeat(child.depth - 1);
|
|
1061
|
+
const fav = nodeFav(child);
|
|
1062
|
+
const hint = (child.child_count ?? 0) > 0
|
|
1063
|
+
? ` [+${child.child_count} → ${child.id}]`
|
|
1064
|
+
: "";
|
|
892
1065
|
if (curator) {
|
|
893
|
-
|
|
894
|
-
? ` (${child.child_count} — use id="${child.id}" to expand)`
|
|
895
|
-
: "";
|
|
896
|
-
lines.push(`${indent}[${child.id}] L${child.depth}: ${child.content}${hint}`);
|
|
1066
|
+
lines.push(`${indent}[${child.id}]${fav} ${child.title}${hint}`);
|
|
897
1067
|
}
|
|
898
1068
|
else {
|
|
899
|
-
|
|
900
|
-
? ` [+${child.child_count} → ${child.id}]`
|
|
901
|
-
: "";
|
|
902
|
-
lines.push(`${indent}${child.depth}.${child.seq} ${child.content}${hint}`);
|
|
1069
|
+
lines.push(`${indent}${child.id}${fav} ${child.title}${hint}`);
|
|
903
1070
|
}
|
|
904
|
-
|
|
905
|
-
|
|
1071
|
+
// Don't recurse into grandchildren — titles only, drill for content
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Render children with full content (expand mode).
|
|
1076
|
+
* Shows complete node text and recurses into grandchildren.
|
|
1077
|
+
* At the depth boundary (children loaded but THEIR children are not),
|
|
1078
|
+
* renders as titles instead of full content.
|
|
1079
|
+
*/
|
|
1080
|
+
function renderChildrenExpanded(lines, children, curator) {
|
|
1081
|
+
for (const child of children) {
|
|
1082
|
+
const indent = " ".repeat(child.depth - 1);
|
|
1083
|
+
const fav = nodeFav(child);
|
|
1084
|
+
const hasLoadedChildren = child.children && child.children.length > 0;
|
|
1085
|
+
const isBoundary = !hasLoadedChildren && (child.child_count ?? 0) > 0;
|
|
1086
|
+
if (hasLoadedChildren) {
|
|
1087
|
+
// Inner node: full content + recurse
|
|
1088
|
+
if (curator) {
|
|
1089
|
+
lines.push(`${indent}[${child.id}]${fav} ${child.content}`);
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
lines.push(`${indent}${child.id}${fav} ${child.content}`);
|
|
1093
|
+
}
|
|
1094
|
+
renderChildrenExpanded(lines, child.children, curator);
|
|
1095
|
+
}
|
|
1096
|
+
else if (isBoundary) {
|
|
1097
|
+
// Boundary: title only + child count hint
|
|
1098
|
+
const hint = ` [+${child.child_count} → ${child.id}]`;
|
|
1099
|
+
if (curator) {
|
|
1100
|
+
lines.push(`${indent}[${child.id}]${fav} ${child.title}${hint}`);
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
lines.push(`${indent}${child.id}${fav} ${child.title}${hint}`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
else {
|
|
1107
|
+
// Leaf node (no children at all): full content
|
|
1108
|
+
if (curator) {
|
|
1109
|
+
lines.push(`${indent}[${child.id}]${fav} ${child.content}`);
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
lines.push(`${indent}${child.id}${fav} ${child.content}`);
|
|
1113
|
+
}
|
|
906
1114
|
}
|
|
907
1115
|
}
|
|
908
1116
|
}
|