@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
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Formatting utilities for TUI display
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Format timestamp as human-readable relative time
|
|
7
|
-
* @param {number} ms - Milliseconds
|
|
8
|
-
* @returns {string} Formatted time (e.g., "2m 30s", "1h 15m")
|
|
9
|
-
*/
|
|
10
|
-
function formatTimestamp(ms) {
|
|
11
|
-
if (!ms || ms < 0) return '-';
|
|
12
|
-
|
|
13
|
-
const seconds = Math.floor(ms / 1000);
|
|
14
|
-
const minutes = Math.floor(seconds / 60);
|
|
15
|
-
const hours = Math.floor(minutes / 60);
|
|
16
|
-
const days = Math.floor(hours / 24);
|
|
17
|
-
|
|
18
|
-
if (days > 0) return `${days}d ${hours % 24}h`;
|
|
19
|
-
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
20
|
-
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
21
|
-
return `${seconds}s`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Format bytes as human-readable size
|
|
26
|
-
* @param {number} bytes - Bytes
|
|
27
|
-
* @returns {string} Formatted size (e.g., "1.5 MB", "512 KB")
|
|
28
|
-
*/
|
|
29
|
-
function formatBytes(bytes) {
|
|
30
|
-
if (!bytes || bytes === 0) return '0 B';
|
|
31
|
-
if (bytes < 0) return '-';
|
|
32
|
-
|
|
33
|
-
const units = ['B', 'KB', 'MB', 'GB'];
|
|
34
|
-
let size = bytes;
|
|
35
|
-
let unitIndex = 0;
|
|
36
|
-
|
|
37
|
-
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
38
|
-
size /= 1024;
|
|
39
|
-
unitIndex++;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Format CPU percentage
|
|
47
|
-
* @param {number} cpu - CPU percentage (0-100)
|
|
48
|
-
* @returns {string} Formatted CPU (e.g., "23.5%", "0.1%")
|
|
49
|
-
*/
|
|
50
|
-
function formatCPU(cpu) {
|
|
51
|
-
if (cpu === undefined || cpu === null || cpu < 0) return '0.0%';
|
|
52
|
-
return `${cpu.toFixed(1)}%`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Get state icon and color
|
|
57
|
-
* @param {string} state - Task state (pending, running, completed, failed, etc.)
|
|
58
|
-
* @returns {string} Colored icon
|
|
59
|
-
*/
|
|
60
|
-
function stateIcon(state) {
|
|
61
|
-
const icons = {
|
|
62
|
-
pending: '○',
|
|
63
|
-
running: '●',
|
|
64
|
-
completed: '✓',
|
|
65
|
-
failed: '✗',
|
|
66
|
-
killed: '⊗',
|
|
67
|
-
unknown: '?',
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const colors = {
|
|
71
|
-
pending: 'gray',
|
|
72
|
-
running: 'cyan',
|
|
73
|
-
completed: 'green',
|
|
74
|
-
failed: 'red',
|
|
75
|
-
killed: 'red',
|
|
76
|
-
unknown: 'gray',
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const icon = icons[state] || icons.unknown;
|
|
80
|
-
const color = colors[state] || colors.unknown;
|
|
81
|
-
|
|
82
|
-
return `{${color}-fg}${icon}{/}`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Truncate string to max length with ellipsis
|
|
87
|
-
* @param {string} str - String to truncate
|
|
88
|
-
* @param {number} maxLen - Maximum length
|
|
89
|
-
* @returns {string} Truncated string
|
|
90
|
-
*/
|
|
91
|
-
function truncate(str, maxLen) {
|
|
92
|
-
if (!str) return '';
|
|
93
|
-
if (str.length <= maxLen) return str;
|
|
94
|
-
return str.substring(0, maxLen - 1) + '…';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Parse event type from Claude JSON stream
|
|
99
|
-
* @param {string} line - Raw log line
|
|
100
|
-
* @returns {object|null} Parsed event with type, text, toolName, error
|
|
101
|
-
*/
|
|
102
|
-
function parseEvent(line) {
|
|
103
|
-
const { trimmed, timestamp } = extractTimestamp(line);
|
|
104
|
-
|
|
105
|
-
// Keep non-JSON lines as-is
|
|
106
|
-
if (!trimmed.startsWith('{')) {
|
|
107
|
-
return trimmed ? { type: 'raw', text: trimmed, timestamp } : null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Parse JSON events
|
|
111
|
-
const event = safeParseEvent(trimmed);
|
|
112
|
-
if (!event) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return parseStreamEvent(event, timestamp);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function extractTimestamp(line) {
|
|
120
|
-
let trimmed = line.trim();
|
|
121
|
-
|
|
122
|
-
// Strip timestamp prefix if present: [1234567890]{...} -> {...}
|
|
123
|
-
const timestampMatch = trimmed.match(/^\[(\d+)\](.*)$/);
|
|
124
|
-
let timestamp = Date.now();
|
|
125
|
-
if (timestampMatch) {
|
|
126
|
-
timestamp = parseInt(timestampMatch[1]);
|
|
127
|
-
trimmed = timestampMatch[2];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return { trimmed, timestamp };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function safeParseEvent(trimmed) {
|
|
134
|
-
try {
|
|
135
|
-
return JSON.parse(trimmed);
|
|
136
|
-
} catch {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function parseStreamEvent(event, timestamp) {
|
|
142
|
-
if (event.type === 'stream_event') {
|
|
143
|
-
return parseStreamDelta(event, timestamp);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (event.type === 'assistant') {
|
|
147
|
-
return parseAssistantMessage(event, timestamp);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (event.type === 'result') {
|
|
151
|
-
return parseResultEvent(event, timestamp);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function parseStreamDelta(event, timestamp) {
|
|
158
|
-
const eventType = event.event?.type;
|
|
159
|
-
|
|
160
|
-
if (eventType === 'content_block_delta') {
|
|
161
|
-
return {
|
|
162
|
-
type: 'text',
|
|
163
|
-
text: event.event?.delta?.text || '',
|
|
164
|
-
timestamp,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (eventType === 'content_block_start') {
|
|
169
|
-
return parseToolUseEvent(event.event?.content_block, timestamp);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function parseToolUseEvent(block, timestamp) {
|
|
176
|
-
if (block?.type === 'tool_use' && block?.name) {
|
|
177
|
-
return {
|
|
178
|
-
type: 'tool',
|
|
179
|
-
toolName: block.name,
|
|
180
|
-
timestamp,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function parseAssistantMessage(event, timestamp) {
|
|
188
|
-
const contentBlocks = event.message?.content;
|
|
189
|
-
if (!Array.isArray(contentBlocks)) {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
let text = '';
|
|
194
|
-
for (const content of contentBlocks) {
|
|
195
|
-
if (content.type === 'text') {
|
|
196
|
-
text += content.text;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return text ? { type: 'text', text, timestamp } : null;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function parseResultEvent(event, timestamp) {
|
|
204
|
-
if (!event.is_error) {
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
type: 'error',
|
|
210
|
-
text: event.result || 'Unknown error',
|
|
211
|
-
timestamp,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export { formatTimestamp, formatBytes, formatCPU, stateIcon, truncate, parseEvent };
|
package/task-lib/tui/index.js
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
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;
|
package/task-lib/tui/layout.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
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 };
|
package/task-lib/tui/renderer.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
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
|
-
* Format task prompt for display (truncate if needed)
|
|
28
|
-
* @param {string|undefined} prompt - Task prompt
|
|
29
|
-
* @returns {string} Formatted prompt or empty string
|
|
30
|
-
* @private
|
|
31
|
-
*/
|
|
32
|
-
_formatTaskPrompt(prompt) {
|
|
33
|
-
if (!prompt) return '';
|
|
34
|
-
const truncated = prompt.length > 80 ? prompt.substring(0, 80) + '...' : prompt;
|
|
35
|
-
return `{bold}Task:{/bold} {gray-fg}${truncated}{/}`;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Render task info box
|
|
40
|
-
* @param {object} taskInfo - Task metadata
|
|
41
|
-
* @param {object} stats - CPU/memory stats
|
|
42
|
-
*/
|
|
43
|
-
renderTaskInfo(taskInfo, stats = {}) {
|
|
44
|
-
if (!taskInfo) return;
|
|
45
|
-
|
|
46
|
-
const icon = stateIcon(taskInfo.status || 'unknown');
|
|
47
|
-
const runtime = taskInfo.createdAt ? formatTimestamp(Date.now() - taskInfo.createdAt) : '-';
|
|
48
|
-
const cpu = stats.cpu !== undefined ? formatCPU(stats.cpu) : '0.0%';
|
|
49
|
-
const memory = stats.memory !== undefined ? formatBytes(stats.memory) : '0 B';
|
|
50
|
-
|
|
51
|
-
const content = [
|
|
52
|
-
`${icon} {bold}Status:{/bold} {white-fg}${taskInfo.status || 'unknown'}{/}`,
|
|
53
|
-
`{bold}Runtime:{/bold} {white-fg}${runtime}{/}`,
|
|
54
|
-
`{bold}CPU:{/bold} {white-fg}${cpu}{/} {bold}Memory:{/bold} {white-fg}${memory}{/}`,
|
|
55
|
-
this._formatTaskPrompt(taskInfo.prompt),
|
|
56
|
-
]
|
|
57
|
-
.filter(Boolean)
|
|
58
|
-
.join(' ');
|
|
59
|
-
|
|
60
|
-
if (this.widgets.taskInfoBox && this.widgets.taskInfoBox.setContent) {
|
|
61
|
-
this.widgets.taskInfoBox.setContent(content);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Render log entry to logs widget
|
|
67
|
-
* @param {string} line - Raw log line
|
|
68
|
-
*/
|
|
69
|
-
renderLogLine(line) {
|
|
70
|
-
if (!line) return;
|
|
71
|
-
|
|
72
|
-
const event = parseEvent(line);
|
|
73
|
-
if (!event) return;
|
|
74
|
-
|
|
75
|
-
let logMessage = '';
|
|
76
|
-
|
|
77
|
-
switch (event.type) {
|
|
78
|
-
case 'text':
|
|
79
|
-
// Plain text output
|
|
80
|
-
logMessage = event.text;
|
|
81
|
-
break;
|
|
82
|
-
|
|
83
|
-
case 'tool':
|
|
84
|
-
// Tool invocation
|
|
85
|
-
logMessage = `{cyan-fg}[Tool: ${event.toolName}]{/}`;
|
|
86
|
-
break;
|
|
87
|
-
|
|
88
|
-
case 'error':
|
|
89
|
-
// Error message
|
|
90
|
-
logMessage = `{red-fg}[ERROR] ${event.text}{/}`;
|
|
91
|
-
break;
|
|
92
|
-
|
|
93
|
-
case 'raw':
|
|
94
|
-
// Raw non-JSON line
|
|
95
|
-
logMessage = `{gray-fg}${event.text}{/}`;
|
|
96
|
-
break;
|
|
97
|
-
|
|
98
|
-
default:
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (this.widgets.logsBox && this.widgets.logsBox.log) {
|
|
103
|
-
this.widgets.logsBox.log(logMessage);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Render multiple log lines at once
|
|
109
|
-
* @param {string[]} lines - Array of log lines
|
|
110
|
-
*/
|
|
111
|
-
renderLogLines(lines) {
|
|
112
|
-
if (!Array.isArray(lines)) return;
|
|
113
|
-
|
|
114
|
-
for (const line of lines) {
|
|
115
|
-
this.renderLogLine(line);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Trigger screen render
|
|
121
|
-
*/
|
|
122
|
-
render() {
|
|
123
|
-
if (this.screen && this.screen.render) {
|
|
124
|
-
this.screen.render();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export default Renderer;
|