@covibes/zeroshot 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -14
- package/cli/commands/providers.js +8 -9
- package/cli/index.js +3032 -2409
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +1 -1
- package/cluster-templates/base-templates/full-workflow.json +72 -188
- package/cluster-templates/base-templates/worker-validator.json +59 -3
- package/cluster-templates/conductor-bootstrap.json +4 -4
- package/lib/docker-config.js +8 -0
- package/lib/git-remote-utils.js +165 -0
- package/lib/id-detector.js +10 -7
- package/lib/provider-defaults.js +62 -0
- package/lib/provider-names.js +2 -1
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +161 -63
- package/package.json +7 -2
- package/scripts/setup-merge-queue.sh +170 -0
- package/src/agent/agent-config.js +135 -82
- package/src/agent/agent-context-builder.js +297 -188
- package/src/agent/agent-hook-executor.js +310 -113
- package/src/agent/agent-lifecycle.js +385 -325
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +824 -565
- package/src/agent/output-extraction.js +41 -24
- package/src/agent/output-reformatter.js +1 -1
- package/src/agent/schema-utils.js +108 -73
- package/src/agent-wrapper.js +10 -1
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +85 -34
- package/src/config-validator.js +922 -657
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +289 -199
- package/src/issue-providers/README.md +305 -0
- package/src/issue-providers/azure-devops-provider.js +273 -0
- package/src/issue-providers/base-provider.js +232 -0
- package/src/issue-providers/github-provider.js +179 -0
- package/src/issue-providers/gitlab-provider.js +241 -0
- package/src/issue-providers/index.js +196 -0
- package/src/issue-providers/jira-provider.js +239 -0
- package/src/ledger.js +22 -5
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1107 -811
- package/src/preflight.js +313 -159
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +53 -25
- package/src/providers/anthropic/index.js +72 -2
- package/src/providers/anthropic/output-parser.js +32 -14
- package/src/providers/base-provider.js +70 -0
- package/src/providers/capabilities.js +9 -0
- package/src/providers/google/output-parser.js +47 -38
- package/src/providers/index.js +19 -3
- package/src/providers/openai/cli-builder.js +14 -3
- package/src/providers/openai/index.js +1 -0
- package/src/providers/openai/output-parser.js +44 -30
- package/src/providers/opencode/cli-builder.js +42 -0
- package/src/providers/opencode/index.js +103 -0
- package/src/providers/opencode/models.js +55 -0
- package/src/providers/opencode/output-parser.js +122 -0
- package/src/schemas/sub-cluster.js +68 -39
- package/src/status-footer.js +94 -48
- package/src/sub-cluster-wrapper.js +76 -35
- package/src/template-resolver.js +12 -9
- package/src/tui/data-poller.js +123 -99
- package/src/tui/formatters.js +4 -3
- package/src/tui/keybindings.js +259 -318
- package/src/tui/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +118 -81
- package/task-lib/claude-recovery.js +61 -24
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +2 -2
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +84 -27
- package/task-lib/scheduler.js +1 -1
- package/task-lib/store.js +467 -168
- package/task-lib/tui/formatters.js +94 -45
- package/task-lib/tui/renderer.js +13 -3
- package/task-lib/tui.js +73 -32
- package/task-lib/watcher.js +128 -90
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -139
package/src/tui/renderer.js
CHANGED
|
@@ -37,11 +37,9 @@ class Renderer {
|
|
|
37
37
|
* @param {Array} clusters - Array of cluster objects
|
|
38
38
|
*/
|
|
39
39
|
renderClustersTable(clusters) {
|
|
40
|
-
|
|
41
|
-
clusters = [];
|
|
42
|
-
}
|
|
40
|
+
const clusterList = !clusters || !Array.isArray(clusters) ? [] : clusters;
|
|
43
41
|
|
|
44
|
-
const data =
|
|
42
|
+
const data = clusterList.map((c) => {
|
|
45
43
|
if (!c) return ['', '', '', ''];
|
|
46
44
|
|
|
47
45
|
const icon = stateIcon(c.state || 'unknown');
|
|
@@ -68,23 +66,19 @@ class Renderer {
|
|
|
68
66
|
* @param {Map} resourceStats - Map of PID -> {cpu, memory}
|
|
69
67
|
*/
|
|
70
68
|
renderSystemStats(clusters, resourceStats) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
if (!resourceStats || !(resourceStats instanceof Map)) {
|
|
75
|
-
resourceStats = new Map();
|
|
76
|
-
}
|
|
69
|
+
const clusterList = !clusters || !Array.isArray(clusters) ? [] : clusters;
|
|
70
|
+
const statsMap = !resourceStats || !(resourceStats instanceof Map) ? new Map() : resourceStats;
|
|
77
71
|
|
|
78
72
|
// Calculate aggregate stats
|
|
79
|
-
const activeClusters =
|
|
80
|
-
const totalAgents =
|
|
73
|
+
const activeClusters = clusterList.filter((c) => c && c.state === 'running').length;
|
|
74
|
+
const totalAgents = clusterList.reduce((sum, c) => sum + (c?.agentCount || 0), 0);
|
|
81
75
|
|
|
82
76
|
// Calculate average CPU and memory from resource stats
|
|
83
77
|
let totalCpu = 0;
|
|
84
78
|
let totalMemory = 0;
|
|
85
79
|
let statCount = 0;
|
|
86
80
|
|
|
87
|
-
|
|
81
|
+
statsMap.forEach((stat) => {
|
|
88
82
|
if (stat && typeof stat.cpu === 'number' && typeof stat.memory === 'number') {
|
|
89
83
|
totalCpu += stat.cpu;
|
|
90
84
|
totalMemory += stat.memory;
|
|
@@ -125,18 +119,14 @@ class Renderer {
|
|
|
125
119
|
return;
|
|
126
120
|
}
|
|
127
121
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
if (!resourceStats || !(resourceStats instanceof Map)) {
|
|
132
|
-
resourceStats = new Map();
|
|
133
|
-
}
|
|
122
|
+
const agentList = !agents || !Array.isArray(agents) ? [] : agents;
|
|
123
|
+
const statsMap = !resourceStats || !(resourceStats instanceof Map) ? new Map() : resourceStats;
|
|
134
124
|
|
|
135
|
-
const data =
|
|
125
|
+
const data = agentList.map((a) => {
|
|
136
126
|
if (!a) return ['', '', '', '', '', ''];
|
|
137
127
|
|
|
138
128
|
const pid = a.pid;
|
|
139
|
-
const stats =
|
|
129
|
+
const stats = statsMap.get(pid) || { cpu: 0, memory: 0 };
|
|
140
130
|
|
|
141
131
|
const agentId = truncate(a.id || '', 12);
|
|
142
132
|
const role = truncate(a.role || '', 12);
|
|
@@ -96,6 +96,115 @@ let finalResultJson = null;
|
|
|
96
96
|
let outputBuffer = '';
|
|
97
97
|
let streamingModeError = null;
|
|
98
98
|
|
|
99
|
+
function splitBufferLines(buffer, chunk) {
|
|
100
|
+
const nextBuffer = buffer + chunk;
|
|
101
|
+
const lines = nextBuffer.split('\n');
|
|
102
|
+
const remaining = lines.pop() || '';
|
|
103
|
+
return { lines, remaining };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function captureStreamingError(line, timestamp) {
|
|
107
|
+
if (!enableRecovery) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const detectedError = detectStreamingModeError(line);
|
|
112
|
+
if (!detectedError) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
streamingModeError = { ...detectedError, timestamp };
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function maybeCaptureStructuredOutput(line) {
|
|
121
|
+
try {
|
|
122
|
+
const json = JSON.parse(line);
|
|
123
|
+
if (json.structured_output) {
|
|
124
|
+
finalResultJson = line;
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// Not JSON, skip
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function handleSilentJsonLines(lines, timestamp) {
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
if (!line.trim()) continue;
|
|
134
|
+
if (captureStreamingError(line, timestamp)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
maybeCaptureStructuredOutput(line);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function handleStreamingLines(lines, timestamp) {
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
if (captureStreamingError(line, timestamp)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
log(`[${timestamp}]${line}\n`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function flushOutputBuffer(timestamp) {
|
|
151
|
+
if (!outputBuffer.trim()) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!enableRecovery) {
|
|
156
|
+
if (!silentJsonMode) {
|
|
157
|
+
log(`[${timestamp}]${outputBuffer}\n`);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (captureStreamingError(outputBuffer, timestamp)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (silentJsonMode) {
|
|
167
|
+
maybeCaptureStructuredOutput(outputBuffer);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
log(`[${timestamp}]${outputBuffer}\n`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function attemptRecovery(code, timestamp) {
|
|
175
|
+
if (!(enableRecovery && code !== 0 && streamingModeError?.sessionId)) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const recovered = recoverStructuredOutput(streamingModeError.sessionId);
|
|
180
|
+
if (recovered?.payload) {
|
|
181
|
+
const recoveredLine = JSON.stringify(recovered.payload);
|
|
182
|
+
if (silentJsonMode) {
|
|
183
|
+
finalResultJson = recoveredLine;
|
|
184
|
+
} else {
|
|
185
|
+
log(`[${timestamp}]${recoveredLine}\n`);
|
|
186
|
+
}
|
|
187
|
+
} else if (streamingModeError.line) {
|
|
188
|
+
if (silentJsonMode) {
|
|
189
|
+
log(streamingModeError.line + '\n');
|
|
190
|
+
} else {
|
|
191
|
+
log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return recovered;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function writeCompletionFooter(code, signal) {
|
|
199
|
+
if (config.outputFormat === 'json') {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
log(`\n${'='.repeat(50)}\n`);
|
|
204
|
+
log(`Finished: ${new Date().toISOString()}\n`);
|
|
205
|
+
log(`Exit code: ${code}, Signal: ${signal}\n`);
|
|
206
|
+
}
|
|
207
|
+
|
|
99
208
|
const server = new AttachServer({
|
|
100
209
|
id: taskId,
|
|
101
210
|
socketPath,
|
|
@@ -111,44 +220,13 @@ server.on('output', (data) => {
|
|
|
111
220
|
const chunk = data.toString();
|
|
112
221
|
const timestamp = Date.now();
|
|
113
222
|
|
|
223
|
+
const { lines, remaining } = splitBufferLines(outputBuffer, chunk);
|
|
224
|
+
outputBuffer = remaining;
|
|
225
|
+
|
|
114
226
|
if (silentJsonMode) {
|
|
115
|
-
|
|
116
|
-
const lines = outputBuffer.split('\n');
|
|
117
|
-
outputBuffer = lines.pop() || '';
|
|
118
|
-
|
|
119
|
-
for (const line of lines) {
|
|
120
|
-
if (!line.trim()) continue;
|
|
121
|
-
if (enableRecovery) {
|
|
122
|
-
const detectedError = detectStreamingModeError(line);
|
|
123
|
-
if (detectedError) {
|
|
124
|
-
streamingModeError = { ...detectedError, timestamp };
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
const json = JSON.parse(line);
|
|
130
|
-
if (json.structured_output) {
|
|
131
|
-
finalResultJson = line;
|
|
132
|
-
}
|
|
133
|
-
} catch {
|
|
134
|
-
// Not JSON, skip
|
|
135
|
-
}
|
|
136
|
-
}
|
|
227
|
+
handleSilentJsonLines(lines, timestamp);
|
|
137
228
|
} else {
|
|
138
|
-
|
|
139
|
-
const lines = outputBuffer.split('\n');
|
|
140
|
-
outputBuffer = lines.pop() || '';
|
|
141
|
-
|
|
142
|
-
for (const line of lines) {
|
|
143
|
-
if (enableRecovery) {
|
|
144
|
-
const detectedError = detectStreamingModeError(line);
|
|
145
|
-
if (detectedError) {
|
|
146
|
-
streamingModeError = { ...detectedError, timestamp };
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
log(`[${timestamp}]${line}\n`);
|
|
151
|
-
}
|
|
229
|
+
handleStreamingLines(lines, timestamp);
|
|
152
230
|
}
|
|
153
231
|
});
|
|
154
232
|
|
|
@@ -156,56 +234,15 @@ server.on('exit', async ({ exitCode, signal }) => {
|
|
|
156
234
|
const timestamp = Date.now();
|
|
157
235
|
const code = exitCode;
|
|
158
236
|
|
|
159
|
-
|
|
160
|
-
if (enableRecovery) {
|
|
161
|
-
const detectedError = detectStreamingModeError(outputBuffer);
|
|
162
|
-
if (detectedError) {
|
|
163
|
-
streamingModeError = { ...detectedError, timestamp };
|
|
164
|
-
} else if (silentJsonMode) {
|
|
165
|
-
try {
|
|
166
|
-
const json = JSON.parse(outputBuffer);
|
|
167
|
-
if (json.structured_output) {
|
|
168
|
-
finalResultJson = outputBuffer;
|
|
169
|
-
}
|
|
170
|
-
} catch {
|
|
171
|
-
// Not valid JSON
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
log(`[${timestamp}]${outputBuffer}\n`);
|
|
175
|
-
}
|
|
176
|
-
} else if (!silentJsonMode) {
|
|
177
|
-
log(`[${timestamp}]${outputBuffer}\n`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
237
|
+
flushOutputBuffer(timestamp);
|
|
180
238
|
|
|
181
|
-
|
|
182
|
-
if (enableRecovery && code !== 0 && streamingModeError?.sessionId) {
|
|
183
|
-
recovered = recoverStructuredOutput(streamingModeError.sessionId);
|
|
184
|
-
if (recovered?.payload) {
|
|
185
|
-
const recoveredLine = JSON.stringify(recovered.payload);
|
|
186
|
-
if (silentJsonMode) {
|
|
187
|
-
finalResultJson = recoveredLine;
|
|
188
|
-
} else {
|
|
189
|
-
log(`[${timestamp}]${recoveredLine}\n`);
|
|
190
|
-
}
|
|
191
|
-
} else if (streamingModeError.line) {
|
|
192
|
-
if (silentJsonMode) {
|
|
193
|
-
log(streamingModeError.line + '\n');
|
|
194
|
-
} else {
|
|
195
|
-
log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
239
|
+
const recovered = attemptRecovery(code, timestamp);
|
|
199
240
|
|
|
200
241
|
if (silentJsonMode && finalResultJson) {
|
|
201
242
|
log(finalResultJson + '\n');
|
|
202
243
|
}
|
|
203
244
|
|
|
204
|
-
|
|
205
|
-
log(`\n${'='.repeat(50)}\n`);
|
|
206
|
-
log(`Finished: ${new Date().toISOString()}\n`);
|
|
207
|
-
log(`Exit code: ${code}, Signal: ${signal}\n`);
|
|
208
|
-
}
|
|
245
|
+
writeCompletionFooter(code, signal);
|
|
209
246
|
|
|
210
247
|
const resolvedCode = recovered?.payload ? 0 : code;
|
|
211
248
|
const status = resolvedCode === 0 ? 'completed' : 'failed';
|
|
@@ -213,7 +250,7 @@ server.on('exit', async ({ exitCode, signal }) => {
|
|
|
213
250
|
await updateTask(taskId, {
|
|
214
251
|
status,
|
|
215
252
|
exitCode: resolvedCode,
|
|
216
|
-
error: resolvedCode
|
|
253
|
+
error: resolvedCode !== 0 && signal ? `Killed by ${signal}` : null,
|
|
217
254
|
socketPath: null,
|
|
218
255
|
});
|
|
219
256
|
} catch (updateError) {
|
|
@@ -66,40 +66,80 @@ export function recoverStructuredOutput(sessionId) {
|
|
|
66
66
|
const jsonlPath = findSessionJsonlPath(sessionId);
|
|
67
67
|
if (!jsonlPath) return null;
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
const fileContents = readJsonlFile(jsonlPath);
|
|
70
|
+
if (!fileContents) return null;
|
|
71
|
+
|
|
72
|
+
const { structuredOutput, usage } = findStructuredOutput(fileContents);
|
|
73
|
+
|
|
74
|
+
if (!structuredOutput) return null;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
payload: buildStructuredOutputPayload(sessionId, structuredOutput, usage),
|
|
78
|
+
sourcePath: jsonlPath,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readJsonlFile(jsonlPath) {
|
|
70
83
|
try {
|
|
71
|
-
|
|
84
|
+
return readFileSync(jsonlPath, 'utf8');
|
|
72
85
|
} catch {
|
|
73
86
|
return null;
|
|
74
87
|
}
|
|
88
|
+
}
|
|
75
89
|
|
|
90
|
+
function findStructuredOutput(fileContents) {
|
|
76
91
|
const lines = fileContents.split('\n');
|
|
77
92
|
let structuredOutput = null;
|
|
78
93
|
let usage = null;
|
|
79
94
|
|
|
80
95
|
for (const line of lines) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
structuredOutput = block.input;
|
|
91
|
-
if (message?.usage && typeof message.usage === 'object') {
|
|
92
|
-
usage = message.usage;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
} catch {
|
|
97
|
-
// Skip invalid JSON lines
|
|
96
|
+
const entry = parseJsonLine(line);
|
|
97
|
+
if (!entry) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const extracted = extractStructuredOutputFromEntry(entry);
|
|
102
|
+
if (extracted) {
|
|
103
|
+
structuredOutput = extracted.structuredOutput;
|
|
104
|
+
usage = extracted.usage;
|
|
98
105
|
}
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
|
|
108
|
+
return { structuredOutput, usage };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseJsonLine(line) {
|
|
112
|
+
if (!line.trim()) return null;
|
|
113
|
+
try {
|
|
114
|
+
return JSON.parse(line);
|
|
115
|
+
} catch {
|
|
116
|
+
// Skip invalid JSON lines
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
102
120
|
|
|
121
|
+
function extractStructuredOutputFromEntry(entry) {
|
|
122
|
+
const message = entry?.message;
|
|
123
|
+
const content = message?.content;
|
|
124
|
+
if (!Array.isArray(content)) return null;
|
|
125
|
+
|
|
126
|
+
let structuredOutput = null;
|
|
127
|
+
let usage = null;
|
|
128
|
+
for (const block of content) {
|
|
129
|
+
if (block?.type === 'tool_use' && block?.name === 'StructuredOutput' && block?.input) {
|
|
130
|
+
structuredOutput = block.input;
|
|
131
|
+
usage = message?.usage && typeof message.usage === 'object' ? message.usage : null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!structuredOutput) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { structuredOutput, usage };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildStructuredOutputPayload(sessionId, structuredOutput, usage) {
|
|
103
143
|
const payload = {
|
|
104
144
|
type: 'result',
|
|
105
145
|
subtype: 'success',
|
|
@@ -112,8 +152,5 @@ export function recoverStructuredOutput(sessionId) {
|
|
|
112
152
|
payload.usage = usage;
|
|
113
153
|
}
|
|
114
154
|
|
|
115
|
-
return
|
|
116
|
-
payload,
|
|
117
|
-
sourcePath: jsonlPath,
|
|
118
|
-
};
|
|
155
|
+
return payload;
|
|
119
156
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadTasks } from '../store.js';
|
|
3
|
+
import { isProcessRunning } from '../runner.js';
|
|
4
|
+
|
|
5
|
+
export function listEpisodes(options = {}) {
|
|
6
|
+
const tasks = loadTasks();
|
|
7
|
+
const taskList = Object.values(tasks);
|
|
8
|
+
|
|
9
|
+
if (taskList.length === 0) {
|
|
10
|
+
console.log(chalk.dim('No episodes found.'));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Sort by creation date, oldest first (chronological order)
|
|
15
|
+
taskList.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
|
|
16
|
+
|
|
17
|
+
// Filter by status if specified
|
|
18
|
+
let filtered = taskList;
|
|
19
|
+
if (options.status) {
|
|
20
|
+
filtered = taskList.filter((t) => t.status === options.status);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Limit results
|
|
24
|
+
const limit = options.limit || 20;
|
|
25
|
+
filtered = filtered.slice(0, limit);
|
|
26
|
+
|
|
27
|
+
// Table format (default) or verbose format
|
|
28
|
+
if (options.verbose) {
|
|
29
|
+
// Verbose format (old behavior)
|
|
30
|
+
console.log(chalk.bold(`\nEpisodes (${filtered.length}/${taskList.length})\n`));
|
|
31
|
+
|
|
32
|
+
for (const task of filtered) {
|
|
33
|
+
// Verify running status
|
|
34
|
+
let status = task.status;
|
|
35
|
+
if (status === 'running' && !isProcessRunning(task.pid)) {
|
|
36
|
+
status = 'stale';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const statusColor =
|
|
40
|
+
{
|
|
41
|
+
running: chalk.green,
|
|
42
|
+
completed: chalk.green,
|
|
43
|
+
failed: chalk.red,
|
|
44
|
+
stale: chalk.yellow,
|
|
45
|
+
}[status] || chalk.dim;
|
|
46
|
+
|
|
47
|
+
const age = getAge(task.createdAt);
|
|
48
|
+
const timestamp = new Date(task.createdAt).toLocaleString();
|
|
49
|
+
|
|
50
|
+
console.log(
|
|
51
|
+
`${statusColor('●')} ${chalk.cyan(task.id)} ${statusColor(`[${status}]`)} ${chalk.dim(age + ' • ' + timestamp)}`
|
|
52
|
+
);
|
|
53
|
+
console.log(` ${chalk.dim('CWD:')} ${task.cwd}`);
|
|
54
|
+
console.log(` ${chalk.dim('Prompt:')} ${task.prompt}`);
|
|
55
|
+
if (task.pid && status === 'running') {
|
|
56
|
+
console.log(` ${chalk.dim('PID:')} ${task.pid}`);
|
|
57
|
+
}
|
|
58
|
+
if (task.error) {
|
|
59
|
+
console.log(` ${chalk.red('Error:')} ${task.error}`);
|
|
60
|
+
}
|
|
61
|
+
console.log();
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
// Table format (clean, default)
|
|
65
|
+
console.log(chalk.bold(`\n=== Episodes (${filtered.length}/${taskList.length}) ===`));
|
|
66
|
+
console.log(`${'ID'.padEnd(25)} ${'Status'.padEnd(12)} ${'Age'.padEnd(10)} CWD`);
|
|
67
|
+
console.log('-'.repeat(100));
|
|
68
|
+
|
|
69
|
+
for (const task of filtered) {
|
|
70
|
+
// Verify running status
|
|
71
|
+
let status = task.status;
|
|
72
|
+
if (status === 'running' && !isProcessRunning(task.pid)) {
|
|
73
|
+
status = 'stale';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const statusColor =
|
|
77
|
+
{
|
|
78
|
+
running: chalk.green,
|
|
79
|
+
completed: chalk.green,
|
|
80
|
+
failed: chalk.red,
|
|
81
|
+
stale: chalk.yellow,
|
|
82
|
+
}[status] || chalk.dim;
|
|
83
|
+
|
|
84
|
+
const age = getAge(task.createdAt);
|
|
85
|
+
const cwd = task.cwd.replace(process.env.HOME, '~');
|
|
86
|
+
|
|
87
|
+
console.log(
|
|
88
|
+
`${chalk.cyan(task.id.padEnd(25))} ${statusColor(status.padEnd(12))} ${chalk.dim(age.padEnd(10))} ${chalk.dim(cwd)}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
console.log();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getAge(dateStr) {
|
|
96
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
97
|
+
const mins = Math.floor(diff / 60000);
|
|
98
|
+
const hours = Math.floor(mins / 60);
|
|
99
|
+
const days = Math.floor(hours / 24);
|
|
100
|
+
|
|
101
|
+
if (days > 0) return `${days}d ago`;
|
|
102
|
+
if (hours > 0) return `${hours}h ago`;
|
|
103
|
+
if (mins > 0) return `${mins}m ago`;
|
|
104
|
+
return 'just now';
|
|
105
|
+
}
|
|
@@ -11,8 +11,8 @@ export function listTasks(options = {}) {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
// Sort by creation date,
|
|
15
|
-
taskList.sort((a, b) => new Date(
|
|
14
|
+
// Sort by creation date, oldest first (chronological)
|
|
15
|
+
taskList.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
|
|
16
16
|
|
|
17
17
|
// Filter by status if specified
|
|
18
18
|
let filtered = taskList;
|