ac-framework 2.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ac-framework",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Agentic Coding Framework - Multi-assistant configuration system with OpenSpec workflows",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -47,6 +47,10 @@ 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
+
50
54
  function workerCommand(sessionId, role, roleLog) {
51
55
  return `bash -lc 'node "${runnerPath}" agents worker --session ${sessionId} --role ${role} 2>&1 | tee -a "${roleLog}"'`;
52
56
  }
@@ -95,26 +99,26 @@ export async function tmuxSessionExists(sessionName) {
95
99
  }
96
100
 
97
101
  async function writeZellijLayout({ layoutPath, sessionId, sessionDir }) {
98
- const panes = COLLAB_ROLES.map((role) => {
102
+ const paneNode = (role) => {
99
103
  const roleLog = roleLogPath(sessionDir, role);
100
104
  const cmd = workerCommand(sessionId, role, roleLog).replace(/"/g, '\\"');
101
- return ` pane name="${role}" command="bash" args { "-lc" "${cmd}" }`;
102
- });
105
+ return [
106
+ ` pane name="${role}" command="bash" {`,
107
+ ` args "-lc" "${cmd}"`,
108
+ ' }',
109
+ ].join('\n');
110
+ };
103
111
 
104
112
  const content = [
105
113
  'layout {',
106
- ' default_tab_template {',
107
- ' tab name="SynapseGrid" {',
108
- ' pane split_direction="vertical" {',
109
- ' pane split_direction="horizontal" {',
110
- panes[0],
111
- panes[1],
112
- ' }',
113
- ' pane split_direction="horizontal" {',
114
- panes[2],
115
- panes[3],
116
- ' }',
117
- ' }',
114
+ ' pane split_direction="vertical" {',
115
+ ' pane split_direction="horizontal" {',
116
+ paneNode(COLLAB_ROLES[0]),
117
+ paneNode(COLLAB_ROLES[1]),
118
+ ' }',
119
+ ' pane split_direction="horizontal" {',
120
+ paneNode(COLLAB_ROLES[2]),
121
+ paneNode(COLLAB_ROLES[3]),
118
122
  ' }',
119
123
  ' }',
120
124
  '}',
@@ -132,27 +136,25 @@ export async function spawnZellijSession({
132
136
  waitForSessionMs = 10000,
133
137
  pollIntervalMs = 250,
134
138
  runCommandImpl,
135
- spawnImpl,
136
139
  }) {
137
140
  const layoutPath = resolve(sessionDir, 'synapsegrid-layout.kdl');
138
141
  await writeZellijLayout({ layoutPath, sessionId, sessionDir });
139
142
  const command = binaryPath || process.env.ACFM_ZELLIJ_BIN || 'zellij';
143
+ const runner = runCommandImpl || runCommand;
140
144
 
141
- const spawnFn = spawnImpl || spawn;
142
- const child = spawnFn(command, ['--session', sessionName, '--layout', layoutPath], {
145
+ const existing = await zellijSessionExists(sessionName, binaryPath, { runCommandImpl: runner });
146
+ if (existing) {
147
+ return { layoutPath };
148
+ }
149
+
150
+ await runner(command, ['--layout', layoutPath, 'attach', '--create-background', sessionName], {
143
151
  cwd: sessionDir,
144
- env: process.env,
145
- detached: true,
146
- stdio: 'ignore',
147
152
  });
148
- if (typeof child.unref === 'function') {
149
- child.unref();
150
- }
151
153
 
152
154
  const startedAt = Date.now();
153
155
  while ((Date.now() - startedAt) < waitForSessionMs) {
154
156
  // eslint-disable-next-line no-await-in-loop
155
- const exists = await zellijSessionExists(sessionName, binaryPath, { runCommandImpl });
157
+ const exists = await zellijSessionExists(sessionName, binaryPath, { runCommandImpl: runner });
156
158
  if (exists) {
157
159
  return { layoutPath };
158
160
  }
@@ -171,7 +173,10 @@ export async function zellijSessionExists(sessionName, binaryPath, options = {})
171
173
  const runner = options.runCommandImpl || runCommand;
172
174
  const command = binaryPath || process.env.ACFM_ZELLIJ_BIN || 'zellij';
173
175
  const result = await runner(command, ['list-sessions']);
174
- const lines = result.stdout.split('\n').map((line) => line.trim()).filter(Boolean);
176
+ const lines = stripAnsi(result.stdout)
177
+ .split('\n')
178
+ .map((line) => line.trim())
179
+ .filter(Boolean);
175
180
  return lines.some((line) => line === sessionName || line.startsWith(`${sessionName} `));
176
181
  } catch {
177
182
  return false;
@@ -1503,7 +1503,15 @@ Examples:
1503
1503
  try {
1504
1504
  await runZellij(['delete-session', muxSessionName], { binaryPath: zellijPath });
1505
1505
  } catch {
1506
- // ignore if already closed
1506
+ try {
1507
+ await runZellij(['kill-session', muxSessionName], { binaryPath: zellijPath });
1508
+ } catch {
1509
+ try {
1510
+ await runZellij(['delete-session', '--force', muxSessionName], { binaryPath: zellijPath });
1511
+ } catch {
1512
+ // ignore if already closed
1513
+ }
1514
+ }
1507
1515
  }
1508
1516
  }
1509
1517
  if (multiplexer === 'tmux' && muxSessionName && hasCommand('tmux')) {