@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.
Files changed (82) hide show
  1. package/README.md +94 -14
  2. package/cli/commands/providers.js +8 -9
  3. package/cli/index.js +3032 -2409
  4. package/cli/message-formatters-normal.js +28 -6
  5. package/cluster-templates/base-templates/debug-workflow.json +1 -1
  6. package/cluster-templates/base-templates/full-workflow.json +72 -188
  7. package/cluster-templates/base-templates/worker-validator.json +59 -3
  8. package/cluster-templates/conductor-bootstrap.json +4 -4
  9. package/lib/docker-config.js +8 -0
  10. package/lib/git-remote-utils.js +165 -0
  11. package/lib/id-detector.js +10 -7
  12. package/lib/provider-defaults.js +62 -0
  13. package/lib/provider-names.js +2 -1
  14. package/lib/settings/claude-auth.js +78 -0
  15. package/lib/settings.js +161 -63
  16. package/package.json +7 -2
  17. package/scripts/setup-merge-queue.sh +170 -0
  18. package/src/agent/agent-config.js +135 -82
  19. package/src/agent/agent-context-builder.js +297 -188
  20. package/src/agent/agent-hook-executor.js +310 -113
  21. package/src/agent/agent-lifecycle.js +385 -325
  22. package/src/agent/agent-stuck-detector.js +7 -7
  23. package/src/agent/agent-task-executor.js +824 -565
  24. package/src/agent/output-extraction.js +41 -24
  25. package/src/agent/output-reformatter.js +1 -1
  26. package/src/agent/schema-utils.js +108 -73
  27. package/src/agent-wrapper.js +10 -1
  28. package/src/agents/git-pusher-template.js +285 -0
  29. package/src/claude-task-runner.js +85 -34
  30. package/src/config-validator.js +922 -657
  31. package/src/input-helpers.js +65 -0
  32. package/src/isolation-manager.js +289 -199
  33. package/src/issue-providers/README.md +305 -0
  34. package/src/issue-providers/azure-devops-provider.js +273 -0
  35. package/src/issue-providers/base-provider.js +232 -0
  36. package/src/issue-providers/github-provider.js +179 -0
  37. package/src/issue-providers/gitlab-provider.js +241 -0
  38. package/src/issue-providers/index.js +196 -0
  39. package/src/issue-providers/jira-provider.js +239 -0
  40. package/src/ledger.js +22 -5
  41. package/src/lib/safe-exec.js +88 -0
  42. package/src/orchestrator.js +1107 -811
  43. package/src/preflight.js +313 -159
  44. package/src/process-metrics.js +98 -56
  45. package/src/providers/anthropic/cli-builder.js +53 -25
  46. package/src/providers/anthropic/index.js +72 -2
  47. package/src/providers/anthropic/output-parser.js +32 -14
  48. package/src/providers/base-provider.js +70 -0
  49. package/src/providers/capabilities.js +9 -0
  50. package/src/providers/google/output-parser.js +47 -38
  51. package/src/providers/index.js +19 -3
  52. package/src/providers/openai/cli-builder.js +14 -3
  53. package/src/providers/openai/index.js +1 -0
  54. package/src/providers/openai/output-parser.js +44 -30
  55. package/src/providers/opencode/cli-builder.js +42 -0
  56. package/src/providers/opencode/index.js +103 -0
  57. package/src/providers/opencode/models.js +55 -0
  58. package/src/providers/opencode/output-parser.js +122 -0
  59. package/src/schemas/sub-cluster.js +68 -39
  60. package/src/status-footer.js +94 -48
  61. package/src/sub-cluster-wrapper.js +76 -35
  62. package/src/template-resolver.js +12 -9
  63. package/src/tui/data-poller.js +123 -99
  64. package/src/tui/formatters.js +4 -3
  65. package/src/tui/keybindings.js +259 -318
  66. package/src/tui/renderer.js +11 -21
  67. package/task-lib/attachable-watcher.js +118 -81
  68. package/task-lib/claude-recovery.js +61 -24
  69. package/task-lib/commands/episodes.js +105 -0
  70. package/task-lib/commands/list.js +2 -2
  71. package/task-lib/commands/logs.js +231 -189
  72. package/task-lib/commands/schedules.js +111 -61
  73. package/task-lib/config.js +0 -2
  74. package/task-lib/runner.js +84 -27
  75. package/task-lib/scheduler.js +1 -1
  76. package/task-lib/store.js +467 -168
  77. package/task-lib/tui/formatters.js +94 -45
  78. package/task-lib/tui/renderer.js +13 -3
  79. package/task-lib/tui.js +73 -32
  80. package/task-lib/watcher.js +128 -90
  81. package/src/agents/git-pusher-agent.json +0 -20
  82. package/src/github.js +0 -139
@@ -37,11 +37,9 @@ class Renderer {
37
37
  * @param {Array} clusters - Array of cluster objects
38
38
  */
39
39
  renderClustersTable(clusters) {
40
- if (!clusters || !Array.isArray(clusters)) {
41
- clusters = [];
42
- }
40
+ const clusterList = !clusters || !Array.isArray(clusters) ? [] : clusters;
43
41
 
44
- const data = clusters.map((c) => {
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
- if (!clusters || !Array.isArray(clusters)) {
72
- clusters = [];
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 = clusters.filter((c) => c && c.state === 'running').length;
80
- const totalAgents = clusters.reduce((sum, c) => sum + (c?.agentCount || 0), 0);
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
- resourceStats.forEach((stat) => {
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
- if (!agents || !Array.isArray(agents)) {
129
- agents = [];
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 = agents.map((a) => {
125
+ const data = agentList.map((a) => {
136
126
  if (!a) return ['', '', '', '', '', ''];
137
127
 
138
128
  const pid = a.pid;
139
- const stats = resourceStats.get(pid) || { cpu: 0, memory: 0 };
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
- outputBuffer += chunk;
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
- outputBuffer += chunk;
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
- if (outputBuffer.trim()) {
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
- let recovered = null;
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
- if (config.outputFormat !== 'json') {
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 === 0 ? null : signal ? `Killed by ${signal}` : null,
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
- let fileContents;
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
- fileContents = readFileSync(jsonlPath, 'utf8');
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
- if (!line.trim()) continue;
82
- try {
83
- const entry = JSON.parse(line);
84
- const message = entry?.message;
85
- const content = message?.content;
86
- if (!Array.isArray(content)) continue;
87
-
88
- for (const block of content) {
89
- if (block?.type === 'tool_use' && block?.name === 'StructuredOutput' && block?.input) {
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
- if (!structuredOutput) return null;
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, newest first
15
- taskList.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
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;