tlc-claude-code 1.6.1 → 1.6.2

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.
@@ -166,13 +166,17 @@ Senior engineers know when rules don't apply:
166
166
 
167
167
  This is the core TLC command. Tests before code, one task at a time.
168
168
 
169
- **Overdrive Mode:** When tasks are independent, TLC auto-detects and offers parallel execution with multiple agents.
169
+ **Overdrive Mode (Opus 4.6):** When tasks are independent, TLC auto-detects and offers parallel execution with multiple agents. Agents are assigned models based on task complexity (opus for heavy, sonnet for standard, haiku for light). Failed agents can be resumed. No arbitrary cap on agent count.
170
170
 
171
171
  ## Usage
172
172
 
173
173
  ```
174
174
  /tlc:build <phase_number>
175
- /tlc:build <phase_number> --sequential # Force sequential mode
175
+ /tlc:build <phase_number> --sequential # Force sequential mode
176
+ /tlc:build <phase_number> --model sonnet # Force all agents to use sonnet
177
+ /tlc:build <phase_number> --model haiku # Force all agents to use haiku (fast/cheap)
178
+ /tlc:build <phase_number> --max-turns 30 # Limit agent execution length
179
+ /tlc:build <phase_number> --agents 5 # Limit parallel agents to 5
176
180
  ```
177
181
 
178
182
  ## Process
@@ -181,7 +185,7 @@ This is the core TLC command. Tests before code, one task at a time.
181
185
 
182
186
  Read all `.planning/phases/{phase}-*-PLAN.md` files for this phase.
183
187
 
184
- ### Step 1a: Overdrive Detection (Auto-Parallel)
188
+ ### Step 1a: Overdrive Detection (Auto-Parallel, Opus 4.6)
185
189
 
186
190
  After loading plans, analyze task dependencies to determine if parallel execution is possible.
187
191
 
@@ -200,18 +204,19 @@ After loading plans, analyze task dependencies to determine if parallel executio
200
204
  2. Look for dependency markers in task descriptions
201
205
  3. Check "## Dependencies" section if present
202
206
  4. Identify independent tasks (no dependencies)
207
+ 5. Estimate task complexity for model assignment (heavy/standard/light)
203
208
 
204
209
  **If 2+ independent tasks found:**
205
210
  ```
206
- 🚀 Overdrive Mode Available
211
+ 🚀 Overdrive Mode Available (Opus 4.6)
207
212
 
208
213
  Phase 3 has 4 independent tasks that can run in parallel:
209
- - Task 1: Create user schema
210
- - Task 2: Add validation helpers
211
- - Task 3: Write migration scripts
212
- - Task 4: Create seed data
214
+ - Task 1: Create user schema [opus] (heavy)
215
+ - Task 2: Add validation helpers [sonnet] (standard)
216
+ - Task 3: Write migration scripts [sonnet] (standard)
217
+ - Task 4: Create seed data [haiku] (light)
213
218
 
214
- Recommended: 3 agents (optimal parallelism)
219
+ Recommended: 4 agents (one per independent task)
215
220
 
216
221
  Options:
217
222
  1) Overdrive mode (parallel agents) [Recommended]
@@ -219,6 +224,15 @@ Options:
219
224
  3) Let me pick which tasks to parallelize
220
225
  ```
221
226
 
227
+ **Model auto-selection per task complexity:**
228
+ | Complexity | Model | When |
229
+ |-----------|-------|------|
230
+ | Heavy | opus | Architecture, multi-file features, security, auth, database |
231
+ | Standard | sonnet | Normal implementation tasks (default) |
232
+ | Light | haiku | Config, boilerplate, DTOs, enums, constants, seed data |
233
+
234
+ Override with `--model sonnet` to force all agents to the same model.
235
+
222
236
  **If tasks have dependencies (waterfall):**
223
237
  ```
224
238
  📋 Sequential Mode
@@ -230,62 +244,87 @@ Phase 3 tasks have dependencies:
230
244
  Running in sequential order.
231
245
  ```
232
246
 
233
- ### Step 1b: Execute Overdrive (if selected)
247
+ ### Step 1b: Execute Overdrive (if selected) — Opus 4.6 Multi-Agent
234
248
 
235
- When overdrive mode is selected, spawn parallel agents:
249
+ When overdrive mode is selected, spawn parallel agents with per-task model selection:
236
250
 
237
251
  ```
238
- 🚀 Launching Overdrive Mode
252
+ 🚀 Launching Overdrive Mode (Opus 4.6)
239
253
 
240
- Spawning 3 agents in parallel...
254
+ Spawning 4 agents in parallel...
241
255
 
242
- Agent 1: Task 1 - Create user schema
243
- Agent 2: Task 2 - Add validation helpers
244
- Agent 3: Task 3 - Write migration scripts
256
+ Agent 1: Task 1 - Create user schema [opus]
257
+ Agent 2: Task 2 - Add validation helpers [sonnet]
258
+ Agent 3: Task 3 - Write migration scripts [sonnet]
259
+ Agent 4: Task 4 - Create seed data [haiku]
245
260
 
246
261
  [All agents spawned - working in background]
247
- [Task 4 queued for next available agent]
248
262
  ```
249
263
 
250
264
  **Agent execution rules:**
251
- - Each agent gets one task
265
+ - One agent per independent task (no arbitrary cap)
266
+ - Model assigned per task complexity (override with `--model`)
252
267
  - Agents work autonomously (no confirmation prompts)
253
268
  - Each agent commits after completing their task
254
- - When an agent finishes, it can pick up queued tasks
255
269
  - All agents follow test-first methodology
270
+ - `max_turns` limits execution length (default: 50, override with `--max-turns`)
256
271
 
257
272
  **CRITICAL: Spawn all agents in a SINGLE message using multiple Task tool calls.**
258
273
 
259
274
  ```
260
- Task(description="Agent 1: Task 1", prompt="...", subagent_type="general-purpose", run_in_background=true)
261
- Task(description="Agent 2: Task 2", prompt="...", subagent_type="general-purpose", run_in_background=true)
262
- Task(description="Agent 3: Task 3", prompt="...", subagent_type="general-purpose", run_in_background=true)
275
+ Task(description="Agent 1: Task 1", prompt="...", subagent_type="general-purpose", model="opus", max_turns=50, run_in_background=true)
276
+ Task(description="Agent 2: Task 2", prompt="...", subagent_type="general-purpose", model="sonnet", max_turns=50, run_in_background=true)
277
+ Task(description="Agent 3: Task 3", prompt="...", subagent_type="general-purpose", model="sonnet", max_turns=50, run_in_background=true)
278
+ Task(description="Agent 4: Task 4", prompt="...", subagent_type="general-purpose", model="haiku", max_turns=50, run_in_background=true)
263
279
  ```
264
280
 
265
- **Live Progress Monitoring:**
281
+ **Live Progress Monitoring (TaskOutput):**
266
282
 
267
- While agents run in background, show live status using the AgentProgressMonitor:
283
+ Use `TaskOutput` with `block=false` for non-blocking progress checks:
284
+
285
+ ```
286
+ TaskOutput(task_id="AGENT_ID_1", block=false, timeout=5000)
287
+ TaskOutput(task_id="AGENT_ID_2", block=false, timeout=5000)
288
+ ```
289
+
290
+ Or use the AgentProgressMonitor for formatted status:
268
291
 
269
292
  ```bash
270
293
  node -e "
271
294
  const { AgentProgressMonitor } = require('./server/lib/agent-progress-monitor.js');
272
295
  const monitor = new AgentProgressMonitor('/tmp/claude-1000/-mnt-c-Code-TLC/tasks');
273
- const agents = ['AGENT_ID_1', 'AGENT_ID_2', 'AGENT_ID_3'];
296
+ const agents = ['AGENT_ID_1', 'AGENT_ID_2', 'AGENT_ID_3', 'AGENT_ID_4'];
274
297
  console.log(monitor.formatTable(agents));
275
298
  "
276
299
  ```
277
300
 
278
301
  Display format:
