ac-framework 1.9.4 → 1.9.6

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.
Files changed (114) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +21 -1
  3. package/bin/postinstall.js +2 -0
  4. package/framework/mobile_development/.agent/workflows/ac-lite.md +70 -179
  5. package/framework/mobile_development/.agent/workflows/ac.md +273 -404
  6. package/framework/mobile_development/.amazonq/prompts/ac-lite.md +70 -179
  7. package/framework/mobile_development/.amazonq/prompts/ac.md +273 -404
  8. package/framework/mobile_development/.antigravity/workflows/ac-lite.md +70 -179
  9. package/framework/mobile_development/.antigravity/workflows/ac.md +273 -404
  10. package/framework/mobile_development/.augment/commands/ac-lite.md +70 -179
  11. package/framework/mobile_development/.augment/commands/ac.md +273 -404
  12. package/framework/mobile_development/.claude/commands/opsx/ac-lite.md +70 -179
  13. package/framework/mobile_development/.claude/commands/opsx/ac.md +273 -404
  14. package/framework/mobile_development/.cline/commands/opsx/ac-lite.md +70 -179
  15. package/framework/mobile_development/.cline/commands/opsx/ac.md +273 -404
  16. package/framework/mobile_development/.clinerules/workflows/ac-lite.md +70 -179
  17. package/framework/mobile_development/.clinerules/workflows/ac.md +273 -404
  18. package/framework/mobile_development/.codebuddy/commands/opsx/ac-lite.md +70 -179
  19. package/framework/mobile_development/.codebuddy/commands/opsx/ac.md +273 -404
  20. package/framework/mobile_development/.continue/prompts/ac-lite.md +70 -179
  21. package/framework/mobile_development/.continue/prompts/ac.md +273 -404
  22. package/framework/mobile_development/.cospec/openspec/commands/ac-lite.md +70 -179
  23. package/framework/mobile_development/.cospec/openspec/commands/ac.md +273 -404
  24. package/framework/mobile_development/.crush/commands/opsx/ac-lite.md +70 -179
  25. package/framework/mobile_development/.crush/commands/opsx/ac.md +273 -404
  26. package/framework/mobile_development/.cursor/commands/ac-lite.md +70 -179
  27. package/framework/mobile_development/.cursor/commands/ac.md +273 -404
  28. package/framework/mobile_development/.factory/commands/ac-lite.md +70 -179
  29. package/framework/mobile_development/.factory/commands/ac.md +273 -404
  30. package/framework/mobile_development/.gemini/commands/opsx/ac-lite.md +70 -179
  31. package/framework/mobile_development/.gemini/commands/opsx/ac.md +273 -404
  32. package/framework/mobile_development/.github/prompts/ac-lite.md +70 -179
  33. package/framework/mobile_development/.github/prompts/ac.md +273 -404
  34. package/framework/mobile_development/.github/prompts/ac.prompt.md +282 -177
  35. package/framework/mobile_development/.iflow/commands/ac-lite.md +70 -179
  36. package/framework/mobile_development/.iflow/commands/ac.md +273 -404
  37. package/framework/mobile_development/.kilocode/workflows/ac-lite.md +70 -179
  38. package/framework/mobile_development/.kilocode/workflows/ac.md +273 -404
  39. package/framework/mobile_development/.kimi/workflows/ac-lite.md +70 -179
  40. package/framework/mobile_development/.kimi/workflows/ac.md +273 -404
  41. package/framework/mobile_development/.opencode/command/ac-lite.md +70 -179
  42. package/framework/mobile_development/.opencode/command/ac.md +273 -404
  43. package/framework/mobile_development/.qoder/commands/opsx/ac-lite.md +70 -179
  44. package/framework/mobile_development/.qoder/commands/opsx/ac.md +273 -404
  45. package/framework/mobile_development/.qwen/commands/ac-lite.md +70 -179
  46. package/framework/mobile_development/.qwen/commands/ac.md +273 -404
  47. package/framework/mobile_development/.roo/commands/ac-lite.md +70 -179
  48. package/framework/mobile_development/.roo/commands/ac.md +273 -404
  49. package/framework/mobile_development/.windsurf/workflows/ac-lite.md +70 -179
  50. package/framework/mobile_development/.windsurf/workflows/ac.md +273 -404
  51. package/framework/mobile_development/AGENTS.md +205 -416
  52. package/framework/mobile_development/CLAUDE.md +205 -416
  53. package/framework/mobile_development/GEMINI.md +205 -416
  54. package/framework/mobile_development/copilot-instructions.md +205 -416
  55. package/framework/new_project/.agent/workflows/ac.md +39 -0
  56. package/framework/new_project/.amazonq/prompts/ac.md +39 -0
  57. package/framework/new_project/.antigravity/workflows/ac.md +39 -0
  58. package/framework/new_project/.augment/commands/ac.md +39 -0
  59. package/framework/new_project/.claude/commands/opsx/ac.md +39 -0
  60. package/framework/new_project/.cline/commands/opsx/ac.md +39 -0
  61. package/framework/new_project/.clinerules/workflows/ac.md +39 -0
  62. package/framework/new_project/.codebuddy/commands/opsx/ac.md +39 -0
  63. package/framework/new_project/.continue/prompts/ac.md +39 -0
  64. package/framework/new_project/.cospec/openspec/commands/ac.md +39 -0
  65. package/framework/new_project/.crush/commands/opsx/ac.md +39 -0
  66. package/framework/new_project/.cursor/commands/ac.md +39 -0
  67. package/framework/new_project/.factory/commands/ac.md +39 -0
  68. package/framework/new_project/.gemini/commands/opsx/ac.md +39 -0
  69. package/framework/new_project/.github/prompts/ac.md +39 -0
  70. package/framework/new_project/.iflow/commands/ac.md +39 -0
  71. package/framework/new_project/.kilocode/workflows/ac.md +39 -0
  72. package/framework/new_project/.kimi/workflows/ac.md +39 -0
  73. package/framework/new_project/.opencode/command/ac.md +39 -0
  74. package/framework/new_project/.qoder/commands/opsx/ac.md +39 -0
  75. package/framework/new_project/.qwen/commands/ac.md +39 -0
  76. package/framework/new_project/.roo/commands/ac.md +39 -0
  77. package/framework/new_project/.windsurf/workflows/ac.md +39 -0
  78. package/framework/new_project/AGENTS.md +27 -0
  79. package/framework/web_development/.agent/workflows/ac.md +39 -0
  80. package/framework/web_development/.amazonq/prompts/ac.md +39 -0
  81. package/framework/web_development/.antigravity/workflows/ac.md +39 -0
  82. package/framework/web_development/.augment/commands/ac.md +39 -0
  83. package/framework/web_development/.claude/commands/opsx/ac.md +39 -0
  84. package/framework/web_development/.cline/commands/opsx/ac.md +39 -0
  85. package/framework/web_development/.clinerules/workflows/ac.md +39 -0
  86. package/framework/web_development/.codebuddy/commands/opsx/ac.md +39 -0
  87. package/framework/web_development/.continue/prompts/ac.md +39 -0
  88. package/framework/web_development/.cospec/openspec/commands/ac.md +39 -0
  89. package/framework/web_development/.crush/commands/opsx/ac.md +39 -0
  90. package/framework/web_development/.cursor/commands/ac.md +39 -0
  91. package/framework/web_development/.factory/commands/ac.md +39 -0
  92. package/framework/web_development/.gemini/commands/opsx/ac.md +39 -0
  93. package/framework/web_development/.github/prompts/ac.md +39 -0
  94. package/framework/web_development/.iflow/commands/ac.md +39 -0
  95. package/framework/web_development/.kilocode/workflows/ac.md +39 -0
  96. package/framework/web_development/.kimi/workflows/ac.md +39 -0
  97. package/framework/web_development/.opencode/command/ac.md +39 -0
  98. package/framework/web_development/.qoder/commands/opsx/ac.md +39 -0
  99. package/framework/web_development/.qwen/commands/ac.md +39 -0
  100. package/framework/web_development/.roo/commands/ac.md +39 -0
  101. package/framework/web_development/.windsurf/workflows/ac.md +39 -0
  102. package/framework/web_development/AGENTS.md +27 -0
  103. package/package.json +1 -1
  104. package/src/agents/config-store.js +48 -0
  105. package/src/agents/model-selection.js +38 -0
  106. package/src/agents/opencode-client.js +3 -2
  107. package/src/agents/orchestrator.js +10 -3
  108. package/src/agents/runtime.js +80 -0
  109. package/src/agents/state-store.js +3 -0
  110. package/src/commands/agents.js +301 -85
  111. package/src/commands/init.js +33 -0
  112. package/src/mcp/collab-server.js +299 -0
  113. package/src/services/dependency-installer.js +20 -1
  114. package/src/services/mcp-installer.js +82 -54
