ac-framework 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/agents/runtime.js +187 -31
- package/src/commands/agents.js +62 -13
- package/src/mcp/collab-server.js +25 -2
package/README.md
CHANGED
|
@@ -142,6 +142,7 @@ Each role runs in turn against a shared, accumulating context so outputs from on
|
|
|
142
142
|
|---|---|
|
|
143
143
|
| `acfm agents setup` | Install optional dependencies (`opencode` and `zellij`/`tmux`) |
|
|
144
144
|
| `acfm agents doctor` | Validate OpenCode/multiplexer/model preflight before start |
|
|
145
|
+
| `acfm agents doctor --verbose` | Include zellij capability probe details for strategy diagnostics |
|
|
145
146
|
| `acfm agents install-mcps` | Install SynapseGrid MCP server for detected assistants |
|
|
146
147
|
| `acfm agents uninstall-mcps` | Remove SynapseGrid MCP server from assistants |
|
|
147
148
|
| `acfm agents start --task "..." --model-coder provider/model` | Start session with optional per-role models |
|
|
@@ -190,6 +191,7 @@ When driving SynapseGrid from another agent via MCP, prefer asynchronous run too
|
|
|
190
191
|
- Default SynapseGrid model fallback is `opencode/mimo-v2-pro-free`.
|
|
191
192
|
- Run `acfm agents doctor` when panes look idle to confirm model/provider preflight health.
|
|
192
193
|
- When zellij is managed by AC Framework, its binary path is saved in `~/.acfm/config.json` and executed directly by SynapseGrid.
|
|
194
|
+
- `acfm agents start --json` now includes startup strategy diagnostics for zellij (`attach_with_layout`, fallbacks, and per-strategy errors).
|
|
193
195
|
|
|
194
196
|
Each collaborative session now keeps human-readable artifacts under `~/.acfm/synapsegrid/<sessionId>/`:
|
|
195
197
|
- `transcript.jsonl`: full chronological message stream
|
package/package.json
CHANGED
package/src/agents/runtime.js
CHANGED
|
@@ -47,8 +47,80 @@ function sleep(ms) {
|
|
|
47
47
|
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function stripAnsi(text) {
|
|
51
|
+
return String(text || '').replace(/\x1B\[[0-9;]*m/g, '');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function commandSupports(command, args, pattern, runner) {
|
|
55
|
+
try {
|
|
56
|
+
const result = await runner(command, args);
|
|
57
|
+
return pattern.test(`${result.stdout || ''}\n${result.stderr || ''}`);
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function probeZellijCapabilities(binaryPath, options = {}) {
|
|
64
|
+
const runner = options.runCommandImpl || runCommand;
|
|
65
|
+
const command = binaryPath || process.env.ACFM_ZELLIJ_BIN || 'zellij';
|
|
66
|
+
const capabilities = {
|
|
67
|
+
binary: command,
|
|
68
|
+
version: null,
|
|
69
|
+
attachCreateBackground: false,
|
|
70
|
+
actionNewTabLayout: false,
|
|
71
|
+
actionNewPane: false,
|
|
72
|
+
listPanesJson: false,
|
|
73
|
+
setupCheck: false,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const versionResult = await runner(command, ['--version']);
|
|
78
|
+
capabilities.version = stripAnsi(versionResult.stdout || versionResult.stderr || '').trim() || null;
|
|
79
|
+
} catch {
|
|
80
|
+
capabilities.version = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
capabilities.attachCreateBackground = await commandSupports(
|
|
84
|
+
command,
|
|
85
|
+
['attach', '--help'],
|
|
86
|
+
/--create-background/,
|
|
87
|
+
runner,
|
|
88
|
+
);
|
|
89
|
+
capabilities.actionNewTabLayout = await commandSupports(
|
|
90
|
+
command,
|
|
91
|
+
['action', 'new-tab', '--help'],
|
|
92
|
+
/--layout/,
|
|
93
|
+
runner,
|
|
94
|
+
);
|
|
95
|
+
capabilities.actionNewPane = await commandSupports(
|
|
96
|
+
command,
|
|
97
|
+
['action', 'new-pane', '--help'],
|
|
98
|
+
/USAGE:/,
|
|
99
|
+
runner,
|
|
100
|
+
);
|
|
101
|
+
capabilities.listPanesJson = await commandSupports(
|
|
102
|
+
command,
|
|
103
|
+
['action', 'list-panes', '--help'],
|
|
104
|
+
/--json/,
|
|
105
|
+
runner,
|
|
106
|
+
);
|
|
107
|
+
capabilities.setupCheck = await commandSupports(
|
|
108
|
+
command,
|
|
109
|
+
['setup', '--help'],
|
|
110
|
+
/--check/,
|
|
111
|
+
runner,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return capabilities;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function workerShellCommand(sessionId, role, roleLog) {
|
|
118
|
+
return `node "${runnerPath}" agents worker --session ${sessionId} --role ${role} 2>&1 | tee -a "${roleLog}"`;
|
|
119
|
+
}
|
|
120
|
+
|
|
50
121
|
function workerCommand(sessionId, role, roleLog) {
|
|
51
|
-
|
|
122
|
+
const shell = workerShellCommand(sessionId, role, roleLog);
|
|
123
|
+
return `bash -lc '${shell}'`;
|
|
52
124
|
}
|
|
53
125
|
|
|
54
126
|
export async function runTmux(command, args, options = {}) {
|
|
@@ -95,26 +167,26 @@ export async function tmuxSessionExists(sessionName) {
|
|
|
95
167
|
}
|
|
96
168
|
|
|
97
169
|
async function writeZellijLayout({ layoutPath, sessionId, sessionDir }) {
|
|
98
|
-
const
|
|
170
|
+
const paneNode = (role) => {
|
|
99
171
|
const roleLog = roleLogPath(sessionDir, role);
|
|
100
172
|
const cmd = workerCommand(sessionId, role, roleLog).replace(/"/g, '\\"');
|
|
101
|
-
return
|
|
102
|
-
|
|
173
|
+
return [
|
|
174
|
+
` pane name="${role}" command="bash" {`,
|
|
175
|
+
` args "-lc" "${cmd}"`,
|
|
176
|
+
' }',
|
|
177
|
+
].join('\n');
|
|
178
|
+
};
|
|
103
179
|
|
|
104
180
|
const content = [
|
|
105
181
|
'layout {',
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
panes[2],
|
|
115
|
-
panes[3],
|
|
116
|
-
' }',
|
|
117
|
-
' }',
|
|
182
|
+
' pane split_direction="vertical" {',
|
|
183
|
+
' pane split_direction="horizontal" {',
|
|
184
|
+
paneNode(COLLAB_ROLES[0]),
|
|
185
|
+
paneNode(COLLAB_ROLES[1]),
|
|
186
|
+
' }',
|
|
187
|
+
' pane split_direction="horizontal" {',
|
|
188
|
+
paneNode(COLLAB_ROLES[2]),
|
|
189
|
+
paneNode(COLLAB_ROLES[3]),
|
|
118
190
|
' }',
|
|
119
191
|
' }',
|
|
120
192
|
'}',
|
|
@@ -132,36 +204,114 @@ export async function spawnZellijSession({
|
|
|
132
204
|
waitForSessionMs = 10000,
|
|
133
205
|
pollIntervalMs = 250,
|
|
134
206
|
runCommandImpl,
|
|
135
|
-
|
|
207
|
+
capabilities = null,
|
|
136
208
|
}) {
|
|
137
209
|
const layoutPath = resolve(sessionDir, 'synapsegrid-layout.kdl');
|
|
138
210
|
await writeZellijLayout({ layoutPath, sessionId, sessionDir });
|
|
139
211
|
const command = binaryPath || process.env.ACFM_ZELLIJ_BIN || 'zellij';
|
|
212
|
+
const runner = runCommandImpl || runCommand;
|
|
213
|
+
const caps = capabilities || await probeZellijCapabilities(binaryPath, { runCommandImpl: runner });
|
|
214
|
+
const strategyErrors = [];
|
|
140
215
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
216
|
+
const existing = await zellijSessionExists(sessionName, binaryPath, { runCommandImpl: runner });
|
|
217
|
+
if (existing) {
|
|
218
|
+
return { layoutPath, strategy: 'already_exists', capabilities: caps, strategyErrors };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const strategies = [];
|
|
222
|
+
if (caps.attachCreateBackground) {
|
|
223
|
+
strategies.push({
|
|
224
|
+
name: 'attach_with_layout',
|
|
225
|
+
run: async () => {
|
|
226
|
+
await runner(command, ['--layout', layoutPath, 'attach', '--create-background', sessionName], {
|
|
227
|
+
cwd: sessionDir,
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (caps.attachCreateBackground && caps.actionNewTabLayout) {
|
|
234
|
+
strategies.push({
|
|
235
|
+
name: 'attach_then_newtab_layout',
|
|
236
|
+
run: async () => {
|
|
237
|
+
await runner(command, ['attach', '--create-background', sessionName], { cwd: sessionDir });
|
|
238
|
+
await runner(command, ['--session', sessionName, 'action', 'new-tab', '--name', 'SynapseGrid', '--layout', layoutPath], {
|
|
239
|
+
cwd: sessionDir,
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (caps.attachCreateBackground && caps.actionNewPane) {
|
|
246
|
+
strategies.push({
|
|
247
|
+
name: 'attach_then_action_panes',
|
|
248
|
+
run: async () => {
|
|
249
|
+
await runner(command, ['attach', '--create-background', sessionName], { cwd: sessionDir });
|
|
250
|
+
const role0 = COLLAB_ROLES[0];
|
|
251
|
+
const role0Log = roleLogPath(sessionDir, role0);
|
|
252
|
+
await runner(
|
|
253
|
+
command,
|
|
254
|
+
['--session', sessionName, 'action', 'new-tab', '--name', 'SynapseGrid', '--', 'bash', '-lc', workerShellCommand(sessionId, role0, role0Log)],
|
|
255
|
+
{ cwd: sessionDir },
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const role1 = COLLAB_ROLES[1];
|
|
259
|
+
const role1Log = roleLogPath(sessionDir, role1);
|
|
260
|
+
await runner(
|
|
261
|
+
command,
|
|
262
|
+
['--session', sessionName, 'action', 'new-pane', '--direction', 'right', '--', 'bash', '-lc', workerShellCommand(sessionId, role1, role1Log)],
|
|
263
|
+
{ cwd: sessionDir },
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const role2 = COLLAB_ROLES[2];
|
|
267
|
+
const role2Log = roleLogPath(sessionDir, role2);
|
|
268
|
+
await runner(
|
|
269
|
+
command,
|
|
270
|
+
['--session', sessionName, 'action', 'new-pane', '--direction', 'down', '--', 'bash', '-lc', workerShellCommand(sessionId, role2, role2Log)],
|
|
271
|
+
{ cwd: sessionDir },
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const role3 = COLLAB_ROLES[3];
|
|
275
|
+
const role3Log = roleLogPath(sessionDir, role3);
|
|
276
|
+
await runner(
|
|
277
|
+
command,
|
|
278
|
+
['--session', sessionName, 'action', 'new-pane', '--direction', 'right', '--', 'bash', '-lc', workerShellCommand(sessionId, role3, role3Log)],
|
|
279
|
+
{ cwd: sessionDir },
|
|
280
|
+
);
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let strategyUsed = null;
|
|
286
|
+
for (const strategy of strategies) {
|
|
287
|
+
try {
|
|
288
|
+
// eslint-disable-next-line no-await-in-loop
|
|
289
|
+
await strategy.run();
|
|
290
|
+
strategyUsed = strategy.name;
|
|
291
|
+
break;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
strategyErrors.push({ strategy: strategy.name, error: error.message });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!strategyUsed) {
|
|
298
|
+
const details = strategyErrors.map((item) => `${item.strategy}: ${item.error}`).join(' | ');
|
|
299
|
+
throw new Error(`Unable to initialize zellij session using supported strategies. ${details || 'No compatible strategy available.'}`);
|
|
150
300
|
}
|
|
151
301
|
|
|
152
302
|
const startedAt = Date.now();
|
|
153
303
|
while ((Date.now() - startedAt) < waitForSessionMs) {
|
|
154
304
|
// eslint-disable-next-line no-await-in-loop
|
|
155
|
-
const exists = await zellijSessionExists(sessionName, binaryPath, { runCommandImpl });
|
|
305
|
+
const exists = await zellijSessionExists(sessionName, binaryPath, { runCommandImpl: runner });
|
|
156
306
|
if (exists) {
|
|
157
|
-
return { layoutPath };
|
|
307
|
+
return { layoutPath, strategy: strategyUsed, capabilities: caps, strategyErrors };
|
|
158
308
|
}
|
|
159
309
|
// eslint-disable-next-line no-await-in-loop
|
|
160
310
|
await sleep(pollIntervalMs);
|
|
161
311
|
}
|
|
162
312
|
|
|
163
313
|
throw new Error(
|
|
164
|
-
`Timed out waiting for zellij session '${sessionName}' to start (binary: ${command}). ` +
|
|
314
|
+
`Timed out waiting for zellij session '${sessionName}' to start (binary: ${command}, strategy: ${strategyUsed || 'none'}). ` +
|
|
165
315
|
'Try `acfm agents doctor` or fallback with `acfm agents start --mux tmux ...`'
|
|
166
316
|
);
|
|
167
317
|
}
|
|
@@ -171,8 +321,14 @@ export async function zellijSessionExists(sessionName, binaryPath, options = {})
|
|
|
171
321
|
const runner = options.runCommandImpl || runCommand;
|
|
172
322
|
const command = binaryPath || process.env.ACFM_ZELLIJ_BIN || 'zellij';
|
|
173
323
|
const result = await runner(command, ['list-sessions']);
|
|
174
|
-
const lines = result.stdout
|
|
175
|
-
|
|
324
|
+
const lines = stripAnsi(result.stdout)
|
|
325
|
+
.split('\n')
|
|
326
|
+
.map((line) => line.trim())
|
|
327
|
+
.filter(Boolean);
|
|
328
|
+
return lines.some((line) => {
|
|
329
|
+
if (line === sessionName || line.startsWith(`${sessionName} `)) return true;
|
|
330
|
+
return line.includes(sessionName);
|
|
331
|
+
});
|
|
176
332
|
} catch {
|
|
177
333
|
return false;
|
|
178
334
|
}
|
package/src/commands/agents.js
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
writeMeetingSummary,
|
|
32
32
|
} from '../agents/state-store.js';
|
|
33
33
|
import {
|
|
34
|
+
probeZellijCapabilities,
|
|
34
35
|
roleLogPath,
|
|
35
36
|
runTmux,
|
|
36
37
|
runZellij,
|
|
@@ -153,6 +154,16 @@ async function attachToMux(multiplexer, sessionName, readonly = false, zellijPat
|
|
|
153
154
|
await runTmux('tmux', args, { stdio: 'inherit' });
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
async function readZellijCapabilities(config) {
|
|
158
|
+
const zellijPath = resolveConfiguredZellijPath(config);
|
|
159
|
+
if (!zellijPath) return null;
|
|
160
|
+
try {
|
|
161
|
+
return await probeZellijCapabilities(zellijPath);
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
156
167
|
function toMarkdownTranscript(state, transcript) {
|
|
157
168
|
const displayedRound = Math.min(state.round, state.maxRounds);
|
|
158
169
|
const lines = [
|
|
@@ -1270,6 +1281,7 @@ Examples:
|
|
|
1270
1281
|
const muxResolution = resolveMultiplexerWithPaths(config, configuredMux);
|
|
1271
1282
|
let selectedMux = muxResolution.selected;
|
|
1272
1283
|
let zellijPath = muxResolution.zellijPath;
|
|
1284
|
+
const tmuxPath = muxResolution.tmuxPath;
|
|
1273
1285
|
if (!selectedMux) {
|
|
1274
1286
|
if (configuredMux !== 'tmux' && shouldUseManagedZellij(config)) {
|
|
1275
1287
|
const installResult = await installManagedZellijLatest();
|
|
@@ -1336,41 +1348,64 @@ Examples:
|
|
|
1336
1348
|
const muxSessionName = `acfm-synapse-${state.sessionId.slice(0, 8)}`;
|
|
1337
1349
|
const sessionDir = getSessionDir(state.sessionId);
|
|
1338
1350
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1351
|
+
let activeMux = selectedMux;
|
|
1352
|
+
let startupDiagnostics = null;
|
|
1353
|
+
try {
|
|
1354
|
+
if (selectedMux === 'zellij') {
|
|
1355
|
+
startupDiagnostics = await spawnZellijSession({
|
|
1356
|
+
sessionName: muxSessionName,
|
|
1357
|
+
sessionDir,
|
|
1358
|
+
sessionId: state.sessionId,
|
|
1359
|
+
binaryPath: zellijPath,
|
|
1360
|
+
});
|
|
1361
|
+
} else {
|
|
1362
|
+
await spawnTmuxSession({
|
|
1363
|
+
sessionName: muxSessionName,
|
|
1364
|
+
sessionDir,
|
|
1365
|
+
sessionId: state.sessionId,
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
} catch (muxError) {
|
|
1369
|
+
const canFallbackToTmux = configuredMux === 'auto' && selectedMux === 'zellij' && Boolean(tmuxPath);
|
|
1370
|
+
if (!canFallbackToTmux) {
|
|
1371
|
+
throw muxError;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
if (!opts.json) {
|
|
1375
|
+
console.log(chalk.yellow(`zellij startup failed, falling back to tmux: ${muxError.message}`));
|
|
1376
|
+
}
|
|
1347
1377
|
await spawnTmuxSession({
|
|
1348
1378
|
sessionName: muxSessionName,
|
|
1349
1379
|
sessionDir,
|
|
1350
1380
|
sessionId: state.sessionId,
|
|
1351
1381
|
});
|
|
1382
|
+
activeMux = 'tmux';
|
|
1352
1383
|
}
|
|
1353
1384
|
|
|
1354
1385
|
const updated = await saveSessionState({
|
|
1355
1386
|
...state,
|
|
1356
|
-
multiplexer:
|
|
1387
|
+
multiplexer: activeMux,
|
|
1357
1388
|
multiplexerSessionName: muxSessionName,
|
|
1358
|
-
tmuxSessionName:
|
|
1389
|
+
tmuxSessionName: activeMux === 'tmux' ? muxSessionName : null,
|
|
1359
1390
|
});
|
|
1360
1391
|
|
|
1361
1392
|
output({
|
|
1362
1393
|
sessionId: updated.sessionId,
|
|
1363
|
-
multiplexer:
|
|
1394
|
+
multiplexer: activeMux,
|
|
1364
1395
|
multiplexerSessionName: muxSessionName,
|
|
1365
1396
|
status: updated.status,
|
|
1397
|
+
startupDiagnostics,
|
|
1366
1398
|
}, opts.json);
|
|
1367
1399
|
if (!opts.json) {
|
|
1368
1400
|
printStartSummary(updated);
|
|
1401
|
+
if (startupDiagnostics?.strategy) {
|
|
1402
|
+
console.log(chalk.dim(` zellij strategy: ${startupDiagnostics.strategy}`));
|
|
1403
|
+
}
|
|
1369
1404
|
printModelConfig(updated);
|
|
1370
1405
|
}
|
|
1371
1406
|
|
|
1372
1407
|
if (opts.attach) {
|
|
1373
|
-
await attachToMux(
|
|
1408
|
+
await attachToMux(activeMux, muxSessionName, false, zellijPath);
|
|
1374
1409
|
}
|
|
1375
1410
|
} catch (error) {
|
|
1376
1411
|
output({ error: error.message }, opts.json);
|
|
@@ -1503,7 +1538,15 @@ Examples:
|
|
|
1503
1538
|
try {
|
|
1504
1539
|
await runZellij(['delete-session', muxSessionName], { binaryPath: zellijPath });
|
|
1505
1540
|
} catch {
|
|
1506
|
-
|
|
1541
|
+
try {
|
|
1542
|
+
await runZellij(['kill-session', muxSessionName], { binaryPath: zellijPath });
|
|
1543
|
+
} catch {
|
|
1544
|
+
try {
|
|
1545
|
+
await runZellij(['delete-session', '--force', muxSessionName], { binaryPath: zellijPath });
|
|
1546
|
+
} catch {
|
|
1547
|
+
// ignore if already closed
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1507
1550
|
}
|
|
1508
1551
|
}
|
|
1509
1552
|
if (multiplexer === 'tmux' && muxSessionName && hasCommand('tmux')) {
|
|
@@ -1608,6 +1651,7 @@ Examples:
|
|
|
1608
1651
|
agents
|
|
1609
1652
|
.command('doctor')
|
|
1610
1653
|
.description('Run diagnostics for SynapseGrid/OpenCode runtime')
|
|
1654
|
+
.option('--verbose', 'Include backend capability details', false)
|
|
1611
1655
|
.option('--json', 'Output as JSON')
|
|
1612
1656
|
.action(async (opts) => {
|
|
1613
1657
|
try {
|
|
@@ -1630,8 +1674,13 @@ Examples:
|
|
|
1630
1674
|
defaultModel,
|
|
1631
1675
|
defaultRoleModels: cfg.agents.defaultRoleModels,
|
|
1632
1676
|
preflight: null,
|
|
1677
|
+
zellijCapabilities: null,
|
|
1633
1678
|
};
|
|
1634
1679
|
|
|
1680
|
+
if (opts.verbose && zellijPath) {
|
|
1681
|
+
result.zellijCapabilities = await readZellijCapabilities(cfg);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1635
1684
|
if (opencodeBin) {
|
|
1636
1685
|
result.preflight = await preflightModel({
|
|
1637
1686
|
opencodeBin,
|
package/src/mcp/collab-server.js
CHANGED
|
@@ -18,6 +18,7 @@ import { buildEffectiveRoleModels, sanitizeRoleModels } from '../agents/model-se
|
|
|
18
18
|
import { runWorkerIteration } from '../agents/orchestrator.js';
|
|
19
19
|
import { getSessionDir } from '../agents/state-store.js';
|
|
20
20
|
import {
|
|
21
|
+
probeZellijCapabilities,
|
|
21
22
|
spawnTmuxSession,
|
|
22
23
|
spawnZellijSession,
|
|
23
24
|
tmuxSessionExists,
|
|
@@ -84,6 +85,16 @@ function resolveConfiguredZellijPath(config) {
|
|
|
84
85
|
return resolveCommandPath('zellij');
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
async function readZellijCapabilities(config) {
|
|
89
|
+
const zellijPath = resolveConfiguredZellijPath(config);
|
|
90
|
+
if (!zellijPath) return null;
|
|
91
|
+
try {
|
|
92
|
+
return await probeZellijCapabilities(zellijPath);
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
async function muxExists(multiplexer, sessionName, zellijPath = null) {
|
|
88
99
|
if (multiplexer === 'zellij') return zellijSessionExists(sessionName, zellijPath);
|
|
89
100
|
return tmuxSessionExists(sessionName);
|
|
@@ -152,7 +163,13 @@ class MCPCollabServer {
|
|
|
152
163
|
const sessionName = `acfm-synapse-${state.sessionId.slice(0, 8)}`;
|
|
153
164
|
const sessionDir = getSessionDir(state.sessionId);
|
|
154
165
|
if (multiplexer === 'zellij') {
|
|
155
|
-
await spawnZellijSession({
|
|
166
|
+
await spawnZellijSession({
|
|
167
|
+
sessionName,
|
|
168
|
+
sessionDir,
|
|
169
|
+
sessionId: state.sessionId,
|
|
170
|
+
binaryPath: zellijPath,
|
|
171
|
+
capabilities: await readZellijCapabilities(config),
|
|
172
|
+
});
|
|
156
173
|
} else {
|
|
157
174
|
await spawnTmuxSession({ sessionName, sessionDir, sessionId: state.sessionId });
|
|
158
175
|
}
|
|
@@ -477,7 +494,13 @@ class MCPCollabServer {
|
|
|
477
494
|
const sessionDir = getSessionDir(state.sessionId);
|
|
478
495
|
if (multiplexer === 'zellij') {
|
|
479
496
|
if (!zellijPath) throw new Error('zellij is not installed. Run: acfm agents setup');
|
|
480
|
-
await spawnZellijSession({
|
|
497
|
+
await spawnZellijSession({
|
|
498
|
+
sessionName,
|
|
499
|
+
sessionDir,
|
|
500
|
+
sessionId: state.sessionId,
|
|
501
|
+
binaryPath: zellijPath,
|
|
502
|
+
capabilities: await readZellijCapabilities(config),
|
|
503
|
+
});
|
|
481
504
|
} else {
|
|
482
505
|
if (!hasCommand('tmux')) throw new Error('tmux is not installed. Run: acfm agents setup');
|
|
483
506
|
await spawnTmuxSession({ sessionName, sessionDir, sessionId: state.sessionId });
|