claude-multi-session 2.3.1 → 2.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-multi-session",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "Multi-session orchestrator for Claude Code CLI — spawn, control, pause, resume, and send multiple inputs to Claude Code sessions programmatically",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -469,6 +469,45 @@ class ArtifactStore {
469
469
  }
470
470
  }
471
471
 
472
+ /**
473
+ * Track that a session read an artifact
474
+ * @param {string} artifactId - The artifact that was read
475
+ * @param {string} reader - The session name that read it
476
+ * @param {number} version - The version that was read
477
+ */
478
+ trackRead(artifactId, reader, version) {
479
+ if (!reader) return; // Skip if no reader identified
480
+
481
+ const readsPath = path.join(this.dataDir, artifactId, 'reads.json');
482
+
483
+ // Read existing reads log
484
+ let reads = readJsonSafe(readsPath, []);
485
+
486
+ // Add this read event
487
+ reads.push({
488
+ reader,
489
+ version,
490
+ readAt: new Date().toISOString(),
491
+ });
492
+
493
+ // Write back (not immutable — reads are append-only log)
494
+ const dir = path.join(this.dataDir, artifactId);
495
+ if (!fs.existsSync(dir)) {
496
+ fs.mkdirSync(dir, { recursive: true });
497
+ }
498
+ fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2));
499
+ }
500
+
501
+ /**
502
+ * Get the read log for an artifact
503
+ * @param {string} artifactId - The artifact to check
504
+ * @returns {Array} Array of read events: [{ reader, version, readAt }]
505
+ */
506
+ getReads(artifactId) {
507
+ const readsPath = path.join(this.dataDir, artifactId, 'reads.json');
508
+ return readJsonSafe(readsPath, []);
509
+ }
510
+
472
511
  /**
473
512
  * List artifacts with optional filtering
474
513
  * @param {Object} [filters={}] - Filter criteria
@@ -497,6 +536,17 @@ class ArtifactStore {
497
536
  results = results.filter(item => item.tags && item.tags.includes(tag));
498
537
  }
499
538
 
539
+ // Enrich with read counts
540
+ results = results.map(item => {
541
+ const reads = this.getReads(item.artifactId);
542
+ const uniqueReaders = [...new Set(reads.map(r => r.reader))];
543
+ return {
544
+ ...item,
545
+ readCount: reads.length,
546
+ uniqueReaders,
547
+ };
548
+ });
549
+
500
550
  return results;
501
551
  }
502
552
 
package/src/mcp-server.js CHANGED
@@ -25,6 +25,8 @@
25
25
  * - tools/call → execute a tool and return result
26
26
  */
27
27
 
28
+ const fs = require('fs');
29
+ const path = require('path');
28
30
  const readline = require('readline');
29
31
  const SessionManager = require('./manager');
30
32
  const Delegate = require('./delegate');
@@ -47,6 +49,9 @@ const DecisionJournal = require('./decision-journal');
47
49
  const PatternRegistry = require('./pattern-registry');
48
50
  const StaleDetector = require('./stale-detector');
49
51
 
52
+ // Capture version at load time — used to detect stale server processes
53
+ const LOADED_VERSION = require('../package.json').version;
54
+
50
55
  // =============================================================================
51
56
  // Server State — persists across all tool calls
52
57
  // =============================================================================
@@ -390,6 +395,18 @@ const TOOLS = [
390
395
  },
391
396
  },
392
397
 
398
+ // ── Server Version ─────────────────────────────────────────────────────
399
+ {
400
+ name: 'server_version',
401
+ description:
402
+ 'Check the running MCP server version and detect staleness. ' +
403
+ 'Call this if tools seem missing or behave unexpectedly.',
404
+ inputSchema: {
405
+ type: 'object',
406
+ properties: {},
407
+ },
408
+ },
409
+
393
410
  // ══════════════════════════════════════════════════════════════════════════
394
411
  // TEAM HUB v2 — 32 new tools for team collaboration
395
412
  // ══════════════════════════════════════════════════════════════════════════
