ac-framework 1.9.7 → 1.9.9

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.
@@ -156,7 +156,8 @@ async function setupPersistentMemory() {
156
156
  async function setupCollaborativeSystem() {
157
157
  const hasOpenCode = hasCommand('opencode');
158
158
  const hasTmux = hasCommand('tmux');
159
- const alreadyReady = hasOpenCode && hasTmux;
159
+ const hasZellij = hasCommand('zellij');
160
+ const alreadyReady = hasOpenCode && (hasZellij || hasTmux);
160
161
 
161
162
  console.log();
162
163
  await animatedSeparator(60);
@@ -168,10 +169,10 @@ async function setupCollaborativeSystem() {
168
169
  console.log(
169
170
  chalk.hex('#636E72')(
170
171
  ` ${COLLAB_SYSTEM_NAME} launches a real-time collaborative agent war-room with\n` +
171
- ' 4 coordinated roles (planner, critic, coder, reviewer) in tmux panes.\n\n' +
172
+ ' 4 coordinated roles (planner, critic, coder, reviewer) in multiplexer panes.\n\n' +
172
173
  ' Each round is turn-based with shared incremental context, so every\n' +
173
174
  ' contribution from one agent is fed to the next, not isolated fan-out.\n\n' +
174
- ` Dependencies: ${chalk.hex('#DFE6E9')('OpenCode')} + ${chalk.hex('#DFE6E9')('tmux')}`
175
+ ` Dependencies: ${chalk.hex('#DFE6E9')('OpenCode')} + ${chalk.hex('#DFE6E9')('zellij')} (${chalk.hex('#DFE6E9')('tmux')} fallback)`
175
176
  )
176
177
  );
177
178
  console.log();
@@ -223,7 +224,8 @@ async function setupCollaborativeSystem() {
223
224
 
224
225
  if (alreadyReady) {
225
226
  console.log();
226
- console.log(chalk.hex('#00B894')(' ◆ OpenCode and tmux are already available.'));
227
+ const mux = hasZellij ? 'zellij' : 'tmux';
228
+ console.log(chalk.hex('#00B894')(` ◆ OpenCode and ${mux} are already available.`));
227
229
  await installCollabMcpConnections();
228
230
  console.log(chalk.hex('#636E72')(' Run `acfm agents start --task "..."` to launch collaboration.'));
229
231
  console.log();
@@ -234,11 +236,13 @@ async function setupCollaborativeSystem() {
234
236
  console.log(chalk.hex('#B2BEC3')(` Installing ${COLLAB_SYSTEM_NAME} dependencies...`));
235
237
  console.log();
236
238
 
237
- const result = ensureCollabDependencies();
239
+ const result = ensureCollabDependencies({ installZellij: true, installTmux: true });
238
240
 
239
241
  const oColor = result.opencode.success ? chalk.hex('#00B894') : chalk.hex('#D63031');
242
+ const zColor = result.zellij.success ? chalk.hex('#00B894') : chalk.hex('#D63031');
240
243
  const tColor = result.tmux.success ? chalk.hex('#00B894') : chalk.hex('#D63031');
241
244
  console.log(oColor(` ◆ OpenCode: ${result.opencode.message}`));
245
+ console.log(zColor(` ◆ zellij: ${result.zellij.message}`));
242
246
  console.log(tColor(` ◆ tmux: ${result.tmux.message}`));
243
247
  console.log();
244
248
 
@@ -7,12 +7,23 @@
7
7
 
8
8
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
9
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { spawn } from 'node:child_process';
11
+ import { existsSync } from 'node:fs';
12
+ import { readFile } from 'node:fs/promises';
13
+ import { dirname, resolve } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
10
15
  import { z } from 'zod';
11
16
  import { COLLAB_ROLES } from '../agents/constants.js';
12
17
  import { buildEffectiveRoleModels, sanitizeRoleModels } from '../agents/model-selection.js';
13
18
  import { runWorkerIteration } from '../agents/orchestrator.js';
14
19
  import { getSessionDir } from '../agents/state-store.js';
15
- import { spawnTmuxSession, tmuxSessionExists } from '../agents/runtime.js';
20
+ import {
21
+ spawnTmuxSession,
22
+ spawnZellijSession,
23
+ tmuxSessionExists,
24
+ zellijSessionExists,
25
+ resolveMultiplexer,
26
+ } from '../agents/runtime.js';
16
27
  import {
17
28
  addUserMessage,
18
29
  createSession,
@@ -24,6 +35,49 @@ import {
24
35
  stopSession,
25
36
  } from '../agents/state-store.js';
26
37
  import { hasCommand, resolveCommandPath } from '../services/dependency-installer.js';
38
+ import { loadAgentsConfig } from '../agents/config-store.js';
39
+
40
+ const __dirname = dirname(fileURLToPath(import.meta.url));
41
+ const runnerPath = resolve(__dirname, '../../bin/acfm.js');
42
+
43
+ function summarizeRun(state) {
44
+ const run = state.run || null;
45
+ return {
46
+ status: run?.status || 'idle',
47
+ runId: run?.runId || null,
48
+ currentRole: run?.currentRole || state.activeAgent || null,
49
+ round: run?.round || state.round,
50
+ policy: run?.policy || null,
51
+ lastError: run?.lastError || null,
52
+ eventCount: Array.isArray(run?.events) ? run.events.length : 0,
53
+ };
54
+ }
55
+
56
+ function latestRunEvent(state) {
57
+ const events = state?.run?.events;
58
+ if (!Array.isArray(events) || events.length === 0) return null;
59
+ return events[events.length - 1];
60
+ }
61
+
62
+ async function readSessionArtifact(sessionId, filename) {
63
+ const path = resolve(getSessionDir(sessionId), filename);
64
+ if (!existsSync(path)) return null;
65
+ return readFile(path, 'utf8');
66
+ }
67
+
68
+ function launchAutopilot(sessionId) {
69
+ const child = spawn('node', [runnerPath, 'agents', 'autopilot', '--session', sessionId], {
70
+ cwd: process.cwd(),
71
+ detached: true,
72
+ stdio: 'ignore',
73
+ });
74
+ child.unref();
75
+ }
76
+
77
+ async function muxExists(multiplexer, sessionName) {
78
+ if (multiplexer === 'zellij') return zellijSessionExists(sessionName);
79
+ return tmuxSessionExists(sessionName);
80
+ }
27
81
 
28
82
  class MCPCollabServer {
29
83
  constructor() {
@@ -50,9 +104,14 @@ class MCPCollabServer {
50
104
  reviewer: z.string().optional(),
51
105
  }).partial().optional().describe('Optional per-role models (provider/model)'),
52
106
  cwd: z.string().optional().describe('Working directory for agents'),
53
- spawnWorkers: z.boolean().default(true).describe('Create tmux workers and panes'),
107
+ spawnWorkers: z.boolean().default(true).describe('Create multiplexer workers and panes'),
108
+ runPolicy: z.object({
109
+ timeoutPerRoleMs: z.number().int().positive().optional(),
110
+ retryOnTimeout: z.number().int().min(0).optional(),
111
+ fallbackOnFailure: z.enum(['retry', 'skip', 'abort']).optional(),
112
+ }).partial().optional().describe('Optional run execution policy'),
54
113
  },
55
- async ({ task, maxRounds, model, roleModels, cwd, spawnWorkers }) => {
114
+ async ({ task, maxRounds, model, roleModels, cwd, spawnWorkers, runPolicy }) => {
56
115
  try {
57
116
  const workingDirectory = cwd || process.cwd();
58
117
  const opencodeBin = resolveCommandPath('opencode');
@@ -60,8 +119,11 @@ class MCPCollabServer {
60
119
  throw new Error('OpenCode binary not found in PATH. Run: acfm agents setup');
61
120
  }
62
121
 
63
- if (spawnWorkers && !hasCommand('tmux')) {
64
- throw new Error('tmux is not installed. Run: acfm agents setup');
122
+ const config = await loadAgentsConfig();
123
+ const configuredMux = config.agents.multiplexer || 'auto';
124
+ const multiplexer = resolveMultiplexer(configuredMux, hasCommand('tmux'), hasCommand('zellij'));
125
+ if (spawnWorkers && !multiplexer) {
126
+ throw new Error('No multiplexer found (zellij/tmux). Run: acfm agents setup');
65
127
  }
66
128
 
67
129
  const state = await createSession(task, {
@@ -71,18 +133,32 @@ class MCPCollabServer {
71
133
  roleModels: sanitizeRoleModels(roleModels),
72
134
  workingDirectory,
73
135
  opencodeBin,
136
+ runPolicy,
137
+ multiplexer: multiplexer || configuredMux,
74
138
  });
75
139
  let updated = state;
76
140
  if (spawnWorkers) {
77
- const tmuxSessionName = `acfm-synapse-${state.sessionId.slice(0, 8)}`;
141
+ const sessionName = `acfm-synapse-${state.sessionId.slice(0, 8)}`;
78
142
  const sessionDir = getSessionDir(state.sessionId);
79
- await spawnTmuxSession({ sessionName: tmuxSessionName, sessionDir, sessionId: state.sessionId });
80
- updated = await saveSessionState({ ...state, tmuxSessionName });
143
+ if (multiplexer === 'zellij') {
144
+ await spawnZellijSession({ sessionName, sessionDir, sessionId: state.sessionId });
145
+ } else {
146
+ await spawnTmuxSession({ sessionName, sessionDir, sessionId: state.sessionId });
147
+ }
148
+ updated = await saveSessionState({
149
+ ...state,
150
+ multiplexer,
151
+ multiplexerSessionName: sessionName,
152
+ tmuxSessionName: multiplexer === 'tmux' ? sessionName : null,
153
+ });
81
154
  }
82
155
  await setCurrentSession(state.sessionId);
83
156
 
84
- const tmuxSessionName = updated.tmuxSessionName || null;
85
- const attachCommand = tmuxSessionName ? `tmux attach -t ${tmuxSessionName}` : null;
157
+ const mux = updated.multiplexer || null;
158
+ const muxSessionName = updated.multiplexerSessionName || updated.tmuxSessionName || null;
159
+ const attachCommand = muxSessionName
160
+ ? (mux === 'zellij' ? `zellij attach ${muxSessionName}` : `tmux attach -t ${muxSessionName}`)
161
+ : null;
86
162
  return {
87
163
  content: [{
88
164
  type: 'text',
@@ -93,7 +169,9 @@ class MCPCollabServer {
93
169
  model: updated.model || null,
94
170
  roleModels: updated.roleModels || {},
95
171
  effectiveRoleModels: buildEffectiveRoleModels(updated, updated.model || null),
96
- tmuxSessionName,
172
+ run: summarizeRun(updated),
173
+ multiplexer: mux,
174
+ multiplexerSessionName: muxSessionName,
97
175
  attachCommand,
98
176
  }, null, 2),
99
177
  }],
@@ -104,6 +182,191 @@ class MCPCollabServer {
104
182
  }
105
183
  );
106
184
 
185
+ this.server.tool(
186
+ 'collab_invoke_team',
187
+ 'Invoke full 4-agent collaborative run and return async run handle',
188
+ {
189
+ sessionId: z.string().optional().describe('Session ID (defaults to current session)'),
190
+ waitMs: z.number().int().min(0).max(30000).default(0).describe('Optional wait for progress before returning'),
191
+ },
192
+ async ({ sessionId, waitMs }) => {
193
+ try {
194
+ const id = sessionId || await loadCurrentSessionId();
195
+ if (!id) throw new Error('No active session found');
196
+ let state = await loadSessionState(id);
197
+ if (state.status !== 'running') {
198
+ throw new Error(`Session is ${state.status}. Resume/start before invoking.`);
199
+ }
200
+
201
+ if (!state.multiplexerSessionName && !state.tmuxSessionName) {
202
+ launchAutopilot(state.sessionId);
203
+ }
204
+
205
+ if (waitMs > 0) {
206
+ const started = Date.now();
207
+ const initialEvents = state.run?.events?.length || 0;
208
+ while (Date.now() - started < waitMs) {
209
+ await new Promise((r) => setTimeout(r, 250));
210
+ state = await loadSessionState(id);
211
+ const currentEvents = state.run?.events?.length || 0;
212
+ if (state.status !== 'running' || currentEvents > initialEvents) break;
213
+ }
214
+ }
215
+
216
+ return {
217
+ content: [{
218
+ type: 'text',
219
+ text: JSON.stringify({
220
+ success: true,
221
+ sessionId: state.sessionId,
222
+ status: state.status,
223
+ run: summarizeRun(state),
224
+ latestEvent: latestRunEvent(state),
225
+ multiplexer: state.multiplexer || null,
226
+ multiplexerSessionName: state.multiplexerSessionName || state.tmuxSessionName || null,
227
+ attachCommand: state.multiplexerSessionName
228
+ ? (state.multiplexer === 'zellij'
229
+ ? `zellij attach ${state.multiplexerSessionName}`
230
+ : `tmux attach -t ${state.multiplexerSessionName}`)
231
+ : (state.tmuxSessionName ? `tmux attach -t ${state.tmuxSessionName}` : null),
232
+ }, null, 2),
233
+ }],
234
+ };
235
+ } catch (error) {
236
+ return { content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }], isError: true };
237
+ }
238
+ }
239
+ );
240
+
241
+ this.server.tool(
242
+ 'collab_wait_run',
243
+ 'Wait for collaborative run to complete/fail or timeout',
244
+ {
245
+ sessionId: z.string().optional().describe('Session ID (defaults to current session)'),
246
+ waitMs: z.number().int().positive().max(60000).default(10000).describe('Max wait in milliseconds'),
247
+ },
248
+ async ({ sessionId, waitMs }) => {
249
+ try {
250
+ const id = sessionId || await loadCurrentSessionId();
251
+ if (!id) throw new Error('No active session found');
252
+ const started = Date.now();
253
+ let state = await loadSessionState(id);
254
+
255
+ while (Date.now() - started < waitMs) {
256
+ const runStatus = state.run?.status || 'idle';
257
+ if (state.status !== 'running' || ['completed', 'failed', 'cancelled'].includes(runStatus)) break;
258
+ await new Promise((r) => setTimeout(r, 300));
259
+ state = await loadSessionState(id);
260
+ }
261
+
262
+ return {
263
+ content: [{
264
+ type: 'text',
265
+ text: JSON.stringify({
266
+ success: true,
267
+ sessionId: state.sessionId,
268
+ status: state.status,
269
+ run: summarizeRun(state),
270
+ latestEvent: latestRunEvent(state),
271
+ timedOut: state.status === 'running' && !['completed', 'failed', 'cancelled'].includes(state.run?.status || 'idle'),
272
+ }, null, 2),
273
+ }],
274
+ };
275
+ } catch (error) {
276
+ return { content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }], isError: true };
277
+ }
278
+ }
279
+ );
280
+
281
+ this.server.tool(
282
+ 'collab_get_result',
283
+ 'Get final consolidated team output and run diagnostics',
284
+ {
285
+ sessionId: z.string().optional().describe('Session ID (defaults to current session)'),
286
+ },
287
+ async ({ sessionId }) => {
288
+ try {
289
+ const id = sessionId || await loadCurrentSessionId();
290
+ if (!id) throw new Error('No active session found');
291
+ const state = await loadSessionState(id);
292
+ const transcript = await loadTranscript(id);
293
+ const run = summarizeRun(state);
294
+
295
+ return {
296
+ content: [{
297
+ type: 'text',
298
+ text: JSON.stringify({
299
+ success: true,
300
+ sessionId: state.sessionId,
301
+ status: state.status,
302
+ run,
303
+ latestEvent: latestRunEvent(state),
304
+ finalSummary: state.run?.finalSummary || null,
305
+ sharedContext: state.run?.sharedContext || null,
306
+ meetingSummary: await readSessionArtifact(id, 'meeting-summary.md'),
307
+ meetingLog: await readSessionArtifact(id, 'meeting-log.md'),
308
+ lastMessage: state.messages?.[state.messages.length - 1] || null,
309
+ transcriptCount: transcript.length,
310
+ }, null, 2),
311
+ }],
312
+ };
313
+ } catch (error) {
314
+ return { content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }], isError: true };
315
+ }
316
+ }
317
+ );
318
+
319
+ this.server.tool(
320
+ 'collab_cancel_run',
321
+ 'Cancel active collaborative run while keeping session state',
322
+ {
323
+ sessionId: z.string().optional().describe('Session ID (defaults to current session)'),
324
+ },
325
+ async ({ sessionId }) => {
326
+ try {
327
+ const id = sessionId || await loadCurrentSessionId();
328
+ if (!id) throw new Error('No active session found');
329
+ const state = await loadSessionState(id);
330
+ const run = state.run || {};
331
+ const updated = await saveSessionState({
332
+ ...state,
333
+ status: 'running',
334
+ activeAgent: null,
335
+ run: {
336
+ ...run,
337
+ status: 'cancelled',
338
+ currentRole: null,
339
+ finishedAt: new Date().toISOString(),
340
+ lastError: {
341
+ code: 'RUN_CANCELLED',
342
+ message: 'Cancelled by MCP caller',
343
+ },
344
+ events: [
345
+ ...(Array.isArray(run.events) ? run.events : []),
346
+ {
347
+ id: `evt-${Date.now()}`,
348
+ type: 'run_cancelled',
349
+ timestamp: new Date().toISOString(),
350
+ },
351
+ ],
352
+ },
353
+ });
354
+ return {
355
+ content: [{
356
+ type: 'text',
357
+ text: JSON.stringify({
358
+ success: true,
359
+ sessionId: updated.sessionId,
360
+ run: summarizeRun(updated),
361
+ }, null, 2),
362
+ }],
363
+ };
364
+ } catch (error) {
365
+ return { content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }], isError: true };
366
+ }
367
+ }
368
+ );
369
+
107
370
  this.server.tool(
108
371
  'collab_send_message',
109
372
  'Send a user message to the active collaborative session',
@@ -150,6 +413,7 @@ class MCPCollabServer {
150
413
  const state = await runWorkerIteration(id, role, {
151
414
  cwd: process.cwd(),
152
415
  opencodeBin: loaded.opencodeBin || resolveCommandPath('opencode') || undefined,
416
+ timeoutMs: loaded.run?.policy?.timeoutPerRoleMs || 180000,
153
417
  });
154
418
 
155
419
  return {
@@ -164,6 +428,8 @@ class MCPCollabServer {
164
428
  model: state.model || null,
165
429
  roleModels: state.roleModels || {},
166
430
  effectiveRoleModels: buildEffectiveRoleModels(state, state.model || null),
431
+ run: summarizeRun(state),
432
+ latestEvent: latestRunEvent(state),
167
433
  messageCount: state.messages.length,
168
434
  }, null, 2),
169
435
  }],
@@ -176,10 +442,10 @@ class MCPCollabServer {
176
442
 
177
443
  this.server.tool(
178
444
  'collab_resume_session',
179
- 'Resume session and recreate tmux workers if needed',
445
+ 'Resume session and recreate workers if needed',
180
446
  {
181
447
  sessionId: z.string().optional().describe('Session ID (defaults to current session)'),
182
- recreateWorkers: z.boolean().default(true).describe('Recreate tmux session when missing'),
448
+ recreateWorkers: z.boolean().default(true).describe('Recreate multiplexer session when missing'),
183
449
  },
184
450
  async ({ sessionId, recreateWorkers }) => {
185
451
  try {
@@ -187,24 +453,37 @@ class MCPCollabServer {
187
453
  if (!id) throw new Error('No active session found');
188
454
  let state = await loadSessionState(id);
189
455
 
190
- const tmuxSessionName = state.tmuxSessionName || `acfm-synapse-${state.sessionId.slice(0, 8)}`;
191
- const tmuxExists = hasCommand('tmux') ? await tmuxSessionExists(tmuxSessionName) : false;
456
+ const multiplexer = state.multiplexer || resolveMultiplexer('auto', hasCommand('tmux'), hasCommand('zellij'));
457
+ if (!multiplexer) {
458
+ throw new Error('No multiplexer found (zellij/tmux). Run: acfm agents setup');
459
+ }
460
+ const sessionName = state.multiplexerSessionName || state.tmuxSessionName || `acfm-synapse-${state.sessionId.slice(0, 8)}`;
461
+ const sessionExists = await muxExists(multiplexer, sessionName);
192
462
 
193
- if (!tmuxExists && recreateWorkers) {
194
- if (!hasCommand('tmux')) {
195
- throw new Error('tmux is not installed. Run: acfm agents setup');
196
- }
463
+ if (!sessionExists && recreateWorkers) {
197
464
  const sessionDir = getSessionDir(state.sessionId);
198
- await spawnTmuxSession({ sessionName: tmuxSessionName, sessionDir, sessionId: state.sessionId });
465
+ if (multiplexer === 'zellij') {
466
+ if (!hasCommand('zellij')) throw new Error('zellij is not installed. Run: acfm agents setup');
467
+ await spawnZellijSession({ sessionName, sessionDir, sessionId: state.sessionId });
468
+ } else {
469
+ if (!hasCommand('tmux')) throw new Error('tmux is not installed. Run: acfm agents setup');
470
+ await spawnTmuxSession({ sessionName, sessionDir, sessionId: state.sessionId });
471
+ }
199
472
  }
200
473
 
201
474
  state = await saveSessionState({
202
475
  ...state,
203
476
  status: 'running',
204
- tmuxSessionName,
477
+ multiplexer,
478
+ multiplexerSessionName: sessionName,
479
+ tmuxSessionName: multiplexer === 'tmux' ? sessionName : state.tmuxSessionName || null,
205
480
  });
206
481
  await setCurrentSession(state.sessionId);
207
482
 
483
+ const attachCommand = multiplexer === 'zellij'
484
+ ? `zellij attach ${sessionName}`
485
+ : `tmux attach -t ${sessionName}`;
486
+
208
487
  return {
209
488
  content: [{
210
489
  type: 'text',
@@ -212,8 +491,10 @@ class MCPCollabServer {
212
491
  success: true,
213
492
  sessionId: state.sessionId,
214
493
  status: state.status,
215
- tmuxSessionName,
216
- recreatedWorkers: !tmuxExists && recreateWorkers,
494
+ multiplexer,
495
+ multiplexerSessionName: sessionName,
496
+ recreatedWorkers: !sessionExists && recreateWorkers,
497
+ attachCommand,
217
498
  }, null, 2),
218
499
  }],
219
500
  };
@@ -244,6 +525,9 @@ class MCPCollabServer {
244
525
  text: JSON.stringify({
245
526
  state,
246
527
  effectiveRoleModels: buildEffectiveRoleModels(state, state.model || null),
528
+ run: summarizeRun(state),
529
+ latestEvent: latestRunEvent(state),
530
+ sharedContext: state.run?.sharedContext || null,
247
531
  transcript,
248
532
  }, null, 2),
249
533
  }],
@@ -254,6 +538,71 @@ class MCPCollabServer {
254
538
  }
255
539
  );
256
540
 
541
+ this.server.tool(
542
+ 'collab_get_transcript',
543
+ 'Get transcript messages with optional role filtering',
544
+ {
545
+ sessionId: z.string().optional().describe('Session ID (defaults to current session)'),
546
+ role: z.enum(['planner', 'critic', 'coder', 'reviewer', 'all']).default('all').describe('Role filter'),
547
+ limit: z.number().int().positive().max(500).default(100).describe('Max messages to return'),
548
+ },
549
+ async ({ sessionId, role, limit }) => {
550
+ try {
551
+ const id = sessionId || await loadCurrentSessionId();
552
+ if (!id) throw new Error('No active session found');
553
+ const transcript = await loadTranscript(id);
554
+ const filtered = transcript
555
+ .filter((msg) => role === 'all' || msg.from === role)
556
+ .slice(-limit);
557
+
558
+ return {
559
+ content: [{
560
+ type: 'text',
561
+ text: JSON.stringify({
562
+ sessionId: id,
563
+ role,
564
+ count: filtered.length,
565
+ transcript: filtered,
566
+ }, null, 2),
567
+ }],
568
+ };
569
+ } catch (error) {
570
+ return { content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }], isError: true };
571
+ }
572
+ }
573
+ );
574
+
575
+ this.server.tool(
576
+ 'collab_get_meeting_log',
577
+ 'Get generated meeting log and summary artifacts',
578
+ {
579
+ sessionId: z.string().optional().describe('Session ID (defaults to current session)'),
580
+ },
581
+ async ({ sessionId }) => {
582
+ try {
583
+ const id = sessionId || await loadCurrentSessionId();
584
+ if (!id) throw new Error('No active session found');
585
+ const state = await loadSessionState(id);
586
+
587
+ return {
588
+ content: [{
589
+ type: 'text',
590
+ text: JSON.stringify({
591
+ sessionId: id,
592
+ status: state.status,
593
+ finalSummary: state.run?.finalSummary || null,
594
+ sharedContext: state.run?.sharedContext || null,
595
+ meetingSummary: await readSessionArtifact(id, 'meeting-summary.md'),
596
+ meetingLog: await readSessionArtifact(id, 'meeting-log.md'),
597
+ }, null, 2),
598
+ }],
599
+ };
600
+ } catch (error) {
601
+ return { content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }], isError: true };
602
+ }
603
+ }
604
+ );
605
+
257
606
  this.server.tool(
258
607
  'collab_stop_session',
259
608
  'Stop current collaborative session',
@@ -271,7 +620,12 @@ class MCPCollabServer {
271
620
  return {
272
621
  content: [{
273
622
  type: 'text',
274
- text: JSON.stringify({ success: true, sessionId: id, status: updated.status }, null, 2),
623
+ text: JSON.stringify({
624
+ success: true,
625
+ sessionId: id,
626
+ status: updated.status,
627
+ run: summarizeRun(updated),
628
+ }, null, 2),
275
629
  }],
276
630
  };
277
631
  } catch (error) {