claude-multi-session 1.0.1 → 2.3.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/src/mcp-server.js CHANGED
@@ -28,6 +28,24 @@
28
28
  const readline = require('readline');
29
29
  const SessionManager = require('./manager');
30
30
  const Delegate = require('./delegate');
31
+ const { buildFullTeamPrompt, getOrchestratorGuideSection } = require('./prompts');
32
+
33
+ // Team Hub imports — all the new Layer 1, 2, 3 modules
34
+ const TeamHub = require('./team-hub');
35
+ const ArtifactStore = require('./artifact-store');
36
+ const ContractStore = require('./contract-store');
37
+ const DependencyResolver = require('./dependency-resolver');
38
+ const LineageGraph = require('./lineage-graph');
39
+ const PipelineEngine = require('./pipeline-engine');
40
+ const SnapshotEngine = require('./snapshot-engine');
41
+
42
+ // Layer 0: Session Continuity Engine
43
+ const SessionSnapshot = require('./session-snapshot');
44
+ const DiffEngine = require('./diff-engine');
45
+ const BriefingGenerator = require('./briefing-generator');
46
+ const DecisionJournal = require('./decision-journal');
47
+ const PatternRegistry = require('./pattern-registry');
48
+ const StaleDetector = require('./stale-detector');
31
49
 
32
50
  // =============================================================================
33
51
  // Server State — persists across all tool calls
@@ -41,6 +59,46 @@ const manager = new SessionManager();
41
59
  // Track delegates per session (for continue/finish/abort)
42
60
  const delegates = new Map();
43
61
 
62
+ // Team Hub instances (created lazily on first team tool call)
63
+ // These are cached per team name to avoid recreating them on every call
64
+ let teamHub = null;
65
+ let artifactStore = null;
66
+ let contractStore = null;
67
+ let resolver = null;
68
+ let lineageGraph = null;
69
+ let pipelineEngine = null;
70
+ let snapshotEngine = null;
71
+ let currentTeamName = null;
72
+
73
+ /**
74
+ * Get or create Team Hub instances for a given team
75
+ * This function lazily creates the instances on first use
76
+ * and caches them for subsequent calls to the same team
77
+ *
78
+ * @param {string} teamName - Name of the team (default: 'default')
79
+ * @returns {Object} Object with all team instances
80
+ */
81
+ function getTeamInstances(teamName = 'default') {
82
+ // If team name changed or instances not yet created, recreate them
83
+ if (!teamHub || currentTeamName !== teamName) {
84
+ teamHub = new TeamHub(teamName);
85
+ artifactStore = new ArtifactStore(teamName);
86
+ contractStore = new ContractStore(teamName);
87
+ resolver = new DependencyResolver(teamName);
88
+ lineageGraph = new LineageGraph(teamName);
89
+ pipelineEngine = new PipelineEngine(teamName);
90
+ snapshotEngine = new SnapshotEngine(teamName);
91
+
92
+ // Set pipeline engine reference in resolver (avoid circular dependency)
93
+ resolver.setPipelineEngine(pipelineEngine);
94
+
95
+ // Update the current team name
96
+ currentTeamName = teamName;
97
+ }
98
+
99
+ return { teamHub, artifactStore, contractStore, resolver, lineageGraph, pipelineEngine, snapshotEngine };
100
+ }
101
+
44
102
  // =============================================================================
45
103
  // Tool Definitions — these appear in Claude's tool list
46
104
  // =============================================================================