@@ -98,6 +98,45 @@ acfm memory stats
98
98
 
99
99
  **User communication:** "Memory saved: [brief description]" when auto-saving occurs.
100
100
 
101
+ ### SynapseGrid Collaborative MCP Protocol (Optional)
102
+
103
+ If SynapseGrid is enabled in `acfm init`, AC Framework installs the collaborative MCP server automatically for detected assistants.
104
+
105
+ **Session-start requirement when collaboration is enabled:**
106
+ 1. Prefer the available SynapseGrid MCP tools for collaborative session control before falling back to direct CLI.
107
+ 2. Use shared session state and transcript as the source of truth for role-by-role collaboration.
108
+ 3. If collaborative MCP is unavailable, use CLI fallback commands and keep behavior equivalent.
109
+
110
+ **How to use SynapseGrid collaboration:**
111
+ ```text
112
+ Preferred: use SynapseGrid MCP tools (ac-framework-collab) for start/status/step/resume/stop.
113
+ Fallback: use AC Framework agents CLI commands directly.
114
+ ```
115
+ ```bash
116
+ # Install/update collaborative MCP server integrations
117
+ acfm agents install-mcps
118
+
119
+ # Start collaborative runtime with visible 4-pane tmux war-room
120
+ acfm agents start --task "design and implement feature X" --attach --model-coder provider/model
121
+
122
+ # If already running in background, attach to panes and inspect logs
123
+ acfm agents live
124
+ acfm agents logs --role all --lines 120
125
+
126
+ # Resume/list/export operations
127
+ acfm agents resume
128
+ acfm agents list
129
+ acfm agents export --format md --out synapse-session.md
130
+
131
+ # Fast diagnostics for OpenCode resolution errors
132
+ acfm agents setup
133
+ ```
134
+
135
+ **Troubleshooting notes:**
136
+ - If transcript shows `Agent failed: spawn opencode ENOENT`, run `acfm agents setup` and restart/resume session.
137
+ - If session exists but panes are not visible, use `acfm agents live` (or `acfm agents attach`) or `tmux attach -t <session-name>`.
138
+ - For MCP-first flows, ensure session start enables worker spawning so tmux panes are created.
139
+ - Per-role models can be set at start (`--model-planner`, `--model-critic`, `--model-coder`, `--model-reviewer`) or persisted via `acfm agents model set`.
101
140
 