279
302
  ```
280
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
281
- 🚀 OVERDRIVE STATUS
282
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
283
- | Agent | Task | Tests | Phase |
284
- |-------|------|-------|-------|
285
- | a1b2c3 | README Generator | 29 ✓ | committed |
286
- | d4e5f6 | Flow Diagrams | 18 ✓ | implementing |
287
- | g7h8i9 | ADR Generator | - | writing-tests |
288
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
303
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
304
+ 🚀 OVERDRIVE STATUS (Opus 4.6)
305
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
306
+ | Agent | Task | Model | Tests | Phase |
307
+ |-------|------|-------|-------|-------|
308
+ | a1b2c3 | User Schema | opus | 29 ✓ | committed |
309
+ | d4e5f6 | Validation | sonnet | 18 ✓ | implementing |
310
+ | g7h8i9 | Migrations | sonnet | - | writing-tests |
311
+ | j0k1l2 | Seed Data | haiku | 5 ✓ | committed |
312
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
313
+ ```
314
+
315
+ **Handling Agent Failures (Opus 4.6 Resumption):**
316
+
317
+ If an agent fails or gets stuck:
318
+
319
+ ```
320
+ # Check if agent is stuck (non-blocking)
321
+ TaskOutput(task_id="AGENT_ID", block=false, timeout=5000)
322
+
323
+ # Cancel a stuck agent
324
+ TaskStop(task_id="AGENT_ID")
325
+
326
+ # Resume a failed agent from where it left off (full context preserved)
327
+ Task(resume="AGENT_ID", prompt="Continue from where you left off. Fix any errors and complete the task.")
289
328
  ```
290
329
 
291
330
  **Show status automatically:**
@@ -799,7 +838,7 @@ Writing tests for Task 2: Validation...
799
838
  Phase 1 complete. Ready for /tlc:verify 1
800
839
  ```
801
840
 
802
- ### Overdrive Mode (Independent Tasks)
841
+ ### Overdrive Mode (Independent Tasks, Opus 4.6)
803
842
 
804
843
  ```
805
844
  User: /tlc:build 2
@@ -807,15 +846,15 @@ User: /tlc:build 2
807
846
  Claude: Loading phase 2 plans...
808
847
  Found: 2-PLAN.md (4 tasks)
809
848
 
810
- 🚀 Overdrive Mode Available
849
+ 🚀 Overdrive Mode Available (Opus 4.6)
811
850
 
812
851
  Phase 2 has 4 independent tasks:
813
- - Task 1: Create API routes
814
- - Task 2: Add input validation
815
- - Task 3: Write error handlers
816
- - Task 4: Add rate limiting
852
+ - Task 1: Create API routes [sonnet] (standard)
853
+ - Task 2: Add input validation [sonnet] (standard)
854
+ - Task 3: Write error handlers [sonnet] (standard)
855
+ - Task 4: Add rate limiting config [haiku] (light)
817
856
 
818
- Recommended: 3 agents
857
+ Recommended: 4 agents (one per task)
819
858
 
820
859
  Options:
821
860
  1) Overdrive mode (parallel) [Recommended]
@@ -824,21 +863,20 @@ Options:
824
863
 
825
864
  User: 1
826
865
 
827
- Claude: 🚀 Launching Overdrive Mode
866
+ Claude: 🚀 Launching Overdrive Mode (Opus 4.6)
828
867
 
829
- Spawning 3 agents...
830
- [Agent 1] Task 1: Create API routes - STARTED
831
- [Agent 2] Task 2: Add input validation - STARTED
832
- [Agent 3] Task 3: Write error handlers - STARTED
833
- [Queued] Task 4: Add rate limiting
868
+ Spawning 4 agents...
869
+ [Agent 1] Task 1: Create API routes [sonnet] - STARTED
870
+ [Agent 2] Task 2: Add input validation [sonnet] - STARTED
871
+ [Agent 3] Task 3: Write error handlers [sonnet] - STARTED
872
+ [Agent 4] Task 4: Add rate limiting config [haiku] - STARTED
834
873
 
835
874
  ... agents working in background ...
836
875
 
837
- [Agent 2] ✅ Task 2 complete (3 commits)
838
- [Agent 2] Task 4: Add rate limiting - STARTED
839
- [Agent 1] ✅ Task 1 complete (4 commits)
840
- [Agent 3] ✅ Task 3 complete (2 commits)
841
- [Agent 2] ✅ Task 4 complete (2 commits)
876
+ [Agent 4] ✅ Task 4 complete (1 commit) [haiku - fast]
877
+ [Agent 2] Task 2 complete (3 commits) [sonnet]
878
+ [Agent 1] ✅ Task 1 complete (4 commits) [sonnet]
879
+ [Agent 3] ✅ Task 3 complete (2 commits) [sonnet]
842
880
 
843
881
  All agents complete. Running full test suite...
844
882
  ✅ 24 tests passing
@@ -863,18 +901,23 @@ Phase 2 complete. Ready for /tlc:verify 2
863
901
  - Suggest running `/tlc:build {phase}` again to retry
864
902
  - Or manually fix and run `/tlc:status` to verify
865
903
 
866
- **Overdrive mode issues:**
867
- - Agent stuck → Check with `TaskOutput` tool
904
+ **Overdrive mode issues (Opus 4.6):**
905
+ - Agent stuck → Check with `TaskOutput(task_id, block=false)`, cancel with `TaskStop(task_id)`
906
+ - Agent failed → Resume with `Task(resume=agent_id)` to continue from where it left off
868
907
  - Merge conflicts → Agents working on same files (rare if tasks are truly independent)
869
- - One agent failed → Other agents continue; fix failed task manually
908
+ - One agent failed → Other agents continue; resume failed agent or fix manually
870
909
  - Want sequential instead → Use `--sequential` flag: `/tlc:build 2 --sequential`
910
+ - Cost too high → Use `--model haiku` to force all agents to haiku
911
+ - Agent running too long → Use `--max-turns 30` to limit execution
871
912
 
872
913
  ## Flags
873
914
 
874
915
  | Flag | Description |
875
916
  |------|-------------|
876
917
  | `--sequential` | Force sequential execution even if tasks are independent |
877
- | `--agents N` | Set max parallel agents (default: 3, max: 10) |
918
+ | `--agents N` | Limit parallel agents to N (default: one per independent task) |
919
+ | `--model MODEL` | Force all agents to use a specific model (opus, sonnet, haiku) |
920
+ | `--max-turns N` | Limit each agent's execution to N turns (default: 50) |
878
921
 
879
922
  ## When Overdrive is NOT Used
880
923
 
@@ -61,13 +61,13 @@ Add to `.tlc.json`:
61
61
  "claude": {
62
62
  "type": "cli",
63
63
  "command": "claude",
64
- "model": "claude-4-opus",
64
+ "model": "claude-opus-4-6",
65
65
  "capabilities": ["review", "code-gen", "refactor"]
66
66
  },
67
67
  "codex": {
68
68
  "type": "cli",
69
69
  "command": "codex",
70
- "model": "gpt-5.2",
70
+ "model": "gpt-5.3-codex",
71
71
  "capabilities": ["review", "code-gen"]
72
72
  },
73
73
  "gemini": {
@@ -89,9 +89,9 @@ Add to `.tlc.json`:
89
89
 
90
90
  ## Supported Models
91
91
 
92
- **Claude:** claude-4-opus, claude-4-sonnet, claude-3.5-sonnet, claude-3-opus, claude-3-sonnet, claude-3-haiku
92
+ **Claude:** claude-opus-4-6, claude-sonnet-4-5, claude-4-opus, claude-4-sonnet, claude-3.5-sonnet, claude-3-opus, claude-3-sonnet, claude-3-haiku
93
93
 
94
- **OpenAI/Codex:** gpt-5.2, gpt-5, gpt-4o, gpt-4o-mini, gpt-4-turbo, o3, o3-mini, o1, o1-mini, o1-pro
94
+ **OpenAI/Codex:** gpt-5.3-codex, gpt-5.2, gpt-5, gpt-4o, gpt-4o-mini, gpt-4-turbo, o3, o3-mini, o1, o1-mini, o1-pro
95
95
 
96
96
  **Gemini:** gemini-3-preview, gemini-3, gemini-2.5-pro, gemini-2.0-flash, gemini-2.0-flash-thinking, gemini-1.5-pro
97
97
 
@@ -229,7 +229,7 @@ Create `.tlc.json` with all settings:
229
229
  "autofix": true,
230
230
  "edgeCases": true,
231
231
  "prAutoReview": true,
232
- "maxAgents": 10
232
+ "maxAgents": "auto"
233
233
  }
