@yugabytedb/meko-mcp 1.0.6 → 1.1.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 +189 -36
- package/assets/hooks-handlers/lib/capture.js +177 -78
- package/assets/hooks-handlers/lib/package.json +1 -0
- package/assets/hooks-handlers/test_single_conversation.sh +1 -1
- package/assets/skills/meko-mcp-tools/SKILL.md +167 -53
- package/assets/skills/meko-mcp-tools/references/_sections.md +1 -1
- package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +84 -22
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +89 -56
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +15 -7
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +17 -9
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +94 -5
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +52 -37
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +108 -37
- package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +6 -10
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +30 -18
- package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +55 -69
- package/bin/create-meko-setup.mjs +152 -26
- package/lib/clients/aliases.mjs +30 -0
- package/lib/clients/claude-code.mjs +384 -69
- package/lib/clients/claude-desktop.mjs +43 -8
- package/lib/clients/codex.mjs +977 -0
- package/lib/clients/common.mjs +142 -1
- package/lib/clients/cursor.mjs +73 -5
- package/lib/clients/index.mjs +2 -0
- package/lib/config.mjs +30 -0
- package/lib/detect.mjs +52 -44
- package/lib/installer.mjs +9 -2
- package/lib/mcp-transport.mjs +63 -4
- package/lib/migrate/adapters/memory-md.mjs +13 -6
- package/lib/migrate/datapack.mjs +167 -0
- package/lib/migrate/id.mjs +127 -9
- package/lib/migrate/mcp-client.mjs +1 -1
- package/lib/migrate/orchestrator.mjs +68 -10
- package/lib/migrate.mjs +346 -3
- package/lib/prompt.mjs +50 -76
- package/lib/validate.mjs +314 -10
- package/lib/version-check.mjs +178 -0
- package/package.json +2 -1
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Environment:
|
|
14
14
|
* MEKO_MCP_URL MCP server URL (default: http://localhost:8000/mcp)
|
|
15
|
-
* MEKO_AGENT_ID Agent identifier
|
|
15
|
+
* MEKO_AGENT_ID Agent identifier override; if unset, derived from the
|
|
16
|
+
* session's cwd as `claude_code:<repo-basename>` (or
|
|
17
|
+
* `meko_agent` if cwd is unknown). See deriveAgentId().
|
|
16
18
|
* MEKO_API_KEY API key for Cloud Meko auth (optional, omit for local)
|
|
17
19
|
* MEKO_API_TIMEOUT Request timeout in seconds (default: 10)
|
|
18
20
|
* MEKO_WATERMARK_DIR Watermark directory (default: ~/.claude/meko-capture)
|
|
@@ -26,7 +28,58 @@ const https = require("https");
|
|
|
26
28
|
// --- Config ---
|
|
27
29
|
|
|
28
30
|
const MEKO_MCP_URL = process.env.MEKO_MCP_URL || "http://localhost:8000/mcp";
|
|
29
|
-
|
|
31
|
+
|
|
32
|
+
// Server-side fallback for empty agent_id; the cross-project common bucket.
|
|
33
|
+
// Mirror of COMMON_BUCKET_AGENT_ID in installer/lib/migrate/id.mjs.
|
|
34
|
+
const COMMON_BUCKET_AGENT_ID = "meko_agent";
|
|
35
|
+
const AGENT_ID_MAX_LEN = 64;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Derive an `agent_id` for a Meko write. Mirror of `deriveAgentId` in
|
|
39
|
+
* installer/lib/migrate/id.mjs — kept inline because capture.js is published
|
|
40
|
+
* in a separate package with no module dependencies on the installer.
|
|
41
|
+
*
|
|
42
|
+
* - `envOverride` (typically MEKO_AGENT_ID) wins if non-empty.
|
|
43
|
+
* - Coding clients (claude_code, cursor) → `<client>:<repo-basename>`,
|
|
44
|
+
* with the basename lowercased and non-`[a-z0-9-]` runs collapsed to `-`.
|
|
45
|
+
* Outside a repo → bare client name (keeps coding-agent traffic out of
|
|
46
|
+
* the common bucket).
|
|
47
|
+
* - Loose clients (claude-desktop) → `claude_desktop`.
|
|
48
|
+
* - Anything else → `meko_agent` (common cross-project bucket).
|
|
49
|
+
*/
|
|
50
|
+
function deriveAgentId(opts) {
|
|
51
|
+
const override = (opts && opts.envOverride ? String(opts.envOverride) : "").trim();
|
|
52
|
+
if (override) return override;
|
|
53
|
+
const client = (opts && opts.client ? String(opts.client) : "").trim();
|
|
54
|
+
if (client === "claude-desktop" || client === "claude_desktop") return "claude_desktop";
|
|
55
|
+
if (client !== "claude_code" && client !== "cursor") return COMMON_BUCKET_AGENT_ID;
|
|
56
|
+
const rawBase = opts && opts.cwd ? path.basename(opts.cwd) : "";
|
|
57
|
+
if (!rawBase || rawBase === "." || rawBase === "/") return client;
|
|
58
|
+
const project = rawBase.toLowerCase()
|
|
59
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
60
|
+
.replace(/^-+|-+$/g, "")
|
|
61
|
+
.slice(0, AGENT_ID_MAX_LEN);
|
|
62
|
+
if (!project) return client;
|
|
63
|
+
return `${client}:${project}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the agent_id for the current session. Frozen at SessionStart;
|
|
68
|
+
* subsequent hook invocations re-derive from the watermark + env, not
|
|
69
|
+
* from a stale module-level cache.
|
|
70
|
+
*
|
|
71
|
+
* Resolution order:
|
|
72
|
+
* 1. MEKO_AGENT_ID env override (verbatim).
|
|
73
|
+
* 2. cwd from the transcript header (peekTranscriptMetadata).
|
|
74
|
+
* 3. process.cwd() — last resort if no transcript metadata yet.
|
|
75
|
+
*/
|
|
76
|
+
function resolveSessionAgentId(transcriptPath) {
|
|
77
|
+
const envOverride = process.env.MEKO_AGENT_ID;
|
|
78
|
+
if (envOverride && envOverride.trim()) return envOverride.trim();
|
|
79
|
+
const meta = transcriptPath ? peekTranscriptMetadata(transcriptPath) : null;
|
|
80
|
+
const cwd = (meta && meta.cwd) || process.cwd();
|
|
81
|
+
return deriveAgentId({ client: "claude_code", cwd, envOverride });
|
|
82
|
+
}
|
|
30
83
|
const MEKO_API_KEY = process.env.MEKO_API_KEY || "";
|
|
31
84
|
const MEKO_API_TIMEOUT = parseInt(process.env.MEKO_API_TIMEOUT || "10", 10) * 1000;
|
|
32
85
|
const WATERMARK_DIR =
|
|
@@ -257,6 +310,20 @@ function writeWatermark(wmPath, convId, lineNum, agentId) {
|
|
|
257
310
|
);
|
|
258
311
|
}
|
|
259
312
|
|
|
313
|
+
// note: Subagents cannot auto-discover the parent's watermark today.
|
|
314
|
+
// The watermark dir is flat and keyed by session UUID, and a subagent
|
|
315
|
+
// process has no way to learn its parent's session UUID:
|
|
316
|
+
// - no CLAUDE_SESSION_ID / CLAUDE_PARENT_SESSION_ID env var
|
|
317
|
+
// - ~/.claude/session-env/<uuid>/ directories exist but are empty
|
|
318
|
+
// - cwd is shared with the parent but isn't a unique session key
|
|
319
|
+
// (parent may have multiple concurrent sessions in one project;
|
|
320
|
+
// findMostRecentProjectWatermark() keys off transcript_path,
|
|
321
|
+
// which Agent / Task spawn prompts do not expose)
|
|
322
|
+
// Verified on Claude Code 2.1.126 via issue #49 spike (2026-05-01).
|
|
323
|
+
// Enforcement is shifted to the skill: parents prepend an inherited-
|
|
324
|
+
// context block to every Task / Agent spawn prompt. See
|
|
325
|
+
// skills/skills/meko-mcp-tools/SKILL.md "When spawning subagents".
|
|
326
|
+
|
|
260
327
|
// --- MCP JSON-RPC client (Streamable HTTP with session init) ---
|
|
261
328
|
|
|
262
329
|
/**
|
|
@@ -274,7 +341,7 @@ function mcpPost(jsonBody) {
|
|
|
274
341
|
"Content-Length": Buffer.byteLength(body),
|
|
275
342
|
};
|
|
276
343
|
if (MEKO_API_KEY) {
|
|
277
|
-
headers["
|
|
344
|
+
headers["Authorization"] = `Bearer ${MEKO_API_KEY}`;
|
|
278
345
|
}
|
|
279
346
|
if (mcpSessionId) {
|
|
280
347
|
headers["Mcp-Session-Id"] = mcpSessionId;
|
|
@@ -398,43 +465,43 @@ function extractToolResult(response) {
|
|
|
398
465
|
}
|
|
399
466
|
|
|
400
467
|
/**
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* pick the one with the highest `row_count`. Falls back to "agent" when the
|
|
406
|
-
* user has no prior memories. Never returns null — always returns a usable
|
|
407
|
-
* string.
|
|
468
|
+
* Peek the first JSONL line of a transcript to harvest `cwd`, `gitBranch`,
|
|
469
|
+
* and `timestamp`. These are attached as `metadata` on the hook's
|
|
470
|
+
* `conversation_create` so hook-captured traces match the metadata shape
|
|
471
|
+
* the migrator emits (see installer/lib/migrate/orchestrator.mjs).
|
|
408
472
|
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
473
|
+
* Reads a bounded 4KB prefix, not the whole file — on PreCompact / SessionEnd
|
|
474
|
+
* the transcript can be several MB, and we only need the first line's JSON
|
|
475
|
+
* header. If the header happens to exceed 4KB (pathological; a Claude Code
|
|
476
|
+
* header is usually <1KB), JSON.parse will reject the truncated prefix and
|
|
477
|
+
* we fall back to empty metadata via the catch. Same graceful degradation
|
|
478
|
+
* as any other read/parse failure — metadata is best-effort enrichment.
|
|
411
479
|
*/
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (
|
|
434
|
-
|
|
480
|
+
function peekTranscriptMetadata(transcriptPath) {
|
|
481
|
+
const empty = { cwd: null, gitBranch: null, startedAt: null };
|
|
482
|
+
if (!transcriptPath) return empty;
|
|
483
|
+
let fd;
|
|
484
|
+
try {
|
|
485
|
+
fd = fs.openSync(transcriptPath, "r");
|
|
486
|
+
const buffer = Buffer.alloc(4096);
|
|
487
|
+
const bytesRead = fs.readSync(fd, buffer, 0, 4096, 0);
|
|
488
|
+
if (bytesRead === 0) return empty;
|
|
489
|
+
const content = buffer.toString("utf-8", 0, bytesRead);
|
|
490
|
+
const firstNewline = content.indexOf("\n");
|
|
491
|
+
const firstLine = firstNewline === -1 ? content : content.slice(0, firstNewline);
|
|
492
|
+
const obj = JSON.parse(firstLine);
|
|
493
|
+
return {
|
|
494
|
+
cwd: typeof obj.cwd === "string" ? obj.cwd : null,
|
|
495
|
+
gitBranch: typeof obj.gitBranch === "string" ? obj.gitBranch : null,
|
|
496
|
+
startedAt: typeof obj.timestamp === "string" ? obj.timestamp : null,
|
|
497
|
+
};
|
|
498
|
+
} catch {
|
|
499
|
+
return empty;
|
|
500
|
+
} finally {
|
|
501
|
+
if (fd !== undefined) {
|
|
502
|
+
try { fs.closeSync(fd); } catch { /* FD already gone; nothing to do */ }
|
|
435
503
|
}
|
|
436
504
|
}
|
|
437
|
-
return best.rows > 0 ? best.agent : "agent";
|
|
438
505
|
}
|
|
439
506
|
|
|
440
507
|
/**
|
|
@@ -469,23 +536,32 @@ async function fetchRecentMemories(agentId, { limit = 10, budget = 2000 } = {})
|
|
|
469
536
|
return summaries;
|
|
470
537
|
}
|
|
471
538
|
|
|
472
|
-
async function createConversation(sessionId) {
|
|
473
|
-
const
|
|
539
|
+
async function createConversation(sessionId, agentId, metadata = null) {
|
|
540
|
+
const payload = {
|
|
474
541
|
scope: "write",
|
|
475
|
-
agent_id:
|
|
542
|
+
agent_id: agentId,
|
|
476
543
|
title: "Claude Code session (auto-captured)",
|
|
477
544
|
session_id: sessionId,
|
|
478
|
-
}
|
|
545
|
+
};
|
|
546
|
+
if (metadata) {
|
|
547
|
+
payload.metadata = JSON.stringify({
|
|
548
|
+
source: "hook",
|
|
549
|
+
cwd: metadata.cwd ?? null,
|
|
550
|
+
gitBranch: metadata.gitBranch ?? null,
|
|
551
|
+
startedAt: metadata.startedAt ?? null,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
const response = await mcpCall("conversation_create", payload);
|
|
479
555
|
const result = extractToolResult(response);
|
|
480
556
|
return result && result.id ? result.id : null;
|
|
481
557
|
}
|
|
482
558
|
|
|
483
|
-
async function addMessage(convId, exchange) {
|
|
559
|
+
async function addMessage(convId, agentId, exchange) {
|
|
484
560
|
const seed = `${convId}:${exchange.user_uuid}`;
|
|
485
561
|
await mcpCall("conversation_add_message", {
|
|
486
562
|
scope: "write",
|
|
487
563
|
conversation_id: convId,
|
|
488
|
-
agent_id:
|
|
564
|
+
agent_id: agentId,
|
|
489
565
|
input: exchange.input,
|
|
490
566
|
output: exchange.output,
|
|
491
567
|
reasoning: exchange.reasoning,
|
|
@@ -546,7 +622,7 @@ function findMostRecentProjectWatermark(transcriptPath, currentSessionId) {
|
|
|
546
622
|
}
|
|
547
623
|
|
|
548
624
|
function buildSessionStartContext(convId, sessionId, agentId, memories, opts) {
|
|
549
|
-
const resolvedAgentId = agentId ||
|
|
625
|
+
const resolvedAgentId = agentId || COMMON_BUCKET_AGENT_ID;
|
|
550
626
|
const resumed = Boolean(opts && opts.resumed);
|
|
551
627
|
const opening = resumed
|
|
552
628
|
? `Resuming Meko conversation **${convId}** from a prior Claude Code session. The watermark file is written — PreCompact and SessionEnd hooks will append this session's new exchanges to the same conversation.`
|
|
@@ -568,7 +644,7 @@ ${opening}
|
|
|
568
644
|
|
|
569
645
|
### What you MUST do with Meko tools
|
|
570
646
|
|
|
571
|
-
- **agent_id**: use "${resolvedAgentId}"
|
|
647
|
+
- **agent_id**: use "${resolvedAgentId}" verbatim for every Meko MCP tool call in this session. This was derived from the cwd as \`<client>:<repo-basename>\` so memories stay scoped to this project. For genuinely cross-project facts (user identity, global preferences) pass agent_id="${COMMON_BUCKET_AGENT_ID}" — that's the common bucket any agent can read regardless of project.
|
|
572
648
|
- **conversation_id**: Use "${convId}" for all MCP tool calls that accept it
|
|
573
649
|
- **scope**: Only "read", "write", or "admin"
|
|
574
650
|
|
|
@@ -588,7 +664,8 @@ Before any action that deletes, overwrites, or restructures user content (Write-
|
|
|
588
664
|
When Meko MCP tools are available, ALWAYS use them instead of local file-based memory (e.g., writing to ~/.claude/projects/.../memory/). If a Meko tool call fails, fall back to local storage and tell the user.${memoryBlock}`;
|
|
589
665
|
}
|
|
590
666
|
|
|
591
|
-
function buildSessionStartFallbackContext() {
|
|
667
|
+
function buildSessionStartFallbackContext(agentId) {
|
|
668
|
+
const resolvedAgentId = agentId || COMMON_BUCKET_AGENT_ID;
|
|
592
669
|
return `## Meko Memory Instructions
|
|
593
670
|
|
|
594
671
|
The SessionStart hook could not reach the Meko MCP server to create a conversation. Automatic capture may not work for this session.
|
|
@@ -596,7 +673,7 @@ The SessionStart hook could not reach the Meko MCP server to create a conversati
|
|
|
596
673
|
### What you should still do
|
|
597
674
|
|
|
598
675
|
- **Use Meko MCP tools** for memory_add, memory_search, etc. when available
|
|
599
|
-
- **agent_id**:
|
|
676
|
+
- **agent_id**: use "${resolvedAgentId}" for project-scoped writes; use "${COMMON_BUCKET_AGENT_ID}" for cross-project common facts
|
|
600
677
|
- **conversation_id**: Use nil UUID "00000000-0000-0000-0000-000000000000" as placeholder
|
|
601
678
|
- **scope**: Only "read", "write", or "admin"
|
|
602
679
|
|
|
@@ -660,6 +737,7 @@ async function handleSessionStart(hookInput) {
|
|
|
660
737
|
: null;
|
|
661
738
|
|
|
662
739
|
const source = hookInput.source || "";
|
|
740
|
+
const derivedAgentId = resolveSessionAgentId(transcriptPath);
|
|
663
741
|
|
|
664
742
|
// Check if watermark already exists (same session ID re-fires SessionStart, e.g. /clear, /compact)
|
|
665
743
|
if (sessionId) {
|
|
@@ -667,15 +745,20 @@ async function handleSessionStart(hookInput) {
|
|
|
667
745
|
const existing = readWatermark(wmPath);
|
|
668
746
|
if (existing.conversation_id) {
|
|
669
747
|
process.stderr.write(`[meko-capture] Session ${sessionId}: reusing existing conversation ${existing.conversation_id}\n`);
|
|
670
|
-
//
|
|
671
|
-
//
|
|
672
|
-
//
|
|
748
|
+
// Prefer the watermark's cached agent_id so a user-set bucket survives
|
|
749
|
+
// re-fires of SessionStart (e.g. /clear, /compact). The exception is
|
|
750
|
+
// the legacy literal "agent" left by pre-contract installers — we
|
|
751
|
+
// override that with the freshly derived `claude_code:<repo>` so
|
|
752
|
+
// upgraded users stop writing into the ghost namespace.
|
|
753
|
+
const cached = (existing.agent_id || "").trim();
|
|
754
|
+
const resumedAgentId = cached && cached !== "agent" ? cached : derivedAgentId;
|
|
755
|
+
// Skip preload on resume (we don't know what's already in context).
|
|
673
756
|
process.stdout.write(
|
|
674
757
|
hookOutput(
|
|
675
758
|
buildSessionStartContext(
|
|
676
759
|
existing.conversation_id,
|
|
677
760
|
sessionId,
|
|
678
|
-
|
|
761
|
+
resumedAgentId,
|
|
679
762
|
null,
|
|
680
763
|
),
|
|
681
764
|
),
|
|
@@ -683,6 +766,7 @@ async function handleSessionStart(hookInput) {
|
|
|
683
766
|
return;
|
|
684
767
|
}
|
|
685
768
|
}
|
|
769
|
+
const agentId = derivedAgentId;
|
|
686
770
|
|
|
687
771
|
// Resume: Claude Code created a new session ID for a resumed thread. Find the most recent
|
|
688
772
|
// prior watermark for this project and reuse its conversation_id so continuity is preserved.
|
|
@@ -693,14 +777,23 @@ async function handleSessionStart(hookInput) {
|
|
|
693
777
|
if (prior && prior.conversation_id) {
|
|
694
778
|
const currentLineCount = fs.existsSync(transcriptPath) ? countLines(transcriptPath) : 0;
|
|
695
779
|
const wmPath = path.join(WATERMARK_DIR, `${sessionId}.watermark.json`);
|
|
696
|
-
|
|
780
|
+
// Preserve a non-legacy cached agent_id from the prior watermark so a
|
|
781
|
+
// user-set bucket (e.g. MEKO_AGENT_ID=foo, or a project-scoped
|
|
782
|
+
// claude_code:<other-repo> from the original session) survives the
|
|
783
|
+
// resume. Mirrors the same-session reuse branch above. The legacy
|
|
784
|
+
// literal "agent" is overridden with the freshly derived per-project
|
|
785
|
+
// bucket so upgraded users stop writing into the ghost namespace.
|
|
786
|
+
const priorAgentId = (prior.agent_id || "").trim();
|
|
787
|
+
const resumedAgentId =
|
|
788
|
+
priorAgentId && priorAgentId !== "agent" ? priorAgentId : agentId;
|
|
789
|
+
writeWatermark(wmPath, prior.conversation_id, currentLineCount, resumedAgentId);
|
|
697
790
|
process.stderr.write(`[meko-capture] Session ${sessionId}: resuming conversation ${prior.conversation_id} from ${prior.sessionId}, skipping ${currentLineCount} copied lines\n`);
|
|
698
791
|
process.stdout.write(
|
|
699
792
|
hookOutput(
|
|
700
793
|
buildSessionStartContext(
|
|
701
794
|
prior.conversation_id,
|
|
702
795
|
sessionId,
|
|
703
|
-
|
|
796
|
+
resumedAgentId,
|
|
704
797
|
null,
|
|
705
798
|
{ resumed: true },
|
|
706
799
|
),
|
|
@@ -711,55 +804,50 @@ async function handleSessionStart(hookInput) {
|
|
|
711
804
|
process.stderr.write(`[meko-capture] Session ${sessionId}: source=resume but no prior project watermark found; creating new conversation\n`);
|
|
712
805
|
}
|
|
713
806
|
|
|
714
|
-
// Create conversation via MCP
|
|
807
|
+
// Create conversation via MCP. Attach session metadata (cwd / gitBranch /
|
|
808
|
+
// startedAt) from the transcript header so hook traces match the migrator.
|
|
809
|
+
const transcriptMetadata = peekTranscriptMetadata(transcriptPath);
|
|
715
810
|
let convId = null;
|
|
716
811
|
try {
|
|
717
812
|
await mcpInitialize();
|
|
718
|
-
convId = await createConversation(sessionId || "unknown");
|
|
813
|
+
convId = await createConversation(sessionId || "unknown", agentId, transcriptMetadata);
|
|
719
814
|
} catch (err) {
|
|
720
815
|
process.stderr.write(`[meko-capture] SessionStart: MCP unavailable (${err.message}). Falling back to agent-driven setup.\n`);
|
|
721
|
-
process.stdout.write(hookOutput(buildSessionStartFallbackContext()));
|
|
816
|
+
process.stdout.write(hookOutput(buildSessionStartFallbackContext(agentId)));
|
|
722
817
|
return;
|
|
723
818
|
}
|
|
724
819
|
|
|
725
820
|
if (!convId) {
|
|
726
821
|
process.stderr.write("[meko-capture] SessionStart: conversation_create returned no ID.\n");
|
|
727
|
-
process.stdout.write(hookOutput(buildSessionStartFallbackContext()));
|
|
822
|
+
process.stdout.write(hookOutput(buildSessionStartFallbackContext(agentId)));
|
|
728
823
|
return;
|
|
729
824
|
}
|
|
730
825
|
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
//
|
|
826
|
+
// Preload recent memories with a 4s budget (SessionStart timeout is 15s).
|
|
827
|
+
// Best-effort enrichment — on timeout/failure we still deliver the core
|
|
828
|
+
// context. agent_id is derived per-session from the cwd; the old
|
|
829
|
+
// db_summarize_database discovery helper was removed (it depended on a
|
|
830
|
+
// paid-tier tool and only worked "correctly" via its fallback branch
|
|
831
|
+
// anyway — see scratch/2026-04-30-multi-tenant-mem0-audit.md Probe 6).
|
|
734
832
|
const PRELOAD_BUDGET_MS = 4000;
|
|
735
|
-
const
|
|
736
|
-
(
|
|
737
|
-
const agentId = await discoverAgentId();
|
|
738
|
-
const memories = await fetchRecentMemories(agentId);
|
|
739
|
-
return { agentId, memories };
|
|
740
|
-
})(),
|
|
833
|
+
const memories = await withTimeout(
|
|
834
|
+
fetchRecentMemories(agentId),
|
|
741
835
|
PRELOAD_BUDGET_MS,
|
|
742
|
-
|
|
836
|
+
[],
|
|
743
837
|
{ onTimeout: abortInflightRequests },
|
|
744
838
|
);
|
|
745
839
|
|
|
746
|
-
// Write watermark (now cachinG agent_id too)
|
|
747
840
|
if (sessionId) {
|
|
748
841
|
const wmPath = path.join(WATERMARK_DIR, `${sessionId}.watermark.json`);
|
|
749
|
-
writeWatermark(wmPath, convId, 0,
|
|
842
|
+
writeWatermark(wmPath, convId, 0, agentId);
|
|
750
843
|
process.stderr.write(
|
|
751
|
-
`[meko-capture] SessionStart: created conversation ${convId}, agent_id=${
|
|
844
|
+
`[meko-capture] SessionStart: created conversation ${convId}, agent_id=${agentId}, preloaded ${memories.length} memories, watermark at ${wmPath}\n`,
|
|
752
845
|
);
|
|
753
846
|
}
|
|
754
847
|
|
|
755
848
|
process.stdout.write(
|
|
756
849
|
hookOutput(
|
|
757
|
-
buildSessionStartContext(
|
|
758
|
-
convId,
|
|
759
|
-
sessionId,
|
|
760
|
-
preloadResult.agentId,
|
|
761
|
-
preloadResult.memories,
|
|
762
|
-
),
|
|
850
|
+
buildSessionStartContext(convId, sessionId, agentId, memories),
|
|
763
851
|
),
|
|
764
852
|
);
|
|
765
853
|
}
|
|
@@ -807,6 +895,15 @@ async function main() {
|
|
|
807
895
|
const lastLine = watermark.last_line_number || 0;
|
|
808
896
|
const currentLines = countLines(transcriptPath);
|
|
809
897
|
|
|
898
|
+
// Resolve agent_id for this hook invocation. Prefer the value the
|
|
899
|
+
// SessionStart hook persisted in the watermark; if it's missing or a
|
|
900
|
+
// legacy literal ("agent"), fall back to a fresh derivation from the
|
|
901
|
+
// transcript's cwd so writes land in the right bucket.
|
|
902
|
+
const cachedAgentId = (watermark.agent_id || "").trim();
|
|
903
|
+
const agentId = cachedAgentId && cachedAgentId !== "agent"
|
|
904
|
+
? cachedAgentId
|
|
905
|
+
: resolveSessionAgentId(transcriptPath);
|
|
906
|
+
|
|
810
907
|
// Nothing new
|
|
811
908
|
if (currentLines <= lastLine) {
|
|
812
909
|
process.stdout.write(hookOutput(""));
|
|
@@ -839,15 +936,17 @@ async function main() {
|
|
|
839
936
|
|
|
840
937
|
for (const exchange of exchanges) {
|
|
841
938
|
try {
|
|
842
|
-
await addMessage(convId, exchange);
|
|
939
|
+
await addMessage(convId, agentId, exchange);
|
|
843
940
|
captured++;
|
|
844
941
|
} catch (err) {
|
|
845
942
|
process.stderr.write(`[meko-capture] Failed to add message (uuid=${exchange.user_uuid}): ${err.message}\n`);
|
|
846
943
|
}
|
|
847
944
|
}
|
|
848
945
|
|
|
849
|
-
// Update watermark
|
|
850
|
-
|
|
946
|
+
// Update watermark with the resolved agent_id. If the cached value was a
|
|
947
|
+
// legacy literal we now overwrite it so subsequent hooks see the derived
|
|
948
|
+
// bucket directly.
|
|
949
|
+
writeWatermark(wmPath, convId, currentLines, agentId);
|
|
851
950
|
|
|
852
951
|
// Output
|
|
853
952
|
const context =
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "type": "commonjs" }
|
|
@@ -17,7 +17,7 @@ set -euo pipefail
|
|
|
17
17
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
18
18
|
CAPTURE_JS="$SCRIPT_DIR/lib/capture.js"
|
|
19
19
|
MCP_URL="${1:-${MEKO_MCP_URL:-http://localhost:8000/mcp}}"
|
|
20
|
-
AGENT_ID="${MEKO_AGENT_ID:-
|
|
20
|
+
AGENT_ID="${MEKO_AGENT_ID:-agent}"
|
|
21
21
|
|
|
22
22
|
# Temp dirs for isolation
|
|
23
23
|
WATERMARK_DIR="$(mktemp -d -t meko-test-wm-XXXXXX)"
|