102
141
  ### OpenSpec Skills (The heart of the framework)
103
142
 
@@ -98,6 +98,45 @@ acfm memory stats
98
98
 
99
99
  **User communication:** "Memory saved: [brief description]" when auto-saving occurs.
100
100
 
101
+ ### SynapseGrid Collaborative MCP Protocol (Optional)
102
+
103
+ If SynapseGrid is enabled in `acfm init`, AC Framework installs the collaborative MCP server automatically for detected assistants.
104
+
105
+ **Session-start requirement when collaboration is enabled:**
106
+ 1. Prefer the available SynapseGrid MCP tools for collaborative session control before falling back to direct CLI.
107
+ 2. Use shared session state and transcript as the source of truth for role-by-role collaboration.
108
+ 3. If collaborative MCP is unavailable, use CLI fallback commands and keep behavior equivalent.
109
+
110
+ **How to use SynapseGrid collaboration:**
111
+ ```text
112
+ Preferred: use SynapseGrid MCP tools (ac-framework-collab) for start/status/step/resume/stop.
113
+ Fallback: use AC Framework agents CLI commands directly.
114
+ ```
115
+ ```bash
116
+ # Install/update collaborative MCP server integrations
117
+ acfm agents install-mcps
118
+
119
+ # Start collaborative runtime with visible 4-pane tmux war-room
120
+ acfm agents start --task "design and implement feature X" --attach --model-coder provider/model
121
+
122
+ # If already running in background, attach to panes and inspect logs
123
+ acfm agents live
124
+ acfm agents logs --role all --lines 120
125
+
126
+ # Resume/list/export operations
127
+ acfm agents resume
128
+ acfm agents list
129
+ acfm agents export --format md --out synapse-session.md
130
+
131
+ # Fast diagnostics for OpenCode resolution errors
132
+ acfm agents setup
133
+ ```
134
+
135
+ **Troubleshooting notes:**
136
+ - If transcript shows `Agent failed: spawn opencode ENOENT`, run `acfm agents setup` and restart/resume session.
137
+ - If session exists but panes are not visible, use `acfm agents live` (or `acfm agents attach`) or `tmux attach -t <session-name>`.
138
+ - For MCP-first flows, ensure session start enables worker spawning so tmux panes are created.
139
+ - Per-role models can be set at start (`--model-planner`, `--model-critic`, `--model-coder`, `--model-reviewer`) or persisted via `acfm agents model set`.
101
140
 
