@covibes/zeroshot 5.2.1 → 5.4.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.
Files changed (98) hide show
  1. package/CHANGELOG.md +174 -189
  2. package/README.md +226 -195
  3. package/cli/commands/providers.js +149 -0
  4. package/cli/index.js +3145 -2366
  5. package/cli/lib/first-run.js +40 -3
  6. package/cli/message-formatters-normal.js +28 -6
  7. package/cluster-templates/base-templates/debug-workflow.json +24 -78
  8. package/cluster-templates/base-templates/full-workflow.json +99 -316
  9. package/cluster-templates/base-templates/single-worker.json +23 -15
  10. package/cluster-templates/base-templates/worker-validator.json +105 -36
  11. package/cluster-templates/conductor-bootstrap.json +9 -7
  12. package/lib/docker-config.js +14 -1
  13. package/lib/git-remote-utils.js +165 -0
  14. package/lib/id-detector.js +10 -7
  15. package/lib/provider-defaults.js +62 -0
  16. package/lib/provider-detection.js +59 -0
  17. package/lib/provider-names.js +57 -0
  18. package/lib/settings/claude-auth.js +78 -0
  19. package/lib/settings.js +298 -15
  20. package/lib/stream-json-parser.js +4 -238
  21. package/package.json +27 -6
  22. package/scripts/setup-merge-queue.sh +170 -0
  23. package/scripts/validate-templates.js +100 -0
  24. package/src/agent/agent-config.js +140 -63
  25. package/src/agent/agent-context-builder.js +336 -165
  26. package/src/agent/agent-hook-executor.js +337 -67
  27. package/src/agent/agent-lifecycle.js +386 -287
  28. package/src/agent/agent-stuck-detector.js +7 -7
  29. package/src/agent/agent-task-executor.js +944 -683
  30. package/src/agent/output-extraction.js +217 -0
  31. package/src/agent/output-reformatter.js +175 -0
  32. package/src/agent/schema-utils.js +146 -0
  33. package/src/agent-wrapper.js +112 -31
  34. package/src/agents/git-pusher-template.js +285 -0
  35. package/src/claude-task-runner.js +145 -44
  36. package/src/config-router.js +13 -13
  37. package/src/config-validator.js +1049 -563
  38. package/src/input-helpers.js +65 -0
  39. package/src/isolation-manager.js +499 -320
  40. package/src/issue-providers/README.md +305 -0
  41. package/src/issue-providers/azure-devops-provider.js +273 -0
  42. package/src/issue-providers/base-provider.js +232 -0
  43. package/src/issue-providers/github-provider.js +179 -0
  44. package/src/issue-providers/gitlab-provider.js +241 -0
  45. package/src/issue-providers/index.js +196 -0
  46. package/src/issue-providers/jira-provider.js +239 -0
  47. package/src/ledger.js +50 -11
  48. package/src/lib/safe-exec.js +88 -0
  49. package/src/orchestrator.js +1348 -757
  50. package/src/preflight.js +306 -149
  51. package/src/process-metrics.js +98 -56
  52. package/src/providers/anthropic/cli-builder.js +73 -0
  53. package/src/providers/anthropic/index.js +204 -0
  54. package/src/providers/anthropic/models.js +23 -0
  55. package/src/providers/anthropic/output-parser.js +177 -0
  56. package/src/providers/base-provider.js +251 -0
  57. package/src/providers/capabilities.js +60 -0
  58. package/src/providers/google/cli-builder.js +55 -0
  59. package/src/providers/google/index.js +116 -0
  60. package/src/providers/google/models.js +24 -0
  61. package/src/providers/google/output-parser.js +101 -0
  62. package/src/providers/index.js +91 -0
  63. package/src/providers/openai/cli-builder.js +133 -0
  64. package/src/providers/openai/index.js +136 -0
  65. package/src/providers/openai/models.js +21 -0
  66. package/src/providers/openai/output-parser.js +143 -0
  67. package/src/providers/opencode/cli-builder.js +42 -0
  68. package/src/providers/opencode/index.js +103 -0
  69. package/src/providers/opencode/models.js +55 -0
  70. package/src/providers/opencode/output-parser.js +122 -0
  71. package/src/schemas/sub-cluster.js +68 -39
  72. package/src/status-footer.js +94 -48
  73. package/src/sub-cluster-wrapper.js +92 -36
  74. package/src/task-runner.js +8 -6
  75. package/src/template-resolver.js +12 -9
  76. package/src/tui/data-poller.js +123 -99
  77. package/src/tui/formatters.js +4 -3
  78. package/src/tui/keybindings.js +259 -318
  79. package/src/tui/layout.js +20 -3
  80. package/src/tui/renderer.js +11 -21
  81. package/task-lib/attachable-watcher.js +150 -111
  82. package/task-lib/claude-recovery.js +156 -0
  83. package/task-lib/commands/episodes.js +105 -0
  84. package/task-lib/commands/list.js +3 -3
  85. package/task-lib/commands/logs.js +231 -189
  86. package/task-lib/commands/resume.js +3 -2
  87. package/task-lib/commands/run.js +12 -3
  88. package/task-lib/commands/schedules.js +111 -61
  89. package/task-lib/config.js +0 -2
  90. package/task-lib/runner.js +128 -50
  91. package/task-lib/scheduler.js +3 -3
  92. package/task-lib/store.js +464 -152
  93. package/task-lib/tui/formatters.js +94 -45
  94. package/task-lib/tui/renderer.js +13 -3
  95. package/task-lib/tui.js +73 -32
  96. package/task-lib/watcher.js +157 -100
  97. package/src/agents/git-pusher-agent.json +0 -20
  98. package/src/github.js +0 -103