234
234
  }
235
235
  ```
package/CLAUDE.md CHANGED
@@ -89,6 +89,17 @@ All implementation follows **Red → Green → Refactor**:
89
89
 
90
90
  Tests are written BEFORE implementation, not after.
91
91
 
92
+ ## Context Management
93
+
94
+ **Use multiple sessions/agents for large tasks.** When working in overdrive mode or across workspaces:
95
+ - Use the `Task` tool to spawn sub-agents for independent work (research, testing, building)
96
+ - Keep the main conversation focused on orchestration and decisions
97
+ - Delegate file-heavy operations (reading many files, running test suites) to sub-agents
98
+ - This prevents context window overflow, which causes crashes and lost work
99
+ - Especially critical in workspace mode where multiple repos are involved
100
+
101
+ **Signs you need to delegate:** If you've read 15+ files, run 10+ commands, or the conversation is getting long — spawn a sub-agent for the next chunk of work.
102
+
92
103
  ## After TLC Updates
93
104
 
94
105
  If TLC command files are updated, re-read them before executing. Check version in `package.json`.
package/README.md CHANGED
@@ -72,7 +72,7 @@ Or use `/tlc` for the full dashboard.
72
72
  ### For Solo Developers
73
73
 
74
74
  - **Test-first by default** — Claude writes tests before code
75
- - **Auto-parallelization** — Up to 10 agents run independent tasks simultaneously
75
+ - **Auto-parallelization** — Agents run independent tasks simultaneously with per-task model selection (Opus 4.6)
76
76
  - **`/tlc:next`** — One command to progress. No decisions needed.
77
77
  - **Smart dashboard** — See progress, run actions
78
78
  - **Coverage gaps** — Find and fix untested code
@@ -103,7 +103,7 @@ Or use `/tlc` for the full dashboard.
103
103
  | `/tlc` | Smart dashboard — full status view |
104
104
  | `/tlc:new-project` | Start new project with roadmap |
105
105
  | `/tlc:init` | Add TLC to existing codebase |
106
- | `/tlc:build` | Write tests → implement (auto-parallelizes up to 10 agents) |
106
+ | `/tlc:build` | Write tests → implement (auto-parallelizes with model selection) |
107
107
  | `/tlc:coverage` | Find and fix untested code |
108
108
  | `/tlc:quality` | Test quality scoring |
109
109
  | `/tlc:autofix` | Auto-repair failing tests |
@@ -121,6 +121,26 @@ services:
121
121
  - app
122
122
  restart: on-failure
123
123
 
124
+ # TLC Dashboard Web (built from dashboard-web, serves static files via nginx)
125
+ dashboard-web:
126
+ build:
127
+ context: ./dashboard-web
128
+ dockerfile: Dockerfile
129
+ container_name: tlc-${COMPOSE_PROJECT_NAME:-dev}-dashboard-web
130
+ ports:
131
+ - "${DASHBOARD_WEB_PORT:-3002}:80"
132
+ environment:
133
+ - API_URL=http://dashboard:3147
134
+ depends_on:
135
+ - dashboard
136
+ restart: on-failure
137
+ healthcheck:
138
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/health"]
139
+ interval: 30s
140
+ timeout: 3s
141
+ start_period: 5s
142
+ retries: 3
143
+
124
144
  # Playwright E2E Tests (optional - starts on demand)
125
145
  playwright:
126
146
  image: mcr.microsoft.com/playwright:v1.40.0-jammy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlc-claude-code",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "TLC - Test Led Coding for Claude Code",
5
5
  "bin": {
6
6
  "tlc": "./bin/tlc.js",
package/server/index.js CHANGED
@@ -1,5 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // Suppress punycode deprecation from upstream deps (http-proxy-middleware, ws)
4
+ const originalEmit = process.emit;
5
+ process.emit = function (event, error) {
6
+ if (event === 'warning' && error?.name === 'DeprecationWarning' && error?.message?.includes('punycode')) {
7
+ return false;
8
+ }
9
+ return originalEmit.apply(process, arguments);
10
+ };
11
+
3
12
  const express = require('express');
4
13
  const { createServer } = require('http');
5
14
  const { WebSocketServer } = require('ws');
@@ -50,6 +59,7 @@ let appProcess = null;
50
59
  let appPort = 3000;
51
60
  let wsClients = new Set();
52
61
  const logs = { app: [], test: [], git: [] };
62
+ const commandHistory = [];
53
63
 
54
64
  // Create Express app
55
65
  const app = express();
@@ -944,6 +954,177 @@ app.post('/api/test', (req, res) => {
944
954
  res.json({ success: true });
945
955
  });
946
956
 
957
+ // ============================================
958
+ // Command Execution API
959
+ // ============================================
960
+
961
+ // GET /api/commands/history - Return command execution history
962
+ app.get('/api/commands/history', (req, res) => {
963
+ res.json({ success: true, history: commandHistory });
964
+ });
965
+
966
+ // POST /api/commands/:command - Execute a TLC command
967
+ app.post('/api/commands/:command', (req, res) => {
968
+ const command = req.params.command;
969
+ const { args } = req.body || {};
970
+
971
+ // Whitelist of allowed TLC commands
972
+ const allowedCommands = ['plan', 'build', 'verify', 'bug', 'claim', 'release', 'who', 'progress'];
973
+ if (!allowedCommands.includes(command)) {
974
+ return res.status(400).json({ success: false, error: `Unknown command: ${command}. Allowed: ${allowedCommands.join(', ')}` });
975
+ }
976
+
977
+ const entry = {
978
+ id: `cmd-${Date.now()}`,
979
+ command,
980
+ args: args || null,
981
+ status: 'running',
982
+ startedAt: new Date().toISOString(),
983
+ output: '',
984
+ };
985
+ commandHistory.push(entry);
986
+ if (commandHistory.length > 100) commandHistory.shift();
987
+
988
+ addLog('app', `Executing command: tlc:${command}${args ? ' ' + args : ''}`, 'info');
989
+ broadcast('command-started', { id: entry.id, command });
990
+
991
+ // Build the CLI command
992
+ const cliArgs = ['tlc', command];
993
+ if (args) cliArgs.push(args);
994
+ const fullCmd = `npx ${cliArgs.join(' ')}`;
995
+
996
+ const cmdProcess = spawn('npx', cliArgs.slice(1), {
997
+ cwd: PROJECT_DIR,
998
+ env: { ...process.env },
999
+ shell: true,
1000
+ });
1001
+
1002
+ let output = '';
1003
+
1004
+ cmdProcess.stdout.on('data', (data) => {
1005
+ const text = data.toString();
1006
+ output += text;
1007
+ broadcast('command-output', { id: entry.id, data: text, stream: 'stdout' });
1008
+ });
1009
+
1010
+ cmdProcess.stderr.on('data', (data) => {
1011
+ const text = data.toString();
1012
+ output += text;
1013
+ broadcast('command-output', { id: entry.id, data: text, stream: 'stderr' });
1014
+ });
1015
+
1016
+ cmdProcess.on('exit', (code) => {
1017
+ entry.status = code === 0 ? 'completed' : 'failed';
1018
+ entry.exitCode = code;
1019
+ entry.output = output;
1020
+ entry.completedAt = new Date().toISOString();
1021
+ broadcast('command-completed', { id: entry.id, exitCode: code });
1022
+ addLog('app', `Command tlc:${command} ${code === 0 ? 'completed' : 'failed'} (exit ${code})`, code === 0 ? 'info' : 'error');
1023
+ });
1024
+
1025
+ cmdProcess.on('error', (err) => {
1026
+ entry.status = 'failed';
1027
+ entry.output = err.message;
1028
+ entry.completedAt = new Date().toISOString();
1029
+ broadcast('command-completed', { id: entry.id, error: err.message });
1030
+ addLog('app', `Command tlc:${command} error: ${err.message}`, 'error');
1031
+ });
1032
+
1033
+ res.json({ success: true, id: entry.id, command, message: `Command tlc:${command} started` });
1034
+ });
1035
+
1036
+ // ============================================
1037
+ // Dashboard Completion API (Phase 62)
1038
+ // ============================================
1039
+
1040
+ // GET /api/config - Read .tlc.json configuration
1041
+ app.get('/api/config', (req, res) => {
1042
+ try {
1043
+ const tlcPath = path.join(PROJECT_DIR, '.tlc.json');
1044
+ if (!fs.existsSync(tlcPath)) {
1045
+ return res.json({});
1046
+ }
1047
+ const config = JSON.parse(fs.readFileSync(tlcPath, 'utf-8'));
1048
+ res.json(config);
1049
+ } catch (err) {
1050
+ res.status(500).json({ error: err.message });
1051
+ }
1052
+ });
1053
+
1054
+ // PUT /api/config - Write .tlc.json configuration
1055
+ app.put('/api/config', (req, res) => {
1056
+ try {
1057
+ const tlcPath = path.join(PROJECT_DIR, '.tlc.json');
1058
+ const config = req.body;
1059
+ if (!config || typeof config !== 'object') {
1060
+ return res.status(400).json({ error: 'Invalid config object' });
1061
+ }
1062
+ fs.writeFileSync(tlcPath, JSON.stringify(config, null, 2));
1063
+ res.json({ success: true, config });
1064
+ } catch (err) {
1065
+ res.status(500).json({ error: err.message });
1066
+ }
1067
+ });
1068
+
1069
+ // GET /api/health - System health status
1070
+ app.get('/api/health', (req, res) => {
1071
+ const memUsage = process.memoryUsage();
1072
+ res.json({
1073
+ status: 'ok',
1074
+ timestamp: new Date().toISOString(),
1075
+ uptime: process.uptime(),
1076
+ memory: {
1077
+ rss: memUsage.rss,
1078
+ heapUsed: memUsage.heapUsed,
1079
+ heapTotal: memUsage.heapTotal,
1080
+ },
1081
+ appRunning: appProcess !== null,
1082
+ appPort,
1083
+ });
1084
+ });
1085
+
1086
+ // GET /api/tasks/:id - Get single task by ID
1087
+ app.get('/api/tasks/:id', (req, res) => {
1088
+ const plan = parsePlan(PROJECT_DIR);
1089
+ const task = (plan.tasks || []).find(t => t.id === req.params.id);
1090
+ if (!task) {
1091
+ return res.status(404).json({ error: 'Task not found' });
1092
+ }
1093
+ res.json(task);
1094
+ });
1095
+
1096
+ // PATCH /api/tasks/:id - Update task status/owner
1097
+ app.patch('/api/tasks/:id', (req, res) => {
1098
+ const plan = parsePlan(PROJECT_DIR);
1099
+ const task = (plan.tasks || []).find(t => t.id === req.params.id);
1100
+ if (!task) {
1101
+ return res.status(404).json({ error: 'Task not found' });
1102
+ }
1103
+ const updates = req.body;
1104
+ const updated = { ...task, ...updates };
1105
+ broadcast('task-update', updated);
1106
+ addLog('app', `Task ${req.params.id} updated`, 'info');
1107
+ res.json(updated);
1108
+ });
1109
+
1110
+ // DELETE /api/tasks/:id - Delete task
1111
+ app.delete('/api/tasks/:id', (req, res) => {
1112
+ broadcast('task-update', { id: req.params.id, deleted: true });
1113
+ addLog('app', `Task ${req.params.id} deleted`, 'info');
1114
+ res.status(204).send();
1115
+ });
1116
+
1117
+ // DELETE /api/logs/:type - Clear logs by type
1118
+ app.delete('/api/logs/:type', (req, res) => {
1119
+ const type = req.params.type;
1120
+ if (logs[type]) {
1121
+ logs[type] = [];
1122
+ res.json({ success: true });
1123
+ } else {
1124
+ res.status(404).json({ error: 'Unknown log type' });
1125
+ }
1126
+ });
1127
+
947
1128
  // ============================================
948
1129
  // Agent Registry API (Phase 32)
949
1130
  // ============================================
@@ -1014,6 +1195,37 @@ app.get('/api/agents/:id', (req, res) => {
1014
1195
  }
1015
1196
  });
1016
1197
 
1198
+ // Stop a running agent
1199
+ app.post('/api/agents/:id/stop', (req, res) => {
1200
+ try {
1201
+ const registry = getAgentRegistry();
1202
+ const agent = registry.getAgent(req.params.id);
1203
+ if (!agent) {
1204
+ return res.status(404).json({ success: false, error: 'Agent not found' });
1205
+ }
1206
+
1207
+ // Transition state to stopped
1208
+ if (agent.stateMachine) {
1209
+ const currentState = agent.stateMachine.getState();
1210
+ if (currentState === 'stopped' || currentState === 'completed') {
1211
+ return res.status(400).json({ success: false, error: `Agent is already ${currentState}` });
1212
+ }
1213
+ const result = agent.stateMachine.transition('stopped', { reason: req.body.reason || 'Stopped via API' });
1214
+ if (!result.success) {
1215
+ // If formal transition fails, force the state
1216
+ agent.stateMachine.forceState('stopped', { reason: req.body.reason || 'Force stopped via API' });
1217
+ }
1218
+ }
1219
+
1220
+ broadcast('agent-updated', formatAgent(agent));
1221
+ addLog('app', `Agent ${agent.name || req.params.id} stopped`, 'info');
1222
+
1223
+ res.json({ success: true, agent: formatAgent(agent) });
1224
+ } catch (err) {
1225
+ res.status(500).json({ success: false, error: err.message });
1226
+ }
1227
+ });
1228
+
1017
1229
  // Register new agent
1018
1230
  app.post('/api/agents', (req, res) => {
1019
1231
  try {
@@ -5,11 +5,11 @@
5
5
 
6
6
  const { BaseAdapter } = require('./base-adapter.js');
7
7
 
8
- // Latest model: claude-opus-4-5-20251101 (Claude Opus 4.5)
9
- const CLAUDE_MODEL = 'claude-opus-4-5-20251101';
8
+ // Latest model: claude-opus-4-6-20260205 (Claude Opus 4.6)
9
+ const CLAUDE_MODEL = 'claude-opus-4-6-20260205';
10
10
 
11
11
  const CLAUDE_PRICING = {
12
- // Pricing per 1M tokens (Claude Opus 4.5)
12
+ // Pricing per 1M tokens (Claude Opus 4.6)
13
13
  inputPerMillion: 15.00,
14
14
  outputPerMillion: 75.00,
15
15
  };
@@ -166,7 +166,7 @@ describe('ClaudeAdapter', () => {
166
166
  });
167
167
 
168
168
  describe('CLAUDE_PRICING', () => {
169
- it('exports default pricing for Opus 4.5', () => {
169
+ it('exports default pricing for Opus 4.6', () => {
170
170
  expect(CLAUDE_PRICING.inputPerMillion).toBe(15.00);
171
171
  expect(CLAUDE_PRICING.outputPerMillion).toBe(75.00);
172
172
  });
@@ -174,7 +174,7 @@ describe('ClaudeAdapter', () => {
174
174
 
175
175
  describe('CLAUDE_MODEL', () => {
176
176
  it('exports latest model identifier', () => {
177
- expect(CLAUDE_MODEL).toBe('claude-opus-4-5-20251101');
177
+ expect(CLAUDE_MODEL).toBe('claude-opus-4-6-20260205');
178
178
  });
179
179
  });
180
180
  });
@@ -4,11 +4,11 @@
4
4
 
5
5
  const { BaseAdapter } = require('./base-adapter.js');
6
6
 
7
- // Latest model: o3 (January 2025)
8
- const OPENAI_MODEL = 'o3';
7
+ // Latest model: gpt-5.3-codex (February 2026)
8
+ const OPENAI_MODEL = 'gpt-5.3-codex';
9
9
 
10
10
  const OPENAI_PRICING = {
11
- // Pricing per 1M tokens (o3)
11
+ // Pricing per 1M tokens (gpt-5.3-codex)
12
12
  inputPerMillion: 10.00,
13
13
  outputPerMillion: 40.00,
14
14
  };
@@ -214,13 +214,13 @@ describe('OpenAIAdapter', () => {
214
214
  });
215
215
 
216
216
  describe('exports', () => {
217
- it('exports OPENAI_PRICING for o3', () => {
217
+ it('exports OPENAI_PRICING for gpt-5.3-codex', () => {
218
218
  expect(OPENAI_PRICING.inputPerMillion).toBe(10.00);
219
219
  expect(OPENAI_PRICING.outputPerMillion).toBe(40.00);
220
220
  });
221
221
 
222
222
  it('exports OPENAI_MODEL', () => {
223
- expect(OPENAI_MODEL).toBe('o3');
223
+ expect(OPENAI_MODEL).toBe('gpt-5.3-codex');
224
224
  });
225
225
 
226
226
  it('exports DEFAULT_RATE_LIMITS', () => {
@@ -19,6 +19,7 @@ const DEFAULT_PRICING = {
19
19
  'claude-4-opus': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.075 },
20
20
  'claude-4-sonnet': { inputPer1kTokens: 0.003, outputPer1kTokens: 0.015 },
21
21
  'claude-opus-4-5-20251101': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.075 },
22
+ 'claude-opus-4-6-20260205': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.075 },
22
23
 
23
24
  // OpenAI models
24
25
  'gpt-4': { inputPer1kTokens: 0.03, outputPer1kTokens: 0.06 },
@@ -33,6 +34,7 @@ const DEFAULT_PRICING = {
33
34
  'o1-pro': { inputPer1kTokens: 0.15, outputPer1kTokens: 0.6 },
34
35
  'o3': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.06 },
35
36
  'o3-mini': { inputPer1kTokens: 0.0011, outputPer1kTokens: 0.0044 },
37
+ 'gpt-5.3-codex': { inputPer1kTokens: 0.01, outputPer1kTokens: 0.04 },
36
38
 
37
39
  // DeepSeek models
38
40
  'deepseek-r1': { inputPer1kTokens: 0.00055, outputPer1kTokens: 0.00219 },
@@ -5,14 +5,99 @@
5
5
  * This is NOT a command - it's integrated into /tlc:build
6
6
  * When a plan has independent tasks, they run in parallel automatically.
7
7
  *
8
- * Default behavior: Auto-parallelize up to 10 agents based on task dependencies
8
+ * Default behavior: Auto-parallelize with one agent per independent task
9
9
  * Use --sequential to force one-at-a-time execution
10
10
  * Use --agents N to limit parallelism to specific number
11
+ *
12
+ * Opus 4.6 Multi-Agent Features:
13
+ * - Model selection per agent (opus/sonnet/haiku) based on task complexity
14
+ * - Agent resumption via `resume` parameter for retry/continuation
15
+ * - TaskOutput for non-blocking progress checks on background agents
16
+ * - TaskStop for cancelling stuck agents
17
+ * - Specialized agent types: general-purpose, Bash, Explore, Plan
18
+ * - max_turns to limit agent execution length
11
19
  */
12
20
 
13
21
  const fs = require('fs');
14
22
  const path = require('path');
15
23
 
24
+ /**
25
+ * Valid subagent types for the Task tool (Opus 4.6)
26
+ */
27
+ const AGENT_TYPES = {
28
+ BUILD: 'general-purpose',
29
+ SHELL: 'Bash',
30
+ EXPLORE: 'Explore',
31
+ PLAN: 'Plan',
32
+ };
33
+
34
+ /**
35
+ * Model tiers for cost/capability optimization (Opus 4.6)
36
+ * Agents are assigned models based on task complexity.
37
+ */
38
+ const MODEL_TIERS = {
39
+ HEAVY: 'opus', // Complex multi-file features, architectural work
40
+ STANDARD: 'sonnet', // Normal implementation tasks (default)
41
+ LIGHT: 'haiku', // Simple tasks: config, boilerplate, single-file changes
42
+ };
43
+
44
+ /**
45
+ * Default max turns per agent to prevent runaway execution
46
+ */
47
+ const DEFAULT_MAX_TURNS = 50;
48
+
49
+ /**
50
+ * Estimate task complexity from its description/title
51
+ * @param {Object} task - Task with id and title
52
+ * @returns {'heavy'|'standard'|'light'} Complexity tier
53
+ */
54
+ function estimateTaskComplexity(task) {
55
+ const title = (task.title || '').toLowerCase();
56
+
57
+ const heavyPatterns = [
58
+ /architect/i, /refactor/i, /migration/i, /redesign/i,
59
+ /integration/i, /multi.?file/i, /cross.?cutting/i,
60
+ /security/i, /auth/i, /database/i, /schema/i,
61
+ ];
62
+
63
+ const lightPatterns = [
64
+ /config/i, /boilerplate/i, /rename/i, /update.*readme/i,
65
+ /add.*comment/i, /fix.*typo/i, /seed/i, /constant/i,
66
+ /enum/i, /dto/i, /interface/i,
67
+ ];
68
+
69
+ if (heavyPatterns.some(p => p.test(title))) {
70
+ return 'heavy';
71
+ }
72
+ if (lightPatterns.some(p => p.test(title))) {
73
+ return 'light';
74
+ }
75
+ return 'standard';
76
+ }
77
+
78
+ /**
79
+ * Get model for a task based on its complexity
80
+ * @param {Object} task - Task object
81
+ * @param {string} [modelOverride] - Force a specific model
82
+ * @returns {string} Model name (opus, sonnet, haiku)
83
+ */
84
+ function getModelForTask(task, modelOverride) {
85
+ if (modelOverride) {
86
+ return modelOverride;
87
+ }
88
+
89
+ const complexity = estimateTaskComplexity(task);
90
+
91
+ switch (complexity) {
92
+ case 'heavy':
93
+ return MODEL_TIERS.HEAVY;
94
+ case 'light':
95
+ return MODEL_TIERS.LIGHT;
96
+ default:
97
+ return MODEL_TIERS.STANDARD;
98
+ }
99
+ }
100
+
16
101
  /**
17
102
  * Parse overdrive command arguments
18
103
  * @param {string} args - Command arguments
@@ -21,10 +106,12 @@ const path = require('path');
21
106
  function parseOverdriveArgs(args = '') {
22
107
  const options = {
23
108
  phase: null,
24
- agents: 'auto', // Auto-detect based on independent tasks (up to 10)
109
+ agents: 'auto', // One agent per independent task
25
110
  mode: 'build', // build, test, fix
26
111
  dryRun: false,
27
112
  sequential: false,
113
+ model: null, // null = auto-select per task complexity
114
+ maxTurns: DEFAULT_MAX_TURNS,
28
115
  };
29
116
 
30
117
  const parts = args.trim().split(/\s+/).filter(Boolean);
@@ -35,9 +122,16 @@ function parseOverdriveArgs(args = '') {
35
122
  if (/^\d+$/.test(part)) {
36
123
  options.phase = parseInt(part, 10);
37
124
  } else if (part === '--agents' && parts[i + 1]) {
38
- options.agents = Math.min(parseInt(parts[++i], 10), 10); // Max 10 agents
125
+ options.agents = parseInt(parts[++i], 10);
39
126
  } else if (part === '--mode' && parts[i + 1]) {
40
127
  options.mode = parts[++i];
128
+ } else if (part === '--model' && parts[i + 1]) {
129
+ const model = parts[++i].toLowerCase();
130
+ if (['opus', 'sonnet', 'haiku'].includes(model)) {
131
+ options.model = model;
132
+ }
133
+ } else if (part === '--max-turns' && parts[i + 1]) {
134
+ options.maxTurns = parseInt(parts[++i], 10);
41
135
  } else if (part === '--dry-run') {
42
136
  options.dryRun = true;
43
137
  } else if (part === '--sequential' || part === '-s') {
@@ -104,6 +198,18 @@ function loadPhaseTasks(projectDir, phase) {
104
198
  };
105
199
  }
106
200
 
201
+ /**
202
+ * Determine the best agent type for a task
203
+ * @param {Object} task - Task object
204
+ * @param {string} mode - Execution mode (build, test, fix)
205
+ * @returns {string} Agent type for Task tool subagent_type
206
+ */
207
+ function selectAgentType(task, mode) {
208
+ // Build/test/fix all require full general-purpose agent
209
+ // (reads files, writes code, runs tests, commits)
210
+ return AGENT_TYPES.BUILD;
211
+ }
212
+
107
213
  /**
108
214
  * Generate agent prompts for parallel execution
109
215
  * @param {Array} tasks - Tasks to distribute
@@ -111,7 +217,7 @@ function loadPhaseTasks(projectDir, phase) {
111
217
  * @returns {Array} Agent prompts
112
218
  */
113
219
  function generateAgentPrompts(tasks, options = {}) {
114
- const { mode = 'build', projectDir, phase } = options;
220
+ const { mode = 'build', projectDir, phase, model, maxTurns } = options;
115
221
 
116
222
  return tasks.map((task, index) => {
117
223
  const basePrompt = `You are Agent ${index + 1} working on Phase ${phase}.
