ac-framework 1.9.8 → 2.0.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/README.md +18 -10
- package/package.json +1 -1
- package/src/agents/config-store.js +16 -0
- package/src/agents/orchestrator.js +74 -4
- package/src/agents/runtime.js +78 -3
- package/src/agents/state-store.js +173 -4
- package/src/commands/agents.js +547 -64
- package/src/commands/init.js +16 -5
- package/src/mcp/collab-server.js +88 -26
- package/src/services/dependency-installer.js +247 -5
package/src/commands/agents.js
CHANGED
|
@@ -18,6 +18,7 @@ import { runWorkerIteration } from '../agents/orchestrator.js';
|
|
|
18
18
|
import {
|
|
19
19
|
addUserMessage,
|
|
20
20
|
createSession,
|
|
21
|
+
ensureSessionArtifacts,
|
|
21
22
|
getSessionDir,
|
|
22
23
|
loadCurrentSessionId,
|
|
23
24
|
loadSessionState,
|
|
@@ -26,8 +27,19 @@ import {
|
|
|
26
27
|
saveSessionState,
|
|
27
28
|
setCurrentSession,
|
|
28
29
|
stopSession,
|
|
30
|
+
sessionArtifactPaths,
|
|
31
|
+
writeMeetingSummary,
|
|
29
32
|
} from '../agents/state-store.js';
|
|
30
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
roleLogPath,
|
|
35
|
+
runTmux,
|
|
36
|
+
runZellij,
|
|
37
|
+
spawnTmuxSession,
|
|
38
|
+
spawnZellijSession,
|
|
39
|
+
tmuxSessionExists,
|
|
40
|
+
zellijSessionExists,
|
|
41
|
+
resolveMultiplexer,
|
|
42
|
+
} from '../agents/runtime.js';
|
|
31
43
|
import { getAgentsConfigPath, loadAgentsConfig, updateAgentsConfig } from '../agents/config-store.js';
|
|
32
44
|
import {
|
|
33
45
|
buildEffectiveRoleModels,
|
|
@@ -35,7 +47,13 @@ import {
|
|
|
35
47
|
normalizeModelId,
|
|
36
48
|
sanitizeRoleModels,
|
|
37
49
|
} from '../agents/model-selection.js';
|
|
38
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
ensureCollabDependencies,
|
|
52
|
+
hasCommand,
|
|
53
|
+
resolveCommandPath,
|
|
54
|
+
resolveManagedZellijPath,
|
|
55
|
+
installManagedZellijLatest,
|
|
56
|
+
} from '../services/dependency-installer.js';
|
|
39
57
|
|
|
40
58
|
function output(data, json) {
|
|
41
59
|
if (json) {
|
|
@@ -60,18 +78,81 @@ async function ensureSessionId(required = true) {
|
|
|
60
78
|
function printStartSummary(state) {
|
|
61
79
|
console.log(chalk.green(`✓ ${COLLAB_SYSTEM_NAME} session started`));
|
|
62
80
|
console.log(chalk.dim(` Session: ${state.sessionId}`));
|
|
63
|
-
|
|
81
|
+
const multiplexer = state.multiplexer || 'auto';
|
|
82
|
+
const muxSessionName = state.multiplexerSessionName || state.tmuxSessionName || '-';
|
|
83
|
+
console.log(chalk.dim(` Multiplexer: ${multiplexer}`));
|
|
84
|
+
console.log(chalk.dim(` Session name: ${muxSessionName}`));
|
|
64
85
|
console.log(chalk.dim(` Task: ${state.task}`));
|
|
65
86
|
console.log(chalk.dim(` Roles: ${state.roles.join(', ')}`));
|
|
66
87
|
console.log();
|
|
67
88
|
console.log(chalk.cyan('Attach with:'));
|
|
68
|
-
|
|
89
|
+
if (multiplexer === 'zellij') {
|
|
90
|
+
console.log(chalk.white(` zellij attach ${muxSessionName}`));
|
|
91
|
+
} else {
|
|
92
|
+
console.log(chalk.white(` tmux attach -t ${muxSessionName}`));
|
|
93
|
+
}
|
|
69
94
|
console.log(chalk.white(' acfm agents live'));
|
|
70
95
|
console.log();
|
|
71
96
|
console.log(chalk.cyan('Interact with:'));
|
|
72
97
|
console.log(chalk.white(' acfm agents send "your message"'));
|
|
73
98
|
}
|
|
74
99
|
|
|
100
|
+
function validateMultiplexer(value) {
|
|
101
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
102
|
+
if (!['auto', 'zellij', 'tmux'].includes(normalized)) {
|
|
103
|
+
throw new Error('--mux must be one of: auto|zellij|tmux');
|
|
104
|
+
}
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function sessionMuxName(state) {
|
|
109
|
+
return state.multiplexerSessionName || state.tmuxSessionName || `acfm-synapse-${state.sessionId.slice(0, 8)}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function sessionExistsForMux(multiplexer, sessionName, zellijPath = null) {
|
|
113
|
+
if (multiplexer === 'zellij') return zellijSessionExists(sessionName, zellijPath);
|
|
114
|
+
return tmuxSessionExists(sessionName);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveConfiguredZellijPath(config) {
|
|
118
|
+
const strategy = config?.agents?.zellij?.strategy || 'auto';
|
|
119
|
+
if (strategy === 'system') {
|
|
120
|
+
return resolveCommandPath('zellij');
|
|
121
|
+
}
|
|
122
|
+
const managed = resolveManagedZellijPath(config);
|
|
123
|
+
if (managed) return managed;
|
|
124
|
+
return resolveCommandPath('zellij');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function shouldUseManagedZellij(config) {
|
|
128
|
+
const strategy = config?.agents?.zellij?.strategy || 'auto';
|
|
129
|
+
if (strategy === 'managed') return true;
|
|
130
|
+
if (strategy === 'system') return false;
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function resolveMultiplexerWithPaths(config, requestedMux = 'auto') {
|
|
135
|
+
const zellijPath = resolveConfiguredZellijPath(config);
|
|
136
|
+
const tmuxPath = resolveCommandPath('tmux');
|
|
137
|
+
const selected = resolveMultiplexer(requestedMux, Boolean(tmuxPath), Boolean(zellijPath));
|
|
138
|
+
return {
|
|
139
|
+
selected,
|
|
140
|
+
zellijPath,
|
|
141
|
+
tmuxPath,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function attachToMux(multiplexer, sessionName, readonly = false, zellijPath = null) {
|
|
146
|
+
if (multiplexer === 'zellij') {
|
|
147
|
+
await runZellij(['attach', sessionName], { stdio: 'inherit', binaryPath: zellijPath });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const args = ['attach'];
|
|
151
|
+
if (readonly) args.push('-r');
|
|
152
|
+
args.push('-t', sessionName);
|
|
153
|
+
await runTmux('tmux', args, { stdio: 'inherit' });
|
|
154
|
+
}
|
|
155
|
+
|
|
75
156
|
function toMarkdownTranscript(state, transcript) {
|
|
76
157
|
const displayedRound = Math.min(state.round, state.maxRounds);
|
|
77
158
|
const lines = [
|
|
@@ -161,6 +242,18 @@ async function readSessionArtifact(sessionId, filename) {
|
|
|
161
242
|
return readFile(path, 'utf8');
|
|
162
243
|
}
|
|
163
244
|
|
|
245
|
+
async function collectArtifactStatus(sessionId) {
|
|
246
|
+
await ensureSessionArtifacts(sessionId);
|
|
247
|
+
const paths = sessionArtifactPaths(sessionId);
|
|
248
|
+
return {
|
|
249
|
+
sessionId,
|
|
250
|
+
checkedAt: new Date().toISOString(),
|
|
251
|
+
artifacts: Object.fromEntries(
|
|
252
|
+
Object.entries(paths).map(([key, value]) => [key, { path: value, exists: existsSync(value) }])
|
|
253
|
+
),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
164
257
|
async function preflightModel({ opencodeBin, model, cwd }) {
|
|
165
258
|
const selected = normalizeModelId(model) || DEFAULT_SYNAPSE_MODEL;
|
|
166
259
|
try {
|
|
@@ -181,31 +274,96 @@ export function agentsCommand() {
|
|
|
181
274
|
const agents = new Command('agents')
|
|
182
275
|
.description(`${COLLAB_SYSTEM_NAME} — collaborative multi-agent system powered by OpenCode`);
|
|
183
276
|
|
|
277
|
+
agents.addHelpText('after', `
|
|
278
|
+
Examples:
|
|
279
|
+
acfm agents start --task "Implement auth flow" --mux auto
|
|
280
|
+
acfm agents setup
|
|
281
|
+
acfm agents artifacts
|
|
282
|
+
acfm agents runtime get
|
|
283
|
+
acfm agents runtime install-zellij
|
|
284
|
+
acfm agents runtime set zellij
|
|
285
|
+
acfm agents model choose
|
|
286
|
+
acfm agents model list
|
|
287
|
+
acfm agents transcript --role all --limit 40
|
|
288
|
+
acfm agents summary
|
|
289
|
+
acfm agents export --format md --out ./session.md
|
|
290
|
+
`);
|
|
291
|
+
|
|
184
292
|
agents
|
|
185
293
|
.command('setup')
|
|
186
|
-
.description('Install optional collaboration dependencies (OpenCode + tmux)')
|
|
294
|
+
.description('Install optional collaboration dependencies (OpenCode + zellij/tmux)')
|
|
295
|
+
.option('--yes', 'Install dependencies without interactive confirmation', false)
|
|
187
296
|
.option('--json', 'Output as JSON')
|
|
188
297
|
.action(async (opts) => {
|
|
189
|
-
|
|
298
|
+
let installZellij = true;
|
|
299
|
+
let installTmux = true;
|
|
300
|
+
|
|
301
|
+
if (!opts.yes && !opts.json) {
|
|
302
|
+
const answers = await inquirer.prompt([
|
|
303
|
+
{
|
|
304
|
+
type: 'confirm',
|
|
305
|
+
name: 'installZellij',
|
|
306
|
+
message: 'Install zellij (recommended, multiplatform backend)?',
|
|
307
|
+
default: true,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
type: 'confirm',
|
|
311
|
+
name: 'installTmux',
|
|
312
|
+
message: 'Install tmux as fallback backend?',
|
|
313
|
+
default: true,
|
|
314
|
+
},
|
|
315
|
+
]);
|
|
316
|
+
installZellij = Boolean(answers.installZellij);
|
|
317
|
+
installTmux = Boolean(answers.installTmux);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const result = ensureCollabDependencies({
|
|
321
|
+
installZellij,
|
|
322
|
+
installTmux,
|
|
323
|
+
preferManagedZellij: installZellij,
|
|
324
|
+
});
|
|
325
|
+
const awaited = await result;
|
|
190
326
|
let collabMcp = null;
|
|
191
327
|
|
|
192
|
-
if (
|
|
328
|
+
if (awaited.success) {
|
|
193
329
|
const { detectAndInstallMCPs } = await import('../services/mcp-installer.js');
|
|
194
330
|
collabMcp = detectAndInstallMCPs({ target: 'collab' });
|
|
195
331
|
}
|
|
196
332
|
|
|
197
|
-
const payload = { ...
|
|
333
|
+
const payload = { ...awaited, collabMcp };
|
|
198
334
|
output(payload, opts.json);
|
|
199
335
|
if (!opts.json) {
|
|
200
|
-
const oLabel =
|
|
201
|
-
const tLabel =
|
|
202
|
-
|
|
203
|
-
console.log(`
|
|
336
|
+
const oLabel = awaited.opencode.success ? chalk.green('ok') : chalk.red('failed');
|
|
337
|
+
const tLabel = awaited.tmux.success ? chalk.green('ok') : chalk.red('failed');
|
|
338
|
+
const zLabel = awaited.zellij.success ? chalk.green('ok') : chalk.red('failed');
|
|
339
|
+
console.log(`OpenCode: ${oLabel} - ${awaited.opencode.message}`);
|
|
340
|
+
console.log(`zellij: ${zLabel} - ${awaited.zellij.message}`);
|
|
341
|
+
if (awaited.zellij.binaryPath) {
|
|
342
|
+
console.log(chalk.dim(` ${awaited.zellij.binaryPath}`));
|
|
343
|
+
}
|
|
344
|
+
console.log(`tmux: ${tLabel} - ${awaited.tmux.message}`);
|
|
204
345
|
if (collabMcp) {
|
|
205
346
|
console.log(`Collab MCP: ${chalk.green('ok')} - installed ${collabMcp.success}/${collabMcp.installed} on detected assistants`);
|
|
206
347
|
}
|
|
207
348
|
}
|
|
208
|
-
|
|
349
|
+
|
|
350
|
+
if (awaited.zellij.success && awaited.zellij.source === 'managed' && awaited.zellij.binaryPath) {
|
|
351
|
+
await updateAgentsConfig((current) => ({
|
|
352
|
+
agents: {
|
|
353
|
+
defaultModel: current.agents.defaultModel,
|
|
354
|
+
defaultRoleModels: { ...current.agents.defaultRoleModels },
|
|
355
|
+
multiplexer: current.agents.multiplexer || 'auto',
|
|
356
|
+
zellij: {
|
|
357
|
+
strategy: 'managed',
|
|
358
|
+
binaryPath: awaited.zellij.binaryPath,
|
|
359
|
+
version: awaited.zellij.version || null,
|
|
360
|
+
source: 'managed',
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!awaited.success) process.exit(1);
|
|
209
367
|
});
|
|
210
368
|
|
|
211
369
|
agents
|
|
@@ -286,10 +444,10 @@ export function agentsCommand() {
|
|
|
286
444
|
}
|
|
287
445
|
console.log(chalk.bold('SynapseGrid Sessions'));
|
|
288
446
|
for (const item of sessions) {
|
|
289
|
-
|
|
447
|
+
console.log(
|
|
290
448
|
`${chalk.cyan(item.sessionId.slice(0, 8))} ${item.status.padEnd(10)} ` +
|
|
291
449
|
`round ${String(item.round).padStart(2)}/${String(item.maxRounds).padEnd(2)} ` +
|
|
292
|
-
`${item.tmuxSessionName || '-'} ${item.task}`
|
|
450
|
+
`${item.multiplexer || 'auto'}:${item.multiplexerSessionName || item.tmuxSessionName || '-'} ${item.task}`
|
|
293
451
|
);
|
|
294
452
|
}
|
|
295
453
|
}
|
|
@@ -302,15 +460,19 @@ export function agentsCommand() {
|
|
|
302
460
|
|
|
303
461
|
agents
|
|
304
462
|
.command('attach')
|
|
305
|
-
.description('Attach terminal to active SynapseGrid
|
|
463
|
+
.description('Attach terminal to active SynapseGrid multiplexer session')
|
|
306
464
|
.action(async () => {
|
|
307
465
|
try {
|
|
308
466
|
const sessionId = await ensureSessionId(true);
|
|
309
467
|
const state = await loadSessionState(sessionId);
|
|
310
|
-
|
|
311
|
-
|
|
468
|
+
const cfg = await loadAgentsConfig();
|
|
469
|
+
const zellijPath = resolveConfiguredZellijPath(cfg);
|
|
470
|
+
const multiplexer = state.multiplexer || 'tmux';
|
|
471
|
+
const muxSessionName = sessionMuxName(state);
|
|
472
|
+
if (!muxSessionName) {
|
|
473
|
+
throw new Error('No multiplexer session registered for active collaborative session');
|
|
312
474
|
}
|
|
313
|
-
await
|
|
475
|
+
await attachToMux(multiplexer, muxSessionName, false, zellijPath);
|
|
314
476
|
} catch (error) {
|
|
315
477
|
console.error(chalk.red(`Error: ${error.message}`));
|
|
316
478
|
process.exit(1);
|
|
@@ -319,23 +481,24 @@ export function agentsCommand() {
|
|
|
319
481
|
|
|
320
482
|
agents
|
|
321
483
|
.command('live')
|
|
322
|
-
.description('Attach to live
|
|
484
|
+
.description('Attach to live collaboration view (all agent panes)')
|
|
323
485
|
.option('--readonly', 'Attach in read-only mode', false)
|
|
324
486
|
.action(async (opts) => {
|
|
325
487
|
try {
|
|
326
488
|
const sessionId = await ensureSessionId(true);
|
|
327
489
|
const state = await loadSessionState(sessionId);
|
|
328
|
-
|
|
329
|
-
|
|
490
|
+
const cfg = await loadAgentsConfig();
|
|
491
|
+
const zellijPath = resolveConfiguredZellijPath(cfg);
|
|
492
|
+
const multiplexer = state.multiplexer || 'tmux';
|
|
493
|
+
const muxSessionName = sessionMuxName(state);
|
|
494
|
+
if (!muxSessionName) {
|
|
495
|
+
throw new Error('No multiplexer session registered for active collaborative session');
|
|
330
496
|
}
|
|
331
|
-
const
|
|
332
|
-
if (!
|
|
333
|
-
throw new Error(
|
|
497
|
+
const sessionExists = await sessionExistsForMux(multiplexer, muxSessionName, zellijPath);
|
|
498
|
+
if (!sessionExists) {
|
|
499
|
+
throw new Error(`${multiplexer} session ${muxSessionName} no longer exists. Run: acfm agents resume`);
|
|
334
500
|
}
|
|
335
|
-
|
|
336
|
-
if (opts.readonly) args.push('-r');
|
|
337
|
-
args.push('-t', state.tmuxSessionName);
|
|
338
|
-
await runTmux('tmux', args, { stdio: 'inherit' });
|
|
501
|
+
await attachToMux(multiplexer, muxSessionName, Boolean(opts.readonly), zellijPath);
|
|
339
502
|
} catch (error) {
|
|
340
503
|
console.error(chalk.red(`Error: ${error.message}`));
|
|
341
504
|
process.exit(1);
|
|
@@ -344,48 +507,68 @@ export function agentsCommand() {
|
|
|
344
507
|
|
|
345
508
|
agents
|
|
346
509
|
.command('resume')
|
|
347
|
-
.description('Resume a previous session and optionally recreate
|
|
510
|
+
.description('Resume a previous session and optionally recreate multiplexer workers')
|
|
348
511
|
.option('--session <id>', 'Session ID to resume (defaults to current)')
|
|
349
|
-
.option('--no-recreate', 'Do not recreate
|
|
350
|
-
.option('--no-attach', 'Do not attach
|
|
512
|
+
.option('--no-recreate', 'Do not recreate multiplexer session/workers when missing')
|
|
513
|
+
.option('--no-attach', 'Do not attach multiplexer after resume')
|
|
351
514
|
.option('--json', 'Output as JSON')
|
|
352
515
|
.action(async (opts) => {
|
|
353
516
|
try {
|
|
354
517
|
const sessionId = opts.session || await ensureSessionId(true);
|
|
355
518
|
let state = await loadSessionState(sessionId);
|
|
519
|
+
const multiplexer = state.multiplexer || 'tmux';
|
|
520
|
+
const cfg = await loadAgentsConfig();
|
|
521
|
+
const zellijPath = resolveConfiguredZellijPath(cfg);
|
|
522
|
+
const tmuxPath = resolveCommandPath('tmux');
|
|
523
|
+
|
|
524
|
+
if (multiplexer === 'zellij' && !zellijPath) {
|
|
525
|
+
throw new Error('zellij is not installed. Run: acfm agents setup');
|
|
526
|
+
}
|
|
527
|
+
if (multiplexer === 'tmux' && !tmuxPath) {
|
|
528
|
+
throw new Error('tmux is not installed. Run: acfm agents setup');
|
|
529
|
+
}
|
|
356
530
|
|
|
357
|
-
const
|
|
358
|
-
const
|
|
531
|
+
const muxSessionName = sessionMuxName(state);
|
|
532
|
+
const muxExists = await sessionExistsForMux(multiplexer, muxSessionName, zellijPath);
|
|
359
533
|
|
|
360
|
-
if (!
|
|
361
|
-
if (!hasCommand('tmux')) {
|
|
362
|
-
throw new Error('tmux is not installed. Run: acfm agents setup');
|
|
363
|
-
}
|
|
534
|
+
if (!muxExists && opts.recreate) {
|
|
364
535
|
const sessionDir = getSessionDir(state.sessionId);
|
|
365
|
-
|
|
536
|
+
if (multiplexer === 'zellij') {
|
|
537
|
+
await spawnZellijSession({
|
|
538
|
+
sessionName: muxSessionName,
|
|
539
|
+
sessionDir,
|
|
540
|
+
sessionId: state.sessionId,
|
|
541
|
+
binaryPath: zellijPath,
|
|
542
|
+
});
|
|
543
|
+
} else {
|
|
544
|
+
await spawnTmuxSession({ sessionName: muxSessionName, sessionDir, sessionId: state.sessionId });
|
|
545
|
+
}
|
|
366
546
|
}
|
|
367
547
|
|
|
368
548
|
state = await saveSessionState({
|
|
369
549
|
...state,
|
|
370
550
|
status: 'running',
|
|
371
|
-
|
|
551
|
+
multiplexer,
|
|
552
|
+
multiplexerSessionName: muxSessionName,
|
|
553
|
+
tmuxSessionName: multiplexer === 'tmux' ? muxSessionName : (state.tmuxSessionName || null),
|
|
372
554
|
});
|
|
373
555
|
await setCurrentSession(state.sessionId);
|
|
374
556
|
|
|
375
557
|
output({
|
|
376
558
|
sessionId: state.sessionId,
|
|
377
559
|
status: state.status,
|
|
378
|
-
|
|
379
|
-
|
|
560
|
+
multiplexer,
|
|
561
|
+
multiplexerSessionName: muxSessionName,
|
|
562
|
+
recreatedSession: !muxExists && Boolean(opts.recreate),
|
|
380
563
|
}, opts.json);
|
|
381
564
|
|
|
382
565
|
if (!opts.json) {
|
|
383
566
|
console.log(chalk.green(`✓ Resumed session ${state.sessionId}`));
|
|
384
|
-
console.log(chalk.dim(`
|
|
567
|
+
console.log(chalk.dim(` ${multiplexer}: ${muxSessionName}`));
|
|
385
568
|
}
|
|
386
569
|
|
|
387
570
|
if (opts.attach) {
|
|
388
|
-
await
|
|
571
|
+
await attachToMux(multiplexer, muxSessionName, false, zellijPath);
|
|
389
572
|
}
|
|
390
573
|
} catch (error) {
|
|
391
574
|
output({ error: error.message }, opts.json);
|
|
@@ -453,6 +636,129 @@ export function agentsCommand() {
|
|
|
453
636
|
.command('model')
|
|
454
637
|
.description('Manage default SynapseGrid model configuration');
|
|
455
638
|
|
|
639
|
+
const runtime = agents
|
|
640
|
+
.command('runtime')
|
|
641
|
+
.description('Manage SynapseGrid runtime backend settings');
|
|
642
|
+
|
|
643
|
+
runtime
|
|
644
|
+
.command('get')
|
|
645
|
+
.description('Show configured multiplexer backend')
|
|
646
|
+
.option('--json', 'Output as JSON')
|
|
647
|
+
.action(async (opts) => {
|
|
648
|
+
try {
|
|
649
|
+
const cfg = await loadAgentsConfig();
|
|
650
|
+
const configured = validateMultiplexer(cfg.agents.multiplexer || 'auto');
|
|
651
|
+
const zellijPath = resolveConfiguredZellijPath(cfg);
|
|
652
|
+
const tmuxPath = resolveCommandPath('tmux');
|
|
653
|
+
const resolved = resolveMultiplexer(configured, Boolean(tmuxPath), Boolean(zellijPath));
|
|
654
|
+
const payload = {
|
|
655
|
+
configPath: getAgentsConfigPath(),
|
|
656
|
+
multiplexer: configured,
|
|
657
|
+
resolved,
|
|
658
|
+
available: {
|
|
659
|
+
zellij: Boolean(zellijPath),
|
|
660
|
+
tmux: Boolean(tmuxPath),
|
|
661
|
+
},
|
|
662
|
+
zellij: cfg.agents.zellij,
|
|
663
|
+
zellijPath,
|
|
664
|
+
};
|
|
665
|
+
output(payload, opts.json);
|
|
666
|
+
if (!opts.json) {
|
|
667
|
+
console.log(chalk.bold('SynapseGrid runtime backend'));
|
|
668
|
+
console.log(chalk.dim(`Config: ${payload.configPath}`));
|
|
669
|
+
console.log(chalk.dim(`Configured: ${configured}`));
|
|
670
|
+
console.log(chalk.dim(`Resolved: ${resolved || 'none'}`));
|
|
671
|
+
console.log(chalk.dim(`zellij=${payload.available.zellij} tmux=${payload.available.tmux}`));
|
|
672
|
+
if (payload.zellijPath) {
|
|
673
|
+
console.log(chalk.dim(`zellij path: ${payload.zellijPath}`));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
} catch (error) {
|
|
677
|
+
output({ error: error.message }, opts.json);
|
|
678
|
+
if (!opts.json) console.error(chalk.red(`Error: ${error.message}`));
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
runtime
|
|
684
|
+
.command('set <mux>')
|
|
685
|
+
.description('Set multiplexer backend: auto|zellij|tmux')
|
|
686
|
+
.option('--json', 'Output as JSON')
|
|
687
|
+
.action(async (mux, opts) => {
|
|
688
|
+
try {
|
|
689
|
+
const selected = validateMultiplexer(mux);
|
|
690
|
+
const updated = await updateAgentsConfig((current) => ({
|
|
691
|
+
agents: {
|
|
692
|
+
defaultModel: current.agents.defaultModel,
|
|
693
|
+
defaultRoleModels: { ...current.agents.defaultRoleModels },
|
|
694
|
+
multiplexer: selected,
|
|
695
|
+
zellij: {
|
|
696
|
+
...(current.agents.zellij || { strategy: 'auto', binaryPath: null, version: null, source: null }),
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
}));
|
|
700
|
+
const zellijPath = resolveConfiguredZellijPath(updated);
|
|
701
|
+
const tmuxPath = resolveCommandPath('tmux');
|
|
702
|
+
const resolved = resolveMultiplexer(updated.agents.multiplexer, Boolean(tmuxPath), Boolean(zellijPath));
|
|
703
|
+
const payload = {
|
|
704
|
+
success: true,
|
|
705
|
+
configPath: getAgentsConfigPath(),
|
|
706
|
+
multiplexer: updated.agents.multiplexer,
|
|
707
|
+
resolved,
|
|
708
|
+
};
|
|
709
|
+
output(payload, opts.json);
|
|
710
|
+
if (!opts.json) {
|
|
711
|
+
console.log(chalk.green('✓ SynapseGrid runtime backend updated'));
|
|
712
|
+
console.log(chalk.dim(` Configured: ${payload.multiplexer}`));
|
|
713
|
+
console.log(chalk.dim(` Resolved: ${payload.resolved || 'none'}`));
|
|
714
|
+
}
|
|
715
|
+
} catch (error) {
|
|
716
|
+
output({ error: error.message }, opts.json);
|
|
717
|
+
if (!opts.json) console.error(chalk.red(`Error: ${error.message}`));
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
runtime
|
|
723
|
+
.command('install-zellij')
|
|
724
|
+
.description('Install latest zellij release managed by AC Framework')
|
|
725
|
+
.option('--json', 'Output as JSON')
|
|
726
|
+
.action(async (opts) => {
|
|
727
|
+
try {
|
|
728
|
+
const result = await installManagedZellijLatest();
|
|
729
|
+
if (!result.success) {
|
|
730
|
+
output(result, opts.json);
|
|
731
|
+
if (!opts.json) console.error(chalk.red(`Error: ${result.message}`));
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
await updateAgentsConfig((current) => ({
|
|
736
|
+
agents: {
|
|
737
|
+
defaultModel: current.agents.defaultModel,
|
|
738
|
+
defaultRoleModels: { ...current.agents.defaultRoleModels },
|
|
739
|
+
multiplexer: current.agents.multiplexer || 'auto',
|
|
740
|
+
zellij: {
|
|
741
|
+
strategy: result.source === 'system' ? 'system' : 'managed',
|
|
742
|
+
binaryPath: result.binaryPath,
|
|
743
|
+
version: result.version || null,
|
|
744
|
+
source: result.source || 'managed',
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
}));
|
|
748
|
+
|
|
749
|
+
output(result, opts.json);
|
|
750
|
+
if (!opts.json) {
|
|
751
|
+
console.log(chalk.green('✓ Managed zellij ready'));
|
|
752
|
+
console.log(chalk.dim(` Version: ${result.version || 'unknown'}`));
|
|
753
|
+
console.log(chalk.dim(` Binary: ${result.binaryPath}`));
|
|
754
|
+
}
|
|
755
|
+
} catch (error) {
|
|
756
|
+
output({ error: error.message }, opts.json);
|
|
757
|
+
if (!opts.json) console.error(chalk.red(`Error: ${error.message}`));
|
|
758
|
+
process.exit(1);
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
|
|
456
762
|
model
|
|
457
763
|
.command('list')
|
|
458
764
|
.description('List available OpenCode models grouped by provider')
|
|
@@ -510,6 +816,7 @@ export function agentsCommand() {
|
|
|
510
816
|
configPath: getAgentsConfigPath(),
|
|
511
817
|
defaultModel: config.agents.defaultModel,
|
|
512
818
|
defaultRoleModels: config.agents.defaultRoleModels,
|
|
819
|
+
multiplexer: config.agents.multiplexer,
|
|
513
820
|
};
|
|
514
821
|
output(payload, opts.json);
|
|
515
822
|
if (!opts.json) {
|
|
@@ -519,6 +826,7 @@ export function agentsCommand() {
|
|
|
519
826
|
for (const role of COLLAB_ROLES) {
|
|
520
827
|
console.log(chalk.dim(`- ${role}: ${payload.defaultRoleModels[role] || '(none)'}`));
|
|
521
828
|
}
|
|
829
|
+
console.log(chalk.dim(`Multiplexer: ${payload.multiplexer || 'auto'}`));
|
|
522
830
|
}
|
|
523
831
|
} catch (error) {
|
|
524
832
|
output({ error: error.message }, opts.json);
|
|
@@ -594,6 +902,10 @@ export function agentsCommand() {
|
|
|
594
902
|
agents: {
|
|
595
903
|
defaultModel: current.agents.defaultModel,
|
|
596
904
|
defaultRoleModels: { ...current.agents.defaultRoleModels },
|
|
905
|
+
multiplexer: current.agents.multiplexer || 'auto',
|
|
906
|
+
zellij: {
|
|
907
|
+
...(current.agents.zellij || { strategy: 'auto', binaryPath: null, version: null, source: null }),
|
|
908
|
+
},
|
|
597
909
|
},
|
|
598
910
|
};
|
|
599
911
|
|
|
@@ -654,6 +966,10 @@ export function agentsCommand() {
|
|
|
654
966
|
agents: {
|
|
655
967
|
defaultModel: current.agents.defaultModel,
|
|
656
968
|
defaultRoleModels: { ...current.agents.defaultRoleModels },
|
|
969
|
+
multiplexer: current.agents.multiplexer || 'auto',
|
|
970
|
+
zellij: {
|
|
971
|
+
...(current.agents.zellij || { strategy: 'auto', binaryPath: null, version: null, source: null }),
|
|
972
|
+
},
|
|
657
973
|
},
|
|
658
974
|
};
|
|
659
975
|
if (role === 'all') {
|
|
@@ -702,6 +1018,10 @@ export function agentsCommand() {
|
|
|
702
1018
|
agents: {
|
|
703
1019
|
defaultModel: current.agents.defaultModel,
|
|
704
1020
|
defaultRoleModels: { ...current.agents.defaultRoleModels },
|
|
1021
|
+
multiplexer: current.agents.multiplexer || 'auto',
|
|
1022
|
+
zellij: {
|
|
1023
|
+
...(current.agents.zellij || { strategy: 'auto', binaryPath: null, version: null, source: null }),
|
|
1024
|
+
},
|
|
705
1025
|
},
|
|
706
1026
|
};
|
|
707
1027
|
if (role === 'all') {
|
|
@@ -810,6 +1130,66 @@ export function agentsCommand() {
|
|
|
810
1130
|
}
|
|
811
1131
|
});
|
|
812
1132
|
|
|
1133
|
+
agents
|
|
1134
|
+
.command('artifacts')
|
|
1135
|
+
.description('Show SynapseGrid artifact paths and existence status')
|
|
1136
|
+
.option('--session <id>', 'Session ID (defaults to current)')
|
|
1137
|
+
.option('--watch', 'Continuously watch artifact status', false)
|
|
1138
|
+
.option('--interval <ms>', 'Polling interval in milliseconds for --watch', '1500')
|
|
1139
|
+
.option('--json', 'Output as JSON')
|
|
1140
|
+
.action(async (opts) => {
|
|
1141
|
+
try {
|
|
1142
|
+
const sessionId = opts.session || await ensureSessionId(true);
|
|
1143
|
+
const intervalMs = Number.parseInt(opts.interval, 10);
|
|
1144
|
+
if (!Number.isInteger(intervalMs) || intervalMs <= 0) {
|
|
1145
|
+
throw new Error('--interval must be a positive integer');
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const printSnapshot = async () => {
|
|
1149
|
+
const snapshot = await collectArtifactStatus(sessionId);
|
|
1150
|
+
if (opts.json) {
|
|
1151
|
+
process.stdout.write(JSON.stringify(snapshot) + '\n');
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
console.log(chalk.bold('SynapseGrid artifacts'));
|
|
1156
|
+
console.log(chalk.dim(`Session: ${snapshot.sessionId}`));
|
|
1157
|
+
console.log(chalk.dim(`Checked: ${snapshot.checkedAt}`));
|
|
1158
|
+
for (const [key, meta] of Object.entries(snapshot.artifacts)) {
|
|
1159
|
+
console.log(chalk.dim(`${key}: ${meta.exists ? 'ok' : 'missing'} -> ${meta.path}`));
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
if (!opts.watch) {
|
|
1164
|
+
const snapshot = await collectArtifactStatus(sessionId);
|
|
1165
|
+
output(snapshot, opts.json);
|
|
1166
|
+
if (!opts.json) {
|
|
1167
|
+
console.log(chalk.bold('SynapseGrid artifacts'));
|
|
1168
|
+
for (const [key, meta] of Object.entries(snapshot.artifacts)) {
|
|
1169
|
+
console.log(chalk.dim(`${key}: ${meta.exists ? 'ok' : 'missing'} -> ${meta.path}`));
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (!opts.json) {
|
|
1176
|
+
console.log(chalk.cyan('Watching artifacts (Ctrl+C to stop)\n'));
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
while (true) {
|
|
1180
|
+
if (!opts.json) {
|
|
1181
|
+
process.stdout.write('\x1Bc');
|
|
1182
|
+
}
|
|
1183
|
+
await printSnapshot();
|
|
1184
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, intervalMs));
|
|
1185
|
+
}
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
output({ error: error.message }, opts.json);
|
|
1188
|
+
if (!opts.json) console.error(chalk.red(`Error: ${error.message}`));
|
|
1189
|
+
process.exit(1);
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
|
|
813
1193
|
agents
|
|
814
1194
|
.command('export')
|
|
815
1195
|
.description('Export collaborative transcript')
|
|
@@ -865,17 +1245,15 @@ export function agentsCommand() {
|
|
|
865
1245
|
.option('--model-critic <id>', 'Model for critic role (provider/model)')
|
|
866
1246
|
.option('--model-coder <id>', 'Model for coder role (provider/model)')
|
|
867
1247
|
.option('--model-reviewer <id>', 'Model for reviewer role (provider/model)')
|
|
1248
|
+
.option('--mux <name>', 'Multiplexer backend: auto|zellij|tmux')
|
|
868
1249
|
.option('--cwd <path>', 'Working directory for agents', process.cwd())
|
|
869
|
-
.option('--attach', 'Attach
|
|
1250
|
+
.option('--attach', 'Attach multiplexer immediately after start', false)
|
|
870
1251
|
.option('--json', 'Output as JSON')
|
|
871
1252
|
.action(async (opts) => {
|
|
872
1253
|
try {
|
|
873
1254
|
if (!hasCommand('opencode')) {
|
|
874
1255
|
throw new Error('OpenCode is not installed. Run: acfm agents setup');
|
|
875
1256
|
}
|
|
876
|
-
if (!hasCommand('tmux')) {
|
|
877
|
-
throw new Error('tmux is not installed. Run: acfm agents setup');
|
|
878
|
-
}
|
|
879
1257
|
const opencodeBin = resolveCommandPath('opencode');
|
|
880
1258
|
if (!opencodeBin) {
|
|
881
1259
|
throw new Error('OpenCode binary not found. Run: acfm agents setup');
|
|
@@ -888,6 +1266,35 @@ export function agentsCommand() {
|
|
|
888
1266
|
}
|
|
889
1267
|
|
|
890
1268
|
const config = await loadAgentsConfig();
|
|
1269
|
+
const configuredMux = validateMultiplexer(opts.mux || config.agents.multiplexer || 'auto');
|
|
1270
|
+
const muxResolution = resolveMultiplexerWithPaths(config, configuredMux);
|
|
1271
|
+
let selectedMux = muxResolution.selected;
|
|
1272
|
+
let zellijPath = muxResolution.zellijPath;
|
|
1273
|
+
if (!selectedMux) {
|
|
1274
|
+
if (configuredMux !== 'tmux' && shouldUseManagedZellij(config)) {
|
|
1275
|
+
const installResult = await installManagedZellijLatest();
|
|
1276
|
+
if (installResult.success && installResult.binaryPath) {
|
|
1277
|
+
await updateAgentsConfig((current) => ({
|
|
1278
|
+
agents: {
|
|
1279
|
+
defaultModel: current.agents.defaultModel,
|
|
1280
|
+
defaultRoleModels: { ...current.agents.defaultRoleModels },
|
|
1281
|
+
multiplexer: current.agents.multiplexer || 'auto',
|
|
1282
|
+
zellij: {
|
|
1283
|
+
strategy: 'managed',
|
|
1284
|
+
binaryPath: installResult.binaryPath,
|
|
1285
|
+
version: installResult.version || null,
|
|
1286
|
+
source: 'managed',
|
|
1287
|
+
},
|
|
1288
|
+
},
|
|
1289
|
+
}));
|
|
1290
|
+
zellijPath = installResult.binaryPath;
|
|
1291
|
+
selectedMux = resolveMultiplexer(configuredMux, Boolean(resolveCommandPath('tmux')), Boolean(zellijPath));
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (!selectedMux) {
|
|
1296
|
+
throw new Error('No multiplexer found. Install zellij or tmux with: acfm agents setup');
|
|
1297
|
+
}
|
|
891
1298
|
const cliModel = assertValidModelIdOrNull('--model', opts.model || null);
|
|
892
1299
|
const cliRoleModels = parseRoleModelOptions(opts);
|
|
893
1300
|
for (const [role, model] of Object.entries(cliRoleModels)) {
|
|
@@ -922,30 +1329,48 @@ export function agentsCommand() {
|
|
|
922
1329
|
maxRounds,
|
|
923
1330
|
model: globalModel,
|
|
924
1331
|
roleModels,
|
|
1332
|
+
multiplexer: selectedMux,
|
|
925
1333
|
workingDirectory: resolve(opts.cwd),
|
|
926
1334
|
opencodeBin,
|
|
927
1335
|
});
|
|
928
|
-
const
|
|
1336
|
+
const muxSessionName = `acfm-synapse-${state.sessionId.slice(0, 8)}`;
|
|
929
1337
|
const sessionDir = getSessionDir(state.sessionId);
|
|
1338
|
+
|
|
1339
|
+
if (selectedMux === 'zellij') {
|
|
1340
|
+
await spawnZellijSession({
|
|
1341
|
+
sessionName: muxSessionName,
|
|
1342
|
+
sessionDir,
|
|
1343
|
+
sessionId: state.sessionId,
|
|
1344
|
+
binaryPath: zellijPath,
|
|
1345
|
+
});
|
|
1346
|
+
} else {
|
|
1347
|
+
await spawnTmuxSession({
|
|
1348
|
+
sessionName: muxSessionName,
|
|
1349
|
+
sessionDir,
|
|
1350
|
+
sessionId: state.sessionId,
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
|
|
930
1354
|
const updated = await saveSessionState({
|
|
931
1355
|
...state,
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
await spawnTmuxSession({
|
|
936
|
-
sessionName: tmuxSessionName,
|
|
937
|
-
sessionDir,
|
|
938
|
-
sessionId: state.sessionId,
|
|
1356
|
+
multiplexer: selectedMux,
|
|
1357
|
+
multiplexerSessionName: muxSessionName,
|
|
1358
|
+
tmuxSessionName: selectedMux === 'tmux' ? muxSessionName : null,
|
|
939
1359
|
});
|
|
940
1360
|
|
|
941
|
-
output({
|
|
1361
|
+
output({
|
|
1362
|
+
sessionId: updated.sessionId,
|
|
1363
|
+
multiplexer: selectedMux,
|
|
1364
|
+
multiplexerSessionName: muxSessionName,
|
|
1365
|
+
status: updated.status,
|
|
1366
|
+
}, opts.json);
|
|
942
1367
|
if (!opts.json) {
|
|
943
1368
|
printStartSummary(updated);
|
|
944
1369
|
printModelConfig(updated);
|
|
945
1370
|
}
|
|
946
1371
|
|
|
947
1372
|
if (opts.attach) {
|
|
948
|
-
await
|
|
1373
|
+
await attachToMux(selectedMux, muxSessionName, false, zellijPath);
|
|
949
1374
|
}
|
|
950
1375
|
} catch (error) {
|
|
951
1376
|
output({ error: error.message }, opts.json);
|
|
@@ -981,6 +1406,7 @@ export function agentsCommand() {
|
|
|
981
1406
|
try {
|
|
982
1407
|
const sessionId = await ensureSessionId(true);
|
|
983
1408
|
const state = await loadSessionState(sessionId);
|
|
1409
|
+
await ensureSessionArtifacts(sessionId, state);
|
|
984
1410
|
const effectiveRoleModels = buildEffectiveRoleModels(state, state.model || null);
|
|
985
1411
|
output({ ...state, effectiveRoleModels }, opts.json);
|
|
986
1412
|
if (!opts.json) {
|
|
@@ -996,14 +1422,20 @@ export function agentsCommand() {
|
|
|
996
1422
|
console.log(chalk.dim(`Run error: ${summary.lastError.message}`));
|
|
997
1423
|
}
|
|
998
1424
|
console.log(chalk.dim(`Global model: ${state.model || '(opencode default)'}`));
|
|
1425
|
+
console.log(chalk.dim(`Multiplexer: ${state.multiplexer || 'auto'} (${sessionMuxName(state)})`));
|
|
999
1426
|
for (const role of COLLAB_ROLES) {
|
|
1000
1427
|
const configured = state.roleModels?.[role] || '-';
|
|
1001
1428
|
const effective = effectiveRoleModels[role] || '(opencode default)';
|
|
1002
1429
|
console.log(chalk.dim(` ${role.padEnd(8)} configured=${configured} effective=${effective}`));
|
|
1003
1430
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1431
|
+
const meetingLogPath = resolve(getSessionDir(state.sessionId), 'meeting-log.md');
|
|
1432
|
+
const meetingSummaryPath = resolve(getSessionDir(state.sessionId), 'meeting-summary.md');
|
|
1433
|
+
const turnsDirPath = resolve(getSessionDir(state.sessionId), 'turns');
|
|
1434
|
+
const rawDirPath = resolve(getSessionDir(state.sessionId), 'turns', 'raw');
|
|
1435
|
+
console.log(chalk.dim(`meeting-log: ${existsSync(meetingLogPath) ? meetingLogPath : 'not generated yet'}`));
|
|
1436
|
+
console.log(chalk.dim(`meeting-summary: ${existsSync(meetingSummaryPath) ? meetingSummaryPath : 'not generated yet'}`));
|
|
1437
|
+
console.log(chalk.dim(`turns: ${existsSync(turnsDirPath) ? turnsDirPath : 'not generated yet'}`));
|
|
1438
|
+
console.log(chalk.dim(`turns/raw: ${existsSync(rawDirPath) ? rawDirPath : 'not generated yet'}`));
|
|
1007
1439
|
}
|
|
1008
1440
|
} catch (error) {
|
|
1009
1441
|
output({ error: error.message }, opts.json);
|
|
@@ -1020,6 +1452,7 @@ export function agentsCommand() {
|
|
|
1020
1452
|
try {
|
|
1021
1453
|
const sessionId = await ensureSessionId(true);
|
|
1022
1454
|
let state = await loadSessionState(sessionId);
|
|
1455
|
+
const meetingSummaryPath = resolve(getSessionDir(state.sessionId), 'meeting-summary.md');
|
|
1023
1456
|
state = await stopSession(state, 'stopped');
|
|
1024
1457
|
if (state.run && state.run.status === 'running') {
|
|
1025
1458
|
state = await saveSessionState({
|
|
@@ -1036,15 +1469,53 @@ export function agentsCommand() {
|
|
|
1036
1469
|
},
|
|
1037
1470
|
});
|
|
1038
1471
|
}
|
|
1039
|
-
|
|
1472
|
+
|
|
1473
|
+
if (!existsSync(meetingSummaryPath)) {
|
|
1474
|
+
const fallbackSummary = [
|
|
1475
|
+
'# SynapseGrid Meeting Summary',
|
|
1476
|
+
'',
|
|
1477
|
+
`Session: ${state.sessionId}`,
|
|
1478
|
+
`Status: ${state.status}`,
|
|
1479
|
+
'',
|
|
1480
|
+
'This summary was auto-generated at stop time because the run did not complete normally.',
|
|
1481
|
+
'',
|
|
1482
|
+
'## Last message',
|
|
1483
|
+
state.messages?.[state.messages.length - 1]?.content || '(none)',
|
|
1484
|
+
'',
|
|
1485
|
+
].join('\n');
|
|
1486
|
+
await writeMeetingSummary(state.sessionId, fallbackSummary);
|
|
1487
|
+
if (state.run && !state.run.finalSummary) {
|
|
1488
|
+
state = await saveSessionState({
|
|
1489
|
+
...state,
|
|
1490
|
+
run: {
|
|
1491
|
+
...state.run,
|
|
1492
|
+
finalSummary: fallbackSummary,
|
|
1493
|
+
},
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
const multiplexer = state.multiplexer || 'tmux';
|
|
1499
|
+
const muxSessionName = sessionMuxName(state);
|
|
1500
|
+
const cfg = await loadAgentsConfig();
|
|
1501
|
+
const zellijPath = resolveConfiguredZellijPath(cfg);
|
|
1502
|
+
if (multiplexer === 'zellij' && muxSessionName && zellijPath) {
|
|
1040
1503
|
try {
|
|
1041
|
-
await
|
|
1504
|
+
await runZellij(['delete-session', muxSessionName], { binaryPath: zellijPath });
|
|
1505
|
+
} catch {
|
|
1506
|
+
// ignore if already closed
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (multiplexer === 'tmux' && muxSessionName && hasCommand('tmux')) {
|
|
1510
|
+
try {
|
|
1511
|
+
await runTmux('tmux', ['kill-session', '-t', muxSessionName]);
|
|
1042
1512
|
} catch {
|
|
1043
1513
|
// ignore if already closed
|
|
1044
1514
|
}
|
|
1045
1515
|
}
|
|
1046
1516
|
output({ sessionId: state.sessionId, status: state.status }, opts.json);
|
|
1047
1517
|
if (!opts.json) console.log(chalk.green('✓ Collaborative session stopped'));
|
|
1518
|
+
|
|
1048
1519
|
} catch (error) {
|
|
1049
1520
|
output({ error: error.message }, opts.json);
|
|
1050
1521
|
if (!opts.json) console.error(chalk.red(`Error: ${error.message}`));
|
|
@@ -1143,10 +1614,19 @@ export function agentsCommand() {
|
|
|
1143
1614
|
const opencodeBin = resolveCommandPath('opencode');
|
|
1144
1615
|
const tmuxInstalled = hasCommand('tmux');
|
|
1145
1616
|
const cfg = await loadAgentsConfig();
|
|
1617
|
+
const zellijPath = resolveConfiguredZellijPath(cfg);
|
|
1618
|
+
const zellijInstalled = Boolean(zellijPath);
|
|
1146
1619
|
const defaultModel = cfg.agents.defaultModel || DEFAULT_SYNAPSE_MODEL;
|
|
1620
|
+
const configuredMux = validateMultiplexer(cfg.agents.multiplexer || 'auto');
|
|
1621
|
+
const resolvedMux = resolveMultiplexer(configuredMux, tmuxInstalled, zellijInstalled);
|
|
1147
1622
|
const result = {
|
|
1148
1623
|
opencodeBin,
|
|
1149
1624
|
tmuxInstalled,
|
|
1625
|
+
zellijInstalled,
|
|
1626
|
+
zellijPath,
|
|
1627
|
+
zellijConfig: cfg.agents.zellij,
|
|
1628
|
+
configuredMultiplexer: configuredMux,
|
|
1629
|
+
resolvedMultiplexer: resolvedMux,
|
|
1150
1630
|
defaultModel,
|
|
1151
1631
|
defaultRoleModels: cfg.agents.defaultRoleModels,
|
|
1152
1632
|
preflight: null,
|
|
@@ -1166,12 +1646,15 @@ export function agentsCommand() {
|
|
|
1166
1646
|
if (!opts.json) {
|
|
1167
1647
|
console.log(chalk.bold('SynapseGrid doctor'));
|
|
1168
1648
|
console.log(chalk.dim(`opencode: ${opencodeBin || 'not found'}`));
|
|
1649
|
+
console.log(chalk.dim(`zellij: ${zellijInstalled ? 'installed' : 'not installed'}`));
|
|
1650
|
+
if (zellijPath) console.log(chalk.dim(`zellij path: ${zellijPath}`));
|
|
1169
1651
|
console.log(chalk.dim(`tmux: ${tmuxInstalled ? 'installed' : 'not installed'}`));
|
|
1652
|
+
console.log(chalk.dim(`multiplexer: configured=${configuredMux} resolved=${resolvedMux || 'none'}`));
|
|
1170
1653
|
console.log(chalk.dim(`default model: ${defaultModel}`));
|
|
1171
1654
|
console.log(chalk.dim(`preflight: ${result.preflight?.ok ? 'ok' : `failed - ${result.preflight?.error || 'unknown error'}`}`));
|
|
1172
1655
|
}
|
|
1173
1656
|
|
|
1174
|
-
if (!result.preflight?.ok) process.exit(1);
|
|
1657
|
+
if (!result.preflight?.ok || !result.resolvedMultiplexer) process.exit(1);
|
|
1175
1658
|
} catch (error) {
|
|
1176
1659
|
output({ error: error.message }, opts.json);
|
|
1177
1660
|
if (!opts.json) console.error(chalk.red(`Error: ${error.message}`));
|