@covibes/zeroshot 5.4.0 → 5.4.1

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 (136) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -465
  3. package/cli/index.js +101 -5231
  4. package/package.json +16 -130
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -149
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -275
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -581
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -208
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -220
  21. package/lib/git-remote-utils.js +0 -165
  22. package/lib/id-detector.js +0 -56
  23. package/lib/provider-defaults.js +0 -62
  24. package/lib/provider-detection.js +0 -59
  25. package/lib/provider-names.js +0 -57
  26. package/lib/settings/claude-auth.js +0 -78
  27. package/lib/settings.js +0 -503
  28. package/lib/stream-json-parser.js +0 -10
  29. package/scripts/fix-node-pty-permissions.js +0 -75
  30. package/scripts/record-demo.sh +0 -279
  31. package/scripts/setup-merge-queue.sh +0 -170
  32. package/scripts/test-install.sh +0 -40
  33. package/scripts/validate-templates.js +0 -100
  34. package/src/agent/agent-config.js +0 -246
  35. package/src/agent/agent-context-builder.js +0 -444
  36. package/src/agent/agent-hook-executor.js +0 -599
  37. package/src/agent/agent-lifecycle.js +0 -743
  38. package/src/agent/agent-stuck-detector.js +0 -256
  39. package/src/agent/agent-task-executor.js +0 -1690
  40. package/src/agent/agent-trigger-evaluator.js +0 -67
  41. package/src/agent/output-extraction.js +0 -217
  42. package/src/agent/output-reformatter.js +0 -175
  43. package/src/agent/schema-utils.js +0 -146
  44. package/src/agent-wrapper.js +0 -579
  45. package/src/agents/git-pusher-template.js +0 -285
  46. package/src/attach/attach-client.js +0 -438
  47. package/src/attach/attach-server.js +0 -543
  48. package/src/attach/index.js +0 -35
  49. package/src/attach/protocol.js +0 -220
  50. package/src/attach/ring-buffer.js +0 -121
  51. package/src/attach/socket-discovery.js +0 -267
  52. package/src/claude-task-runner.js +0 -574
  53. package/src/config-router.js +0 -80
  54. package/src/config-validator.js +0 -1925
  55. package/src/copy-worker.js +0 -43
  56. package/src/input-helpers.js +0 -65
  57. package/src/isolation-manager.js +0 -1471
  58. package/src/issue-providers/README.md +0 -305
  59. package/src/issue-providers/azure-devops-provider.js +0 -273
  60. package/src/issue-providers/base-provider.js +0 -232
  61. package/src/issue-providers/github-provider.js +0 -179
  62. package/src/issue-providers/gitlab-provider.js +0 -241
  63. package/src/issue-providers/index.js +0 -196
  64. package/src/issue-providers/jira-provider.js +0 -239
  65. package/src/ledger.js +0 -631
  66. package/src/lib/safe-exec.js +0 -88
  67. package/src/logic-engine.js +0 -223
  68. package/src/message-bus-bridge.js +0 -139
  69. package/src/message-bus.js +0 -249
  70. package/src/name-generator.js +0 -232
  71. package/src/orchestrator.js +0 -2815
  72. package/src/preflight.js +0 -639
  73. package/src/process-metrics.js +0 -608
  74. package/src/providers/anthropic/cli-builder.js +0 -73
  75. package/src/providers/anthropic/index.js +0 -204
  76. package/src/providers/anthropic/models.js +0 -23
  77. package/src/providers/anthropic/output-parser.js +0 -177
  78. package/src/providers/base-provider.js +0 -251
  79. package/src/providers/capabilities.js +0 -60
  80. package/src/providers/google/cli-builder.js +0 -55
  81. package/src/providers/google/index.js +0 -116
  82. package/src/providers/google/models.js +0 -24
  83. package/src/providers/google/output-parser.js +0 -101
  84. package/src/providers/index.js +0 -91
  85. package/src/providers/openai/cli-builder.js +0 -133
  86. package/src/providers/openai/index.js +0 -136
  87. package/src/providers/openai/models.js +0 -21
  88. package/src/providers/openai/output-parser.js +0 -143
  89. package/src/providers/opencode/cli-builder.js +0 -42
  90. package/src/providers/opencode/index.js +0 -103
  91. package/src/providers/opencode/models.js +0 -55
  92. package/src/providers/opencode/output-parser.js +0 -122
  93. package/src/schemas/sub-cluster.js +0 -185
  94. package/src/status-footer.js +0 -1026
  95. package/src/sub-cluster-wrapper.js +0 -601
  96. package/src/task-runner.js +0 -30
  97. package/src/template-resolver.js +0 -372
  98. package/src/tui/CHANGES.txt +0 -133
  99. package/src/tui/LAYOUT.md +0 -261
  100. package/src/tui/README.txt +0 -192
  101. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  102. package/src/tui/data-poller.js +0 -349
  103. package/src/tui/demo.js +0 -208
  104. package/src/tui/formatters.js +0 -129
  105. package/src/tui/index.js +0 -193
  106. package/src/tui/keybindings.js +0 -324
  107. package/src/tui/layout.js +0 -334
  108. package/src/tui/renderer.js +0 -184
  109. package/task-lib/attachable-watcher.js +0 -309
  110. package/task-lib/claude-recovery.js +0 -156
  111. package/task-lib/commands/clean.js +0 -50
  112. package/task-lib/commands/episodes.js +0 -105
  113. package/task-lib/commands/get-log-path.js +0 -23
  114. package/task-lib/commands/kill.js +0 -32
  115. package/task-lib/commands/list.js +0 -105
  116. package/task-lib/commands/logs.js +0 -453
  117. package/task-lib/commands/resume.js +0 -42
  118. package/task-lib/commands/run.js +0 -57
  119. package/task-lib/commands/schedule.js +0 -105
  120. package/task-lib/commands/scheduler-cmd.js +0 -96
  121. package/task-lib/commands/schedules.js +0 -148
  122. package/task-lib/commands/status.js +0 -44
  123. package/task-lib/commands/unschedule.js +0 -16
  124. package/task-lib/completion.js +0 -9
  125. package/task-lib/config.js +0 -8
  126. package/task-lib/name-generator.js +0 -230
  127. package/task-lib/package.json +0 -3
  128. package/task-lib/runner.js +0 -201
  129. package/task-lib/scheduler.js +0 -252
  130. package/task-lib/store.js +0 -529
  131. package/task-lib/tui/formatters.js +0 -215
  132. package/task-lib/tui/index.js +0 -197
  133. package/task-lib/tui/layout.js +0 -111
  134. package/task-lib/tui/renderer.js +0 -129
  135. package/task-lib/tui.js +0 -425
  136. package/task-lib/watcher.js +0 -226