@@ -555,12 +572,15 @@ const TOOLS = [
555
572
  {
556
573
  name: 'artifact_get',
557
574
  description:
558
- 'Read an artifact (latest version or specific version).',
575
+ 'Read an artifact (latest version or specific version). ' +
576
+ 'IMPORTANT: Pass your session name as the "reader" parameter to track artifact consumption. ' +
577
+ 'The orchestrator uses this to verify workers actually read shared data.',
559
578
  inputSchema: {
560
579
  type: 'object',
561
580
  properties: {
562
581
  artifactId: { type: 'string', description: 'Artifact ID to retrieve' },
563
582
  version: { type: 'number', description: 'Specific version number (omit for latest)' },
583
+ reader: { type: 'string', description: 'Session name reading this artifact (for tracking who consumed it)' },
564
584
  team: { type: 'string', description: 'Team name (default: "default")' },
565
585
  },
566
586
  required: ['artifactId'],
@@ -582,6 +602,20 @@ const TOOLS = [
582
602
  },
583
603
  },
584
604
 
605
+ {
606
+ name: 'artifact_readers',
607
+ description:
608
+ 'Check who has read a specific artifact. Use this to verify that workers actually consumed shared artifacts like conventions or schemas.',
609
+ inputSchema: {
610
+ type: 'object',
611
+ properties: {
612
+ artifactId: { type: 'string', description: 'Artifact ID to check readers for' },
613
+ team: { type: 'string', description: 'Team name (default: "default")' },
614
+ },
615
+ required: ['artifactId'],
616
+ },
617
+ },
618
+
585
619
  {
586
620
  name: 'artifact_history',
587
621
  description:
@@ -1124,6 +1158,10 @@ async function executeTool(toolName, args) {
1124
1158
  case 'abort_task':
1125
1159
  return handleAbort(args);
1126
1160
 
1161
+ // ── Server Version ──────────────────────────────────────────────
1162
+ case 'server_version':
1163
+ return handleServerVersion(args);
1164
+
1127
1165
  // ── Orchestrator Guide ────────────────────────────────────────────
1128
1166
  case 'get_orchestrator_guide':
1129
1167
  return handleGetOrchestratorGuide(args);
@@ -1161,6 +1199,8 @@ async function executeTool(toolName, args) {
1161
1199
  return handleArtifactGet(args);
1162
1200
  case 'artifact_list':
1163
1201
  return handleArtifactList(args);
1202
+ case 'artifact_readers':
1203
+ return handleArtifactReaders(args);
1164
1204
  case 'artifact_history':
1165
1205
  return handleArtifactHistory(args);
1166
1206
 
@@ -1580,6 +1620,32 @@ function handleGetOrchestratorGuide(args) {
1580
1620
  return textResult(guide);
1581
1621
  }
1582
1622
 
1623
+ /**
1624
+ * Check the running MCP server version and detect staleness.
1625
+ * Compares the version loaded into memory at startup against the
1626
+ * version currently installed on disk (package.json).
1627
+ */
1628
+ function handleServerVersion() {
1629
+ let installedVersion = LOADED_VERSION;
1630
+ try {
1631
+ const pkgPath = path.join(__dirname, '..', 'package.json');
1632
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
1633
+ installedVersion = pkg.version;
1634
+ } catch (e) {
1635
+ // If we can't read package.json at runtime, use loaded version
1636
+ }
1637
+
1638
+ const stale = LOADED_VERSION !== installedVersion;
1639
+ return textResult(JSON.stringify({
1640
+ running: LOADED_VERSION,
1641
+ installed: installedVersion,
1642
+ stale,
1643
+ message: stale
1644
+ ? `Server is stale: running v${LOADED_VERSION} but v${installedVersion} is installed. Restart Claude Code to load new tools.`
1645
+ : `Server is up to date (v${LOADED_VERSION}).`
1646
+ }, null, 2));
1647
+ }
1648
+
1583
1649
  // =============================================================================
1584
1650
  // Team Hub Handlers — Layer 1, 2, 3
1585
1651
  // =============================================================================
@@ -1867,7 +1933,13 @@ function handleArtifactGet(args) {
1867
1933
  return errorResult(`Artifact ${args.artifactId} not found`);
1868
1934
  }
1869
1935
 
1870
- return textResult(JSON.stringify({
1936
+ // Track this read if a reader was specified
1937
+ if (args.reader) {
1938
+ artifactStore.trackRead(args.artifactId, args.reader, artifact.version);
1939
+ }
1940
+
1941
+ // Build the response
1942
+ const response = {
1871
1943
  artifactId: artifact.artifactId,
1872
1944
  version: artifact.version,
1873
1945
  type: artifact.type,
@@ -1878,7 +1950,15 @@ function handleArtifactGet(args) {
1878
1950
  summary: artifact.summary,
1879
1951
  lineage: artifact.lineage,
1880
1952
  team: teamName,
1881
- }, null, 2));
1953
+ readBy: artifactStore.getReads(args.artifactId),
1954
+ };
1955
+
1956
+ // Add nudge if reader param was not provided
1957
+ if (!args.reader) {
1958
+ response._hint = 'Tip: Pass your session name as the "reader" parameter to track artifact consumption. Example: artifact_get({ artifactId: "...", reader: "your-session-name" })';
1959
+ }
1960
+
1961
+ return textResult(JSON.stringify(response, null, 2));
1882
1962
  } catch (err) {
1883
1963
  return errorResult(err.message);
1884
1964
  }
@@ -1908,6 +1988,8 @@ function handleArtifactList(args) {
1908
1988
  createdAt: a.createdAt,
1909
1989
  updatedAt: a.updatedAt,
1910
1990
  tags: a.tags,
1991
+ readCount: a.readCount,
1992
+ uniqueReaders: a.uniqueReaders,
1911
1993
  })),
1912
1994
  }, null, 2));
