claude-multi-session 1.0.0 → 2.2.0

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