102
141
  ### OpenSpec Skills (The heart of the framework)
103
142
 
@@ -98,6 +98,45 @@ acfm memory stats
98
98
 
99
99
  **User communication:** "Memory saved: [brief description]" when auto-saving occurs.
100
100
 
101
+ ### SynapseGrid Collaborative MCP Protocol (Optional)
102
+
103
+ If SynapseGrid is enabled in `acfm init`, AC Framework installs the collaborative MCP server automatically for detected assistants.
104
+
105
+ **Session-start requirement when collaboration is enabled:**
106
+ 1. Prefer the available SynapseGrid MCP tools for collaborative session control before falling back to direct CLI.
107
+ 2. Use shared session state and transcript as the source of truth for role-by-role collaboration.
108
+ 3. If collaborative MCP is unavailable, use CLI fallback commands and keep behavior equivalent.
109
+
110
+ **How to use SynapseGrid collaboration:**
111
+ ```text
112
+ Preferred: use SynapseGrid MCP tools (ac-framework-collab) for start/status/step/resume/stop.
113
+ Fallback: use AC Framework agents CLI commands directly.
114
+ ```
115
+ ```bash
116
+ # Install/update collaborative MCP server integrations
117
+ acfm agents install-mcps
118
+
119
+ # Start collaborative runtime with visible 4-pane tmux war-room
120
+ acfm agents start --task "design and implement feature X" --attach --model-coder provider/model
121
+
122
+ # If already running in background, attach to panes and inspect logs
123
+ acfm agents live
124
+ acfm agents logs --role all --lines 120
125
+
126
+ # Resume/list/export operations
127
+ acfm agents resume
128
+ acfm agents list
129
+ acfm agents export --format md --out synapse-session.md
130
+
131
+ # Fast diagnostics for OpenCode resolution errors
132
+ acfm agents setup
133
+ ```
134
+
135
+ **Troubleshooting notes:**
136
+ - If transcript shows `Agent failed: spawn opencode ENOENT`, run `acfm agents setup` and restart/resume session.
137
+ - If session exists but panes are not visible, use `acfm agents live` (or `acfm agents attach`) or `tmux attach -t <session-name>`.
138
+ - For MCP-first flows, ensure session start enables worker spawning so tmux panes are created.
139
+ - Per-role models can be set at start (`--model-planner`, `--model-critic`, `--model-coder`, `--model-reviewer`) or persisted via `acfm agents model set`.
101
140
 
102
141
  ### OpenSpec Skills (The heart of the framework)
103
142
 
@@ -98,6 +98,45 @@ acfm memory stats
98
98
 
99
99
  **User communication:** "Memory saved: [brief description]" when auto-saving occurs.
100
100
 
