@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.
- package/LICENSE +1 -1
- package/README.md +8 -465
- package/cli/index.js +101 -5231
- package/package.json +16 -130
- package/CHANGELOG.md +0 -537
- package/cli/commands/providers.js +0 -149
- package/cli/lib/first-run.js +0 -211
- package/cli/lib/update-checker.js +0 -281
- package/cli/message-formatter-utils.js +0 -75
- package/cli/message-formatters-normal.js +0 -275
- package/cli/message-formatters-watch.js +0 -181
- package/cluster-templates/base-templates/debug-workflow.json +0 -375
- package/cluster-templates/base-templates/full-workflow.json +0 -581
- package/cluster-templates/base-templates/single-worker.json +0 -63
- package/cluster-templates/base-templates/worker-validator.json +0 -208
- package/cluster-templates/conductor-bootstrap.json +0 -124
- package/docker/zeroshot-cluster/Dockerfile +0 -152
- package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
- package/lib/completion.js +0 -174
- package/lib/docker-config.js +0 -220
- package/lib/git-remote-utils.js +0 -165
- package/lib/id-detector.js +0 -56
- package/lib/provider-defaults.js +0 -62
- package/lib/provider-detection.js +0 -59
- package/lib/provider-names.js +0 -57
- package/lib/settings/claude-auth.js +0 -78
- package/lib/settings.js +0 -503
- package/lib/stream-json-parser.js +0 -10
- package/scripts/fix-node-pty-permissions.js +0 -75
- package/scripts/record-demo.sh +0 -279
- package/scripts/setup-merge-queue.sh +0 -170
- package/scripts/test-install.sh +0 -40
- package/scripts/validate-templates.js +0 -100
- package/src/agent/agent-config.js +0 -246
- package/src/agent/agent-context-builder.js +0 -444
- package/src/agent/agent-hook-executor.js +0 -599
- package/src/agent/agent-lifecycle.js +0 -743
- package/src/agent/agent-stuck-detector.js +0 -256
- package/src/agent/agent-task-executor.js +0 -1690
- package/src/agent/agent-trigger-evaluator.js +0 -67
- package/src/agent/output-extraction.js +0 -217
- package/src/agent/output-reformatter.js +0 -175
- package/src/agent/schema-utils.js +0 -146
- package/src/agent-wrapper.js +0 -579
- package/src/agents/git-pusher-template.js +0 -285
- package/src/attach/attach-client.js +0 -438
- package/src/attach/attach-server.js +0 -543
- package/src/attach/index.js +0 -35
- package/src/attach/protocol.js +0 -220
- package/src/attach/ring-buffer.js +0 -121
- package/src/attach/socket-discovery.js +0 -267
- package/src/claude-task-runner.js +0 -574
- package/src/config-router.js +0 -80
- package/src/config-validator.js +0 -1925
- package/src/copy-worker.js +0 -43
- package/src/input-helpers.js +0 -65
- package/src/isolation-manager.js +0 -1471
- package/src/issue-providers/README.md +0 -305
- package/src/issue-providers/azure-devops-provider.js +0 -273
- package/src/issue-providers/base-provider.js +0 -232
- package/src/issue-providers/github-provider.js +0 -179
- package/src/issue-providers/gitlab-provider.js +0 -241
- package/src/issue-providers/index.js +0 -196
- package/src/issue-providers/jira-provider.js +0 -239
- package/src/ledger.js +0 -631
- package/src/lib/safe-exec.js +0 -88
- package/src/logic-engine.js +0 -223
- package/src/message-bus-bridge.js +0 -139
- package/src/message-bus.js +0 -249
- package/src/name-generator.js +0 -232
- package/src/orchestrator.js +0 -2815
- package/src/preflight.js +0 -639
- package/src/process-metrics.js +0 -608
- package/src/providers/anthropic/cli-builder.js +0 -73
- package/src/providers/anthropic/index.js +0 -204
- package/src/providers/anthropic/models.js +0 -23
- package/src/providers/anthropic/output-parser.js +0 -177
- package/src/providers/base-provider.js +0 -251
- package/src/providers/capabilities.js +0 -60
- package/src/providers/google/cli-builder.js +0 -55
- package/src/providers/google/index.js +0 -116
- package/src/providers/google/models.js +0 -24
- package/src/providers/google/output-parser.js +0 -101
- package/src/providers/index.js +0 -91
- package/src/providers/openai/cli-builder.js +0 -133
- package/src/providers/openai/index.js +0 -136
- package/src/providers/openai/models.js +0 -21
- package/src/providers/openai/output-parser.js +0 -143
- package/src/providers/opencode/cli-builder.js +0 -42
- package/src/providers/opencode/index.js +0 -103
- package/src/providers/opencode/models.js +0 -55
- package/src/providers/opencode/output-parser.js +0 -122
- package/src/schemas/sub-cluster.js +0 -185
- package/src/status-footer.js +0 -1026
- package/src/sub-cluster-wrapper.js +0 -601
- package/src/task-runner.js +0 -30
- package/src/template-resolver.js +0 -372
- package/src/tui/CHANGES.txt +0 -133
- package/src/tui/LAYOUT.md +0 -261
- package/src/tui/README.txt +0 -192
- package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
- package/src/tui/data-poller.js +0 -349
- package/src/tui/demo.js +0 -208
- package/src/tui/formatters.js +0 -129
- package/src/tui/index.js +0 -193
- package/src/tui/keybindings.js +0 -324
- package/src/tui/layout.js +0 -334
- package/src/tui/renderer.js +0 -184
- package/task-lib/attachable-watcher.js +0 -309
- package/task-lib/claude-recovery.js +0 -156
- package/task-lib/commands/clean.js +0 -50
- package/task-lib/commands/episodes.js +0 -105
- package/task-lib/commands/get-log-path.js +0 -23
- package/task-lib/commands/kill.js +0 -32
- package/task-lib/commands/list.js +0 -105
- package/task-lib/commands/logs.js +0 -453
- package/task-lib/commands/resume.js +0 -42
- package/task-lib/commands/run.js +0 -57
- package/task-lib/commands/schedule.js +0 -105
- package/task-lib/commands/scheduler-cmd.js +0 -96
- package/task-lib/commands/schedules.js +0 -148
- package/task-lib/commands/status.js +0 -44
- package/task-lib/commands/unschedule.js +0 -16
- package/task-lib/completion.js +0 -9
- package/task-lib/config.js +0 -8
- package/task-lib/name-generator.js +0 -230
- package/task-lib/package.json +0 -3
- package/task-lib/runner.js +0 -201
- package/task-lib/scheduler.js +0 -252
- package/task-lib/store.js +0 -529
- package/task-lib/tui/formatters.js +0 -215
- package/task-lib/tui/index.js +0 -197
- package/task-lib/tui/layout.js +0 -111
- package/task-lib/tui/renderer.js +0 -129
- package/task-lib/tui.js +0 -425
- 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;
|
package/task-lib/watcher.js
DELETED
|
@@ -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
|
-
});
|