@@ -224,7 +282,8 @@ const TOOLS = [
224
282
  'Spawns a child session, sends the task, monitors for issues ' +
225
283
  '(permission denials, cost overruns, turn limits), and returns structured output. ' +
226
284
  'Use this for autonomous task execution with safety guardrails. ' +
227
- 'After delegation, use continue_task to send corrections if the result is not satisfactory.',
285
+ 'After delegation, use continue_task to send corrections if the result is not satisfactory. ' +
286
+ 'Use for SINGLE isolated tasks. For multi-session projects with 3+ workers, use team_spawn instead.',
228
287
  inputSchema: {
229
288
  type: 'object',
230
289
  properties: {
@@ -311,155 +370,1027 @@ const TOOLS = [
311
370
  required: ['sessions'],
312
371
  },
313
372
  },
314
- ];
315
-
316
- // =============================================================================
317
- // Tool Handlers — execute each tool and return result
318
- // =============================================================================
319
373
 
320
- /**
321
- * Execute a tool by name with given arguments.
322
- * Returns { content: [{ type: "text", text: "..." }], isError?: true }
323
- */
324
- async function executeTool(toolName, args) {
325
- try {
326
- switch (toolName) {
327
- // ── Session Management ────────────────────────────────────────────
328
- case 'spawn_session':
329
- return await handleSpawn(args);
330
- case 'send_message':
331
- return await handleSend(args);
332
- case 'resume_session':
333
- return await handleResume(args);
334
- case 'pause_session':
335
- return handlePause(args);
336
- case 'fork_session':
337
- return await handleFork(args);
338
- case 'stop_session':
339
- return handleStop(args);
340
- case 'kill_session':
341
- return handleKill(args);
374
+ // ── Orchestrator Guide ──────────────────────────────────────────────────
375
+ {
376
+ name: 'get_orchestrator_guide',
377
+ description:
378
+ 'Get the multi-session orchestration guide. Call this when you need to orchestrate ' +
379
+ 'a complex multi-session project and want instructions on how to spawn workers, ' +
380
+ 'set up dependencies, and monitor progress.',
381
+ inputSchema: {
382
+ type: 'object',
383
+ properties: {
384
+ section: {
385
+ type: 'string',
386
+ enum: ['full', 'quick-start', 'team-spawn', 'delegate', 'monitoring', 'when-to-use'],
387
+ description: 'Which section of the guide to return (default: full)',
388
+ },
389
+ },
390
+ },
391
+ },
342
392
 
343
- // ── Information ───────────────────────────────────────────────────
344
- case 'get_session_status':
345
- return handleStatus(args);
346
- case 'get_last_output':
347
- return handleLastOutput(args);
348
- case 'list_sessions':
349
- return handleList(args);
350
- case 'get_history':
351
- return handleHistory(args);
352
- case 'delete_session':
353
- return handleDelete(args);
393
+ // ══════════════════════════════════════════════════════════════════════════
394
+ // TEAM HUB v2 — 32 new tools for team collaboration
395
+ // ══════════════════════════════════════════════════════════════════════════
354
396
 
355
- // ── Delegate ──────────────────────────────────────────────────────
356
- case 'delegate_task':
357
- return await handleDelegate(args);
358
- case 'continue_task':
359
- return await handleContinue(args);
360
- case 'finish_task':
361
- return handleFinish(args);
362
- case 'abort_task':
363
- return handleAbort(args);
397
+ // ── Layer 1: Team Chat (8 tools) ────────────────────────────────────────
398
+ {
399
+ name: 'team_spawn',
400
+ description:
401
+ 'Spawn a session with team system prompt injected. The session joins the team roster and ' +
402
+ 'receives context about teammates, communication tools, and collaboration rules. ' +
403
+ 'Spawn ALL independent workers in a SINGLE message. Workers coordinate directly via team_ask — do NOT relay messages.',
404
+ inputSchema: {
405
+ type: 'object',
406
+ properties: {
407
+ name: { type: 'string', description: 'Unique session name (e.g. "db-dev", "api-dev")' },
408
+ prompt: { type: 'string', description: 'Initial prompt to send' },
409
+ role: { type: 'string', description: 'Role in the team (e.g. "database", "backend", "QA")' },
410
+ task: { type: 'string', description: 'Current task description' },
411
+ model: { type: 'string', enum: ['sonnet', 'opus', 'haiku'], description: 'Model to use (default: sonnet)' },
412
+ permission_mode: { type: 'string', enum: ['default', 'acceptEdits', 'bypassPermissions', 'plan'], description: 'Permission mode. Use bypassPermissions to allow sessions to write files without approval (default: bypassPermissions)' },
413
+ team: { type: 'string', description: 'Team name (default: "default")' },
414
+ },
415
+ required: ['name', 'prompt'],
416
+ },
417
+ },
364
418
 
365
- // ── Batch ─────────────────────────────────────────────────────────
366
- case 'batch_spawn':
367
- return await handleBatch(args);
419
+ {
420
+ name: 'team_send_message',
421
+ description:
422
+ 'Send a direct message to a specific teammate. The message appears in their inbox.',
423
+ inputSchema: {
424
+ type: 'object',
425
+ properties: {
426
+ from: { type: 'string', description: 'Sender session name' },
427
+ to: { type: 'string', description: 'Recipient session name' },
428
+ content: { type: 'string', description: 'Message content' },
429
+ priority: { type: 'string', enum: ['low', 'normal', 'high', 'urgent'], description: 'Message priority (default: normal)' },
430
+ team: { type: 'string', description: 'Team name (default: "default")' },
431
+ },
432
+ required: ['from', 'to', 'content'],
433
+ },
434
+ },
368
435
 
369
- default:
370
- return errorResult(`Unknown tool: ${toolName}`);
371
- }
372
- } catch (err) {
373
- return errorResult(err.message);
374
- }
375
- }
436
+ {
437
+ name: 'team_broadcast',
438
+ description:
439
+ 'Send a message to all team members. Appears in everyone\'s inbox.',
440
+ inputSchema: {
441
+ type: 'object',
442
+ properties: {
443
+ from: { type: 'string', description: 'Sender session name' },
444
+ content: { type: 'string', description: 'Message content' },
445
+ priority: { type: 'string', enum: ['low', 'normal', 'high', 'urgent'], description: 'Message priority (default: normal)' },
446
+ team: { type: 'string', description: 'Team name (default: "default")' },
447
+ },
448
+ required: ['from', 'content'],
449
+ },
450
+ },
376
451
 
377
- // ── Session Handlers ──────────────────────────────────────────────────────
452
+ {
453
+ name: 'team_check_inbox',
454
+ description:
455
+ 'Check inbox for unread messages. Sessions should check their inbox before starting work ' +
456
+ 'and after major steps to stay coordinated with the team.',
457
+ inputSchema: {
458
+ type: 'object',
459
+ properties: {
460
+ name: { type: 'string', description: 'Session name whose inbox to check' },
461
+ mark_read: { type: 'boolean', description: 'Mark messages as read after retrieving (default: true)' },
462
+ limit: { type: 'number', description: 'Max messages to return (default: 20)' },
463
+ team: { type: 'string', description: 'Team name (default: "default")' },
464
+ },
465
+ required: ['name'],
466
+ },
467
+ },
378
468
 
379
- async function handleSpawn(args) {
380
- const { session, response } = await manager.spawn(args.name, {
381
- prompt: args.prompt,
382
- model: args.model,
383
- workDir: args.work_dir,
384
- permissionMode: args.permission_mode,
385
- allowedTools: args.allowed_tools,
386
- systemPrompt: args.system_prompt,
387
- maxBudget: args.max_budget,
388
- agent: args.agent,
389
- });
469
+ {
470
+ name: 'team_ask',
471
+ description:
472
+ 'Ask a teammate a question and WAIT for their reply. This is a blocking operation that polls ' +
473
+ 'until the recipient replies or the timeout expires.',
474
+ inputSchema: {
475
+ type: 'object',
476
+ properties: {
477
+ from: { type: 'string', description: 'Sender session name' },
478
+ to: { type: 'string', description: 'Recipient session name' },
479
+ question: { type: 'string', description: 'Question to ask' },
480
+ timeout: { type: 'number', description: 'Timeout in milliseconds (default: 60000 = 1 minute)' },
481
+ team: { type: 'string', description: 'Team name (default: "default")' },
482
+ },
483
+ required: ['from', 'to', 'question'],
484
+ },
485
+ },
390
486
 
391
- const result = {
392
- session_name: session.name,
393
- status: session.status,
394
- model: session.model,
395
- session_id: session.id,
396
- };
487
+ {
488
+ name: 'team_reply',
489
+ description:
490
+ 'Reply to a question you received. Use the message_id from the ask message in your inbox.',
491
+ inputSchema: {
492
+ type: 'object',
493
+ properties: {
494
+ from: { type: 'string', description: 'Your session name' },
495
+ message_id: { type: 'string', description: 'ID of the ask message you\'re replying to' },
496
+ answer: { type: 'string', description: 'Your answer to the question' },
497
+ team: { type: 'string', description: 'Team name (default: "default")' },
498
+ },
499
+ required: ['from', 'message_id', 'answer'],
500
+ },
501
+ },
397
502
 
398
- if (response) {
399
- result.response = response.text;
400
- result.cost = response.cost;
401
- result.turns = response.turns;
402
- result.duration_ms = response.duration;
403
- } else {
404
- result.message = 'Session started (idle). Send a message with send_message.';
405
- }
503
+ {
504
+ name: 'team_roster',
505
+ description:
506
+ 'View the current team roster with all members, their roles, status, and lastSeen times.',
507
+ inputSchema: {
508
+ type: 'object',
509
+ properties: {
510
+ team: { type: 'string', description: 'Team name (default: "default")' },
511
+ },
512
+ },
513
+ },
406
514
 
407
- return textResult(JSON.stringify(result, null, 2));
408
- }
515
+ {
516
+ name: 'team_update_status',
517
+ description:
518
+ 'Update your own status in the team roster. Use this to keep teammates informed of what you\'re doing.',
519
+ inputSchema: {
520
+ type: 'object',
521
+ properties: {
522
+ name: { type: 'string', description: 'Your session name' },
523
+ status: { type: 'string', enum: ['active', 'busy', 'idle', 'offline'], description: 'Your current status' },
524
+ task: { type: 'string', description: 'What you\'re currently working on' },
525
+ role: { type: 'string', description: 'Your role (if it changed)' },
526
+ team: { type: 'string', description: 'Team name (default: "default")' },
527
+ },
528
+ required: ['name'],
529
+ },
530
+ },
409
531
 
410
- async function handleSend(args) {
411
- // Auto-resume if session is not alive
412
- if (!manager.sessions.has(args.name)) {
413
- log(`Session "${args.name}" not alive. Auto-resuming...`);
414
- const response = await manager.resume(args.name, args.message);
415
- return textResult(JSON.stringify({
416
- session_name: args.name,
417
- auto_resumed: true,
418
- response: response?.text || '',
419
- cost: response?.cost || 0,
420
- turns: response?.turns || 0,
421
- duration_ms: response?.duration || 0,
422
- }, null, 2));
423
- }
532
+ // ── Layer 2: Artifacts (4 tools) ─────────────────────────────────────────
533
+ {
534
+ name: 'artifact_publish',
535
+ description:
536
+ 'Publish a structured artifact (versioned, immutable). This is how sessions exchange structured ' +
537
+ 'data like API contracts, schemas, test results. Publishing triggers dependency resolution and pipelines.',
538
+ inputSchema: {
539
+ type: 'object',
540
+ properties: {
541
+ artifactId: { type: 'string', description: 'Unique artifact ID (e.g. "api-contract-user-auth")' },
542
+ type: { type: 'string', description: 'Artifact type (e.g. "api-contract", "schema-change", "test-results", "custom")' },
543
+ name: { type: 'string', description: 'Human-readable name' },
544
+ data: { type: 'object', description: 'The artifact data payload (validated against schema for well-known types)' },
545
+ summary: { type: 'string', description: 'Brief summary of this version' },
546
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags for filtering (e.g. ["auth", "v1"])' },
547
+ publisher: { type: 'string', description: 'Session name publishing this artifact' },
548
+ derivedFrom: { type: 'array', items: { type: 'string' }, description: 'Array of artifact IDs this was derived from (for lineage)' },
549
+ team: { type: 'string', description: 'Team name (default: "default")' },
550
+ },
551
+ required: ['artifactId', 'type', 'name', 'data'],
552
+ },
553
+ },
424
554
 
425
- const response = await manager.send(args.name, args.message);
426
- return textResult(JSON.stringify({
427
- session_name: args.name,
428
- response: response.text,
429
- cost: response.cost,
430
- turns: response.turns,
431
- duration_ms: response.duration,
432
- }, null, 2));
433
- }
555
+ {
556
+ name: 'artifact_get',
557
+ description:
558
+ 'Read an artifact (latest version or specific version).',
559
+ inputSchema: {
560
+ type: 'object',
561
+ properties: {
562
+ artifactId: { type: 'string', description: 'Artifact ID to retrieve' },
563
+ version: { type: 'number', description: 'Specific version number (omit for latest)' },
564
+ team: { type: 'string', description: 'Team name (default: "default")' },
565
+ },
566
+ required: ['artifactId'],
567
+ },
568
+ },
434
569
 
435
- async function handleResume(args) {
436
- const response = await manager.resume(args.name, args.message);
437
- return textResult(JSON.stringify({
438
- session_name: args.name,
439
- status: 'resumed',
440
- response: response?.text || null,
441
- cost: response?.cost || 0,
442
- turns: response?.turns || 0,
443
- }, null, 2));
444
- }
570
+ {
571
+ name: 'artifact_list',
572
+ description:
573
+ 'List all artifacts with optional filters by type, publisher, or tag.',
574
+ inputSchema: {
575
+ type: 'object',
576
+ properties: {
577
+ type: { type: 'string', description: 'Filter by artifact type' },
578
+ publisher: { type: 'string', description: 'Filter by publisher session name' },
579
+ tag: { type: 'string', description: 'Filter by tag' },
580
+ team: { type: 'string', description: 'Team name (default: "default")' },
581
+ },
582
+ },
583
+ },
445
584
 
446
- function handlePause(args) {
447
- manager.pause(args.name);
448
- return textResult(JSON.stringify({
449
- session_name: args.name,
450
- status: 'paused',
451
- }, null, 2));
452
- }
585
+ {
586
+ name: 'artifact_history',
587
+ description:
588
+ 'View the version history of an artifact.',
589
+ inputSchema: {
590
+ type: 'object',
591
+ properties: {
592
+ artifactId: { type: 'string', description: 'Artifact ID' },
593
+ team: { type: 'string', description: 'Team name (default: "default")' },
594
+ },
595
+ required: ['artifactId'],
596
+ },
597
+ },
453
598
 
454
- async function handleFork(args) {
455
- const { session, response } = await manager.fork(args.name, args.new_name, {
456
- message: args.message,
457
- model: args.model,
458
- });
599
+ // ── Layer 2: Contracts (8 tools) ─────────────────────────────────────────
600
+ {
601
+ name: 'contract_create',
602
+ description:
603
+ 'Create a task contract and assign it to ANY teammate (peer-to-peer). Contracts define work with ' +
604
+ 'inputs, outputs, dependencies, and acceptance criteria. The dependency resolver automatically advances ' +
605
+ 'contracts when their dependencies are satisfied.',
606
+ inputSchema: {
607
+ type: 'object',
608
+ properties: {
609
+ contractId: { type: 'string', description: 'Unique contract ID (e.g. "build-auth-api")' },
610
+ title: { type: 'string', description: 'Human-readable title' },
611
+ assignee: { type: 'string', description: 'Session name who will do the work' },
612
+ assigner: { type: 'string', description: 'Session name who created the contract' },
613
+ description: { type: 'string', description: 'Detailed description of the work' },
614
+ inputs: { type: 'object', description: 'Input artifacts and context for the assignee' },
615
+ expectedOutputs: { type: 'array', description: 'What the assignee should produce' },
616
+ dependencies: { type: 'array', description: 'Contract/artifact dependencies that must be satisfied first' },
617
+ acceptanceCriteria: { type: 'array', description: 'How we know the work is done' },
618
+ autoComplete: { type: 'boolean', description: 'Auto-complete when criteria met (default: true)' },
619
+ timeoutMs: { type: 'number', description: 'Timeout in milliseconds (null = no timeout)' },
620
+ maxRetries: { type: 'number', description: 'Maximum reopen attempts (default: 3)' },
621
+ priority: { type: 'string', enum: ['low', 'normal', 'high', 'urgent'], description: 'Priority level (default: normal)' },
622
+ team: { type: 'string', description: 'Team name (default: "default")' },
623
+ },
624
+ required: ['contractId', 'title', 'assignee', 'assigner'],
625
+ },
626
+ },
459
627
 
460
- return textResult(JSON.stringify({
461
- source: args.name,
462
- forked_as: args.new_name,
628
+ {
629
+ name: 'contract_start',
630
+ description:
631
+ 'Start working on a contract. Transitions status from ready to in_progress and returns the contract ' +
632
+ 'details with resolved input artifacts.',
633
+ inputSchema: {
634
+ type: 'object',
635
+ properties: {
636
+ contractId: { type: 'string', description: 'Contract ID to start' },
637
+ team: { type: 'string', description: 'Team name (default: "default")' },
638
+ },
639
+ required: ['contractId'],
640
+ },
641
+ },
642
+
643
+ {
644
+ name: 'contract_complete',
645
+ description:
646
+ 'Mark a contract as completed. This triggers dependency resolution for downstream contracts and ' +
647
+ 'evaluates reactive pipelines.',
648
+ inputSchema: {
649
+ type: 'object',
650
+ properties: {
651
+ contractId: { type: 'string', description: 'Contract ID to complete' },
652
+ summary: { type: 'string', description: 'Summary of what was done' },
653
+ publishedArtifacts: { type: 'array', items: { type: 'string' }, description: 'Artifact IDs that were published' },
654
+ team: { type: 'string', description: 'Team name (default: "default")' },
655
+ },
656
+ required: ['contractId'],
657
+ },
658
+ },
659
+
660
+ {
661
+ name: 'contract_fail',
662
+ description:
663
+ 'Mark a contract as failed. Notifies the assigner and triggers dependency resolution.',
664
+ inputSchema: {
665
+ type: 'object',
666
+ properties: {
667
+ contractId: { type: 'string', description: 'Contract ID to fail' },
668
+ reason: { type: 'string', description: 'Why the contract failed' },
669
+ team: { type: 'string', description: 'Team name (default: "default")' },
670
+ },
671
+ required: ['contractId', 'reason'],
672
+ },
673
+ },
674
+
675
+ {
676
+ name: 'contract_reopen',
677
+ description:
678
+ 'Reopen a failed or completed contract for retry. Increments retryCount and transitions back to ready. ' +
679
+ 'Rejects if retryCount >= maxRetries.',
680
+ inputSchema: {
681
+ type: 'object',
682
+ properties: {
683
+ contractId: { type: 'string', description: 'Contract ID to reopen' },
684
+ reason: { type: 'string', description: 'Why we are reopening' },
685
+ newInputs: { type: 'object', description: 'Optional updated inputs' },
686
+ team: { type: 'string', description: 'Team name (default: "default")' },
687
+ },
688
+ required: ['contractId'],
689
+ },
690
+ },
691
+
692
+ {
693
+ name: 'contract_get',
694
+ description:
695
+ 'Get full details of a contract including status, inputs, outputs, dependencies, and acceptance criteria.',
696
+ inputSchema: {
697
+ type: 'object',
698
+ properties: {
699
+ contractId: { type: 'string', description: 'Contract ID' },
700
+ team: { type: 'string', description: 'Team name (default: "default")' },
701
+ },
702
+ required: ['contractId'],
703
+ },
704
+ },
705
+
706
+ {
707
+ name: 'contract_list',
708
+ description:
709
+ 'List all contracts with optional filters by status, assignee, or assigner.',
710
+ inputSchema: {
711
+ type: 'object',
712
+ properties: {
713
+ status: { type: 'string', enum: ['pending', 'ready', 'in_progress', 'completed', 'failed'], description: 'Filter by status' },
714
+ assignee: { type: 'string', description: 'Filter by assignee session name' },
715
+ assigner: { type: 'string', description: 'Filter by assigner session name' },
716
+ team: { type: 'string', description: 'Team name (default: "default")' },
717
+ },
718
+ },
719
+ },
720
+
721
+ {
722
+ name: 'contract_reassign',
723
+ description:
724
+ 'Reassign a contract to a different session.',
725
+ inputSchema: {
726
+ type: 'object',
727
+ properties: {
728
+ contractId: { type: 'string', description: 'Contract ID' },
729
+ newAssignee: { type: 'string', description: 'New assignee session name' },
730
+ team: { type: 'string', description: 'Team name (default: "default")' },
731
+ },
732
+ required: ['contractId', 'newAssignee'],
733
+ },
734
+ },
735
+
736
+ // ── Layer 3: Lineage (4 tools) ───────────────────────────────────────────
737
+ {
738
+ name: 'artifact_lineage',
739
+ description:
740
+ 'Query the artifact lineage graph. Upstream shows what this was derived from. Downstream shows what ' +
741
+ 'depends on this. Returns the full chain recursively.',
742
+ inputSchema: {
743
+ type: 'object',
744
+ properties: {
745
+ artifactId: { type: 'string', description: 'Artifact ID' },
746
+ direction: { type: 'string', enum: ['upstream', 'downstream'], description: 'Which direction to traverse' },
747
+ version: { type: 'number', description: 'Specific version (omit for latest)' },
748
+ team: { type: 'string', description: 'Team name (default: "default")' },
749
+ },
750
+ required: ['artifactId', 'direction'],
751
+ },
752
+ },
753
+
754
+ {
755
+ name: 'artifact_impact',
756
+ description:
757
+ 'Analyze the impact radius: "If I change this artifact, what downstream artifacts and contracts break?" ' +
758
+ 'Returns all affected artifacts and contracts with impact depth.',
759
+ inputSchema: {
760
+ type: 'object',
761
+ properties: {
762
+ artifactId: { type: 'string', description: 'Artifact ID to analyze' },
763
+ team: { type: 'string', description: 'Team name (default: "default")' },
764
+ },
765
+ required: ['artifactId'],
766
+ },
767
+ },
768
+
769
+ {
770
+ name: 'artifact_stale',
771
+ description:
772
+ 'Find all stale artifacts — artifacts derived from sources that have been updated to newer versions. ' +
773
+ 'This helps identify what needs to be regenerated after changes.',
774
+ inputSchema: {
775
+ type: 'object',
776
+ properties: {
777
+ team: { type: 'string', description: 'Team name (default: "default")' },
778
+ },
779
+ },
780
+ },
781
+
782
+ {
783
+ name: 'team_audit',
784
+ description:
785
+ 'Get the full audit trail for an artifact: who created it, from what inputs, under which contract, ' +
786
+ 'at what time. Follows the provenance chain upstream to the root.',
787
+ inputSchema: {
788
+ type: 'object',
789
+ properties: {
790
+ artifactId: { type: 'string', description: 'Artifact ID' },
791
+ version: { type: 'number', description: 'Specific version (omit for latest)' },
792
+ team: { type: 'string', description: 'Team name (default: "default")' },
793
+ },
794
+ required: ['artifactId'],
795
+ },
796
+ },
797
+
798
+ // ── Layer 3: Pipelines (4 tools) ─────────────────────────────────────────
799
+ {
800
+ name: 'pipeline_create',
801
+ description:
802
+ 'Create a reactive pipeline: "When X happens, automatically do Y." Pipelines contain trigger/condition/action ' +
803
+ 'rules that fire when artifacts are published or contracts change status. This enables self-healing CI loops.',
804
+ inputSchema: {
805
+ type: 'object',
806
+ properties: {
807
+ pipelineId: { type: 'string', description: 'Unique pipeline ID (e.g. "ci-loop")' },
808
+ rules: { type: 'array', description: 'Array of trigger/condition/action rules' },
809
+ owner: { type: 'string', description: 'Session name who owns this pipeline' },
810
+ enabled: { type: 'boolean', description: 'Whether pipeline is active (default: true)' },
811
+ team: { type: 'string', description: 'Team name (default: "default")' },
812
+ },
813
+ required: ['pipelineId', 'rules'],
814
+ },
815
+ },
816
+
817
+ {
818
+ name: 'pipeline_list',
819
+ description:
820
+ 'List all pipelines with optional filter by owner. Shows execution counts and last fired times.',
821
+ inputSchema: {
822
+ type: 'object',
823
+ properties: {
824
+ owner: { type: 'string', description: 'Filter by owner session name' },
825
+ team: { type: 'string', description: 'Team name (default: "default")' },
826
+ },
827
+ },
828
+ },
829
+
830
+ {
831
+ name: 'pipeline_pause',
832
+ description:
833
+ 'Pause a pipeline (disable without deleting). The pipeline stops evaluating rules.',
834
+ inputSchema: {
835
+ type: 'object',
836
+ properties: {
837
+ pipelineId: { type: 'string', description: 'Pipeline ID to pause' },
838
+ team: { type: 'string', description: 'Team name (default: "default")' },
839
+ },
840
+ required: ['pipelineId'],
841
+ },
842
+ },
843
+
844
+ {
845
+ name: 'pipeline_resume',
846
+ description:
847
+ 'Resume a paused pipeline. It will start evaluating rules again on new events.',
848
+ inputSchema: {
849
+ type: 'object',
850
+ properties: {
851
+ pipelineId: { type: 'string', description: 'Pipeline ID to resume' },
852
+ team: { type: 'string', description: 'Team name (default: "default")' },
853
+ },
854
+ required: ['pipelineId'],
855
+ },
856
+ },
857
+
858
+ // ── Layer 3: Snapshots (4 tools) ─────────────────────────────────────────
859
+ {
860
+ name: 'team_snapshot',
861
+ description:
862
+ 'Capture a snapshot of the entire team state (contracts, artifacts, pipelines). Use this before ' +
863
+ 'risky operations or at major milestones.',
864
+ inputSchema: {
865
+ type: 'object',
866
+ properties: {
867
+ snapshotId: { type: 'string', description: 'Unique snapshot ID (e.g. "pre-work", "all-complete")' },
868
+ label: { type: 'string', description: 'Short label for this snapshot' },
869
+ description: { type: 'string', description: 'Longer description' },
870
+ team: { type: 'string', description: 'Team name (default: "default")' },
871
+ },
872
+ required: ['snapshotId'],
873
+ },
874
+ },
875
+
876
+ {
877
+ name: 'team_snapshot_list',
878
+ description:
879
+ 'List all snapshots with timestamp, label, and summary of what was captured.',
880
+ inputSchema: {
881
+ type: 'object',
882
+ properties: {
883
+ team: { type: 'string', description: 'Team name (default: "default")' },
884
+ },
885
+ },
886
+ },
887
+
888
+ {
889
+ name: 'team_rollback',
890
+ description:
891
+ 'Rollback to a previous snapshot. Resets contract and pipeline state. Artifact version files are ' +
892
+ 'preserved on disk but can be marked as rolled-back in the index.',
893
+ inputSchema: {
894
+ type: 'object',
895
+ properties: {
896
+ snapshotId: { type: 'string', description: 'Snapshot ID to rollback to' },
897
+ preserveArtifacts: { type: 'boolean', description: 'Keep artifact index entries (default: true)' },
898
+ team: { type: 'string', description: 'Team name (default: "default")' },
899
+ },
900
+ required: ['snapshotId'],
901
+ },
902
+ },
903
+
904
+ {
905
+ name: 'team_replay',
906
+ description:
907
+ 'Replay from a snapshot with optional overrides to contract inputs. Rollbacks to the snapshot, ' +
908
+ 'applies modifications, then re-triggers dependency resolution. Use this to test "what if" scenarios.',
909
+ inputSchema: {
910
+ type: 'object',
911
+ properties: {
912
+ snapshotId: { type: 'string', description: 'Snapshot ID to replay from' },
913
+ overrides: { type: 'object', description: 'Map of contractId -> { inputs: {...} } to override' },
914
+ team: { type: 'string', description: 'Team name (default: "default")' },
915
+ },
916
+ required: ['snapshotId'],
917
+ },
918
+ },
919
+
920
+ // ── Session Continuity (Layer 0) ──────────────────────────────────────
921
+ {
922
+ name: 'continuity_snapshot',
923
+ description: 'Capture a lightweight snapshot of the current project state (git state, file tree, working context). Use on session end.',
924
+ inputSchema: {
925
+ type: 'object',
926
+ properties: {
927
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
928
+ sessionName: { type: 'string', description: 'Name of the session capturing this snapshot' },
929
+ taskSummary: { type: 'string', description: 'What you were working on' },
930
+ activeFiles: { type: 'array', items: { type: 'string' }, description: 'Files read/edited this session' },
931
+ openQuestions: { type: 'array', items: { type: 'string' }, description: 'Unresolved questions' }
932
+ },
933
+ required: ['projectPath', 'sessionName']
934
+ }
935
+ },
936
+ {
937
+ name: 'continuity_briefing',
938
+ description: 'Generate a diff-aware session briefing. Shows what changed since last session, previous context, decisions, patterns. Use on session start.',
939
+ inputSchema: {
940
+ type: 'object',
941
+ properties: {
942
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
943
+ maxTokens: { type: 'number', description: 'Max briefing length in characters (default: 4000)' },
944
+ includeDecisions: { type: 'boolean', description: 'Include recent decisions (default: true)' },
945
+ includePatterns: { type: 'boolean', description: 'Include pattern rules (default: true)' }
946
+ },
947
+ required: ['projectPath']
948
+ }
949
+ },
950
+ {
951
+ name: 'continuity_status',
952
+ description: 'Quick status check: what changed since the last snapshot. Lighter than a full briefing.',
953
+ inputSchema: {
954
+ type: 'object',
955
+ properties: {
956
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' }
957
+ },
958
+ required: ['projectPath']
959
+ }
960
+ },
961
+ {
962
+ name: 'continuity_diff',
963
+ description: 'Detailed diff between two snapshots or between latest snapshot and current state.',
964
+ inputSchema: {
965
+ type: 'object',
966
+ properties: {
967
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
968
+ fromSnapshot: { type: 'string', description: 'Source snapshot ID (default: latest)' },
969
+ toSnapshot: { type: 'string', description: 'Target snapshot ID (default: current state)' }
970
+ },
971
+ required: ['projectPath']
972
+ }
973
+ },
974
+ {
975
+ name: 'continuity_history',
976
+ description: 'List all snapshots for a project, newest first.',
977
+ inputSchema: {
978
+ type: 'object',
979
+ properties: {
980
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
981
+ limit: { type: 'number', description: 'Max snapshots to return (default: 20)' }
982
+ },
983
+ required: ['projectPath']
984
+ }
985
+ },
986
+ {
987
+ name: 'continuity_stale_check',
988
+ description: 'Check if your previous session context is stale. Shows warnings about changed files, branch switches, etc.',
989
+ inputSchema: {
990
+ type: 'object',
991
+ properties: {
992
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' }
993
+ },
994
+ required: ['projectPath']
995
+ }
996
+ },
997
+ {
998
+ name: 'decision_add',
999
+ description: 'Record an architectural or design decision in the project decision journal.',
1000
+ inputSchema: {
1001
+ type: 'object',
1002
+ properties: {
1003
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
1004
+ decision: { type: 'string', description: 'The decision made (e.g., "Use bcrypt over argon2")' },
1005
+ reason: { type: 'string', description: 'Why this decision was made' },
1006
+ files: { type: 'array', items: { type: 'string' }, description: 'Related file paths' },
1007
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags for categorization' }
1008
+ },
1009
+ required: ['projectPath', 'decision']
1010
+ }
1011
+ },
1012
+ {
1013
+ name: 'decision_list',
1014
+ description: 'List recent decisions from the project decision journal.',
1015
+ inputSchema: {
1016
+ type: 'object',
1017
+ properties: {
1018
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
1019
+ limit: { type: 'number', description: 'Max decisions to return (default: 50)' },
1020
+ tag: { type: 'string', description: 'Filter by tag' }
1021
+ },
1022
+ required: ['projectPath']
1023
+ }
1024
+ },
1025
+ {
1026
+ name: 'decision_search',
1027
+ description: 'Search decisions by keyword across decision text, reasoning, and context.',
1028
+ inputSchema: {
1029
+ type: 'object',
1030
+ properties: {
1031
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
1032
+ keyword: { type: 'string', description: 'Search keyword (case-insensitive)' }
1033
+ },
1034
+ required: ['projectPath', 'keyword']
1035
+ }
1036
+ },
1037
+ {
1038
+ name: 'pattern_add',
1039
+ description: 'Add a coding pattern rule. Patterns are corrections that persist across sessions (e.g., "Always use enum constants for status comparisons").',
1040
+ inputSchema: {
1041
+ type: 'object',
1042
+ properties: {
1043
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
1044
+ rule: { type: 'string', description: 'The pattern rule' },
1045
+ context: { type: 'string', description: 'Where this pattern applies (e.g., "auth module")' },
1046
+ example: { type: 'string', description: 'Code example or explanation' },
1047
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags for categorization' }
1048
+ },
1049
+ required: ['projectPath', 'rule']
1050
+ }
1051
+ },
1052
+ {
1053
+ name: 'pattern_list',
1054
+ description: 'List all coding patterns for a project.',
1055
+ inputSchema: {
1056
+ type: 'object',
1057
+ properties: {
1058
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
1059
+ tag: { type: 'string', description: 'Filter by tag' }
1060
+ },
1061
+ required: ['projectPath']
1062
+ }
1063
+ },
1064
+ {
1065
+ name: 'pattern_remove',
1066
+ description: 'Remove a coding pattern by ID.',
1067
+ inputSchema: {
1068
+ type: 'object',
1069
+ properties: {
1070
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
1071
+ patternId: { type: 'string', description: 'Pattern ID to remove' }
1072
+ },
1073
+ required: ['projectPath', 'patternId']
1074
+ }
1075
+ }
1076
+ ];
1077
+
1078
+ // =============================================================================
1079
+ // Tool Handlers — execute each tool and return result
1080
+ // =============================================================================
1081
+
1082
+ /**
1083
+ * Execute a tool by name with given arguments.
1084
+ * Returns { content: [{ type: "text", text: "..." }], isError?: true }
1085
+ */
1086
+ async function executeTool(toolName, args) {
1087
+ try {
1088
+ switch (toolName) {
1089
+ // ── Session Management ────────────────────────────────────────────
1090
+ case 'spawn_session':
1091
+ return await handleSpawn(args);
1092
+ case 'send_message':
1093
+ return await handleSend(args);
1094
+ case 'resume_session':
1095
+ return await handleResume(args);
1096
+ case 'pause_session':
1097
+ return handlePause(args);
1098
+ case 'fork_session':
1099
+ return await handleFork(args);
1100
+ case 'stop_session':
1101
+ return handleStop(args);
1102
+ case 'kill_session':
1103
+ return handleKill(args);
1104
+
1105
+ // ── Information ───────────────────────────────────────────────────
1106
+ case 'get_session_status':
1107
+ return handleStatus(args);
1108
+ case 'get_last_output':
1109
+ return handleLastOutput(args);
1110
+ case 'list_sessions':
1111
+ return handleList(args);
1112
+ case 'get_history':
1113
+ return handleHistory(args);
1114
+ case 'delete_session':
1115
+ return handleDelete(args);
1116
+
1117
+ // ── Delegate ──────────────────────────────────────────────────────
1118
+ case 'delegate_task':
1119
+ return await handleDelegate(args);
1120
+ case 'continue_task':
1121
+ return await handleContinue(args);
1122
+ case 'finish_task':
1123
+ return handleFinish(args);
1124
+ case 'abort_task':
1125
+ return handleAbort(args);
1126
+
1127
+ // ── Orchestrator Guide ────────────────────────────────────────────
1128
+ case 'get_orchestrator_guide':
1129
+ return handleGetOrchestratorGuide(args);
1130
+
1131
+ // ── Batch ─────────────────────────────────────────────────────────
1132
+ case 'batch_spawn':
1133
+ return await handleBatch(args);
1134
+
1135
+ // ══════════════════════════════════════════════════════════════════
1136
+ // Team Hub Tools
1137
+ // ══════════════════════════════════════════════════════════════════
1138
+
1139
+ // ── Layer 1: Chat ─────────────────────────────────────────────────
1140
+ case 'team_spawn':
1141
+ return await handleTeamSpawn(args);
1142
+ case 'team_send_message':
1143
+ return handleTeamSendMessage(args);
1144
+ case 'team_broadcast':
1145
+ return handleTeamBroadcast(args);
1146
+ case 'team_check_inbox':
1147
+ return handleTeamCheckInbox(args);
1148
+ case 'team_ask':
1149
+ return await handleTeamAsk(args);
1150
+ case 'team_reply':
1151
+ return handleTeamReply(args);
1152
+ case 'team_roster':
1153
+ return handleTeamRoster(args);
1154
+ case 'team_update_status':
1155
+ return handleTeamUpdateStatus(args);
1156
+
1157
+ // ── Layer 2: Artifacts ────────────────────────────────────────────
1158
+ case 'artifact_publish':
1159
+ return await handleArtifactPublish(args);
1160
+ case 'artifact_get':
1161
+ return handleArtifactGet(args);
1162
+ case 'artifact_list':
1163
+ return handleArtifactList(args);
1164
+ case 'artifact_history':
1165
+ return handleArtifactHistory(args);
1166
+
1167
+ // ── Layer 2: Contracts ────────────────────────────────────────────
1168
+ case 'contract_create':
1169
+ return await handleContractCreate(args);
1170
+ case 'contract_start':
1171
+ return handleContractStart(args);
1172
+ case 'contract_complete':
1173
+ return await handleContractComplete(args);
1174
+ case 'contract_fail':
1175
+ return await handleContractFail(args);
1176
+ case 'contract_reopen':
1177
+ return await handleContractReopen(args);
1178
+ case 'contract_get':
1179
+ return handleContractGet(args);
1180
+ case 'contract_list':
1181
+ return handleContractList(args);
1182
+ case 'contract_reassign':
1183
+ return handleContractReassign(args);
1184
+
1185
+ // ── Layer 3: Lineage ──────────────────────────────────────────────
1186
+ case 'artifact_lineage':
1187
+ return handleArtifactLineage(args);
1188
+ case 'artifact_impact':
1189
+ return handleArtifactImpact(args);
1190
+ case 'artifact_stale':
1191
+ return handleArtifactStale(args);
1192
+ case 'team_audit':
1193
+ return handleTeamAudit(args);
1194
+
1195
+ // ── Layer 3: Pipelines ────────────────────────────────────────────
1196
+ case 'pipeline_create':
1197
+ return handlePipelineCreate(args);
1198
+ case 'pipeline_list':
1199
+ return handlePipelineList(args);
1200
+ case 'pipeline_pause':
1201
+ return handlePipelinePause(args);
1202
+ case 'pipeline_resume':
1203
+ return handlePipelineResume(args);
1204
+
1205
+ // ── Layer 3: Snapshots ────────────────────────────────────────────
1206
+ case 'team_snapshot':
1207
+ return handleTeamSnapshot(args);
1208
+ case 'team_snapshot_list':
1209
+ return handleTeamSnapshotList(args);
1210
+ case 'team_rollback':
1211
+ return handleTeamRollback(args);
1212
+ case 'team_replay':
1213
+ return await handleTeamReplay(args);
1214
+
1215
+ // ── Session Continuity (Layer 0) handlers ──
1216
+ case 'continuity_snapshot': {
1217
+ const snap = new SessionSnapshot(args.projectPath);
1218
+ const result = snap.capture(args.sessionName, {
1219
+ activeFiles: args.activeFiles,
1220
+ taskSummary: args.taskSummary,
1221
+ openQuestions: args.openQuestions
1222
+ });
1223
+ return textResult(JSON.stringify(result, null, 2));
1224
+ }
1225
+ case 'continuity_briefing': {
1226
+ const gen = new BriefingGenerator(args.projectPath);
1227
+ const result = gen.generate({
1228
+ maxTokens: args.maxTokens,
1229
+ includeDecisions: args.includeDecisions,
1230
+ includePatterns: args.includePatterns
1231
+ });
1232
+ return textResult(result.markdown);
1233
+ }
1234
+ case 'continuity_status': {
1235
+ const diff = new DiffEngine(args.projectPath);
1236
+ const result = diff.diffFromLatest();
1237
+ return textResult(JSON.stringify(result, null, 2));
1238
+ }
1239
+ case 'continuity_diff': {
1240
+ const diff = new DiffEngine(args.projectPath);
1241
+ let result;
1242
+ if (args.fromSnapshot && args.toSnapshot) {
1243
+ result = diff.diffBetween(args.fromSnapshot, args.toSnapshot);
1244
+ } else {
1245
+ result = diff.diffFromLatest();
1246
+ }
1247
+ return textResult(JSON.stringify(result, null, 2));
1248
+ }
1249
+ case 'continuity_history': {
1250
+ const snap = new SessionSnapshot(args.projectPath);
1251
+ const result = snap.list(args.limit || 20);
1252
+ return textResult(JSON.stringify(result, null, 2));
1253
+ }
1254
+ case 'continuity_stale_check': {
1255
+ const detector = new StaleDetector(args.projectPath);
1256
+ const result = detector.check();
1257
+ return textResult(JSON.stringify(result, null, 2));
1258
+ }
1259
+ case 'decision_add': {
1260
+ const journal = new DecisionJournal(args.projectPath);
1261
+ const result = journal.add({
1262
+ decision: args.decision,
1263
+ reason: args.reason,
1264
+ files: args.files,
1265
+ tags: args.tags
1266
+ });
1267
+ return textResult(JSON.stringify(result, null, 2));
1268
+ }
1269
+ case 'decision_list': {
1270
+ const journal = new DecisionJournal(args.projectPath);
1271
+ const result = journal.list({ limit: args.limit, tag: args.tag });
1272
+ return textResult(JSON.stringify(result, null, 2));
1273
+ }
1274
+ case 'decision_search': {
1275
+ const journal = new DecisionJournal(args.projectPath);
1276
+ const result = journal.search(args.keyword);
1277
+ return textResult(JSON.stringify(result, null, 2));
1278
+ }
1279
+ case 'pattern_add': {
1280
+ const registry = new PatternRegistry(args.projectPath);
1281
+ const result = registry.add({
1282
+ rule: args.rule,
1283
+ context: args.context,
1284
+ example: args.example,
1285
+ tags: args.tags
1286
+ });
1287
+ return textResult(JSON.stringify(result, null, 2));
1288
+ }
1289
+ case 'pattern_list': {
1290
+ const registry = new PatternRegistry(args.projectPath);
1291
+ const result = registry.list({ tag: args.tag });
1292
+ return textResult(JSON.stringify(result, null, 2));
1293
+ }
1294
+ case 'pattern_remove': {
1295
+ const registry = new PatternRegistry(args.projectPath);
1296
+ const result = registry.remove(args.patternId);
1297
+ return textResult(JSON.stringify(result, null, 2));
1298
+ }
1299
+
1300
+ default:
1301
+ return errorResult(`Unknown tool: ${toolName}`);
1302
+ }
1303
+ } catch (err) {
1304
+ return errorResult(err.message);
1305
+ }
1306
+ }
1307
+
1308
+ // ── Session Handlers ──────────────────────────────────────────────────────
1309
+
1310
+ async function handleSpawn(args) {
1311
+ const { session, response } = await manager.spawn(args.name, {
1312
+ prompt: args.prompt,
1313
+ model: args.model,
1314
+ workDir: args.work_dir,
1315
+ permissionMode: args.permission_mode,
1316
+ allowedTools: args.allowed_tools,
1317
+ systemPrompt: args.system_prompt,
1318
+ maxBudget: args.max_budget,
1319
+ agent: args.agent,
1320
+ });
1321
+
1322
+ const result = {
1323
+ session_name: session.name,
1324
+ status: session.status,
1325
+ model: session.model,
1326
+ session_id: session.id,
1327
+ };
1328
+
1329
+ if (response) {
1330
+ result.response = response.text;
1331
+ result.cost = response.cost;
1332
+ result.turns = response.turns;
1333
+ result.duration_ms = response.duration;
1334
+ } else {
1335
+ result.message = 'Session started (idle). Send a message with send_message.';
1336
+ }
1337
+
1338
+ return textResult(JSON.stringify(result, null, 2));
1339
+ }
1340
+
1341
+ async function handleSend(args) {
1342
+ // Auto-resume if session is not alive
1343
+ if (!manager.sessions.has(args.name)) {
1344
+ log(`Session "${args.name}" not alive. Auto-resuming...`);
1345
+ const response = await manager.resume(args.name, args.message);
1346
+ return textResult(JSON.stringify({
1347
+ session_name: args.name,
1348
+ auto_resumed: true,
1349
+ response: response?.text || '',
1350
+ cost: response?.cost || 0,
1351
+ turns: response?.turns || 0,
1352
+ duration_ms: response?.duration || 0,
1353
+ }, null, 2));
1354
+ }
1355
+
1356
+ const response = await manager.send(args.name, args.message);
1357
+ return textResult(JSON.stringify({
1358
+ session_name: args.name,
1359
+ response: response.text,
1360
+ cost: response.cost,
1361
+ turns: response.turns,
1362
+ duration_ms: response.duration,
1363
+ }, null, 2));
1364
+ }
1365
+
1366
+ async function handleResume(args) {
1367
+ const response = await manager.resume(args.name, args.message);
1368
+ return textResult(JSON.stringify({
1369
+ session_name: args.name,
1370
+ status: 'resumed',
1371
+ response: response?.text || null,
1372
+ cost: response?.cost || 0,
1373
+ turns: response?.turns || 0,
1374
+ }, null, 2));
1375
+ }
1376
+
1377
+ function handlePause(args) {
1378
+ manager.pause(args.name);
1379
+ return textResult(JSON.stringify({
1380
+ session_name: args.name,
1381
+ status: 'paused',
1382
+ }, null, 2));
1383
+ }
1384
+
1385
+ async function handleFork(args) {
1386
+ const { session, response } = await manager.fork(args.name, args.new_name, {
1387
+ message: args.message,
1388
+ model: args.model,
1389
+ });
1390
+
1391
+ return textResult(JSON.stringify({
1392
+ source: args.name,
1393
+ forked_as: args.new_name,
463
1394
  status: session.status,
464
1395
  response: response?.text || '',
465
1396
  cost: response?.cost || 0,
@@ -467,174 +1398,1047 @@ async function handleFork(args) {
467
1398
  }, null, 2));
468
1399
  }
469
1400
 
470
- function handleStop(args) {
471
- manager.stop(args.name);
472
- return textResult(JSON.stringify({
473
- session_name: args.name,
474
- status: 'stopped',
475
- message: 'Session stopped. Can be resumed later with resume_session.',
476
- }, null, 2));
1401
+ function handleStop(args) {
1402
+ manager.stop(args.name);
1403
+ return textResult(JSON.stringify({
1404
+ session_name: args.name,
1405
+ status: 'stopped',
1406
+ message: 'Session stopped. Can be resumed later with resume_session.',
1407
+ }, null, 2));
1408
+ }
1409
+
1410
+ function handleKill(args) {
1411
+ manager.kill(args.name);
1412
+ return textResult(JSON.stringify({
1413
+ session_name: args.name,
1414
+ status: 'killed',
1415
+ }, null, 2));
1416
+ }
1417
+
1418
+ // ── Information Handlers ──────────────────────────────────────────────────
1419
+
1420
+ function handleStatus(args) {
1421
+ const info = manager.status(args.name);
1422
+ return textResult(JSON.stringify({
1423
+ name: info.name,
1424
+ status: info.status,
1425
+ model: info.model,
1426
+ session_id: info.claudeSessionId || info.id,
1427
+ work_dir: info.workDir,
1428
+ total_cost: info.totalCostUsd || 0,
1429
+ total_turns: info.totalTurns || 0,
1430
+ interactions: info.interactionCount || (info.interactions || []).length,
1431
+ pid: info.pid || null,
1432
+ }, null, 2));
1433
+ }
1434
+
1435
+ function handleLastOutput(args) {
1436
+ const last = manager.lastOutput(args.name);
1437
+ if (!last) {
1438
+ return textResult(JSON.stringify({ session_name: args.name, output: null, message: 'No output yet.' }, null, 2));
1439
+ }
1440
+ return textResult(JSON.stringify({
1441
+ session_name: args.name,
1442
+ prompt: last.prompt,
1443
+ response: last.response,
1444
+ cost: last.cost,
1445
+ timestamp: last.timestamp,
1446
+ }, null, 2));
1447
+ }
1448
+
1449
+ function handleList(args) {
1450
+ const sessions = manager.list(args.status);
1451
+ const summary = sessions.map(s => ({
1452
+ name: s.name,
1453
+ status: s.status,
1454
+ model: s.model,
1455
+ total_cost: s.totalCostUsd || 0,
1456
+ total_turns: s.totalTurns || 0,
1457
+ interactions: s.interactionCount || (s.interactions || []).length,
1458
+ }));
1459
+ return textResult(JSON.stringify({ total: summary.length, sessions: summary }, null, 2));
1460
+ }
1461
+
1462
+ function handleHistory(args) {
1463
+ const hist = manager.history(args.name);
1464
+ return textResult(JSON.stringify({
1465
+ session_name: args.name,
1466
+ interaction_count: hist.length,
1467
+ interactions: hist.map((h, i) => ({
1468
+ index: i,
1469
+ prompt: h.prompt,
1470
+ response: h.response,
1471
+ cost: h.cost,
1472
+ turns: h.turns,
1473
+ duration_ms: h.duration,
1474
+ timestamp: h.timestamp,
1475
+ })),
1476
+ }, null, 2));
1477
+ }
1478
+
1479
+ function handleDelete(args) {
1480
+ manager.delete(args.name);
1481
+ return textResult(JSON.stringify({
1482
+ session_name: args.name,
1483
+ status: 'deleted',
1484
+ message: 'Session permanently deleted.',
1485
+ }, null, 2));
1486
+ }
1487
+
1488
+ // ── Delegate Handlers ─────────────────────────────────────────────────────
1489
+
1490
+ async function handleDelegate(args) {
1491
+ // Create or reuse a delegate instance for this session
1492
+ let delegate = delegates.get(args.name);
1493
+ if (!delegate) {
1494
+ delegate = new Delegate(manager);
1495
+ delegates.set(args.name, delegate);
1496
+ }
1497
+
1498
+ const result = await delegate.run(args.name, {
1499
+ task: args.task,
1500
+ model: args.model,
1501
+ preset: args.preset,
1502
+ workDir: args.work_dir,
1503
+ maxCost: args.max_cost,
1504
+ maxTurns: args.max_turns,
1505
+ context: args.context,
1506
+ systemPrompt: args.system_prompt,
1507
+ agent: args.agent,
1508
+ safety: args.safety,
1509
+ });
1510
+
1511
+ return textResult(JSON.stringify(result, null, 2));
1512
+ }
1513
+
1514
+ async function handleContinue(args) {
1515
+ // Get or create delegate for this session
1516
+ let delegate = delegates.get(args.name);
1517
+ if (!delegate) {
1518
+ delegate = new Delegate(manager);
1519
+ delegates.set(args.name, delegate);
1520
+ }
1521
+
1522
+ const result = await delegate.continue(args.name, args.message);
1523
+ return textResult(JSON.stringify(result, null, 2));
1524
+ }
1525
+
1526
+ function handleFinish(args) {
1527
+ const delegate = delegates.get(args.name);
1528
+ if (delegate) {
1529
+ delegate.finish(args.name);
1530
+ delegates.delete(args.name);
1531
+ } else {
1532
+ // No delegate — just stop the session
1533
+ try { manager.stop(args.name); } catch (e) {}
1534
+ }
1535
+ return textResult(JSON.stringify({
1536
+ session_name: args.name,
1537
+ status: 'finished',
1538
+ message: 'Task finished. Session stopped.',
1539
+ }, null, 2));
1540
+ }
1541
+
1542
+ function handleAbort(args) {
1543
+ const delegate = delegates.get(args.name);
1544
+ if (delegate) {
1545
+ delegate.abort(args.name);
1546
+ delegates.delete(args.name);
1547
+ } else {
1548
+ try { manager.kill(args.name); } catch (e) {}
1549
+ }
1550
+ return textResult(JSON.stringify({
1551
+ session_name: args.name,
1552
+ status: 'aborted',
1553
+ message: 'Task aborted. Session killed.',
1554
+ }, null, 2));
1555
+ }
1556
+
1557
+ // ── Batch Handler ─────────────────────────────────────────────────────────
1558
+
1559
+ async function handleBatch(args) {
1560
+ const results = await manager.batch(args.sessions);
1561
+ const summary = results.map(r => ({
1562
+ name: r.session?.name || 'unknown',
1563
+ status: r.error ? 'failed' : 'completed',
1564
+ response: r.response?.text || null,
1565
+ cost: r.response?.cost || 0,
1566
+ error: r.error || null,
1567
+ }));
1568
+ return textResult(JSON.stringify({ total: summary.length, results: summary }, null, 2));
1569
+ }
1570
+
1571
+ // ── Orchestrator Guide Handler ─────────────────────────────────────────────
1572
+
1573
+ /**
1574
+ * Return the orchestrator guide (or a specific section of it).
1575
+ * Uses the section constants from src/prompts.js.
1576
+ */
1577
+ function handleGetOrchestratorGuide(args) {
1578
+ const section = args.section || 'full';
1579
+ const guide = getOrchestratorGuideSection(section);
1580
+ return textResult(guide);
1581
+ }
1582
+
1583
+ // =============================================================================
1584
+ // Team Hub Handlers — Layer 1, 2, 3
1585
+ // =============================================================================
1586
+
1587
+ // ── Layer 1: Team Chat Handlers ───────────────────────────────────────────
1588
+
1589
+ /**
1590
+ * Build team system prompt from roster.
1591
+ * Delegates to the centralized prompt builder in src/prompts.js which applies
1592
+ * production-quality prompt engineering techniques (identity anchoring, examples,
1593
+ * anti-patterns, state machines, tool preference hierarchy, etc.)
1594
+ */
1595
+ function buildTeamSystemPrompt(name, role, task, roster, teamName) {
1596
+ return buildFullTeamPrompt({ name, role, task, roster, teamName });
1597
+ }
1598
+
1599
+ async function handleTeamSpawn(args) {
1600
+ try {
1601
+ const teamName = args.team || 'default';
1602
+ const { teamHub } = getTeamInstances(teamName);
1603
+
1604
+ // Join the team roster
1605
+ teamHub.joinTeam(args.name, {
1606
+ role: args.role || 'team member',
1607
+ task: args.task || 'Starting up',
1608
+ model: args.model || 'sonnet',
1609
+ });
1610
+
1611
+ // Get roster to build system prompt
1612
+ const roster = teamHub.getRoster();
1613
+
1614
+ // Build team system prompt (now includes role-specific guidance, examples, anti-patterns)
1615
+ const teamSystemPrompt = buildTeamSystemPrompt(args.name, args.role, args.task, roster, teamName);
1616
+
1617
+ // Spawn the session with team system prompt appended
1618
+ // Default to bypassPermissions so team sessions can write files without manual approval
1619
+ const { session, response } = await manager.spawn(args.name, {
1620
+ prompt: args.prompt,
1621
+ model: args.model,
1622
+ systemPrompt: teamSystemPrompt,
1623
+ permissionMode: args.permission_mode || 'bypassPermissions',
1624
+ });
1625
+
1626
+ const result = {
1627
+ session_name: session.name,
1628
+ status: session.status,
1629
+ model: session.model,
1630
+ team: teamName,
1631
+ role: args.role,
1632
+ roster_size: roster.length,
1633
+ };
1634
+
1635
+ if (response) {
1636
+ result.response = response.text;
1637
+ result.cost = response.cost;
1638
+ result.turns = response.turns;
1639
+ }
1640
+
1641
+ return textResult(JSON.stringify(result, null, 2));
1642
+ } catch (err) {
1643
+ return errorResult(err.message);
1644
+ }
1645
+ }
1646
+
1647
+ function handleTeamSendMessage(args) {
1648
+ try {
1649
+ const teamName = args.team || 'default';
1650
+ const { teamHub } = getTeamInstances(teamName);
1651
+
1652
+ teamHub.sendDirect(args.from, args.to, args.content, {
1653
+ priority: args.priority || 'normal',
1654
+ });
1655
+
1656
+ return textResult(JSON.stringify({
1657
+ status: 'sent',
1658
+ from: args.from,
1659
+ to: args.to,
1660
+ team: teamName,
1661
+ }, null, 2));
1662
+ } catch (err) {
1663
+ return errorResult(err.message);
1664
+ }
1665
+ }
1666
+
1667
+ function handleTeamBroadcast(args) {
1668
+ try {
1669
+ const teamName = args.team || 'default';
1670
+ const { teamHub } = getTeamInstances(teamName);
1671
+
1672
+ const count = teamHub.sendBroadcast(args.from, args.content, {
1673
+ priority: args.priority || 'normal',
1674
+ });
1675
+
1676
+ return textResult(JSON.stringify({
1677
+ status: 'broadcast',
1678
+ from: args.from,
1679
+ recipients: count,
1680
+ team: teamName,
1681
+ }, null, 2));
1682
+ } catch (err) {
1683
+ return errorResult(err.message);
1684
+ }
1685
+ }
1686
+
1687
+ function handleTeamCheckInbox(args) {
1688
+ try {
1689
+ const teamName = args.team || 'default';
1690
+ const { teamHub } = getTeamInstances(teamName);
1691
+
1692
+ const messages = teamHub.getInbox(args.name, {
1693
+ markRead: args.mark_read !== false,
1694
+ limit: args.limit || 20,
1695
+ });
1696
+
1697
+ return textResult(JSON.stringify({
1698
+ session: args.name,
1699
+ team: teamName,
1700
+ count: messages.length,
1701
+ messages: messages.map(m => ({
1702
+ id: m.id,
1703
+ from: m.from,
1704
+ type: m.type,
1705
+ content: m.content,
1706
+ timestamp: m.timestamp,
1707
+ priority: m.priority,
1708
+ replyTo: m.replyTo,
1709
+ })),
1710
+ }, null, 2));
1711
+ } catch (err) {
1712
+ return errorResult(err.message);
1713
+ }
1714
+ }
1715
+
1716
+ async function handleTeamAsk(args) {
1717
+ try {
1718
+ const teamName = args.team || 'default';
1719
+ const { teamHub } = getTeamInstances(teamName);
1720
+
1721
+ // Create the ask
1722
+ const askId = teamHub.createAsk(
1723
+ args.from,
1724
+ args.to,
1725
+ args.question,
1726
+ args.timeout || 60000
1727
+ );
1728
+
1729
+ // Poll for reply
1730
+ const reply = await teamHub.pollForReply(askId, args.timeout || 60000);
1731
+
1732
+ if (reply) {
1733
+ return textResult(JSON.stringify({
1734
+ status: 'answered',
1735
+ question: args.question,
1736
+ answer: reply.answer,
1737
+ from: args.from,
1738
+ to: args.to,
1739
+ team: teamName,
1740
+ }, null, 2));
1741
+ } else {
1742
+ return textResult(JSON.stringify({
1743
+ status: 'timeout',
1744
+ question: args.question,
1745
+ message: 'No reply received within timeout period',
1746
+ team: teamName,
1747
+ }, null, 2));
1748
+ }
1749
+ } catch (err) {
1750
+ return errorResult(err.message);
1751
+ }
1752
+ }
1753
+
1754
+ function handleTeamReply(args) {
1755
+ try {
1756
+ const teamName = args.team || 'default';
1757
+ const { teamHub } = getTeamInstances(teamName);
1758
+
1759
+ teamHub.submitReply(args.from, args.message_id, args.answer);
1760
+
1761
+ return textResult(JSON.stringify({
1762
+ status: 'replied',
1763
+ message_id: args.message_id,
1764
+ from: args.from,
1765
+ team: teamName,
1766
+ }, null, 2));
1767
+ } catch (err) {
1768
+ return errorResult(err.message);
1769
+ }
1770
+ }
1771
+
1772
+ function handleTeamRoster(args) {
1773
+ try {
1774
+ const teamName = args.team || 'default';
1775
+ const { teamHub } = getTeamInstances(teamName);
1776
+
1777
+ const roster = teamHub.getRoster();
1778
+
1779
+ return textResult(JSON.stringify({
1780
+ team: teamName,
1781
+ count: roster.length,
1782
+ members: roster.map(m => ({
1783
+ name: m.name,
1784
+ role: m.role,
1785
+ status: m.status,
1786
+ task: m.task,
1787
+ model: m.model,
1788
+ joinedAt: m.joinedAt,
1789
+ lastSeen: m.lastSeen,
1790
+ })),
1791
+ }, null, 2));
1792
+ } catch (err) {
1793
+ return errorResult(err.message);
1794
+ }
1795
+ }
1796
+
1797
+ function handleTeamUpdateStatus(args) {
1798
+ try {
1799
+ const teamName = args.team || 'default';
1800
+ const { teamHub } = getTeamInstances(teamName);
1801
+
1802
+ const updates = {};
1803
+ if (args.status) updates.status = args.status;
1804
+ if (args.task) updates.task = args.task;
1805
+ if (args.role) updates.role = args.role;
1806
+
1807
+ teamHub.updateMember(args.name, updates);
1808
+
1809
+ return textResult(JSON.stringify({
1810
+ status: 'updated',
1811
+ name: args.name,
1812
+ updates,
1813
+ team: teamName,
1814
+ }, null, 2));
1815
+ } catch (err) {
1816
+ return errorResult(err.message);
1817
+ }
1818
+ }
1819
+
1820
+ // ── Layer 2: Artifact Handlers ────────────────────────────────────────────
1821
+
1822
+ async function handleArtifactPublish(args) {
1823
+ try {
1824
+ const teamName = args.team || 'default';
1825
+ const { artifactStore, resolver } = getTeamInstances(teamName);
1826
+
1827
+ // Publish the artifact
1828
+ const result = artifactStore.publish(args.artifactId, {
1829
+ type: args.type,
1830
+ name: args.name,
1831
+ data: args.data,
1832
+ summary: args.summary,
1833
+ tags: args.tags,
1834
+ publisher: args.publisher,
1835
+ derivedFrom: args.derivedFrom,
1836
+ });
1837
+
1838
+ // Trigger dependency resolution and pipeline evaluation
1839
+ await resolver.resolve({
1840
+ type: 'artifact_published',
1841
+ artifactId: args.artifactId,
1842
+ artifactType: args.type,
1843
+ version: result.version,
1844
+ data: args.data,
1845
+ });
1846
+
1847
+ return textResult(JSON.stringify({
1848
+ status: 'published',
1849
+ artifactId: result.artifactId,
1850
+ version: result.version,
1851
+ type: result.type,
1852
+ team: teamName,
1853
+ }, null, 2));
1854
+ } catch (err) {
1855
+ return errorResult(err.message);
1856
+ }
1857
+ }
1858
+
1859
+ function handleArtifactGet(args) {
1860
+ try {
1861
+ const teamName = args.team || 'default';
1862
+ const { artifactStore } = getTeamInstances(teamName);
1863
+
1864
+ const artifact = artifactStore.get(args.artifactId, args.version);
1865
+
1866
+ if (!artifact) {
1867
+ return errorResult(`Artifact ${args.artifactId} not found`);
1868
+ }
1869
+
1870
+ return textResult(JSON.stringify({
1871
+ artifactId: artifact.artifactId,
1872
+ version: artifact.version,
1873
+ type: artifact.type,
1874
+ name: artifact.name,
1875
+ data: artifact.data,
1876
+ publisher: artifact.publisher,
1877
+ publishedAt: artifact.publishedAt,
1878
+ summary: artifact.summary,
1879
+ lineage: artifact.lineage,
1880
+ team: teamName,
1881
+ }, null, 2));
1882
+ } catch (err) {
1883
+ return errorResult(err.message);
1884
+ }
1885
+ }
1886
+
1887
+ function handleArtifactList(args) {
1888
+ try {
1889
+ const teamName = args.team || 'default';
1890
+ const { artifactStore } = getTeamInstances(teamName);
1891
+
1892
+ const filters = {};
1893
+ if (args.type) filters.type = args.type;
1894
+ if (args.publisher) filters.publisher = args.publisher;
1895
+ if (args.tag) filters.tag = args.tag;
1896
+
1897
+ const artifacts = artifactStore.list(filters);
1898
+
1899
+ return textResult(JSON.stringify({
1900
+ team: teamName,
1901
+ count: artifacts.length,
1902
+ artifacts: artifacts.map(a => ({
1903
+ artifactId: a.artifactId,
1904
+ type: a.type,
1905
+ name: a.name,
1906
+ publisher: a.publisher,
1907
+ latestVersion: a.latestVersion,
1908
+ createdAt: a.createdAt,
1909
+ updatedAt: a.updatedAt,
1910
+ tags: a.tags,
1911
+ })),
1912
+ }, null, 2));
1913
+ } catch (err) {
1914
+ return errorResult(err.message);
1915
+ }
1916
+ }
1917
+
1918
+ function handleArtifactHistory(args) {
1919
+ try {
1920
+ const teamName = args.team || 'default';
1921
+ const { artifactStore } = getTeamInstances(teamName);
1922
+
1923
+ const history = artifactStore.history(args.artifactId);
1924
+
1925
+ return textResult(JSON.stringify({
1926
+ artifactId: args.artifactId,
1927
+ team: teamName,
1928
+ versions: history.length,
1929
+ history: history.map(v => ({
1930
+ version: v.version,
1931
+ publishedAt: v.publishedAt,
1932
+ publisher: v.publisher,
1933
+ summary: v.summary,
1934
+ derivedFrom: v.lineage?.derivedFrom || [],
1935
+ })),
1936
+ }, null, 2));
1937
+ } catch (err) {
1938
+ return errorResult(err.message);
1939
+ }
1940
+ }
1941
+
1942
+ // ── Layer 2: Contract Handlers ────────────────────────────────────────────
1943
+
1944
+ async function handleContractCreate(args) {
1945
+ try {
1946
+ const teamName = args.team || 'default';
1947
+ const { contractStore, resolver, teamHub } = getTeamInstances(teamName);
1948
+
1949
+ // Create the contract
1950
+ const contract = contractStore.create(args.contractId, {
1951
+ title: args.title,
1952
+ description: args.description,
1953
+ assignee: args.assignee,
1954
+ assigner: args.assigner,
1955
+ inputs: args.inputs,
1956
+ expectedOutputs: args.expectedOutputs,
1957
+ dependencies: args.dependencies,
1958
+ acceptanceCriteria: args.acceptanceCriteria,
1959
+ autoComplete: args.autoComplete !== false,
1960
+ timeoutMs: args.timeoutMs,
1961
+ maxRetries: args.maxRetries,
1962
+ priority: args.priority,
1963
+ });
1964
+
1965
+ // Trigger dependency resolution to check if contract is immediately ready
1966
+ await resolver.resolve({
1967
+ type: 'contract_created',
1968
+ contractId: args.contractId,
1969
+ });
1970
+
1971
+ // If contract became ready, send inbox notification to assignee
1972
+ const updatedContract = contractStore.get(args.contractId);
1973
+ if (updatedContract.status === 'ready') {
1974
+ teamHub.sendDirect(
1975
+ 'system',
1976
+ args.assignee,
1977
+ `Contract ready: ${args.title} (${args.contractId})`,
1978
+ { priority: args.priority || 'normal' }
1979
+ );
1980
+ }
1981
+
1982
+ return textResult(JSON.stringify({
1983
+ status: 'created',
1984
+ contractId: contract.contractId,
1985
+ initialStatus: updatedContract.status,
1986
+ assignee: contract.assignee,
1987
+ team: teamName,
1988
+ }, null, 2));
1989
+ } catch (err) {
1990
+ return errorResult(err.message);
1991
+ }
1992
+ }
1993
+
1994
+ function handleContractStart(args) {
1995
+ try {
1996
+ const teamName = args.team || 'default';
1997
+ const { contractStore } = getTeamInstances(teamName);
1998
+
1999
+ const contract = contractStore.start(args.contractId);
2000
+
2001
+ return textResult(JSON.stringify({
2002
+ status: 'started',
2003
+ contractId: contract.contractId,
2004
+ title: contract.title,
2005
+ inputs: contract.inputs,
2006
+ expectedOutputs: contract.expectedOutputs,
2007
+ acceptanceCriteria: contract.acceptanceCriteria,
2008
+ startedAt: contract.startedAt,
2009
+ team: teamName,
2010
+ }, null, 2));
2011
+ } catch (err) {
2012
+ return errorResult(err.message);
2013
+ }
2014
+ }
2015
+
2016
+ async function handleContractComplete(args) {
2017
+ try {
2018
+ const teamName = args.team || 'default';
2019
+ const { contractStore, resolver } = getTeamInstances(teamName);
2020
+
2021
+ // Complete the contract
2022
+ contractStore.complete(args.contractId, {
2023
+ summary: args.summary,
2024
+ publishedArtifacts: args.publishedArtifacts,
2025
+ });
2026
+
2027
+ // Trigger dependency resolution for cascade
2028
+ await resolver.resolve({
2029
+ type: 'contract_completed',
2030
+ contractId: args.contractId,
2031
+ });
2032
+
2033
+ return textResult(JSON.stringify({
2034
+ status: 'completed',
2035
+ contractId: args.contractId,
2036
+ team: teamName,
2037
+ }, null, 2));
2038
+ } catch (err) {
2039
+ return errorResult(err.message);
2040
+ }
2041
+ }
2042
+
2043
+ async function handleContractFail(args) {
2044
+ try {
2045
+ const teamName = args.team || 'default';
2046
+ const { contractStore, resolver } = getTeamInstances(teamName);
2047
+
2048
+ // Fail the contract
2049
+ contractStore.fail(args.contractId, args.reason);
2050
+
2051
+ // Trigger dependency resolution
2052
+ await resolver.resolve({
2053
+ type: 'contract_failed',
2054
+ contractId: args.contractId,
2055
+ reason: args.reason,
2056
+ });
2057
+
2058
+ return textResult(JSON.stringify({
2059
+ status: 'failed',
2060
+ contractId: args.contractId,
2061
+ reason: args.reason,
2062
+ team: teamName,
2063
+ }, null, 2));
2064
+ } catch (err) {
2065
+ return errorResult(err.message);
2066
+ }
2067
+ }
2068
+
2069
+ async function handleContractReopen(args) {
2070
+ try {
2071
+ const teamName = args.team || 'default';
2072
+ const { contractStore, resolver } = getTeamInstances(teamName);
2073
+
2074
+ // Reopen the contract
2075
+ const contract = contractStore.reopen(args.contractId, {
2076
+ reason: args.reason,
2077
+ newInputs: args.newInputs,
2078
+ });
2079
+
2080
+ // Trigger dependency resolution
2081
+ await resolver.resolve({
2082
+ type: 'contract_reopened',
2083
+ contractId: args.contractId,
2084
+ });
2085
+
2086
+ return textResult(JSON.stringify({
2087
+ status: 'reopened',
2088
+ contractId: contract.contractId,
2089
+ retryCount: contract.retryCount,
2090
+ currentStatus: contract.status,
2091
+ team: teamName,
2092
+ }, null, 2));
2093
+ } catch (err) {
2094
+ return errorResult(err.message);
2095
+ }
2096
+ }
2097
+
2098
+ function handleContractGet(args) {
2099
+ try {
2100
+ const teamName = args.team || 'default';
2101
+ const { contractStore } = getTeamInstances(teamName);
2102
+
2103
+ const contract = contractStore.get(args.contractId);
2104
+
2105
+ if (!contract) {
2106
+ return errorResult(`Contract ${args.contractId} not found`);
2107
+ }
2108
+
2109
+ return textResult(JSON.stringify({
2110
+ contractId: contract.contractId,
2111
+ title: contract.title,
2112
+ status: contract.status,
2113
+ assignee: contract.assignee,
2114
+ assigner: contract.assigner,
2115
+ inputs: contract.inputs,
2116
+ expectedOutputs: contract.expectedOutputs,
2117
+ dependencies: contract.dependencies,
2118
+ acceptanceCriteria: contract.acceptanceCriteria,
2119
+ retryCount: contract.retryCount,
2120
+ maxRetries: contract.maxRetries,
2121
+ createdAt: contract.createdAt,
2122
+ startedAt: contract.startedAt,
2123
+ completedAt: contract.completedAt,
2124
+ team: teamName,
2125
+ }, null, 2));
2126
+ } catch (err) {
2127
+ return errorResult(err.message);
2128
+ }
2129
+ }
2130
+
2131
+ function handleContractList(args) {
2132
+ try {
2133
+ const teamName = args.team || 'default';
2134
+ const { contractStore } = getTeamInstances(teamName);
2135
+
2136
+ const filters = {};
2137
+ if (args.status) filters.status = args.status;
2138
+ if (args.assignee) filters.assignee = args.assignee;
2139
+ if (args.assigner) filters.assigner = args.assigner;
2140
+
2141
+ const contracts = contractStore.list(filters);
2142
+
2143
+ return textResult(JSON.stringify({
2144
+ team: teamName,
2145
+ count: contracts.length,
2146
+ contracts: contracts.map(c => ({
2147
+ contractId: c.contractId,
2148
+ title: c.title,
2149
+ status: c.status,
2150
+ assignee: c.assignee,
2151
+ assigner: c.assigner,
2152
+ priority: c.priority,
2153
+ retryCount: c.retryCount,
2154
+ createdAt: c.createdAt,
2155
+ })),
2156
+ }, null, 2));
2157
+ } catch (err) {
2158
+ return errorResult(err.message);
2159
+ }
477
2160
  }
478
2161
 
479
- function handleKill(args) {
480
- manager.kill(args.name);
481
- return textResult(JSON.stringify({
482
- session_name: args.name,
483
- status: 'killed',
484
- }, null, 2));
2162
+ function handleContractReassign(args) {
2163
+ try {
2164
+ const teamName = args.team || 'default';
2165
+ const { contractStore } = getTeamInstances(teamName);
2166
+
2167
+ contractStore.reassign(args.contractId, args.newAssignee);
2168
+
2169
+ return textResult(JSON.stringify({
2170
+ status: 'reassigned',
2171
+ contractId: args.contractId,
2172
+ newAssignee: args.newAssignee,
2173
+ team: teamName,
2174
+ }, null, 2));
2175
+ } catch (err) {
2176
+ return errorResult(err.message);
2177
+ }
485
2178
  }
486
2179
 
487
- // ── Information Handlers ──────────────────────────────────────────────────
2180
+ // ── Layer 3: Lineage Handlers ─────────────────────────────────────────────
488
2181
 
489
- function handleStatus(args) {
490
- const info = manager.status(args.name);
491
- return textResult(JSON.stringify({
492
- name: info.name,
493
- status: info.status,
494
- model: info.model,
495
- session_id: info.claudeSessionId || info.id,
496
- work_dir: info.workDir,
497
- total_cost: info.totalCostUsd || 0,
498
- total_turns: info.totalTurns || 0,
499
- interactions: info.interactionCount || (info.interactions || []).length,
500
- pid: info.pid || null,
501
- }, null, 2));
2182
+ function handleArtifactLineage(args) {
2183
+ try {
2184
+ const teamName = args.team || 'default';
2185
+ const { lineageGraph } = getTeamInstances(teamName);
2186
+
2187
+ let result;
2188
+ if (args.direction === 'upstream') {
2189
+ result = lineageGraph.getUpstream(args.artifactId, args.version);
2190
+ } else {
2191
+ result = lineageGraph.getDownstream(args.artifactId, args.version);
2192
+ }
2193
+
2194
+ return textResult(JSON.stringify({
2195
+ artifactId: args.artifactId,
2196
+ direction: args.direction,
2197
+ chain: result,
2198
+ team: teamName,
2199
+ }, null, 2));
2200
+ } catch (err) {
2201
+ return errorResult(err.message);
2202
+ }
502
2203
  }
503
2204
 
504
- function handleLastOutput(args) {
505
- const last = manager.lastOutput(args.name);
506
- if (!last) {
507
- return textResult(JSON.stringify({ session_name: args.name, output: null, message: 'No output yet.' }, null, 2));
2205
+ function handleArtifactImpact(args) {
2206
+ try {
2207
+ const teamName = args.team || 'default';
2208
+ const { lineageGraph } = getTeamInstances(teamName);
2209
+
2210
+ const impact = lineageGraph.getImpact(args.artifactId);
2211
+
2212
+ return textResult(JSON.stringify({
2213
+ artifactId: args.artifactId,
2214
+ impactRadius: impact.impactRadius,
2215
+ affectedArtifacts: impact.affectedArtifacts,
2216
+ affectedContracts: impact.affectedContracts,
2217
+ team: teamName,
2218
+ }, null, 2));
2219
+ } catch (err) {
2220
+ return errorResult(err.message);
508
2221
  }
509
- return textResult(JSON.stringify({
510
- session_name: args.name,
511
- prompt: last.prompt,
512
- response: last.response,
513
- cost: last.cost,
514
- timestamp: last.timestamp,
515
- }, null, 2));
516
2222
  }
517
2223
 
518
- function handleList(args) {
519
- const sessions = manager.list(args.status);
520
- const summary = sessions.map(s => ({
521
- name: s.name,
522
- status: s.status,
523
- model: s.model,
524
- total_cost: s.totalCostUsd || 0,
525
- total_turns: s.totalTurns || 0,
526
- interactions: s.interactionCount || (s.interactions || []).length,
527
- }));
528
- return textResult(JSON.stringify({ total: summary.length, sessions: summary }, null, 2));
2224
+ function handleArtifactStale(args) {
2225
+ try {
2226
+ const teamName = args.team || 'default';
2227
+ const { lineageGraph } = getTeamInstances(teamName);
2228
+
2229
+ const staleArtifacts = lineageGraph.findStale();
2230
+
2231
+ return textResult(JSON.stringify({
2232
+ team: teamName,
2233
+ count: staleArtifacts.length,
2234
+ staleArtifacts: staleArtifacts,
2235
+ }, null, 2));
2236
+ } catch (err) {
2237
+ return errorResult(err.message);
2238
+ }
529
2239
  }
530
2240
 
531
- function handleHistory(args) {
532
- const hist = manager.history(args.name);
533
- return textResult(JSON.stringify({
534
- session_name: args.name,
535
- interaction_count: hist.length,
536
- interactions: hist.map((h, i) => ({
537
- index: i,
538
- prompt: h.prompt,
539
- response: h.response,
540
- cost: h.cost,
541
- turns: h.turns,
542
- duration_ms: h.duration,
543
- timestamp: h.timestamp,
544
- })),
545
- }, null, 2));
2241
+ function handleTeamAudit(args) {
2242
+ try {
2243
+ const teamName = args.team || 'default';
2244
+ const { lineageGraph } = getTeamInstances(teamName);
2245
+
2246
+ const trail = lineageGraph.getAuditTrail(args.artifactId, args.version);
2247
+
2248
+ return textResult(JSON.stringify({
2249
+ artifactId: args.artifactId,
2250
+ auditTrail: trail,
2251
+ team: teamName,
2252
+ }, null, 2));
2253
+ } catch (err) {
2254
+ return errorResult(err.message);
2255
+ }
546
2256
  }
547
2257
 
548
- function handleDelete(args) {
549
- manager.delete(args.name);
550
- return textResult(JSON.stringify({
551
- session_name: args.name,
552
- status: 'deleted',
553
- message: 'Session permanently deleted.',
554
- }, null, 2));
2258
+ // ── Layer 3: Pipeline Handlers ────────────────────────────────────────────
2259
+
2260
+ function handlePipelineCreate(args) {
2261
+ try {
2262
+ const teamName = args.team || 'default';
2263
+ const { pipelineEngine } = getTeamInstances(teamName);
2264
+
2265
+ const pipeline = pipelineEngine.create(args.pipelineId, {
2266
+ rules: args.rules,
2267
+ owner: args.owner,
2268
+ enabled: args.enabled !== false,
2269
+ });
2270
+
2271
+ return textResult(JSON.stringify({
2272
+ status: 'created',
2273
+ pipelineId: pipeline.pipelineId,
2274
+ rulesCount: pipeline.rules.length,
2275
+ enabled: pipeline.enabled,
2276
+ team: teamName,
2277
+ }, null, 2));
2278
+ } catch (err) {
2279
+ return errorResult(err.message);
2280
+ }
555
2281
  }
556
2282
 
557
- // ── Delegate Handlers ─────────────────────────────────────────────────────
2283
+ function handlePipelineList(args) {
2284
+ try {
2285
+ const teamName = args.team || 'default';
2286
+ const { pipelineEngine } = getTeamInstances(teamName);
558
2287
 
559
- async function handleDelegate(args) {
560
- // Create or reuse a delegate instance for this session
561
- let delegate = delegates.get(args.name);
562
- if (!delegate) {
563
- delegate = new Delegate(manager);
564
- delegates.set(args.name, delegate);
2288
+ const filters = {};
2289
+ if (args.owner) filters.owner = args.owner;
2290
+
2291
+ const pipelines = pipelineEngine.list(filters);
2292
+
2293
+ return textResult(JSON.stringify({
2294
+ team: teamName,
2295
+ count: pipelines.length,
2296
+ pipelines: pipelines.map(p => ({
2297
+ pipelineId: p.pipelineId,
2298
+ owner: p.owner,
2299
+ enabled: p.enabled,
2300
+ rulesCount: p.rules.length,
2301
+ executionCount: p.executionCount,
2302
+ lastExecutedAt: p.lastExecutedAt,
2303
+ })),
2304
+ }, null, 2));
2305
+ } catch (err) {
2306
+ return errorResult(err.message);
565
2307
  }
2308
+ }
566
2309
 
567
- const result = await delegate.run(args.name, {
568
- task: args.task,
569
- model: args.model,
570
- preset: args.preset,
571
- workDir: args.work_dir,
572
- maxCost: args.max_cost,
573
- maxTurns: args.max_turns,
574
- context: args.context,
575
- systemPrompt: args.system_prompt,
576
- agent: args.agent,
577
- safety: args.safety,
578
- });
2310
+ function handlePipelinePause(args) {
2311
+ try {
2312
+ const teamName = args.team || 'default';
2313
+ const { pipelineEngine } = getTeamInstances(teamName);
579
2314
 
580
- return textResult(JSON.stringify(result, null, 2));
2315
+ pipelineEngine.pause(args.pipelineId);
2316
+
2317
+ return textResult(JSON.stringify({
2318
+ status: 'paused',
2319
+ pipelineId: args.pipelineId,
2320
+ team: teamName,
2321
+ }, null, 2));
2322
+ } catch (err) {
2323
+ return errorResult(err.message);
2324
+ }
581
2325
  }
582
2326
 
583
- async function handleContinue(args) {
584
- // Get or create delegate for this session
585
- let delegate = delegates.get(args.name);
586
- if (!delegate) {
587
- delegate = new Delegate(manager);
588
- delegates.set(args.name, delegate);
2327
+ function handlePipelineResume(args) {
2328
+ try {
2329
+ const teamName = args.team || 'default';
2330
+ const { pipelineEngine } = getTeamInstances(teamName);
2331
+
2332
+ pipelineEngine.resume(args.pipelineId);
2333
+
2334
+ return textResult(JSON.stringify({
2335
+ status: 'resumed',
2336
+ pipelineId: args.pipelineId,
2337
+ team: teamName,
2338
+ }, null, 2));
2339
+ } catch (err) {
2340
+ return errorResult(err.message);
589
2341
  }
2342
+ }
590
2343
 
591
- const result = await delegate.continue(args.name, args.message);
592
- return textResult(JSON.stringify(result, null, 2));
2344
+ // ── Layer 3: Snapshot Handlers ────────────────────────────────────────────
2345
+
2346
+ function handleTeamSnapshot(args) {
2347
+ try {
2348
+ const teamName = args.team || 'default';
2349
+ const { snapshotEngine } = getTeamInstances(teamName);
2350
+
2351
+ const snapshot = snapshotEngine.createSnapshot(args.snapshotId, {
2352
+ label: args.label,
2353
+ description: args.description,
2354
+ });
2355
+
2356
+ return textResult(JSON.stringify({
2357
+ status: 'created',
2358
+ snapshotId: snapshot.snapshotId,
2359
+ label: snapshot.label,
2360
+ createdAt: snapshot.createdAt,
2361
+ contractsCount: snapshot.contractsCount,
2362
+ artifactsCount: snapshot.artifactsCount,
2363
+ pipelinesCount: snapshot.pipelinesCount,
2364
+ team: teamName,
2365
+ }, null, 2));
2366
+ } catch (err) {
2367
+ return errorResult(err.message);
2368
+ }
593
2369
  }
594
2370
 
595
- function handleFinish(args) {
596
- const delegate = delegates.get(args.name);
597
- if (delegate) {
598
- delegate.finish(args.name);
599
- delegates.delete(args.name);
600
- } else {
601
- // No delegate — just stop the session
602
- try { manager.stop(args.name); } catch (e) {}
2371
+ function handleTeamSnapshotList(args) {
2372
+ try {
2373
+ const teamName = args.team || 'default';
2374
+ const { snapshotEngine } = getTeamInstances(teamName);
2375
+
2376
+ const snapshots = snapshotEngine.listSnapshots();
2377
+
2378
+ return textResult(JSON.stringify({
2379
+ team: teamName,
2380
+ count: snapshots.length,
2381
+ snapshots: snapshots.map(s => ({
2382
+ snapshotId: s.snapshotId,
2383
+ label: s.label,
2384
+ description: s.description,
2385
+ createdAt: s.createdAt,
2386
+ contractsCount: s.contractsCount,
2387
+ artifactsCount: s.artifactsCount,
2388
+ })),
2389
+ }, null, 2));
2390
+ } catch (err) {
2391
+ return errorResult(err.message);
603
2392
  }
604
- return textResult(JSON.stringify({
605
- session_name: args.name,
606
- status: 'finished',
607
- message: 'Task finished. Session stopped.',
608
- }, null, 2));
609
2393
  }
610
2394
 
611
- function handleAbort(args) {
612
- const delegate = delegates.get(args.name);
613
- if (delegate) {
614
- delegate.abort(args.name);
615
- delegates.delete(args.name);
616
- } else {
617
- try { manager.kill(args.name); } catch (e) {}
2395
+ function handleTeamRollback(args) {
2396
+ try {
2397
+ const teamName = args.team || 'default';
2398
+ const { snapshotEngine } = getTeamInstances(teamName);
2399
+
2400
+ const result = snapshotEngine.rollback(args.snapshotId, {
2401
+ preserveArtifacts: args.preserveArtifacts !== false,
2402
+ });
2403
+
2404
+ return textResult(JSON.stringify({
2405
+ status: 'rolled_back',
2406
+ snapshotId: args.snapshotId,
2407
+ restoredContracts: result.restoredContracts,
2408
+ restoredPipelines: result.restoredPipelines,
2409
+ team: teamName,
2410
+ }, null, 2));
2411
+ } catch (err) {
2412
+ return errorResult(err.message);
618
2413
  }
619
- return textResult(JSON.stringify({
620
- session_name: args.name,
621
- status: 'aborted',
622
- message: 'Task aborted. Session killed.',
623
- }, null, 2));
624
2414
  }
625
2415
 
626
- // ── Batch Handler ─────────────────────────────────────────────────────────
2416
+ async function handleTeamReplay(args) {
2417
+ try {
2418
+ const teamName = args.team || 'default';
2419
+ const { snapshotEngine, resolver } = getTeamInstances(teamName);
627
2420
 
628
- async function handleBatch(args) {
629
- const results = await manager.batch(args.sessions);
630
- const summary = results.map(r => ({
631
- name: r.session?.name || 'unknown',
632
- status: r.error ? 'failed' : 'completed',
633
- response: r.response?.text || null,
634
- cost: r.response?.cost || 0,
635
- error: r.error || null,
636
- }));
637
- return textResult(JSON.stringify({ total: summary.length, results: summary }, null, 2));
2421
+ // Replay from snapshot with overrides
2422
+ const result = snapshotEngine.replay(args.snapshotId, {
2423
+ overrides: args.overrides,
2424
+ });
2425
+
2426
+ // Re-trigger dependency resolution to execute the workflow
2427
+ await resolver.resolve({
2428
+ type: 'replay_triggered',
2429
+ snapshotId: args.snapshotId,
2430
+ });
2431
+
2432
+ return textResult(JSON.stringify({
2433
+ status: 'replayed',
2434
+ snapshotId: args.snapshotId,
2435
+ appliedOverrides: Object.keys(args.overrides || {}).length,
2436
+ restoredContracts: result.restoredContracts,
2437
+ team: teamName,
2438
+ }, null, 2));
2439
+ } catch (err) {
2440
+ return errorResult(err.message);
2441
+ }
638
2442
  }
639
2443
 
640
2444
  // =============================================================================