101
+ ### SynapseGrid Collaborative MCP Protocol (Optional)
102
+
103
+ If SynapseGrid is enabled in `acfm init`, AC Framework installs the collaborative MCP server automatically for detected assistants.
104
+
105
+ **Session-start requirement when collaboration is enabled:**
106
+ 1. Prefer the available SynapseGrid MCP tools for collaborative session control before falling back to direct CLI.
107
+ 2. Use shared session state and transcript as the source of truth for role-by-role collaboration.
108
+ 3. If collaborative MCP is unavailable, use CLI fallback commands and keep behavior equivalent.
109
+
110
+ **How to use SynapseGrid collaboration:**
111
+ ```text
112
+ Preferred: use SynapseGrid MCP tools (ac-framework-collab) for start/status/step/resume/stop.
113
+ Fallback: use AC Framework agents CLI commands directly.
114
+ ```
115
+ ```bash
116
+ # Install/update collaborative MCP server integrations
117
+ acfm agents install-mcps
118
+
119
+ # Start collaborative runtime with visible 4-pane tmux war-room
120
+ acfm agents start --task "design and implement feature X" --attach --model-coder provider/model
121
+
122
+ # If already running in background, attach to panes and inspect logs
123
+ acfm agents live
124
+ acfm agents logs --role all --lines 120
125
+
126
+ # Resume/list/export operations
127
+ acfm agents resume
128
+ acfm agents list
129
+ acfm agents export --format md --out synapse-session.md
130
+
131
+ # Fast diagnostics for OpenCode resolution errors
132
+ acfm agents setup
133
+ ```
134
+
135
+ **Troubleshooting notes:**
136
+ - If transcript shows `Agent failed: spawn opencode ENOENT`, run `acfm agents setup` and restart/resume session.
137
+ - If session exists but panes are not visible, use `acfm agents live` (or `acfm agents attach`) or `tmux attach -t <session-name>`.
138
+ - For MCP-first flows, ensure session start enables worker spawning so tmux panes are created.
139
+ - Per-role models can be set at start (`--model-planner`, `--model-critic`, `--model-coder`, `--model-reviewer`) or persisted via `acfm agents model set`.
101
140
 
102
141
  ### OpenSpec Skills (The heart of the framework)
103
142
 
@@ -98,6 +98,45 @@ acfm memory stats
98
98
 
99
99
  **User communication:** "Memory saved: [brief description]" when auto-saving occurs.
100
100
 
101
+ ### SynapseGrid Collaborative MCP Protocol (Optional)
102
+
103
+ If SynapseGrid is enabled in `acfm init`, AC Framework installs the collaborative MCP server automatically for detected assistants.
104
+
105
+ **Session-start requirement when collaboration is enabled:**
106
+ 1. Prefer the available SynapseGrid MCP tools for collaborative session control before falling back to direct CLI.
107
+ 2. Use shared session state and transcript as the source of truth for role-by-role collaboration.
108
+ 3. If collaborative MCP is unavailable, use CLI fallback commands and keep behavior equivalent.
109
+
110
+ **How to use SynapseGrid collaboration:**
111
+ ```text
112
+ Preferred: use SynapseGrid MCP tools (ac-framework-collab) for start/status/step/resume/stop.
113
+ Fallback: use AC Framework agents CLI commands directly.
114
+ ```
115
+ ```bash
116
+ # Install/update collaborative MCP server integrations
117
+ acfm agents install-mcps
118
+
119
+ # Start collaborative runtime with visible 4-pane tmux war-room
120
+ acfm agents start --task "design and implement feature X" --attach --model-coder provider/model
121
+
122
+ # If already running in background, attach to panes and inspect logs
123
+ acfm agents live
124
+ acfm agents logs --role all --lines 120
125
+
126
+ # Resume/list/export operations
127
+ acfm agents resume
128
+ acfm agents list
129
+ acfm agents export --format md --out synapse-session.md
130
+
131
+ # Fast diagnostics for OpenCode resolution errors
132
+ acfm agents setup
133
+ ```
134
+
135
+ **Troubleshooting notes:**
136
+ - If transcript shows `Agent failed: spawn opencode ENOENT`, run `acfm agents setup` and restart/resume session.
137
+ - If session exists but panes are not visible, use `acfm agents live` (or `acfm agents attach`) or `tmux attach -t <session-name>`.
138
+ - For MCP-first flows, ensure session start enables worker spawning so tmux panes are created.
139
+ - Per-role models can be set at start (`--model-planner`, `--model-critic`, `--model-coder`, `--model-reviewer`) or persisted via `acfm agents model set`.
101
140
 
102
141
  ### OpenSpec Skills (The heart of the framework)