@@ -0,0 +1,105 @@
1
+ import chalk from 'chalk';
2
+ import { loadTasks } from '../store.js';
3
+ import { isProcessRunning } from '../runner.js';
4
+
5
+ export function listEpisodes(options = {}) {
6
+ const tasks = loadTasks();
7
+ const taskList = Object.values(tasks);
8
+
9
+ if (taskList.length === 0) {
10
+ console.log(chalk.dim('No episodes found.'));
11
+ return;
12
+ }
13
+
14
+ // Sort by creation date, oldest first (chronological order)
15
+ taskList.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
16
+
17
+ // Filter by status if specified
18
+ let filtered = taskList;
19
+ if (options.status) {
20
+ filtered = taskList.filter((t) => t.status === options.status);
21
+ }
22
+
23
+ // Limit results
24
+ const limit = options.limit || 20;
25
+ filtered = filtered.slice(0, limit);
26
+
27
+ // Table format (default) or verbose format
28
+ if (options.verbose) {
29
+ // Verbose format (old behavior)
30
+ console.log(chalk.bold(`\nEpisodes (${filtered.length}/${taskList.length})\n`));
31
+
32
+ for (const task of filtered) {
33
+ // Verify running status
34
+ let status = task.status;
35
+ if (status === 'running' && !isProcessRunning(task.pid)) {
36
+ status = 'stale';
37
+ }
38
+
39
+ const statusColor =
40
+ {
41
+ running: chalk.green,
42
+ completed: chalk.green,
43
+ failed: chalk.red,
44
+ stale: chalk.yellow,
45
+ }[status] || chalk.dim;
46
+
47
+ const age = getAge(task.createdAt);
48
+ const timestamp = new Date(task.createdAt).toLocaleString();
49
+
50
+ console.log(
51
+ `${statusColor('●')} ${chalk.cyan(task.id)} ${statusColor(`[${status}]`)} ${chalk.dim(age + ' • ' + timestamp)}`
52
+ );
53
+ console.log(` ${chalk.dim('CWD:')} ${task.cwd}`);
54
+ console.log(` ${chalk.dim('Prompt:')} ${task.prompt}`);
55
+ if (task.pid && status === 'running') {
56
+ console.log(` ${chalk.dim('PID:')} ${task.pid}`);
57
+ }
58
+ if (task.error) {
59
+ console.log(` ${chalk.red('Error:')} ${task.error}`);
60
+ }
61
+ console.log();
62
+ }
63
+ } else {
64
+ // Table format (clean, default)
65
+ console.log(chalk.bold(`\n=== Episodes (${filtered.length}/${taskList.length}) ===`));
66
+ console.log(`${'ID'.padEnd(25)} ${'Status'.padEnd(12)} ${'Age'.padEnd(10)} CWD`);
67
+ console.log('-'.repeat(100));
68
+
69
+ for (const task of filtered) {
70
+ // Verify running status
71
+ let status = task.status;
72
+ if (status === 'running' && !isProcessRunning(task.pid)) {
73
+ status = 'stale';
74
+ }
75
+
76
+ const statusColor =
77
+ {
78
+ running: chalk.green,
79
+ completed: chalk.green,
80
+ failed: chalk.red,
81
+ stale: chalk.yellow,
82
+ }[status] || chalk.dim;
83
+
84
+ const age = getAge(task.createdAt);
85
+ const cwd = task.cwd.replace(process.env.HOME, '~');
86
+
87
+ console.log(
88
+ `${chalk.cyan(task.id.padEnd(25))} ${statusColor(status.padEnd(12))} ${chalk.dim(age.padEnd(10))} ${chalk.dim(cwd)}`
89
+ );
90
+ }
91
+ console.log();
92
+ }
93
+ }
94
+
95
+ function getAge(dateStr) {
96
+ const diff = Date.now() - new Date(dateStr).getTime();
97
+ const mins = Math.floor(diff / 60000);
98
+ const hours = Math.floor(mins / 60);
99
+ const days = Math.floor(hours / 24);
100
+
101
+ if (days > 0) return `${days}d ago`;
102
+ if (hours > 0) return `${hours}h ago`;
103
+ if (mins > 0) return `${mins}m ago`;
104
+ return 'just now';
105
+ }
@@ -11,8 +11,8 @@ export function listTasks(options = {}) {
11
11
  return;
12
12
  }