@@ -142,7 +248,10 @@ GO. Execute now. No questions.`;
142
248
  taskId: task.id,
143
249
  taskTitle: task.title,
144
250
  prompt: basePrompt,
145
- agentType: 'tlc-executor',
251
+ agentType: selectAgentType(task, mode),
252
+ model: getModelForTask(task, model),
253
+ maxTurns: maxTurns || DEFAULT_MAX_TURNS,
254
+ complexity: estimateTaskComplexity(task),
146
255
  };
147
256
  });
148
257
  }
@@ -171,7 +280,7 @@ function distributeTasks(tasks, agentCount) {
171
280
  function formatOverdrivePlan(plan) {
172
281
  const lines = [];
173
282
 
174
- lines.push('# Overdrive Mode');
283
+ lines.push('# Overdrive Mode (Opus 4.6)');
175
284
  lines.push('');
176
285
  lines.push(`**Phase:** ${plan.phase}`);
177
286
  lines.push(`**Mode:** ${plan.mode}`);
@@ -184,16 +293,24 @@ function formatOverdrivePlan(plan) {
184
293
  plan.agentAssignments.forEach((assignment, idx) => {
185
294
  lines.push(`### Agent ${idx + 1}`);
186
295
  assignment.tasks.forEach(task => {
187
- lines.push(`- Task ${task.id}: ${task.title}`);
296
+ const complexity = estimateTaskComplexity(task);
297
+ const model = getModelForTask(task, plan.modelOverride);
298
+ lines.push(`- Task ${task.id}: ${task.title} [${model}] (${complexity})`);
188
299
  });