103
142
 
@@ -98,6 +98,33 @@ acfm memory stats
98
98
 
99
99
  **User communication:** "Memory saved: [brief description]" when auto-saving occurs.
100
100
 
101
+ ### SynapseGrid Collaborative MCP Protocol (Optional)
102
+
103
+ If SynapseGrid is enabled in `acfm init`, AC Framework installs the collaborative MCP server automatically for detected assistants.
104
+
105
+ **Session-start requirement when collaboration is enabled:**
106
+ 1. Prefer the available SynapseGrid MCP tools for collaborative session control before falling back to direct CLI.
107
+ 2. Use shared session state and transcript as the source of truth for role-by-role collaboration.
108
+ 3. If collaborative MCP is unavailable, use CLI fallback commands and keep behavior equivalent.
109
+
110
+ **How to use SynapseGrid collaboration:**
111
+ ```text
112
+ Preferred: use SynapseGrid MCP tools (ac-framework-collab) for session start/status/step/stop.
113
+ Fallback: use AC Framework agents CLI commands directly.
114
+ ```
115
+ ```bash
116
+ # Optional install/reinstall of collaborative MCP servers
117
+ acfm agents install-mcps
118
+
119
+ # Start collaborative runtime manually
120
+ acfm agents start --task "design and implement feature X"
121
+
122
+ # Resume/list/export operations
123
+ acfm agents resume
124
+ acfm agents list
125
+ acfm agents export --format md --out synapse-session.md
126
+ ```
127
+
101
128
 
102
129
  ### OpenSpec Skills (The heart of the framework)
103
130
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ac-framework",
3
- "version": "1.9.4",
3
+ "version": "1.9.6",
4
4
  "description": "Agentic Coding Framework - Multi-assistant configuration system with OpenSpec workflows",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -0,0 +1,48 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { sanitizeRoleModels, normalizeModelId } from './model-selection.js';
6
+
7
+ const ACFM_DIR = join(homedir(), '.acfm');
8
+ const CONFIG_PATH = join(ACFM_DIR, 'config.json');
9
+
10
+ function normalizeConfig(raw) {
11
+ const agents = raw?.agents && typeof raw.agents === 'object' ? raw.agents : {};
12
+ return {
13
+ agents: {
14
+ defaultModel: normalizeModelId(agents.defaultModel) || null,
15
+ defaultRoleModels: sanitizeRoleModels(agents.defaultRoleModels),
16
+ },
17
+ };
18
+ }
19
+
20
+ export async function loadAgentsConfig() {
21
+ if (!existsSync(CONFIG_PATH)) {
22
+ return normalizeConfig({});
23
+ }
24
+
25
+ try {
26
+ const raw = JSON.parse(await readFile(CONFIG_PATH, 'utf8'));
27
+ return normalizeConfig(raw);
28
+ } catch {
29
+ return normalizeConfig({});
30
+ }
31
+ }
32
+
33
+ export async function saveAgentsConfig(config) {
34
+ const normalized = normalizeConfig(config);
35
+ await mkdir(ACFM_DIR, { recursive: true });
36
+ await writeFile(CONFIG_PATH, JSON.stringify(normalized, null, 2) + '\n', 'utf8');
37
+ return normalized;
38
+ }
39
+
40
+ export async function updateAgentsConfig(mutator) {
41
+ const current = await loadAgentsConfig();
42
+ const next = await mutator(current);
43
+ return saveAgentsConfig(next);
44
+ }
45
+
46
+ export function getAgentsConfigPath() {
47
+ return CONFIG_PATH;
48
+ }
@@ -0,0 +1,38 @@
1
+ import { COLLAB_ROLES } from './constants.js';
2
+
3
+ export function normalizeModelId(value) {
4
+ if (typeof value !== 'string') return null;
5
+ const trimmed = value.trim();
6
+ return trimmed.length > 0 ? trimmed : null;
7
+ }
8
+
9
+ export function isValidModelId(value) {
10
+ const normalized = normalizeModelId(value);
11
+ if (!normalized) return false;
12
+ return normalized.includes('/');
13
+ }
14
+
15
+ export function sanitizeRoleModels(input) {
16
+ const out = {};
17
+ if (!input || typeof input !== 'object') return out;
18
+ for (const role of COLLAB_ROLES) {
19
+ const normalized = normalizeModelId(input[role]);
20
+ if (normalized) out[role] = normalized;
21
+ }
22
+ return out;
23
+ }
24
+
25
+ export function resolveRoleModel(state, role, fallbackModel = null) {
26
+ const roleModels = sanitizeRoleModels(state?.roleModels);
27
+ const roleModel = roleModels[role] || null;
28
+ const globalModel = normalizeModelId(state?.model) || normalizeModelId(fallbackModel);
29
+ return roleModel || globalModel || null;
30
+ }
31
+
32
+ export function buildEffectiveRoleModels(state, fallbackModel = null) {
33
+ const effective = {};
34
+ for (const role of COLLAB_ROLES) {
35
+ effective[role] = resolveRoleModel(state, role, fallbackModel);
36
+ }
37
+ return effective;
38
+ }
@@ -22,7 +22,8 @@ function parseOpenCodeRunOutput(stdout) {
22
22
  return stdout.trim();
23
23
  }
