@covibes/zeroshot 1.0.1 → 1.1.3
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/CHANGELOG.md +46 -0
- package/README.md +2 -0
- package/cli/index.js +151 -208
- package/cli/message-formatter-utils.js +75 -0
- package/cli/message-formatters-normal.js +214 -0
- package/cli/message-formatters-watch.js +181 -0
- package/cluster-templates/base-templates/full-workflow.json +10 -5
- package/docker/zeroshot-cluster/Dockerfile +6 -0
- package/package.json +5 -2
- package/src/agent/agent-task-executor.js +237 -112
- package/src/isolation-manager.js +94 -51
- package/src/orchestrator.js +45 -10
- package/src/preflight.js +383 -0
- package/src/process-metrics.js +546 -0
- package/src/status-footer.js +543 -0
- package/task-lib/attachable-watcher.js +202 -0
- package/task-lib/commands/clean.js +50 -0
- package/task-lib/commands/get-log-path.js +23 -0
- package/task-lib/commands/kill.js +32 -0
- package/task-lib/commands/list.js +105 -0
- package/task-lib/commands/logs.js +411 -0
- package/task-lib/commands/resume.js +41 -0
- package/task-lib/commands/run.js +48 -0
- package/task-lib/commands/schedule.js +105 -0
- package/task-lib/commands/scheduler-cmd.js +96 -0
- package/task-lib/commands/schedules.js +98 -0
- package/task-lib/commands/status.js +44 -0
- package/task-lib/commands/unschedule.js +16 -0
- package/task-lib/completion.js +9 -0
- package/task-lib/config.js +10 -0
- package/task-lib/name-generator.js +230 -0
- package/task-lib/package.json +3 -0
- package/task-lib/runner.js +123 -0
- package/task-lib/scheduler.js +252 -0
- package/task-lib/store.js +217 -0
- package/task-lib/tui/formatters.js +166 -0
- package/task-lib/tui/index.js +197 -0
- package/task-lib/tui/layout.js +111 -0
- package/task-lib/tui/renderer.js +119 -0
- package/task-lib/tui.js +384 -0
- package/task-lib/watcher.js +162 -0
- package/cluster-templates/conductor-junior-bootstrap.json +0 -69
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI - Task Logs Dashboard
|
|
3
|
+
*
|
|
4
|
+
* Coordinates:
|
|
5
|
+
* - Screen and layout
|
|
6
|
+
* - Log file polling
|
|
7
|
+
* - Rendering
|
|
8
|
+
* - Keybindings
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import blessed from 'blessed';
|
|
12
|
+
import { createLayout } from './layout.js';
|
|
13
|
+
import Renderer from './renderer.js';
|
|
14
|
+
import { statSync, openSync, readSync, closeSync } from 'fs';
|
|
15
|
+
|
|
16
|
+
class TaskLogsTUI {
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.taskId = options.taskId;
|
|
19
|
+
this.logFile = options.logFile;
|
|
20
|
+
this.taskInfo = options.taskInfo;
|
|
21
|
+
this.pid = options.pid;
|
|
22
|
+
|
|
23
|
+
// State
|
|
24
|
+
this.lastSize = 0;
|
|
25
|
+
this.pollInterval = null;
|
|
26
|
+
this.widgets = null;
|
|
27
|
+
this.screen = null;
|
|
28
|
+
this.renderer = null;
|
|
29
|
+
this.resourceStats = { cpu: 0, memory: 0 };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async start() {
|
|
33
|
+
// Create screen
|
|
34
|
+
this.screen = blessed.screen({
|
|
35
|
+
smartCSR: true,
|
|
36
|
+
title: `Task Logs: ${this.taskId}`,
|
|
37
|
+
dockBorders: true,
|
|
38
|
+
fullUnicode: true,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Create layout
|
|
42
|
+
this.widgets = createLayout(this.screen, this.taskId);
|
|
43
|
+
|
|
44
|
+
// Create renderer
|
|
45
|
+
this.renderer = new Renderer(this.widgets, this.screen);
|
|
46
|
+
|
|
47
|
+
// Setup keybindings
|
|
48
|
+
this._setupKeybindings();
|
|
49
|
+
|
|
50
|
+
// Render initial task info
|
|
51
|
+
this.renderer.renderTaskInfo(this.taskInfo, this.resourceStats);
|
|
52
|
+
|
|
53
|
+
// Read existing log content
|
|
54
|
+
await this._readExistingLogs();
|
|
55
|
+
|
|
56
|
+
// Start polling for new content
|
|
57
|
+
this._startPolling();
|
|
58
|
+
|
|
59
|
+
// Initial render
|
|
60
|
+
this.screen.render();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_setupKeybindings() {
|
|
64
|
+
// Quit on 'q' or Ctrl+C
|
|
65
|
+
this.screen.key(['q', 'C-c'], () => {
|
|
66
|
+
this.exit();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Scroll with arrow keys (blessed handles this automatically for logsBox)
|
|
70
|
+
// Just ensure logsBox has focus
|
|
71
|
+
this.widgets.logsBox.focus();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_readExistingLogs() {
|
|
75
|
+
try {
|
|
76
|
+
const fd = openSync(this.logFile, 'r');
|
|
77
|
+
const size = statSync(this.logFile).size;
|
|
78
|
+
|
|
79
|
+
if (size > 0) {
|
|
80
|
+
const buffer = Buffer.alloc(size);
|
|
81
|
+
readSync(fd, buffer, 0, size, 0);
|
|
82
|
+
closeSync(fd);
|
|
83
|
+
|
|
84
|
+
const content = buffer.toString();
|
|
85
|
+
const lines = content.split('\n').filter((l) => l.trim());
|
|
86
|
+
|
|
87
|
+
this.renderer.renderLogLines(lines);
|
|
88
|
+
this.screen.render();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.lastSize = size;
|
|
92
|
+
} catch {
|
|
93
|
+
// File might not exist yet
|
|
94
|
+
this.lastSize = 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_startPolling() {
|
|
99
|
+
let noChangeCount = 0;
|
|
100
|
+
|
|
101
|
+
this.pollInterval = setInterval(() => {
|
|
102
|
+
try {
|
|
103
|
+
const currentSize = statSync(this.logFile).size;
|
|
104
|
+
|
|
105
|
+
if (currentSize > this.lastSize) {
|
|
106
|
+
// Read new content
|
|
107
|
+
const buffer = Buffer.alloc(currentSize - this.lastSize);
|
|
108
|
+
const fd = openSync(this.logFile, 'r');
|
|
109
|
+
readSync(fd, buffer, 0, buffer.length, this.lastSize);
|
|
110
|
+
closeSync(fd);
|
|
111
|
+
|
|
112
|
+
// Parse and render new lines
|
|
113
|
+
const newContent = buffer.toString();
|
|
114
|
+
const newLines = newContent.split('\n').filter((l) => l.trim());
|
|
115
|
+
|
|
116
|
+
this.renderer.renderLogLines(newLines);
|
|
117
|
+
this.screen.render();
|
|
118
|
+
|
|
119
|
+
this.lastSize = currentSize;
|
|
120
|
+
noChangeCount = 0;
|
|
121
|
+
} else {
|
|
122
|
+
noChangeCount++;
|
|
123
|
+
|
|
124
|
+
// Check if process is still running after 5 seconds of no output
|
|
125
|
+
if (noChangeCount >= 10 && this.pid && !this._isProcessRunning(this.pid)) {
|
|
126
|
+
// Read any final content
|
|
127
|
+
const finalSize = statSync(this.logFile).size;
|
|
128
|
+
if (finalSize > this.lastSize) {
|
|
129
|
+
const buffer = Buffer.alloc(finalSize - this.lastSize);
|
|
130
|
+
const fd = openSync(this.logFile, 'r');
|
|
131
|
+
readSync(fd, buffer, 0, buffer.length, this.lastSize);
|
|
132
|
+
closeSync(fd);
|
|
133
|
+
|
|
134
|
+
const finalContent = buffer.toString();
|
|
135
|
+
const finalLines = finalContent.split('\n').filter((l) => l.trim());
|
|
136
|
+
|
|
137
|
+
this.renderer.renderLogLines(finalLines);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Update task status to completed
|
|
141
|
+
this.taskInfo.status = 'completed';
|
|
142
|
+
this.renderer.renderTaskInfo(this.taskInfo, this.resourceStats);
|
|
143
|
+
this.screen.render();
|
|
144
|
+
|
|
145
|
+
// Show completion message
|
|
146
|
+
this.widgets.logsBox.log('{dim}--- Task completed ---{/}');
|
|
147
|
+
this.screen.render();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Update resource stats periodically
|
|
152
|
+
if (this.pid && this._isProcessRunning(this.pid)) {
|
|
153
|
+
this.resourceStats = this._getResourceStats(this.pid);
|
|
154
|
+
this.renderer.renderTaskInfo(this.taskInfo, this.resourceStats);
|
|
155
|
+
this.screen.render();
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// File might have been deleted
|
|
159
|
+
this.widgets.logsBox.log(`{red-fg}Error reading log: ${error.message}{/}`);
|
|
160
|
+
this.screen.render();
|
|
161
|
+
}
|
|
162
|
+
}, 500); // Poll every 500ms
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
_isProcessRunning(pid) {
|
|
166
|
+
if (!pid) return false;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// Send signal 0 (no-op) to check if process exists
|
|
170
|
+
process.kill(pid, 0);
|
|
171
|
+
return true;
|
|
172
|
+
} catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
_getResourceStats(_pid) {
|
|
178
|
+
// This is a simplified version - full implementation would use pidusage or similar
|
|
179
|
+
// For now, return dummy data (the important part is the UI structure)
|
|
180
|
+
return {
|
|
181
|
+
cpu: 0,
|
|
182
|
+
memory: 0,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
exit() {
|
|
187
|
+
if (this.pollInterval) {
|
|
188
|
+
clearInterval(this.pollInterval);
|
|
189
|
+
}
|
|
190
|
+
if (this.screen) {
|
|
191
|
+
this.screen.destroy();
|
|
192
|
+
}
|
|
193
|
+
process.exit(0);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export default TaskLogsTUI;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Layout for Task Logs
|
|
3
|
+
* Creates a blessed layout for monitoring a single task
|
|
4
|
+
*
|
|
5
|
+
* Layout:
|
|
6
|
+
* - Top: Task info box (ID, status, runtime, CPU, memory)
|
|
7
|
+
* - Middle: Live logs (scrollable, auto-scroll to bottom)
|
|
8
|
+
* - Bottom: Help bar (keyboard shortcuts)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import blessed from 'blessed';
|
|
12
|
+
import contrib from 'blessed-contrib';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create task logs TUI layout
|
|
16
|
+
* @param {blessed.screen} screen - Blessed screen instance
|
|
17
|
+
* @param {string} taskId - Task ID being monitored
|
|
18
|
+
* @returns {object} Layout widgets
|
|
19
|
+
*/
|
|
20
|
+
function createLayout(screen, taskId) {
|
|
21
|
+
// Create 20x12 grid for responsive layout
|
|
22
|
+
const grid = new contrib.grid({ rows: 20, cols: 12, screen });
|
|
23
|
+
|
|
24
|
+
// ============================================================
|
|
25
|
+
// TASK INFO BOX (3 rows x 12 cols)
|
|
26
|
+
// Shows: Task ID, Status, Runtime, CPU, Memory
|
|
27
|
+
// ============================================================
|
|
28
|
+
|
|
29
|
+
const taskInfoBox = grid.set(0, 0, 3, 12, blessed.box, {
|
|
30
|
+
label: ` Task: ${taskId} `,
|
|
31
|
+
content: '',
|
|
32
|
+
tags: true,
|
|
33
|
+
border: { type: 'line', fg: 'cyan' },
|
|
34
|
+
style: {
|
|
35
|
+
border: { fg: 'cyan' },
|
|
36
|
+
label: { fg: 'cyan' },
|
|
37
|
+
},
|
|
38
|
+
padding: {
|
|
39
|
+
left: 2,
|
|
40
|
+
right: 2,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ============================================================
|
|
45
|
+
// LIVE LOGS (15 rows x 12 cols)
|
|
46
|
+
// Scrollable log output with auto-scroll
|
|
47
|
+
// ============================================================
|
|
48
|
+
|
|
49
|
+
const logsBox = grid.set(3, 0, 15, 12, contrib.log, {
|
|
50
|
+
fg: 'white',
|
|
51
|
+
label: ' Live Logs ',
|
|
52
|
+
border: { type: 'line', fg: 'cyan' },
|
|
53
|
+
tags: true,
|
|
54
|
+
style: {
|
|
55
|
+
border: { fg: 'cyan' },
|
|
56
|
+
label: { fg: 'cyan' },
|
|
57
|
+
text: { fg: 'white' },
|
|
58
|
+
},
|
|
59
|
+
scrollable: true,
|
|
60
|
+
mouse: true,
|
|
61
|
+
keyable: true,
|
|
62
|
+
alwaysScroll: true,
|
|
63
|
+
scrollbar: {
|
|
64
|
+
ch: ' ',
|
|
65
|
+
track: {
|
|
66
|
+
bg: 'gray',
|
|
67
|
+
},
|
|
68
|
+
style: {
|
|
69
|
+
inverse: true,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ============================================================
|
|
75
|
+
// HELP BAR (2 rows x 12 cols)
|
|
76
|
+
// Keyboard shortcuts
|
|
77
|
+
// ============================================================
|
|
78
|
+
|
|
79
|
+
const helpBar = grid.set(18, 0, 2, 12, blessed.box, {
|
|
80
|
+
label: ' Help ',
|
|
81
|
+
content:
|
|
82
|
+
'{cyan-fg}[↑/↓]{/} Scroll ' +
|
|
83
|
+
'{cyan-fg}[PgUp/PgDn]{/} Page ' +
|
|
84
|
+
'{cyan-fg}[Home/End]{/} Top/Bottom ' +
|
|
85
|
+
'{cyan-fg}[q]{/} Quit',
|
|
86
|
+
tags: true,
|
|
87
|
+
border: { type: 'line', fg: 'cyan' },
|
|
88
|
+
style: {
|
|
89
|
+
border: { fg: 'cyan' },
|
|
90
|
+
label: { fg: 'cyan' },
|
|
91
|
+
text: { fg: 'white' },
|
|
92
|
+
},
|
|
93
|
+
padding: {
|
|
94
|
+
left: 1,
|
|
95
|
+
right: 1,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Focus on logs by default
|
|
100
|
+
logsBox.focus();
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
screen,
|
|
104
|
+
grid,
|
|
105
|
+
taskInfoBox,
|
|
106
|
+
logsBox,
|
|
107
|
+
helpBar,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { createLayout };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Renderer for Task Logs
|
|
3
|
+
* Transforms task data and log events into widget updates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { formatTimestamp, formatBytes, formatCPU, stateIcon, parseEvent } from './formatters.js';
|
|
7
|
+
|
|
8
|
+
class Renderer {
|
|
9
|
+
/**
|
|
10
|
+
* Create renderer instance
|
|
11
|
+
* @param {object} widgets - Widget objects from layout
|
|
12
|
+
* @param {object} screen - Blessed screen instance
|
|
13
|
+
*/
|
|
14
|
+
constructor(widgets, screen) {
|
|
15
|
+
if (!widgets) {
|
|
16
|
+
throw new Error('Renderer requires widgets object from layout');
|
|
17
|
+
}
|
|
18
|
+
if (!screen) {
|
|
19
|
+
throw new Error('Renderer requires screen instance');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.widgets = widgets;
|
|
23
|
+
this.screen = screen;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Render task info box
|
|
28
|
+
* @param {object} taskInfo - Task metadata
|
|
29
|
+
* @param {object} stats - CPU/memory stats
|
|
30
|
+
*/
|
|
31
|
+
renderTaskInfo(taskInfo, stats = {}) {
|
|
32
|
+
if (!taskInfo) return;
|
|
33
|
+
|
|
34
|
+
const icon = stateIcon(taskInfo.status || 'unknown');
|
|
35
|
+
const runtime = taskInfo.createdAt ? formatTimestamp(Date.now() - taskInfo.createdAt) : '-';
|
|
36
|
+
const cpu = stats.cpu !== undefined ? formatCPU(stats.cpu) : '0.0%';
|
|
37
|
+
const memory = stats.memory !== undefined ? formatBytes(stats.memory) : '0 B';
|
|
38
|
+
|
|
39
|
+
const content = [
|
|
40
|
+
`${icon} {bold}Status:{/bold} {white-fg}${taskInfo.status || 'unknown'}{/}`,
|
|
41
|
+
`{bold}Runtime:{/bold} {white-fg}${runtime}{/}`,
|
|
42
|
+
`{bold}CPU:{/bold} {white-fg}${cpu}{/} {bold}Memory:{/bold} {white-fg}${memory}{/}`,
|
|
43
|
+
taskInfo.prompt
|
|
44
|
+
? `{bold}Task:{/bold} {gray-fg}${taskInfo.prompt.substring(0, 80)}${taskInfo.prompt.length > 80 ? '...' : ''}{/}`
|
|
45
|
+
: '',
|
|
46
|
+
]
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join(' ');
|
|
49
|
+
|
|
50
|
+
if (this.widgets.taskInfoBox && this.widgets.taskInfoBox.setContent) {
|
|
51
|
+
this.widgets.taskInfoBox.setContent(content);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Render log entry to logs widget
|
|
57
|
+
* @param {string} line - Raw log line
|
|
58
|
+
*/
|
|
59
|
+
renderLogLine(line) {
|
|
60
|
+
if (!line) return;
|
|
61
|
+
|
|
62
|
+
const event = parseEvent(line);
|
|
63
|
+
if (!event) return;
|
|
64
|
+
|
|
65
|
+
let logMessage = '';
|
|
66
|
+
|
|
67
|
+
switch (event.type) {
|
|
68
|
+
case 'text':
|
|
69
|
+
// Plain text output
|
|
70
|
+
logMessage = event.text;
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case 'tool':
|
|
74
|
+
// Tool invocation
|
|
75
|
+
logMessage = `{cyan-fg}[Tool: ${event.toolName}]{/}`;
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case 'error':
|
|
79
|
+
// Error message
|
|
80
|
+
logMessage = `{red-fg}[ERROR] ${event.text}{/}`;
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case 'raw':
|
|
84
|
+
// Raw non-JSON line
|
|
85
|
+
logMessage = `{gray-fg}${event.text}{/}`;
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
default:
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (this.widgets.logsBox && this.widgets.logsBox.log) {
|
|
93
|
+
this.widgets.logsBox.log(logMessage);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Render multiple log lines at once
|
|
99
|
+
* @param {string[]} lines - Array of log lines
|
|
100
|
+
*/
|
|
101
|
+
renderLogLines(lines) {
|
|
102
|
+
if (!Array.isArray(lines)) return;
|
|
103
|
+
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
this.renderLogLine(line);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Trigger screen render
|
|
111
|
+
*/
|
|
112
|
+
render() {
|
|
113
|
+
if (this.screen && this.screen.render) {
|
|
114
|
+
this.screen.render();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export default Renderer;
|