189
300
  lines.push('');
190
301
  });
191
302
 
192
303
  lines.push('## Execution');
193
304
  lines.push('');
194
- lines.push('All agents will be spawned simultaneously using Task tool.');
305
+ lines.push('All agents spawned simultaneously via Task tool (Opus 4.6 multi-agent).');
195
306
  lines.push('Each agent works independently until completion.');
196
307
  lines.push('');
308
+ lines.push('**Capabilities:**');
309
+ lines.push('- Model selection per task complexity (opus/sonnet/haiku)');
310
+ lines.push('- Agent resumption for failed tasks (resume parameter)');
311
+ lines.push('- Non-blocking progress checks (TaskOutput block=false)');
312
+ lines.push('- Agent cancellation (TaskStop) for stuck agents');
313
+ lines.push('');
197
314
  lines.push('**Rules enforced:**');
198
315
  lines.push('- No confirmation prompts');
199
316
  lines.push('- No "shall I continue" questions');
@@ -204,7 +321,8 @@ function formatOverdrivePlan(plan) {
204
321
  }
205
322
 
206
323
  /**
207
- * Generate Task tool calls for parallel execution
324
+ * Generate Task tool calls for parallel execution (Opus 4.6)
325
+ * Includes model selection, max_turns, and correct subagent_type.
208
326
  * @param {Array} prompts - Agent prompts
209
327
  * @returns {Array} Task tool call specifications
210
328
  */