24
24
 
25
- export async function runOpenCodePrompt({ prompt, cwd, model, agent, timeoutMs = 180000 }) {
25
+ export async function runOpenCodePrompt({ prompt, cwd, model, agent, timeoutMs = 180000, binaryPath }) {
26
+ const binary = binaryPath || process.env.ACFM_OPENCODE_BIN || 'opencode';
26
27
  const args = ['run', '--format', 'json'];
27
28
  if (model) {
28
29
  args.push('--model', model);
@@ -32,7 +33,7 @@ export async function runOpenCodePrompt({ prompt, cwd, model, agent, timeoutMs =
32
33
  }
33
34
  args.push('--', prompt);
34
35
 
35
- const { stdout, stderr } = await execFileAsync('opencode', args, {
36
+ const { stdout, stderr } = await execFileAsync(binary, args, {
36
37
  cwd,
37
38
  timeout: timeoutMs,
38
39
  maxBuffer: 10 * 1024 * 1024,
@@ -1,6 +1,7 @@
1
1
  import { buildAgentPrompt, ROLE_SYSTEM_PROMPTS } from './role-prompts.js';
2
2
  import { runOpenCodePrompt } from './opencode-client.js';
3
3
  import { nextRole, shouldStop } from './scheduler.js';
4
+ import { resolveRoleModel } from './model-selection.js';
4
5
  import {
5
6
  addAgentMessage,
6
7
  loadSessionState,
@@ -42,11 +43,13 @@ export async function runTurn(sessionId, options = {}) {
42
43
  const prompt = buildRuntimePrompt({ state, role: scheduled.role });
43
44
  let content;
44
45
  try {
46
+ const effectiveModel = resolveRoleModel(state, scheduled.role, options.model);
45
47
  content = await runOpenCodePrompt({
46
48
  prompt,
47
49
  cwd: options.cwd || process.cwd(),
48
- model: options.model,
50
+ model: effectiveModel,
49
51
  agent: options.agent,
52
+ binaryPath: options.opencodeBin,
50
53
  timeoutMs: options.timeoutMs,
51
54
  });
52
55
  } catch (error) {
@@ -105,11 +108,13 @@ export async function executeActiveTurn(sessionId, role, options = {}) {
105
108
  const prompt = buildRuntimePrompt({ state, role });
106
109
  let content;
107
110
  try {
111
+ const effectiveModel = resolveRoleModel(state, role, options.model);
108
112
  content = await runOpenCodePrompt({
109
113
  prompt,
110
114
  cwd: options.cwd || process.cwd(),
111
- model: options.model,
115
+ model: effectiveModel,
112
116
  agent: options.agent,
117
+ binaryPath: options.opencodeBin,
113
118
  timeoutMs: options.timeoutMs,
114
119
  });
115
120
  } catch (error) {
@@ -153,11 +158,13 @@ export async function runWorkerIteration(sessionId, role, options = {}) {
153
158
  const prompt = buildRuntimePrompt({ state, role });
154
159
  let content;
155
160
  try {
161
+ const effectiveModel = resolveRoleModel(state, role, options.model);
156
162
  content = await runOpenCodePrompt({
157
163
  prompt,
158
164
  cwd: options.cwd || process.cwd(),
159
- model: options.model,
165
+ model: effectiveModel,
160
166
  agent: options.agent,
167
+ binaryPath: options.opencodeBin,
161
168
  timeoutMs: options.timeoutMs,
162
169
  });
163
170
  } catch (error) {
@@ -0,0 +1,80 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { COLLAB_ROLES } from './constants.js';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const runnerPath = resolve(__dirname, '../../bin/acfm.js');
8
+
9
+ export function roleLogPath(sessionDir, role) {
10
+ return resolve(sessionDir, `${role}.log`);
11
+ }
12
+
13
+ export function runTmux(command, args, options = {}) {
14
+ return new Promise((resolvePromise, rejectPromise) => {
15
+ const child = spawn(command, args, {
16
+ cwd: options.cwd || process.cwd(),
17
+ stdio: options.stdio || 'pipe',
18
+ env: process.env,
19
+ });
20
+
21
+ let stderr = '';
22
+ let stdout = '';
23
+ if (child.stderr) {
24
+ child.stderr.on('data', (chunk) => {
25
+ stderr += chunk.toString();
26
+ });
27
+ }
28
+ if (child.stdout) {
29
+ child.stdout.on('data', (chunk) => {
30
+ stdout += chunk.toString();
31
+ });
32
+ }
33
+
34
+ child.on('error', rejectPromise);
35
+ child.on('close', (code) => {
36
+ if (code === 0) {
37
+ resolvePromise({ stdout, stderr });
38
+ return;
39
+ }
40
+ rejectPromise(new Error(stderr.trim() || `${command} exited with code ${code}`));
41
+ });
42
+ });
43
+ }
44
+
45
+ export async function spawnTmuxSession({ sessionName, sessionDir, sessionId }) {
46
+ const role0 = COLLAB_ROLES[0];
47
+ await runTmux('tmux', [
48
+ 'new-session',
49
+ '-d',
50
+ '-s',
51
+ sessionName,
52
+ '-n',
53
+ role0,
54
+ `bash -lc 'node "${runnerPath}" agents worker --session ${sessionId} --role ${role0} >> "${roleLogPath(sessionDir, role0)}" 2>&1'`,
55
+ ]);
56
+
57
+ for (let idx = 1; idx < COLLAB_ROLES.length; idx += 1) {
58
+ const role = COLLAB_ROLES[idx];
59
+ await runTmux('tmux', [
60
+ 'split-window',
61
+ '-t',
62
+ sessionName,
63
+ '-v',
64
+ `bash -lc 'node "${runnerPath}" agents worker --session ${sessionId} --role ${role} >> "${roleLogPath(sessionDir, role)}" 2>&1'`,
65
+ ]);
66
+ }
67
+
68
+ await runTmux('tmux', ['select-layout', '-t', sessionName, 'tiled']);
69
+ await runTmux('tmux', ['set-option', '-t', sessionName, 'pane-border-status', 'top']);
70
+ await runTmux('tmux', ['set-option', '-t', sessionName, 'pane-border-format', '#{pane_index}:#{pane_title}']);
71
+ }
72
+
73
+ export async function tmuxSessionExists(sessionName) {
74
+ try {
75
+ await runTmux('tmux', ['has-session', '-t', sessionName]);
76
+ return true;
77
+ } catch {
78
+ return false;
79
+ }
80
+ }
@@ -8,6 +8,7 @@ import {
8
8
  SESSION_ROOT_DIR,
9
9
  CURRENT_SESSION_FILE,
10
10
  } from './constants.js';
11
+ import { sanitizeRoleModels } from './model-selection.js';
11
12
 
12
13
  function sleep(ms) {
13
14
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -53,6 +54,8 @@ function initialState(task, options = {}) {
53
54
  roles: options.roles?.length ? options.roles : COLLAB_ROLES,
54
55
  workingDirectory: options.workingDirectory || process.cwd(),
55
56
  model: options.model || null,
57
+ roleModels: sanitizeRoleModels(options.roleModels),
58
+ opencodeBin: options.opencodeBin || null,
56
59
  tmuxSessionName: options.tmuxSessionName || null,
57
60
  messages: [
58
61
  {