package/task-lib/tui.js DELETED
@@ -1,425 +0,0 @@
1
- /**
2
- * Task TUI - Interactive task viewer
3
- *
4
- * Features:
5
- * - List all tasks
6
- * - Navigate with arrow keys
7
- * - Press Enter to view logs
8
- * - Press Esc to go back to list
9
- */
10
-
11
- import blessed from 'blessed';
12
- import { loadTasks } from './store.js';
13
- import { isProcessRunning } from './runner.js';
14
- import fs from 'fs';
15
- import path from 'path';
16
- import os from 'os';
17
-
18
- // Parse a single log line from JSON stream format
19
- function parseLogLine(line) {
20
- let trimmed = line.trim();
21
-
22
- // Strip timestamp prefix if present: [1234567890]{...} -> {...}
23
- const timestampMatch = trimmed.match(/^\[\d+\](.*)$/);
24
- if (timestampMatch) {
25
- trimmed = timestampMatch[1];
26
- }
27
-
28
- // Keep non-JSON lines
29
- if (!trimmed.startsWith('{')) {
30
- return trimmed ? trimmed + '\n' : '';
31
- }
32
-
33
- // Parse JSON and extract relevant info
34
- const event = safeParseEvent(trimmed);
35
- if (!event) {
36
- return '';
37
- }
38
-
39
- return extractEventText(event);
40
- }
41
-
42
- function safeParseEvent(trimmed) {
43
- try {
44
- return JSON.parse(trimmed);
45
- } catch {
46
- return null;
47
- }
48
- }
49
-
50
- function extractEventText(event) {
51
- if (event.type === 'stream_event') {
52
- return extractStreamEventText(event);
53
- }
54
-
55
- if (event.type === 'assistant') {
56
- return extractAssistantText(event);
57
- }
58
-
59
- if (event.type === 'result') {
60
- return extractResultText(event);
61
- }
62
-
63
- return '';
64
- }
65
-
66
- function extractStreamEventText(event) {
67
- const eventType = event.event?.type;
68
-
69
- if (eventType === 'content_block_delta') {
70
- return event.event?.delta?.text || '';
71
- }
72
-
73
- if (eventType === 'content_block_start') {
74
- return extractToolUseText(event.event?.content_block);
75
- }
76
-
77
- return '';
78
- }
79
-
80
- function extractToolUseText(block) {
81
- if (block?.type === 'tool_use' && block?.name) {
82
- return `\n[Tool: ${block.name}]\n`;
83
- }
84
-
85
- return '';
86
- }
87
-
88
- function extractAssistantText(event) {
89
- const contentBlocks = event.message?.content;
90
-
91
- if (!Array.isArray(contentBlocks)) {
92
- return '';
93
- }
94
-
95
- let output = '';
96
- for (const content of contentBlocks) {
97
- if (content.type === 'text') {
98
- output += content.text;
99
- }
100
- }
101
-
102
- return output;
103
- }
104
-
105
- function extractResultText(event) {
106
- if (event.is_error) {
107
- return `\n[ERROR] ${event.result || 'Unknown error'}\n`;
108
- }
109
-
110
- return '';
111
- }
112
-
113
- class TaskTUI {
114
- constructor(options = {}) {
115
- this.tasks = [];
116
- this.selectedIndex = 0;
117
- this.viewMode = 'list'; // 'list' or 'detail'
118
- this.selectedTask = null;
119
- this.initialScrollDone = false;
120
- this.refreshRate = options.refreshRate || 1000;
121
- }
122
-
123
- start() {
124
- // Create screen
125
- this.screen = blessed.screen({
126
- smartCSR: true,
127
- title: 'Vibe Task Watch',
128
- dockBorders: true,
129
- fullUnicode: true,
130
- });
131
-
132
- // Create main list view
133
- this.listBox = blessed.list({
134
- parent: this.screen,
135
- top: 0,
136
- left: 0,
137
- width: '100%',
138
- height: '100%-2',
139
- keys: true,
140
- vi: true,
141
- mouse: true,
142
- border: {
143
- type: 'line',
144
- },
145
- style: {
146
- selected: {
147
- bg: 'blue',
148
- fg: 'white',
149
- bold: true,
150
- },
151
- border: {
152
- fg: 'cyan',
153
- },
154
- },
155
- tags: true,
156
- });
157
-
158
- // Create detail view (hidden initially)
159
- this.detailBox = blessed.box({
160
- parent: this.screen,
161
- top: 0,
162
- left: 0,
163
- width: '100%',
164
- height: '100%-2',
165
- border: {
166
- type: 'line',
167
- },
168
- style: {
169
- border: {
170
- fg: 'cyan',
171
- },
172
- },
173
- scrollable: true,
174
- alwaysScroll: true,
175
- keys: true,
176
- vi: true,
177
- mouse: true,
178
- scrollbar: {
179
- ch: '│',
180
- style: {
181
- bg: 'cyan',
182
- },
183
- },
184
- tags: false,
185
- hidden: true,
186
- });
187
-
188
- // Status bar
189
- this.statusBar = blessed.box({
190
- parent: this.screen,
191
- bottom: 0,
192
- left: 0,
193
- width: '100%',
194
- height: 2,
195
- content: '',
196
- tags: true,
197
- style: {
198
- bg: 'blue',
199
- fg: 'white',
200
- },
201
- });
202
-
203
- // Setup keybindings
204
- this.setupKeybindings();
205
-
206
- // Initial render
207
- this.refreshData();
208
- this.render();
209
-
210
- // Start polling
211
- this.pollInterval = setInterval(() => {
212
- this.refreshData();
213
- this.render();
214
- }, this.refreshRate);
215
-
216
- // Render screen
217
- this.screen.render();
218
-
219
- // Focus on list after layout is established
220
- this.listBox.focus();
221
- }
222
-
223
- setupKeybindings() {
224
- // Quit
225
- this.screen.key(['q', 'C-c'], () => {
226
- if (this.pollInterval) {
227
- clearInterval(this.pollInterval);
228
- }
229
- process.exit(0);
230
- });
231
-
232
- // Enter - view details
233
- this.listBox.key(['enter', 'space'], () => {
234
- if (this.tasks.length > 0) {
235
- this.selectedTask = this.tasks[this.selectedIndex];
236
- this.viewMode = 'detail';
237
- this.initialScrollDone = false;
238
- this.listBox.hide();
239
- this.detailBox.show();
240
- this.screen.render();
241
- setImmediate(() => {
242
- this.detailBox.focus();
243
- this.render();
244
- });
245
- }
246
- });
247
-
248
- // Escape - back to list
249
- this.screen.key(['escape'], () => {
250
- if (this.viewMode === 'detail') {
251
- this.viewMode = 'list';
252
- this.detailBox.hide();
253
- this.listBox.show();
254
- this.screen.render();
255
- setImmediate(() => {
256
- this.listBox.focus();
257
- this.render();
258
- });
259
- }
260
- });
261
-
262
- // Arrow keys for list
263
- this.listBox.key(['up', 'k'], () => {
264
- if (this.selectedIndex > 0) {
265
- this.selectedIndex--;
266
- this.listBox.select(this.selectedIndex);
267
- this.render();
268
- }
269
- });
270
-
271
- this.listBox.key(['down', 'j'], () => {
272
- if (this.selectedIndex < this.tasks.length - 1) {
273
- this.selectedIndex++;
274
- this.listBox.select(this.selectedIndex);
275
- this.render();
276
- }
277
- });
278
-
279
- // Scroll in detail view
280
- this.detailBox.key(['up', 'k'], () => {
281
- this.detailBox.scroll(-1);
282
- this.screen.render();
283
- });
284
-
285
- this.detailBox.key(['down', 'j'], () => {
286
- this.detailBox.scroll(1);
287
- this.screen.render();
288
- });
289
-
290
- this.detailBox.key(['pageup', 'u'], () => {
291
- this.detailBox.scroll(-10);
292
- this.screen.render();
293
- });
294
-
295
- this.detailBox.key(['pagedown', 'd'], () => {
296
- this.detailBox.scroll(10);
297
- this.screen.render();
298
- });
299
- }
300
-
301
- refreshData() {
302
- const tasks = loadTasks();
303
- this.tasks = Object.values(tasks);
304
-
305
- // Sort by creation date, oldest first (chronological)
306
- this.tasks.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
307
-
308
- // Verify running status
309
- for (const task of this.tasks) {
310
- if (task.status === 'running' && !isProcessRunning(task.pid)) {
311
- task.status = 'stale';
312
- }
313
- }
314
- }
315
-
316
- render() {
317
- if (this.viewMode === 'list') {
318
- this.renderList();
319
- } else {
320
- this.renderDetail();
321
- }
322
- }
323
-
324
- renderList() {
325
- const items = this.tasks.map((task) => {
326
- const statusIcon =
327
- {
328
- running: '{green-fg}●{/}',
329
- completed: '{green-fg}●{/}',
330
- failed: '{red-fg}●{/}',
331
- stale: '{yellow-fg}●{/}',
332
- }[task.status] || '{gray-fg}●{/}';
333
-
334
- const age = this.getAge(task.createdAt);
335
- const cwd = task.cwd.replace(os.homedir(), '~');
336
-
337
- return `${statusIcon} {cyan-fg}${task.id.padEnd(25)}{/} {gray-fg}${task.status.padEnd(10)} ${age.padEnd(10)} ${cwd}{/}`;
338
- });
339
-
340
- this.listBox.setItems(items);
341
- this.listBox.setLabel(
342
- ` Tasks (${this.tasks.length}) - ↑↓ navigate, Enter to view logs, q to quit `
343
- );
344
-
345
- // Update status bar
346
- if (this.tasks.length > 0) {
347
- const task = this.tasks[this.selectedIndex];
348
- this.statusBar.setContent(
349
- ` Selected: {cyan-fg}${task.id}{/} | Status: ${this.getStatusColor(task.status)} | Press Enter to view logs`
350
- );
351
- } else {
352
- this.statusBar.setContent(' No tasks found');
353
- }
354
-
355
- this.screen.render();
356
- }
357
-
358
- renderDetail() {
359
- if (!this.selectedTask) return;
360
-
361
- const task = this.selectedTask;
362
-
363
- // Load and parse log file
364
- const logPath = path.join(os.homedir(), '.claude-zeroshot', 'logs', `${task.id}.log`);
365
- let content = '';
366
-
367
- if (fs.existsSync(logPath)) {
368
- try {
369
- const rawContent = fs.readFileSync(logPath, 'utf8');
370
- const lines = rawContent.split('\n');
371
-
372
- // Parse JSON stream and extract human-readable content
373
- for (const line of lines) {
374
- const parsed = parseLogLine(line);
375
- if (parsed) content += parsed;
376
- }
377
-
378
- // Strip ANSI codes for clean display
379
- content = content.replace(/\x1b\[[0-9;]*m/g, '');
380
- } catch (error) {
381
- content = `Error reading log: ${error.message}`;
382
- }
383
- } else {
384
- content = 'No log file found';
385
- }
386
-
387
- this.detailBox.setContent(content);
388
- this.detailBox.setLabel(` ${task.id} | ${task.status} | ↑↓ scroll, Esc back, q quit `);
389
-
390
- // Update status bar
391
- this.statusBar.setContent(` ${task.id} | Esc to go back`);
392
-
393
- // Scroll to bottom only on first view
394
- if (!this.initialScrollDone) {
395
- this.detailBox.setScrollPerc(100);
396
- this.initialScrollDone = true;
397
- }
398
-
399
- this.screen.render();
400
- }
401
-
402
- getAge(dateStr) {
403
- const diff = Date.now() - new Date(dateStr).getTime();
404
- const mins = Math.floor(diff / 60000);
405
- const hours = Math.floor(mins / 60);
406
- const days = Math.floor(hours / 24);
407
-
408
- if (days > 0) return `${days}d ago`;
409
- if (hours > 0) return `${hours}h ago`;
410
- if (mins > 0) return `${mins}m ago`;
411
- return 'just now';
412
- }
413
-
414
- getStatusColor(status) {
415
- const colors = {
416
- running: '{green-fg}running{/}',
417
- completed: '{green-fg}completed{/}',
418
- failed: '{red-fg}failed{/}',
419
- stale: '{yellow-fg}stale{/}',
420
- };
421
- return colors[status] || status;
422
- }
423
- }
424
-
425
- export default TaskTUI;
@@ -1,226 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Watcher process - spawns and monitors a CLI process
5
- * Runs detached from parent, updates task status on completion
6
- */
7
-
8
- import { spawn } from 'child_process';
9
- import { appendFileSync } from 'fs';
10
- import { updateTask } from './store.js';
11
- import { detectStreamingModeError, recoverStructuredOutput } from './claude-recovery.js';
12
- import { createRequire } from 'module';
13
-
14
- const require = createRequire(import.meta.url);
15
- const { normalizeProviderName } = require('../lib/provider-names');
16
-
17
- const [, , taskId, cwd, logFile, argsJson, configJson] = process.argv;
18
- const args = JSON.parse(argsJson);
19
- const config = configJson ? JSON.parse(configJson) : {};
20
-
21
- function log(msg) {
22
- appendFileSync(logFile, msg);
23
- }
24
-
25
- const providerName = normalizeProviderName(config.provider || 'claude');
26
- const enableRecovery = providerName === 'claude';
27
-
28
- const env = { ...process.env, ...(config.env || {}) };
29
- const command = config.command || 'claude';
30
- const finalArgs = [...args];
31
-
32
- const child = spawn(command, finalArgs, {
33
- cwd,
34
- env,
35
- stdio: ['ignore', 'pipe', 'pipe'],
36
- });
37
-
38
- updateTask(taskId, { pid: child.pid });
39
-
40
- const silentJsonMode =
41
- config.outputFormat === 'json' && config.jsonSchema && config.silentJsonOutput && enableRecovery;
42
-
43
- let finalResultJson = null;
44
- let streamingModeError = null;
45
-
46
- let stdoutBuffer = '';
47
- let stderrBuffer = '';
48
-
49
- function splitBufferLines(buffer, chunk) {
50
- const nextBuffer = buffer + chunk;
51
- const lines = nextBuffer.split('\n');
52
- const remaining = lines.pop() || '';
53
- return { lines, remaining };
54
- }
55
-
56
- function captureStreamingError(line, timestamp) {
57
- if (!enableRecovery) {
58
- return false;
59
- }
60
-
61
- const detectedError = detectStreamingModeError(line);
62
- if (!detectedError) {
63
- return false;
64
- }
65
-
66
- streamingModeError = { ...detectedError, timestamp };
67
- return true;
68
- }
69
-
70
- function maybeCaptureStructuredOutput(line) {
71
- try {
72
- const json = JSON.parse(line);
73
- if (json.structured_output) {
74
- finalResultJson = line;
75
- }
76
- } catch {
77
- // Not JSON, skip
78
- }
79
- }
80
-
81
- function handleSilentJsonLines(lines, timestamp) {
82
- for (const line of lines) {
83
- if (!line.trim()) continue;
84
- if (captureStreamingError(line, timestamp)) {
85
- continue;
86
- }
87
- maybeCaptureStructuredOutput(line);
88
- }
89
- }
90
-
91
- function handleStreamingLines(lines, timestamp) {
92
- for (const line of lines) {
93
- if (captureStreamingError(line, timestamp)) {
94
- continue;
95
- }
96
- log(`[${timestamp}]${line}\n`);
97
- }
98
- }
99
-
100
- function flushStdoutBuffer(timestamp) {
101
- if (!stdoutBuffer.trim()) {
102
- return;
103
- }
104
-
105
- if (!enableRecovery) {
106
- if (!silentJsonMode) {
107
- log(`[${timestamp}]${stdoutBuffer}\n`);
108
- }
109
- return;
110
- }
111
-
112
- if (captureStreamingError(stdoutBuffer, timestamp)) {
113
- return;
114
- }
115
-
116
- if (silentJsonMode) {
117
- maybeCaptureStructuredOutput(stdoutBuffer);
118
- return;
119
- }
120
-
121
- log(`[${timestamp}]${stdoutBuffer}\n`);
122
- }
123
-
124
- function flushStderrBuffer(timestamp) {
125
- if (stderrBuffer.trim()) {
126
- log(`[${timestamp}]${stderrBuffer}\n`);
127
- }
128
- }
129
-
130
- function attemptRecovery(code, timestamp) {
131
- if (!(enableRecovery && code !== 0 && streamingModeError?.sessionId)) {
132
- return null;
133
- }
134
-
135
- const recovered = recoverStructuredOutput(streamingModeError.sessionId);
136
- if (recovered?.payload) {
137
- const recoveredLine = JSON.stringify(recovered.payload);
138
- if (silentJsonMode) {
139
- finalResultJson = recoveredLine;
140
- } else {
141
- log(`[${timestamp}]${recoveredLine}\n`);
142
- }
143
- } else if (streamingModeError.line) {
144
- if (silentJsonMode) {
145
- log(streamingModeError.line + '\n');
146
- } else {
147
- log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
148
- }
149
- }
150
-
151
- return recovered;
152
- }
153
-
154
- function writeCompletionFooter(code, signal) {
155
- if (config.outputFormat === 'json') {
156
- return;
157
- }
158
-
159
- log(`\n${'='.repeat(50)}\n`);
160
- log(`Finished: ${new Date().toISOString()}\n`);
161
- log(`Exit code: ${code}, Signal: ${signal}\n`);
162
- }
163
-
164
- child.stdout.on('data', (data) => {
165
- const chunk = data.toString();
166
- const timestamp = Date.now();
167
-
168
- const { lines, remaining } = splitBufferLines(stdoutBuffer, chunk);
169
- stdoutBuffer = remaining;
170
-
171
- if (silentJsonMode) {
172
- handleSilentJsonLines(lines, timestamp);
173
- } else {
174
- handleStreamingLines(lines, timestamp);
175
- }
176
- });
177
-
178
- child.stderr.on('data', (data) => {
179
- const chunk = data.toString();
180
- const timestamp = Date.now();
181
-
182
- const { lines, remaining } = splitBufferLines(stderrBuffer, chunk);
183
- stderrBuffer = remaining;
184
-
185
- for (const line of lines) {
186
- log(`[${timestamp}]${line}\n`);
187
- }
188
- });
189
-
190
- child.on('close', async (code, signal) => {
191
- const timestamp = Date.now();
192
-
193
- flushStdoutBuffer(timestamp);
194
- flushStderrBuffer(timestamp);
195
-
196
- const recovered = attemptRecovery(code, timestamp);
197
-
198
- if (silentJsonMode && finalResultJson) {
199
- log(finalResultJson + '\n');
200
- }
201
-
202
- writeCompletionFooter(code, signal);
203
-
204
- const resolvedCode = recovered?.payload ? 0 : code;
205
- const status = resolvedCode === 0 ? 'completed' : 'failed';
206
- try {
207
- await updateTask(taskId, {
208
- status,
209
- exitCode: resolvedCode,
210
- error: resolvedCode !== 0 && signal ? `Killed by ${signal}` : null,
211
- });
212
- } catch (updateError) {
213
- log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
214
- }
215
- process.exit(0);
216
- });
217
-
218
- child.on('error', async (err) => {
219
- log(`\nError: ${err.message}\n`);
220
- try {
221
- await updateTask(taskId, { status: 'failed', error: err.message });
222
- } catch (updateError) {
223
- log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
224
- }
225
- process.exit(1);
226
- });