@@ -216,6 +334,8 @@ function generateTaskCalls(prompts) {
216
334
  prompt: p.prompt,
217
335
  subagent_type: p.agentType,
218
336
  run_in_background: true,
337
+ model: p.model,
338
+ max_turns: p.maxTurns,
219
339
  },
220
340
  }));
221
341
  }
@@ -270,12 +390,12 @@ async function executeOverdriveCommand(args = '', context = {}) {
270
390
  };
271
391
  }
272
392
 
273
- // Determine agent count: auto = max parallelism based on independent tasks
393
+ // Determine agent count: auto = one agent per independent task (no cap)
274
394
  let agentCount;
275
395
  if (options.sequential) {
276
396
  agentCount = 1;
277
397
  } else if (options.agents === 'auto') {
278
- // Auto-detect: use as many agents as there are independent tasks (up to 10)
398
+ // Auto-detect: one agent per independent task, no arbitrary cap
279
399
  let planContent = '';
280
400
  try {
281
401
  planContent = fs.readFileSync(phaseInfo.planPath, 'utf-8');
@@ -285,7 +405,7 @@ async function executeOverdriveCommand(args = '', context = {}) {
285
405
  const depAnalysis = analyzeDependencies(planContent);
286
406
  const parallelAnalysis = canParallelize(availableTasks, depAnalysis);
287
407
  agentCount = parallelAnalysis.canParallelize
288
- ? Math.min(parallelAnalysis.independentTasks.length, 10)
408
+ ? parallelAnalysis.independentTasks.length
289
409
  : 1;
290
410
  } else {
291
411
  agentCount = Math.min(options.agents, availableTasks.length);
@@ -293,7 +413,7 @@ async function executeOverdriveCommand(args = '', context = {}) {
293
413
 
294
414
  const taskGroups = distributeTasks(availableTasks, agentCount);
295
415
 
296
- // Generate prompts
416
+ // Generate prompts with model selection and max turns
297
417
  const agentAssignments = taskGroups.map((tasks, idx) => ({
298
418
  agentId: idx + 1,
299
419
  tasks,
@@ -301,6 +421,8 @@ async function executeOverdriveCommand(args = '', context = {}) {
301
421
  mode: options.mode,
302
422
  projectDir,
303
423
  phase: options.phase,
424
+ model: options.model,
425
+ maxTurns: options.maxTurns,
304
426
  }),
305
427
  }));
306
428
 
@@ -310,6 +432,7 @@ async function executeOverdriveCommand(args = '', context = {}) {
310
432
  agentCount,
311
433
  totalTasks: availableTasks.length,
312
434
  agentAssignments,
435
+ modelOverride: options.model,
313
436
  };
314
437
 
315
438
  if (options.dryRun) {
@@ -331,7 +454,7 @@ async function executeOverdriveCommand(args = '', context = {}) {
331
454
  taskCalls,
332
455
  output: formatOverdrivePlan(plan),
333
456
  instructions: `
334
- EXECUTE NOW: Spawn ${agentCount} agents in parallel using the Task tool.
457
+ EXECUTE NOW: Spawn ${agentCount} agents in parallel using the Task tool (Opus 4.6).
335
458
 
336
459
  ${taskCalls.map((tc, i) => `
337
460
  Agent ${i + 1}:
@@ -339,12 +462,18 @@ Task(
339
462
  description="${tc.params.description}",
340
463
  prompt="${tc.params.prompt.slice(0, 100)}...",
341
464
  subagent_type="${tc.params.subagent_type}",
465
+ model="${tc.params.model}",
466
+ max_turns=${tc.params.max_turns},
342
467
  run_in_background=true
343
468
  )
344
469
  `).join('\n')}
345
470
 
346
471
  CRITICAL: Call ALL Task tools in a SINGLE message to run them in parallel.
347
472
  Do NOT wait between spawns. Fire them all at once.
473
+
474
+ MONITORING: Use TaskOutput(task_id, block=false) to check progress.
475
+ STUCK AGENT: Use TaskStop(task_id) to cancel, then resume with Task(resume=agent_id).
476
+ FAILED AGENT: Use Task(resume=agent_id) to continue from where it left off.
348
477
  `,
349
478
  };
350
479
  }
@@ -458,7 +587,7 @@ function canParallelize(tasks, depAnalysis) {
458
587
  canParallelize: true,
459
588
  independentTasks,
460
589
  dependentTasks: tasks.filter(t => dependentTasks.has(t.id)),
461
- recommendedAgents: Math.min(independentTasks.length, 10), // Max 10 parallel agents
590
+ recommendedAgents: independentTasks.length, // One agent per independent task
462
591
  };
463
592
  }
464
593
 
@@ -543,4 +672,11 @@ module.exports = {
543
672
  canParallelize,
544
673
  shouldUseOverdrive,
545
674
  autoParallelize,
675
+ // Opus 4.6 multi-agent functions
676
+ estimateTaskComplexity,
677
+ getModelForTask,
678
+ selectAgentType,
679
+ AGENT_TYPES,
680
+ MODEL_TIERS,
681
+ DEFAULT_MAX_TURNS,
546
682
  };
@@ -9,6 +9,12 @@ import {
9
9
  canParallelize,
10
10
  shouldUseOverdrive,
11
11
  createOverdriveCommand,
12
+ estimateTaskComplexity,
13
+ getModelForTask,
14
+ selectAgentType,
15
+ AGENT_TYPES,
16
+ MODEL_TIERS,
17
+ DEFAULT_MAX_TURNS,
12
18
  } from './overdrive-command.js';
13
19
 
14
20
  describe('overdrive-command', () => {
@@ -21,6 +27,8 @@ describe('overdrive-command', () => {
21
27
  expect(options.mode).toBe('build');
22
28
  expect(options.dryRun).toBe(false);
23
29
  expect(options.sequential).toBe(false);
30
+ expect(options.model).toBeNull();
31
+ expect(options.maxTurns).toBe(DEFAULT_MAX_TURNS);
24
32
  });
25
33
 
26
34
  it('parses phase number', () => {
@@ -28,14 +36,14 @@ describe('overdrive-command', () => {
28
36
  expect(options.phase).toBe(5);
29
37
  });
30
38
 
31
- it('parses --agents flag', () => {
39
+ it('parses --agents flag without cap', () => {
32
40
  const options = parseOverdriveArgs('--agents 4');
33
41
  expect(options.agents).toBe(4);
34
42
  });
35
43
 
36
- it('caps agents at 10', () => {
44
+ it('allows agent count beyond 10', () => {
37
45
  const options = parseOverdriveArgs('--agents 15');
38
- expect(options.agents).toBe(10);
46
+ expect(options.agents).toBe(15);
39
47
  });
40
48
 
41
49
  it('parses --mode flag', () => {
@@ -65,18 +73,35 @@ describe('overdrive-command', () => {
65
73
  expect(options.agents).toBe(1);
66
74
  });
67
75
 
68
- it('parses multiple flags', () => {
69
- const options = parseOverdriveArgs('5 --agents 2 --mode test --dry-run');
76
+ it('parses --model flag', () => {
77
+ expect(parseOverdriveArgs('--model opus').model).toBe('opus');
78
+ expect(parseOverdriveArgs('--model sonnet').model).toBe('sonnet');
79
+ expect(parseOverdriveArgs('--model haiku').model).toBe('haiku');
80
+ });
81
+
82
+ it('ignores invalid model values', () => {
83
+ expect(parseOverdriveArgs('--model gpt4').model).toBeNull();
84
+ });
85
+
86
+ it('parses --max-turns flag', () => {
87
+ const options = parseOverdriveArgs('--max-turns 30');
88
+ expect(options.maxTurns).toBe(30);
89
+ });
90
+
91
+ it('parses multiple flags including new ones', () => {
92
+ const options = parseOverdriveArgs('5 --agents 2 --mode test --model sonnet --max-turns 25 --dry-run');
70
93
 
71
94
  expect(options.phase).toBe(5);
72
95
  expect(options.agents).toBe(2);
73
96
  expect(options.mode).toBe('test');
97
+ expect(options.model).toBe('sonnet');
98
+ expect(options.maxTurns).toBe(25);
74
99
  expect(options.dryRun).toBe(true);
75
100
  });
76
101
  });
77
102
 
78
103
  describe('generateAgentPrompts', () => {
79
- it('generates prompts for tasks', () => {
104
+ it('generates prompts for tasks with model and maxTurns', () => {
80
105
  const tasks = [
81
106
  { id: 1, title: 'Create schema' },
82
107
  { id: 2, title: 'Add validation' },
@@ -93,6 +118,9 @@ describe('overdrive-command', () => {
93
118
  expect(prompts[0].taskTitle).toBe('Create schema');
94
119
  expect(prompts[0].prompt).toContain('Task 1');
95
120
  expect(prompts[0].prompt).toContain('Write tests first');
121
+ expect(prompts[0].agentType).toBe('general-purpose');
122
+ expect(prompts[0].model).toBeDefined();
123
+ expect(prompts[0].maxTurns).toBe(DEFAULT_MAX_TURNS);
96
124
  });
97
125
 
98
126
  it('includes no-question rules', () => {
@@ -123,6 +151,55 @@ describe('overdrive-command', () => {
123
151
 
124
152
  expect(prompts[0].prompt).toContain('Fix any failing tests');
125
153
  });
154
+
155
+ it('assigns model based on task complexity', () => {
156
+ const tasks = [
157
+ { id: 1, title: 'Refactor authentication system' },
158
+ { id: 2, title: 'Add helper function' },
159
+ { id: 3, title: 'Create enum constants' },
160
+ ];
161
+
162
+ const prompts = generateAgentPrompts(tasks, {
163
+ mode: 'build', projectDir: '/p', phase: 1,
164
+ });
165
+
166
+ expect(prompts[0].model).toBe('opus'); // refactor = heavy
167
+ expect(prompts[1].model).toBe('sonnet'); // default = standard
168
+ expect(prompts[2].model).toBe('haiku'); // enum = light
169
+ });
170
+
171
+ it('respects model override', () => {
172
+ const tasks = [
173
+ { id: 1, title: 'Refactor auth' },
174
+ { id: 2, title: 'Create enum' },
175
+ ];
176
+
177
+ const prompts = generateAgentPrompts(tasks, {
178
+ mode: 'build', projectDir: '/p', phase: 1,
179
+ model: 'haiku',
180
+ });
181
+
182
+ expect(prompts[0].model).toBe('haiku');
183
+ expect(prompts[1].model).toBe('haiku');
184
+ });
185
+
186
+ it('respects maxTurns override', () => {
187
+ const prompts = generateAgentPrompts(
188
+ [{ id: 1, title: 'Test' }],
189
+ { mode: 'build', projectDir: '/p', phase: 1, maxTurns: 25 }
190
+ );
191
+
192
+ expect(prompts[0].maxTurns).toBe(25);
193
+ });
194
+
195
+ it('includes complexity estimate', () => {
196
+ const prompts = generateAgentPrompts(
197
+ [{ id: 1, title: 'Database migration' }],
198
+ { mode: 'build', projectDir: '/p', phase: 1 }
199
+ );
200
+
201
+ expect(prompts[0].complexity).toBe('heavy');
202
+ });
126
203
  });
127
204
 
128
205
  describe('distributeTasks', () => {
@@ -207,10 +284,10 @@ describe('overdrive-command', () => {
207
284
  });
208
285
 
209
286
  describe('generateTaskCalls', () => {
210
- it('generates task tool calls', () => {
287
+ it('generates task tool calls with model and max_turns', () => {
211
288
  const prompts = [
212
- { taskId: 1, taskTitle: 'Test', prompt: 'Do task 1', agentType: 'tlc-executor' },
213
- { taskId: 2, taskTitle: 'Test 2', prompt: 'Do task 2', agentType: 'tlc-executor' },
289
+ { taskId: 1, taskTitle: 'Test', prompt: 'Do task 1', agentType: 'general-purpose', model: 'sonnet', maxTurns: 50 },
290
+ { taskId: 2, taskTitle: 'Test 2', prompt: 'Do task 2', agentType: 'general-purpose', model: 'haiku', maxTurns: 30 },
214
291
  ];
215
292
 
216
293
  const calls = generateTaskCalls(prompts);
@@ -219,6 +296,11 @@ describe('overdrive-command', () => {
219
296
  expect(calls[0].tool).toBe('Task');
220
297
  expect(calls[0].params.description).toContain('Agent 1');
221
298
  expect(calls[0].params.run_in_background).toBe(true);
299
+ expect(calls[0].params.subagent_type).toBe('general-purpose');
300
+ expect(calls[0].params.model).toBe('sonnet');
301
+ expect(calls[0].params.max_turns).toBe(50);
302
+ expect(calls[1].params.model).toBe('haiku');
303
+ expect(calls[1].params.max_turns).toBe(30);
222
304
  });
223
305
  });
224
306
 
@@ -326,7 +408,7 @@ Blocked by Task 1
326
408
  expect(result.reason).toContain('Waterfall');
327
409
  });
328
410
 
329
- it('returns true for independent tasks', () => {
411
+ it('returns true for independent tasks with one agent per task', () => {
330
412
  const result = canParallelize(
331
413
  [{ id: 1 }, { id: 2 }, { id: 3 }],
332
414
  { hasDependencies: false, dependencies: [] }
@@ -337,6 +419,14 @@ Blocked by Task 1
337
419
  expect(result.recommendedAgents).toBe(3);
338
420
  });
339
421
 
422
+ it('recommends agents beyond 10 when tasks warrant it', () => {
423
+ const tasks = Array.from({ length: 15 }, (_, i) => ({ id: i + 1 }));
424
+ const result = canParallelize(tasks, { hasDependencies: false, dependencies: [] });
425
+
426
+ expect(result.canParallelize).toBe(true);
427
+ expect(result.recommendedAgents).toBe(15);
428
+ });
429
+
340
430
  it('identifies mixed independent/dependent tasks', () => {
341
431
  const result = canParallelize(
342
432
  [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }],
@@ -372,4 +462,111 @@ Blocked by Task 1
372
462
  expect(handler.distributeTasks).toBeDefined();
373
463
  });
374
464
  });
465
+
466
+ describe('estimateTaskComplexity', () => {
467
+ it('classifies heavy tasks', () => {
468
+ expect(estimateTaskComplexity({ title: 'Refactor auth module' })).toBe('heavy');
469
+ expect(estimateTaskComplexity({ title: 'Database migration scripts' })).toBe('heavy');
470
+ expect(estimateTaskComplexity({ title: 'Redesign user schema' })).toBe('heavy');
471
+ expect(estimateTaskComplexity({ title: 'Security audit implementation' })).toBe('heavy');
472
+ expect(estimateTaskComplexity({ title: 'Integration with payment API' })).toBe('heavy');
473
+ });
474
+
475
+ it('classifies light tasks', () => {
476
+ expect(estimateTaskComplexity({ title: 'Add config constants' })).toBe('light');
477
+ expect(estimateTaskComplexity({ title: 'Create user enum' })).toBe('light');
478
+ expect(estimateTaskComplexity({ title: 'Create DTO for response' })).toBe('light');
479
+ expect(estimateTaskComplexity({ title: 'Add seed data' })).toBe('light');
480
+ expect(estimateTaskComplexity({ title: 'Create interface for service' })).toBe('light');
481
+ });
482
+
483
+ it('classifies standard tasks by default', () => {
484
+ expect(estimateTaskComplexity({ title: 'Add user listing endpoint' })).toBe('standard');
485
+ expect(estimateTaskComplexity({ title: 'Create helper function' })).toBe('standard');
486
+ expect(estimateTaskComplexity({ title: 'Add pagination support' })).toBe('standard');
487
+ });
488
+
489
+ it('handles empty or missing title', () => {
490
+ expect(estimateTaskComplexity({ title: '' })).toBe('standard');
491
+ expect(estimateTaskComplexity({})).toBe('standard');
492
+ });
493
+ });
494
+
495
+ describe('getModelForTask', () => {
496
+ it('returns opus for heavy tasks', () => {
497
+ expect(getModelForTask({ title: 'Refactor auth' })).toBe('opus');
498
+ });
499
+
500
+ it('returns sonnet for standard tasks', () => {
501
+ expect(getModelForTask({ title: 'Add endpoint' })).toBe('sonnet');
502
+ });
503
+
504
+ it('returns haiku for light tasks', () => {
505
+ expect(getModelForTask({ title: 'Create enum' })).toBe('haiku');
506
+ });
507
+
508
+ it('respects model override', () => {
509
+ expect(getModelForTask({ title: 'Refactor auth' }, 'haiku')).toBe('haiku');
510
+ expect(getModelForTask({ title: 'Create enum' }, 'opus')).toBe('opus');
511
+ });
512
+ });
513
+
514
+ describe('selectAgentType', () => {
515
+ it('returns general-purpose for build mode', () => {
516
+ expect(selectAgentType({ title: 'Test' }, 'build')).toBe('general-purpose');
517
+ });
518
+
519
+ it('returns general-purpose for test mode', () => {
520
+ expect(selectAgentType({ title: 'Test' }, 'test')).toBe('general-purpose');
521
+ });
522
+
523
+ it('returns general-purpose for fix mode', () => {
524
+ expect(selectAgentType({ title: 'Test' }, 'fix')).toBe('general-purpose');
525
+ });
526
+ });
527
+
528
+ describe('constants', () => {
529
+ it('exports valid agent types', () => {
530
+ expect(AGENT_TYPES.BUILD).toBe('general-purpose');
531
+ expect(AGENT_TYPES.SHELL).toBe('Bash');
532
+ expect(AGENT_TYPES.EXPLORE).toBe('Explore');
533
+ expect(AGENT_TYPES.PLAN).toBe('Plan');
534
+ });
535
+
536
+ it('exports valid model tiers', () => {
537
+ expect(MODEL_TIERS.HEAVY).toBe('opus');
538
+ expect(MODEL_TIERS.STANDARD).toBe('sonnet');
539
+ expect(MODEL_TIERS.LIGHT).toBe('haiku');
540
+ });
541
+
542
+ it('exports default max turns', () => {
543
+ expect(DEFAULT_MAX_TURNS).toBe(50);
544
+ });
545
+ });
546
+
547
+ describe('formatOverdrivePlan', () => {
548
+ it('includes Opus 4.6 branding and model info', () => {
549
+ const plan = {
550
+ phase: 3,
551
+ mode: 'build',
552
+ agentCount: 2,
553
+ totalTasks: 2,
554
+ modelOverride: null,
555
+ agentAssignments: [
556
+ { tasks: [{ id: 1, title: 'Refactor auth' }] },
557
+ { tasks: [{ id: 2, title: 'Create enum' }] },
558
+ ],
559
+ };
560
+
561
+ const output = formatOverdrivePlan(plan);
562
+
563
+ expect(output).toContain('Opus 4.6');
564
+ expect(output).toContain('[opus]');
565
+ expect(output).toContain('[haiku]');
566
+ expect(output).toContain('Model selection per task complexity');
567
+ expect(output).toContain('Agent resumption');
568
+ expect(output).toContain('TaskOutput');
569
+ expect(output).toContain('TaskStop');
570
+ });
571
+ });
375
572
  });
package/sync.md CHANGED
@@ -229,7 +229,7 @@ Create `.tlc.json` with all settings:
229
229
  "autofix": true,
230
230
  "edgeCases": true,
231
231
  "prAutoReview": true,
232
- "maxAgents": 10
232
+ "maxAgents": "auto"
233
233
  }
234
234
  }
235
235
  ```