1913
1995
  } catch (err) {
@@ -1915,6 +1997,25 @@ function handleArtifactList(args) {
1915
1997
  }
1916
1998
  }
1917
1999
 
2000
+ function handleArtifactReaders(args) {
2001
+ try {
2002
+ const teamName = args.team || 'default';
2003
+ const { artifactStore } = getTeamInstances(teamName);
2004
+
2005
+ const reads = artifactStore.getReads(args.artifactId);
2006
+ const uniqueReaders = [...new Set(reads.map(r => r.reader))];
2007
+
2008
+ return textResult(JSON.stringify({
2009
+ artifactId: args.artifactId,
2010
+ totalReads: reads.length,
2011
+ uniqueReaders,
2012
+ reads,
2013
+ }, null, 2));
2014
+ } catch (err) {
2015
+ return errorResult(err.message);
2016
+ }
2017
+ }
2018
+
1918
2019
  function handleArtifactHistory(args) {
1919
2020
  try {
1920
2021
  const teamName = args.team || 'default';
@@ -2453,6 +2554,28 @@ function errorResult(message) {
2453
2554
  return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
2454
2555
  }
2455
2556
 
2557
+ /**
2558
+ * Append a staleness warning to tool results if the server version is outdated.
2559
+ * Reads package.json from disk on each call to detect post-install version drift.
2560
+ * @param {Object} result - The tool result object
2561
+ * @returns {Object} The result, possibly with a staleness warning appended
2562
+ */
2563
+ function appendStalenessWarning(result) {
2564
+ try {
2565
+ const pkgPath = path.join(__dirname, '..', 'package.json');
2566
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
2567
+ if (pkg.version !== LOADED_VERSION) {
2568
+ const warning = `\n\n⚠️ STALE SERVER: Running v${LOADED_VERSION} but v${pkg.version} is installed. Restart Claude Code to load updated tools.`;
2569
+ if (result && result.content && result.content[0] && result.content[0].text) {
2570
+ result.content[0].text += warning;
2571
+ }
2572
+ }
2573
+ } catch (e) {
2574
+ // Silently ignore — staleness check is best-effort
2575
+ }
2576
+ return result;
2577
+ }
2578
+
2456
2579
  // =============================================================================
2457
2580
  // MCP Protocol Handler — JSON-RPC 2.0 over stdio
2458
2581
  // =============================================================================
@@ -2522,7 +2645,9 @@ async function handleMessage(message) {
2522
2645
  break;
2523
2646
  }
2524
2647
  try {
2525
- const result = await executeTool(params.name, params.arguments || {});
2648
+ let result = await executeTool(params.name, params.arguments || {});
2649
+ // Append staleness warning if server version is outdated
2650
+ result = appendStalenessWarning(result);
2526
2651
  sendResponse(id, result);
2527
2652
  } catch (err) {
2528
2653
  sendResponse(id, errorResult(err.message));
package/src/prompts.js CHANGED
@@ -66,15 +66,25 @@ Your inbox may contain:
66
66
  - Dependency artifacts you need before starting
67
67
  - Updated instructions from the orchestrator
68
68
 
69
- ### Step 1.5: CHECK FOR SHARED CONVENTIONS
70
- Call \`mcp__multi-session__artifact_list\` and look for a conventions or API contract artifact.
71
- If one exists, call \`mcp__multi-session__artifact_get\` to read it and follow those conventions STRICTLY.
72
- Conventions may define: response format, status codes, naming rules, error format, file path rules.
69
+ ### Step 1.5: CHECK FOR SHARED CONVENTIONS AND DEPENDENCY ARTIFACTS
70
+ Call \`mcp__multi-session__artifact_list\` and look for:
71
+ - A conventions or API contract artifact (e.g., "shared-conventions")
72
+ - Any dependency artifacts your task requires (e.g., "db-schema" if you're building routes)
73
73
 
74
- If NO convention artifact exists AND your work must match other workers' output:
74
+ If found, call \`mcp__multi-session__artifact_get\` with your session name as the \`reader\` parameter:
75
+ \`\`\`
76
+ mcp__multi-session__artifact_get({ artifactId: "shared-conventions", reader: "${name}" })
77
+ mcp__multi-session__artifact_get({ artifactId: "db-schema", reader: "${name}" })
78
+ \`\`\`
79
+
80
+ IMPORTANT: The \`reader\` parameter tracks that you consumed the artifact. This is how the orchestrator verifies workers actually read shared data. ALWAYS include your session name as \`reader\`.
81
+
82
+ Follow the conventions STRICTLY. If NO convention artifact exists AND your work must match other workers' output:
75
83
  - Use \`team_ask\` to ask the relevant teammate about their format BEFORE writing code
76
84
  - NEVER guess or assume format — mismatches cause test failures
77
85
 
86
+ Note: team_ask is a **fallback mechanism** for when information wasn't available upfront. If your orchestrator provided thorough prompts with all needed context and conventions, you may never need team_ask — this is the ideal case.
87
+
78
88
  ### Step 2: UPDATE YOUR STATUS
79
89
  Call \`mcp__multi-session__team_update_status\` to set yourself as "active" with your current task.
80
90
 
@@ -112,7 +122,7 @@ mcp__multi-session__team_broadcast({ from: "${name}", content: "Completed: <what
112
122
  | \`team_check_inbox\` | BEFORE starting work, AFTER major steps |
113
123
  | \`team_send_message\` | Direct message to a specific teammate |
114
124
  | \`team_broadcast\` | Announce something to ALL teammates |
115
- | \`team_ask\` | Ask a teammate a question and WAIT for reply |
125
+ | \`team_ask\` | Ask a teammate a question and WAIT for reply (fallback — use when info wasn't provided upfront) |
116
126
  | \`team_reply\` | Reply to a question you received (IMMEDIATELY) |
117
127
  | \`team_update_status\` | Update what you're working on |
118
128
 
@@ -466,8 +476,9 @@ IMPORTANT: You are the ORCHESTRATOR. Your job is to PLAN, SPAWN, and MONITOR —
466
476
 
467
477
  === CRITICAL: ANTI-PATTERN — DO NOT DO THIS ===
468
478
  - Do NOT spawn a session, read its output, then manually relay that output to another session
469
- - Do NOT fix bugs found by one session yourself tell that session to fix them
479
+ - Do NOT fix bugs found by one session yourself UNLESS the fix is ≤ 3 lines AND the worker session has completed. For trivial fixes, you may fix directly but MUST broadcast the change and re-publish any affected artifacts
470
480
  - Do NOT act as a message router between sessions — they can talk directly
481
+ - Note: team_ask is a **fallback** for unexpected ambiguity. In well-orchestrated projects where you provide all context upfront in worker prompts, team_ask may never be called — this is the ideal case and means your prompts were thorough enough.
471
482
  - Do NOT do implementation work that should be delegated
472
483
  - Do NOT fix code yourself when a worker's tests fail — send corrections to the worker that wrote the failing code
473
484
  - Do NOT assume workers will agree on response formats — define shared conventions before spawning
@@ -517,6 +528,34 @@ mcp__multi-session__artifact_publish({
517
528
 
518
529
  NEVER assume workers will independently agree on conventions. Define them explicitly.
519
530
 
531
+ ### Phase Gate: VERIFY Before Spawning
532
+ === CRITICAL: MANDATORY VERIFICATION STEP ===
533
+ Before spawning ANY worker that depends on a previous phase's output, you MUST:
534
+ 1. Call \`artifact_list()\` to confirm the dependency artifact was published
535
+ 2. Call \`artifact_get(artifactId)\` to confirm the artifact contains valid data
536
+ 3. ONLY THEN spawn the dependent workers
537
+
538
+ Example phased workflow:
539
+ \`\`\`
540
+ // Phase 1: Spawn the database worker
541
+ team_spawn({ name: "db-worker", ... })
542
+
543
+ // PHASE GATE: Verify before Phase 2
544
+ artifact_list() // Confirm "db-schema" appears
545
+ artifact_get({ artifactId: "db-schema" }) // Confirm it has valid table definitions
546
+
547
+ // Phase 2: NOW spawn route workers (they depend on the schema)
548
+ team_spawn({ name: "api-worker", ... })
549
+ team_spawn({ name: "test-worker", ... })
550
+ \`\`\`
551
+
552
+ NEVER skip verification. NEVER rely on a worker's self-reported completion — verify the artifact exists yourself.
553
+
554
+ === PHASE COUNTING RULE ===
555
+ At the start of planning, count and list your phases explicitly:
556
+ "Phase 1: [description], Phase 2: [description], ..."
557
+ This creates a self-check — you can verify you ran the verification checklist between each pair.
558
+
520
559
  ### Phase 2: Spawn Workers (use team_spawn, NOT delegate_task)
521
560
  Spawn all independent workers at once. Example:
522
561
 
@@ -565,18 +604,16 @@ mcp__multi-session__contract_create({
565
604
  })
566
605
  \`\`\`
567
606
 
568
- ### Phase 4: Monitor (Don't Micromanage)
569
- Check progress periodically:
570
- \`\`\`
571
- mcp__multi-session__team_roster() See who's active/idle/blocked
572
- mcp__multi-session__contract_list() See contract statuses
573
- mcp__multi-session__artifact_list() See published outputs
574
- \`\`\`
607
+ ### Phase 4: Post-Phase Verification (MANDATORY)
608
+ After ALL workers in a phase complete, and BEFORE spawning the next phase, you MUST run this verification checklist:
609
+
610
+ 1. \`artifact_list()\` confirm all expected artifacts from this phase exist
611
+ 2. \`artifact_get()\` on each critical artifact verify the content is valid and complete
612
+ 3. \`team_roster()\` confirm all phase workers show 'idle' or 'done' status
575
613
 
576
- Only intervene when:
577
- - A session status shows "BLOCKED" for more than a reasonable time
578
- - A contract has failed
579
- - The user asks for a status update
614
+ Only proceed to the next phase when ALL checks pass.
615
+
616
+ IMPORTANT: Count your phases at the start of planning. If you have N phases, you MUST perform this verification checklist exactly N-1 times (between every adjacent pair of phases). Skipping verification for later phases is the #1 cause of test failures in multi-session projects.
580
617
 
581
618
  ### Phase 5: Collect Results
582
619
  When all workers are done:
@@ -726,6 +763,7 @@ const ROLE_PROMPTS = {
726
763
  - Publish test results as artifacts with type "test-results" including: passed, failed, skipped counts and failure details
727
764
  - If tests fail due to bugs in another session's code, use \`team_send_message\` to notify them with the exact error and file path
728
765
  - Use the correct status values as defined in the database schema (e.g., "in_progress" not "in-progress")
766
+ - IMPORTANT: When calling artifact_get, ALWAYS include reader: "<your-session-name>" so the orchestrator can verify you read the artifacts
729
767
  `,
730
768
 
731
769
  'database': `
@@ -822,7 +860,9 @@ IMPORTANT: You are the ORCHESTRATOR. Your job is to PLAN, SPAWN, and MONITOR —
822
860
 
823
861
  4. **Set up dependencies** — Use \`contract_create\` if Task B needs output from Task A.
824
862
 
825
- 5. **Monitor** — Check progress with \`team_roster()\`, \`contract_list()\`, \`artifact_list()\`. Only intervene when a worker is BLOCKED or FAILED.
863
+ 4.5. **Phase Gate** — Before spawning workers that depend on previous workers' output, VERIFY the dependency artifact exists by calling \`artifact_list()\` and \`artifact_get()\`. Never trust self-reported completion verify the artifact.
864
+
865
+ 5. **Post-Phase Verification** — After each phase completes, run the verification checklist: \`artifact_list()\` to confirm artifacts exist, \`artifact_get()\` to verify content, \`team_roster()\` to confirm workers are idle. Only proceed when all checks pass. If you have N phases, verify N-1 times.
826
866
 
827
867
  6. **Collect** — When all workers are idle, check \`artifact_list\` for published outputs and summarize results for the user.
828
868
 
@@ -907,14 +947,30 @@ WHY: Specific file, exact error, expected behavior, and how to verify.
907
947
  `;
908
948
 
909
949
  const ORCHESTRATOR_MONITORING = `
910
- ## How to Monitor Without Micromanaging
950
+ ## Post-Phase Verification (MANDATORY)
951
+
952
+ After ALL workers in a phase complete, and BEFORE spawning the next phase, you MUST run this verification checklist. Do NOT fall back to filesystem checks (Glob, Read) to verify worker output — use these tools:
911
953
 
912
- You MUST check progress using these tools after spawning workers — do NOT fall back to filesystem checks (Glob, Read) to verify worker output:
954
+ ### Verification Checklist (run between EVERY pair of phases)
955
+ \`\`\`
956
+ mcp__multi-session__artifact_list() — Confirm all expected artifacts from this phase exist
957
+ mcp__multi-session__artifact_get() — Verify each critical artifact has valid, complete content
958
+ mcp__multi-session__team_roster() — Confirm all phase workers show 'idle' or 'done' status
959
+ \`\`\`
960
+
961
+ Only proceed to the next phase when ALL checks pass.
962
+
963
+ ### Dependency Verification
964
+ Between spawn phases, use these to verify dependencies:
965
+ \`\`\`
966
+ mcp__multi-session__artifact_get({ artifactId: "db-schema" }) — Verify artifact content
967
+ mcp__multi-session__artifact_readers({ artifactId: "db-schema" }) — Check who read it
968
+ \`\`\`
913
969
 
970
+ After all workers complete, verify every worker consumed the artifacts they needed:
914
971
  \`\`\`
915
- mcp__multi-session__team_roster() — See who's active/idle/blocked
916
- mcp__multi-session__contract_list() — See contract statuses
917
- mcp__multi-session__artifact_list() — See published outputs
972
+ mcp__multi-session__artifact_readers({ artifactId: "shared-conventions" })
973
+ // Should list ALL route workers as readers
918
974
  \`\`\`
919
975
 
920
976
  ### When to Intervene
@@ -937,7 +993,7 @@ When a worker is BLOCKED:
937
993
 
938
994
  NEVER do these:
939
995
  - Do NOT read the worker's full output to understand the problem — their status message should tell you
940
- - Do NOT implement the fix yourself — tell the worker to fix it
996
+ - Do NOT implement fixes > 3 lines yourself — tell the worker to fix it or spawn a fix-worker. For trivial fixes (≤ 3 lines) where the worker is already done, you may fix directly but MUST broadcast the change and re-publish affected artifacts
941
997
  - Do NOT act as a message router between workers — they can use \`team_ask\` directly
942
998
 
943
999
  === ANTI-PATTERN — DO NOT DO THIS ===