13
13
 
14
- // Sort by creation date, newest first
15
- taskList.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
14
+ // Sort by creation date, oldest first (chronological)
15
+ taskList.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
16
16
 
17
17
  // Filter by status if specified
18
18
  let filtered = taskList;
@@ -27,7 +27,7 @@ export function listTasks(options = {}) {
27
27
  // Table format (default) or verbose format
28
28
  if (options.verbose) {
29
29
  // Verbose format (old behavior)
30
- console.log(chalk.bold(`\nClaude Tasks (${filtered.length}/${taskList.length})\n`));
30
+ console.log(chalk.bold(`\nTasks (${filtered.length}/${taskList.length})\n`));
31
31
 
32
32
  for (const task of filtered) {
33
33
  // Verify running status
@@ -30,58 +30,70 @@ function getToolIcon(toolName) {
30
30
  }
31
31
 
32
32
  // Format tool call input for display
33
+ const TOOL_CALL_FORMATTERS = {
34
+ Bash: (input) => (input.command ? `$ ${input.command}` : ''),
35
+ Read: (input) => formatFilePathTail(input.file_path),
36
+ Write: (input) => formatFilePathTail(input.file_path, '→ '),
37
+ Edit: (input) => formatFilePathTail(input.file_path),
38
+ Glob: (input) => input.pattern || '',
39
+ Grep: (input) => (input.pattern ? `/${input.pattern}/` : ''),
40
+ WebFetch: (input) => (input.url ? input.url.substring(0, 50) : ''),
41
+ WebSearch: (input) => (input.query ? `"${input.query}"` : ''),
42
+ Task: (input) => input.description || '',
43
+ TodoWrite: (input) => formatTodoSummary(input),
44
+ AskUserQuestion: (input) => formatQuestionSummary(input),
45
+ };
46
+
33
47
  function formatToolCall(toolName, input) {
34
48
  if (!input) return '';
35
49
 
36
- switch (toolName) {
37
- case 'Bash':
38
- return input.command ? `$ ${input.command}` : '';
39
- case 'Read':
40
- return input.file_path ? input.file_path.split('/').slice(-2).join('/') : '';
41
- case 'Write':
42
- return input.file_path ? `→ ${input.file_path.split('/').slice(-2).join('/')}` : '';
43
- case 'Edit':
44
- return input.file_path ? input.file_path.split('/').slice(-2).join('/') : '';
45
- case 'Glob':
46
- return input.pattern || '';
47
- case 'Grep':
48
- return input.pattern ? `/${input.pattern}/` : '';
49
- case 'WebFetch':
50
- return input.url ? input.url.substring(0, 50) : '';
51
- case 'WebSearch':
52
- return input.query ? `"${input.query}"` : '';
53
- case 'Task':
54
- return input.description || '';
55
- case 'TodoWrite':
56
- if (input.todos && Array.isArray(input.todos)) {
57
- const statusCounts = {};
58
- input.todos.forEach((todo) => {
59
- statusCounts[todo.status] = (statusCounts[todo.status] || 0) + 1;
60
- });
61
- const parts = Object.entries(statusCounts).map(
62
- ([status, count]) => `${count} ${status.replace('_', ' ')}`
63
- );
64
- return `${input.todos.length} todo${input.todos.length === 1 ? '' : 's'} (${parts.join(', ')})`;
65
- }
66
- return '';
67
- case 'AskUserQuestion':
68
- if (input.questions && Array.isArray(input.questions)) {
69
- const q = input.questions[0];
70
- const preview = q.question.substring(0, 50);
71
- return input.questions.length > 1
72
- ? `${input.questions.length} questions: "${preview}..."`
73
- : `"${preview}${q.question.length > 50 ? '...' : ''}"`;
74
- }
75
- return '';
76
- default:
77
- // For unknown tools, show first key-value pair
78
- const keys = Object.keys(input);
79
- if (keys.length > 0) {
80
- const val = String(input[keys[0]]).substring(0, 40);
81
- return val.length < String(input[keys[0]]).length ? val + '...' : val;
82
- }
83
- return '';
50
+ const formatter = TOOL_CALL_FORMATTERS[toolName] || formatUnknownToolCall;
51
+ return formatter(input);
52
+ }
53
+
54
+ function formatFilePathTail(filePath, prefix = '') {
55
+ if (!filePath) return '';
56
+ return `${prefix}${filePath.split('/').slice(-2).join('/')}`;
57
+ }
58
+
59
+ function formatTodoSummary(input) {
60
+ const todos = input.todos;
61
+ if (!Array.isArray(todos)) return '';
62
+
63
+ const statusCounts = {};
64
+ for (const todo of todos) {
65
+ statusCounts[todo.status] = (statusCounts[todo.status] || 0) + 1;
66
+ }
67
+
68
+ const parts = Object.entries(statusCounts).map(
69
+ ([status, count]) => `${count} ${status.replace('_', ' ')}`
70
+ );
71
+
72
+ return `${todos.length} todo${todos.length === 1 ? '' : 's'} (${parts.join(', ')})`;
73
+ }
74
+
75
+ function formatQuestionSummary(input) {
76
+ const questions = input.questions;
77
+ if (!Array.isArray(questions) || questions.length === 0) {
78
+ return '';
79
+ }
80
+
81
+ const preview = questions[0].question.substring(0, 50);
82
+ const suffix = questions[0].question.length > 50 ? '...' : '';
83
+ return questions.length > 1
84
+ ? `${questions.length} questions: "${preview}..."`
85
+ : `"${preview}${suffix}"`;
86
+ }
87
+
88
+ function formatUnknownToolCall(input) {
89
+ const keys = Object.keys(input);
90
+ if (keys.length === 0) {
91
+ return '';
84
92
  }
93
+
94
+ const value = String(input[keys[0]]);
95
+ const preview = value.substring(0, 40);
96
+ return preview.length < value.length ? `${preview}...` : preview;
85
97
  }
86
98
 
87
99
  // Format tool result for display
@@ -98,16 +110,21 @@ function formatToolResult(content, isError, toolName, toolInput) {
98
110
  if (toolName === 'TodoWrite' && toolInput?.todos && Array.isArray(toolInput.todos)) {
99
111
  const todos = toolInput.todos;
100
112
  if (todos.length === 0) return chalk.dim('no todos');
113
+
114
+ // Helper to get status icon
115
+ const getStatusIcon = (todoStatus) => {
116
+ if (todoStatus === 'completed') return '✓';
117
+ if (todoStatus === 'in_progress') return '⧗';
118
+ return '○';
119
+ };
120
+
101
121
  if (todos.length === 1) {
102
- const status =
103
- todos[0].status === 'completed' ? '✓' : todos[0].status === 'in_progress' ? '' : '';
104
- return chalk.dim(
105
- `${status} ${todos[0].content.substring(0, 50)}${todos[0].content.length > 50 ? '...' : ''}`
106
- );
122
+ const status = getStatusIcon(todos[0].status);
123
+ const suffix = todos[0].content.length > 50 ? '...' : '';
124
+ return chalk.dim(`${status} ${todos[0].content.substring(0, 50)}${suffix}`);
107
125
  }
108
126
  // Multiple todos - show first one as preview
109
- const status =
110
- todos[0].status === 'completed' ? '✓' : todos[0].status === 'in_progress' ? '⧗' : '○';
127
+ const status = getStatusIcon(todos[0].status);
111
128
  return chalk.dim(
112
129
  `${status} ${todos[0].content.substring(0, 40)}... (+${todos.length - 1} more)`
113
130
  );
@@ -161,77 +178,95 @@ function accumulateText(text) {
161
178
  }
162
179
 
163
180
  // Process parsed events and output formatted content
181
+ const EVENT_HANDLERS = {
182
+ text: handleTextEvent,
183
+ thinking: handleThinkingEvent,
184
+ thinking_start: handleThinkingEvent,
185
+ tool_start: handleToolStart,
186
+ tool_call: handleToolCall,
187
+ tool_input: handleToolInput,
188
+ tool_result: handleToolResult,
189
+ result: handleResult,
190
+ block_end: handleBlockEnd,
191
+ multi: handleMulti,
192
+ };
193
+
164
194
  function processEvent(event) {
165
- switch (event.type) {
166
- case 'text':
167
- accumulateText(event.text);
168
- break;
169
-
170
- case 'thinking':
171
- case 'thinking_start':
172
- if (event.text) {
173
- console.log(chalk.dim.italic(event.text));
174
- } else if (event.type === 'thinking_start') {
175
- console.log(chalk.dim.italic('💭 thinking...'));
176
- }
177
- break;
178
-
179
- case 'tool_start':
180
- flushLineBuffer();
181
- break;
182
-
183
- case 'tool_call':
184
- flushLineBuffer();
185
- const icon = getToolIcon(event.toolName);
186
- const toolDesc = formatToolCall(event.toolName, event.input);
187
- console.log(`${icon} ${chalk.cyan(event.toolName)} ${chalk.dim(toolDesc)}`);
188
- currentToolCall = { toolName: event.toolName, input: event.input };
189
- break;
190
-
191
- case 'tool_input':
192
- // Streaming tool input JSON - skip (shown in tool_call)
193
- break;
194
-
195
- case 'tool_result':
196
- const status = event.isError ? chalk.red('✗') : chalk.green('✓');
197
- const resultDesc = formatToolResult(
198
- event.content,
199
- event.isError,
200
- currentToolCall?.toolName,
201
- currentToolCall?.input
202
- );
203
- console.log(` ${status} ${resultDesc}`);
204
- break;
205
-
206
- case 'result':
207
- flushLineBuffer();
208
- if (event.error) {
209
- console.log(chalk.red(`\n✗ ERROR: ${event.error}`));
210
- } else {
211
- console.log(chalk.green(`\n✓ Completed`));
212
- if (event.cost) {
213
- console.log(chalk.dim(` Cost: $${event.cost.toFixed(4)}`));
214
- }
215
- if (event.duration) {
216
- const mins = Math.floor(event.duration / 60000);
217
- const secs = Math.floor((event.duration % 60000) / 1000);
218
- console.log(chalk.dim(` Duration: ${mins}m ${secs}s`));
219
- }
220
- }
221
- break;
222
-
223
- case 'block_end':
224
- // Content block ended
225
- break;
226
-
227
- case 'multi':
228
- // Multiple events
229
- if (event.events) {
230
- for (const e of event.events) {
231
- processEvent(e);
232
- }
233
- }
234
- break;
195
+ const handler = EVENT_HANDLERS[event.type];
196
+ if (handler) {
197
+ handler(event);
198
+ }
199
+ }
200
+
201
+ function handleTextEvent(event) {
202
+ accumulateText(event.text);
203
+ }
204
+
205
+ function handleThinkingEvent(event) {
206
+ if (event.text) {
207
+ console.log(chalk.dim.italic(event.text));
208
+ return;
209
+ }
210
+
211
+ if (event.type === 'thinking_start') {
212
+ console.log(chalk.dim.italic('💭 thinking...'));
213
+ }
214
+ }
215
+
216
+ function handleToolStart() {
217
+ flushLineBuffer();
218
+ }
219
+
220
+ function handleToolCall(event) {
221
+ flushLineBuffer();
222
+ const icon = getToolIcon(event.toolName);
223
+ const toolDesc = formatToolCall(event.toolName, event.input);
224
+ console.log(`${icon} ${chalk.cyan(event.toolName)} ${chalk.dim(toolDesc)}`);
225
+ currentToolCall = { toolName: event.toolName, input: event.input };
226
+ }
227
+
228
+ function handleToolInput() {
229
+ // Streaming tool input JSON - skip (shown in tool_call)
230
+ }
231
+
232
+ function handleToolResult(event) {
233
+ const status = event.isError ? chalk.red('✗') : chalk.green('✓');
234
+ const resultDesc = formatToolResult(
235
+ event.content,
236
+ event.isError,
237
+ currentToolCall?.toolName,
238
+ currentToolCall?.input
239
+ );
240
+ console.log(` ${status} ${resultDesc}`);
241
+ }
242
+
243
+ function handleResult(event) {
244
+ flushLineBuffer();
245
+ if (event.error) {
246
+ console.log(chalk.red(`\n✗ ERROR: ${event.error}`));
247
+ return;
248
+ }
249
+
250
+ console.log(chalk.green(`\n✓ Completed`));
251
+ if (event.cost) {
252
+ console.log(chalk.dim(` Cost: $${event.cost.toFixed(4)}`));
253
+ }
254
+ if (event.duration) {
255
+ const mins = Math.floor(event.duration / 60000);
256
+ const secs = Math.floor((event.duration % 60000) / 1000);
257
+ console.log(chalk.dim(` Duration: ${mins}m ${secs}s`));
258
+ }
259
+ }
260
+
261
+ function handleBlockEnd() {
262
+ // Content block ended
263
+ }
264
+
265
+ function handleMulti(event) {
266
+ if (event.events) {
267
+ for (const subEvent of event.events) {
268
+ processEvent(subEvent);
269
+ }
235
270
  }
236
271
  }
237
272
 
@@ -296,23 +331,73 @@ export async function showLogs(taskId, options = {}) {
296
331
  }
297
332
  }
298
333
 
334
+ function parseEventsFromLines(lines) {
335
+ const events = [];
336
+ for (const line of lines) {
337
+ events.push(...parseLogLine(line));
338
+ }
339
+ return events;
340
+ }
341
+
342
+ function renderEvents(events) {
343
+ for (const event of events) {
344
+ processEvent(event);
345
+ }
346
+ }
347
+
348
+ function renderLines(lines) {
349
+ for (const line of lines) {
350
+ renderEvents(parseLogLine(line));
351
+ }
352
+ }
353
+
354
+ function readFileSlice(file, start, end) {
355
+ const length = Math.max(0, end - start);
356
+ if (!length) return '';
357
+
358
+ const buffer = Buffer.alloc(length);
359
+ const fd = openSync(file, 'r');
360
+ readSync(fd, buffer, 0, buffer.length, start);
361
+ closeSync(fd);
362
+
363
+ return buffer.toString();
364
+ }
365
+
366
+ function shouldStopFollowing(noChangeCount, pid) {
367
+ return noChangeCount >= 10 && pid && !isProcessRunning(pid);
368
+ }
369
+
370
+ function handleFinalContent(file, lastSize, interval) {
371
+ const finalSize = statSync(file).size;
372
+ if (finalSize > lastSize) {
373
+ const finalContent = readFileSlice(file, lastSize, finalSize);
374
+ renderLines(finalContent.split('\n'));
375
+ flushLineBuffer();
376
+ }
377
+
378
+ console.log(chalk.dim('\n--- Task completed ---'));
379
+ clearInterval(interval);
380
+ process.exit(0);
381
+ }
382
+
383
+ function handleNewContent(file, lastSize, currentSize) {
384
+ const newContent = readFileSlice(file, lastSize, currentSize);
385
+ if (newContent) {
386
+ renderLines(newContent.split('\n'));
387
+ flushLineBuffer();
388
+ }
389
+ return currentSize;
390
+ }
391
+
299
392
  function tailLines(file, n) {
300
393
  resetState();
301
394
  const rawContent = readFileSync(file, 'utf-8');
302
395
  const rawLines = rawContent.split('\n');
303
396
 
304
397
  // Parse and process all events
305
- const allEvents = [];
306
- for (const line of rawLines) {
307
- const events = parseLogLine(line);
308
- allEvents.push(...events);
309
- }
310
-
398
+ const allEvents = parseEventsFromLines(rawLines);
311
399
  // Tail to last n events
312
- const tailedEvents = allEvents.slice(-n);
313
- for (const event of tailedEvents) {
314
- processEvent(event);
315
- }
400
+ renderEvents(allEvents.slice(-n));
316
401
  flushLineBuffer();
317
402
  }
318
403
 
@@ -323,17 +408,9 @@ async function tailFollow(file, pid, lines = 50) {
323
408
  const rawLines = rawContent.split('\n');
324
409
 
325
410
  // Parse all events first
326
- const allEvents = [];
327
- for (const line of rawLines) {
328
- const events = parseLogLine(line);
329
- allEvents.push(...events);
330
- }
331
-
411
+ const allEvents = parseEventsFromLines(rawLines);
332
412
  // Output only the last N events
333
- const tailedEvents = allEvents.slice(-lines);
334
- for (const event of tailedEvents) {
335
- processEvent(event);
336
- }
413
+ renderEvents(allEvents.slice(-lines));
337
414
  flushLineBuffer();
338
415
 
339
416
  // Poll for changes (more reliable than fs.watch)
@@ -345,51 +422,16 @@ async function tailFollow(file, pid, lines = 50) {
345
422
  const currentSize = statSync(file).size;
346
423
 
347
424
  if (currentSize > lastSize) {
348
- // Read new content
349
- const buffer = Buffer.alloc(currentSize - lastSize);
350
- const fd = openSync(file, 'r');
351
- readSync(fd, buffer, 0, buffer.length, lastSize);
352
- closeSync(fd);
353
-
354
- // Parse and output new lines
355
- const newLines = buffer.toString().split('\n');
356
- for (const line of newLines) {
357
- const events = parseLogLine(line);
358
- for (const event of events) {
359
- processEvent(event);
360
- }
361
- }
362
- flushLineBuffer();
363
-
364
- lastSize = currentSize;
425
+ lastSize = handleNewContent(file, lastSize, currentSize);
365
426
  noChangeCount = 0;
366
- } else {
367
- noChangeCount++;
368
-
369
- // Check if process is still running after 5 seconds of no output
370
- if (noChangeCount >= 10 && pid && !isProcessRunning(pid)) {
371
- // Read any final content
372
- const finalSize = statSync(file).size;
373
- if (finalSize > lastSize) {
374
- const buffer = Buffer.alloc(finalSize - lastSize);
375
- const fd = openSync(file, 'r');
376
- readSync(fd, buffer, 0, buffer.length, lastSize);
377
- closeSync(fd);
378
-
379
- const finalLines = buffer.toString().split('\n');
380
- for (const line of finalLines) {
381
- const events = parseLogLine(line);
382
- for (const event of events) {
383
- processEvent(event);
384
- }
385
- }
386
- flushLineBuffer();
387
- }
388
-
389
- console.log(chalk.dim('\n--- Task completed ---'));
390
- clearInterval(interval);
391
- process.exit(0);
392
- }
427
+ return;
428
+ }
429
+
430
+ noChangeCount++;
431
+
432
+ // Check if process is still running after 5 seconds of no output
433
+ if (shouldStopFollowing(noChangeCount, pid)) {
434
+ handleFinalContent(file, lastSize, interval);
393
435
  }
394
436
  } catch (err) {
395
437
  // File might have been deleted
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import { getTask } from '../store.js';
3
3
  import { spawnTask } from '../runner.js';
4
4
 
5
- export function resumeTask(taskId, newPrompt) {
5
+ export async function resumeTask(taskId, newPrompt) {
6
6
  const task = getTask(taskId);
7
7
 
8
8
  if (!task) {
@@ -23,10 +23,11 @@ export function resumeTask(taskId, newPrompt) {
23
23
  console.log(chalk.dim(`Original prompt: ${task.prompt}`));
24
24
  console.log(chalk.dim(`Resume prompt: ${prompt}`));
25
25
 
26
- const newTask = spawnTask(prompt, {
26
+ const newTask = await spawnTask(prompt, {
27
27
  cwd: task.cwd,
28
28
  continue: true, // Use --continue to load most recent session in that directory
29
29
  sessionId: task.sessionId,
30
+ provider: task.provider,
30
31
  });
31
32
 
32
33
  console.log(chalk.green(`\n✓ Resumed as new task: ${chalk.cyan(newTask.id)}`));
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { spawnTask } from '../runner.js';
3
3
 
4
- export function runTask(prompt, options = {}) {
4
+ export async function runTask(prompt, options = {}) {
5
5
  if (!prompt || prompt.trim().length === 0) {
6
6
  console.log(chalk.red('Error: Prompt is required'));
7
7
  process.exit(1);
@@ -11,10 +11,16 @@ export function runTask(prompt, options = {}) {
11
11
  const jsonSchema = options.jsonSchema;
12
12
  const silentJsonOutput = options.silentJsonOutput || false;
13
13
 
14
- console.log(chalk.dim('Spawning Claude task...'));
14
+ console.log(chalk.dim('Spawning task...'));
15
+ if (options.provider) {
16
+ console.log(chalk.dim(` Provider: ${options.provider}`));
17
+ }
15
18
  if (options.model) {
16
19
  console.log(chalk.dim(` Model: ${options.model}`));
17
20
  }
21
+ if (options.modelLevel) {
22
+ console.log(chalk.dim(` Level: ${options.modelLevel}`));
23
+ }
18
24
  if (jsonSchema && outputFormat === 'json') {
19
25
  console.log(chalk.dim(` JSON Schema: enforced`));
20
26
  if (silentJsonOutput) {
@@ -22,9 +28,12 @@ export function runTask(prompt, options = {}) {
22
28
  }
23
29
  }
24
30
 
25
- const task = spawnTask(prompt, {
31
+ const task = await spawnTask(prompt, {
26
32
  cwd: options.cwd || process.cwd(),
27
33
  model: options.model,
34
+ modelLevel: options.modelLevel,
35
+ reasoningEffort: options.reasoningEffort,
36
+ provider: options.provider,
28
37
  resume: options.resume,
29
38
  continue: options.continue,
30
39
  outputFormat,