@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.
Files changed (38) hide show
  1. package/README.md +189 -36
  2. package/assets/hooks-handlers/lib/capture.js +177 -78
  3. package/assets/hooks-handlers/lib/package.json +1 -0
  4. package/assets/hooks-handlers/test_single_conversation.sh +1 -1
  5. package/assets/skills/meko-mcp-tools/SKILL.md +167 -53
  6. package/assets/skills/meko-mcp-tools/references/_sections.md +1 -1
  7. package/assets/skills/meko-mcp-tools/references/tools-agent-id-conventions.md +84 -22
  8. package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +89 -56
  9. package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +15 -7
  10. package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +17 -9
  11. package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +94 -5
  12. package/assets/skills/meko-mcp-tools/references/tools-overview.md +52 -37
  13. package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +108 -37
  14. package/assets/skills/meko-mcp-tools/references/tools-scope-permissions.md +6 -10
  15. package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +30 -18
  16. package/assets/skills/meko-mcp-tools/references/tools-user-id-conventions.md +55 -69
  17. package/bin/create-meko-setup.mjs +152 -26
  18. package/lib/clients/aliases.mjs +30 -0
  19. package/lib/clients/claude-code.mjs +384 -69
  20. package/lib/clients/claude-desktop.mjs +43 -8
  21. package/lib/clients/codex.mjs +977 -0
  22. package/lib/clients/common.mjs +142 -1
  23. package/lib/clients/cursor.mjs +73 -5
  24. package/lib/clients/index.mjs +2 -0
  25. package/lib/config.mjs +30 -0
  26. package/lib/detect.mjs +52 -44
  27. package/lib/installer.mjs +9 -2
  28. package/lib/mcp-transport.mjs +63 -4
  29. package/lib/migrate/adapters/memory-md.mjs +13 -6
  30. package/lib/migrate/datapack.mjs +167 -0
  31. package/lib/migrate/id.mjs +127 -9
  32. package/lib/migrate/mcp-client.mjs +1 -1
  33. package/lib/migrate/orchestrator.mjs +68 -10
  34. package/lib/migrate.mjs +346 -3
  35. package/lib/prompt.mjs +50 -76
  36. package/lib/validate.mjs +314 -10
  37. package/lib/version-check.mjs +178 -0
  38. 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 (default: claude_code)
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
- const MEKO_AGENT_ID = process.env.MEKO_AGENT_ID || "claude_code";
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["API_KEY"] = MEKO_API_KEY;
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
- * Discover the user's agent_id namespace from existing Mem0 collections.
402
- *
403
- * Mirrors the protocol in skills/skills/meko-mcp-tools/SKILL.md lines 41-49:
404
- * call `db_summarize_database`, scan for `<agent>_mem0_collection` tables,
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
- * Cached in the watermark file so subsequent SessionStart hooks skip the
410
- * `db_summarize_database` round-trip.
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
- async function discoverAgentId() {
413
- const response = await mcpCall("db_summarize_database", {
414
- scope: "read",
415
- conversation_id: "00000000-0000-0000-0000-000000000000",
416
- });
417
- const result = extractToolResult(response);
418
- if (!result) return "agent";
419
-
420
- // The tool may return tables in several shapes depending on server version.
421
- // Defensive: look for any array of { name, row_count } under common keys.
422
- const tables = []
423
- .concat(result.tables || [])
424
- .concat(result.schemas?.flatMap?.((s) => s.tables || []) || []);
425
- if (!Array.isArray(tables) || tables.length === 0) return "agent";
426
-
427
- let best = { agent: "", rows: -1 };
428
- for (const t of tables) {
429
- const name = t.name || t.table_name || "";
430
- const rows = Number(t.row_count ?? t.rows ?? 0);
431
- const m = /^(.+)_mem0_collection$/.exec(name);
432
- if (!m) continue;
433
- if (rows > best.rows) {
434
- best = { agent: m[1], rows };
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 response = await mcpCall("conversation_create", {
539
+ async function createConversation(sessionId, agentId, metadata = null) {
540
+ const payload = {
474
541
  scope: "write",
475
- agent_id: MEKO_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: MEKO_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 || "claude_code";
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}" (discovered from the user's existing namespace — DO NOT substitute "claude_code" just because you're running in Claude Code; see the meko-mcp-tools skill for why).
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**: ALWAYS use "claude_code" (underscore, NEVER hyphens)
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
- // Reuse cached agent_id + skip preload on resume (we don't know what's
671
- // already in context). The skill still has the discovery protocol as a
672
- // safety net.
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
- existing.agent_id || "",
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
- writeWatermark(wmPath, prior.conversation_id, currentLineCount);
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
- prior.agent_id || "",
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
- // Discover agent_id + preload memories with a combined 4s budget (SessionStart
732
- // timeout is 15s). Both are enrichment — on timeout/failure we still deliver
733
- // the core SessionStart context with the MEKO_AGENT_ID default.
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 preloadResult = await withTimeout(
736
- (async () => {
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
- { agentId: MEKO_AGENT_ID, memories: [] },
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, preloadResult.agentId);
842
+ writeWatermark(wmPath, convId, 0, agentId);
750
843
  process.stderr.write(
751
- `[meko-capture] SessionStart: created conversation ${convId}, agent_id=${preloadResult.agentId}, preloaded ${preloadResult.memories.length} memories, watermark at ${wmPath}\n`,
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
- writeWatermark(wmPath, convId, currentLines);
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:-claude_code}"
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)"