@covibes/zeroshot 5.2.1 → 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 (98) hide show
  1. package/CHANGELOG.md +174 -189
  2. package/README.md +226 -195
  3. package/cli/commands/providers.js +149 -0
  4. package/cli/index.js +3145 -2366
  5. package/cli/lib/first-run.js +40 -3
  6. package/cli/message-formatters-normal.js +28 -6
  7. package/cluster-templates/base-templates/debug-workflow.json +24 -78
  8. package/cluster-templates/base-templates/full-workflow.json +99 -316
  9. package/cluster-templates/base-templates/single-worker.json +23 -15
  10. package/cluster-templates/base-templates/worker-validator.json +105 -36
  11. package/cluster-templates/conductor-bootstrap.json +9 -7
  12. package/lib/docker-config.js +14 -1
  13. package/lib/git-remote-utils.js +165 -0
  14. package/lib/id-detector.js +10 -7
  15. package/lib/provider-defaults.js +62 -0
  16. package/lib/provider-detection.js +59 -0
  17. package/lib/provider-names.js +57 -0
  18. package/lib/settings/claude-auth.js +78 -0
  19. package/lib/settings.js +298 -15
  20. package/lib/stream-json-parser.js +4 -238
  21. package/package.json +27 -6
  22. package/scripts/setup-merge-queue.sh +170 -0
  23. package/scripts/validate-templates.js +100 -0
  24. package/src/agent/agent-config.js +140 -63
  25. package/src/agent/agent-context-builder.js +336 -165
  26. package/src/agent/agent-hook-executor.js +337 -67
  27. package/src/agent/agent-lifecycle.js +386 -287
  28. package/src/agent/agent-stuck-detector.js +7 -7
  29. package/src/agent/agent-task-executor.js +944 -683
  30. package/src/agent/output-extraction.js +217 -0
  31. package/src/agent/output-reformatter.js +175 -0
  32. package/src/agent/schema-utils.js +146 -0
  33. package/src/agent-wrapper.js +112 -31
  34. package/src/agents/git-pusher-template.js +285 -0
  35. package/src/claude-task-runner.js +145 -44
  36. package/src/config-router.js +13 -13
  37. package/src/config-validator.js +1049 -563
  38. package/src/input-helpers.js +65 -0
  39. package/src/isolation-manager.js +499 -320
  40. package/src/issue-providers/README.md +305 -0
  41. package/src/issue-providers/azure-devops-provider.js +273 -0
  42. package/src/issue-providers/base-provider.js +232 -0
  43. package/src/issue-providers/github-provider.js +179 -0
  44. package/src/issue-providers/gitlab-provider.js +241 -0
  45. package/src/issue-providers/index.js +196 -0
  46. package/src/issue-providers/jira-provider.js +239 -0
  47. package/src/ledger.js +50 -11
  48. package/src/lib/safe-exec.js +88 -0
  49. package/src/orchestrator.js +1348 -757
  50. package/src/preflight.js +306 -149
  51. package/src/process-metrics.js +98 -56
  52. package/src/providers/anthropic/cli-builder.js +73 -0
  53. package/src/providers/anthropic/index.js +204 -0
  54. package/src/providers/anthropic/models.js +23 -0
  55. package/src/providers/anthropic/output-parser.js +177 -0
  56. package/src/providers/base-provider.js +251 -0
  57. package/src/providers/capabilities.js +60 -0
  58. package/src/providers/google/cli-builder.js +55 -0
  59. package/src/providers/google/index.js +116 -0
  60. package/src/providers/google/models.js +24 -0
  61. package/src/providers/google/output-parser.js +101 -0
  62. package/src/providers/index.js +91 -0
  63. package/src/providers/openai/cli-builder.js +133 -0
  64. package/src/providers/openai/index.js +136 -0
  65. package/src/providers/openai/models.js +21 -0
  66. package/src/providers/openai/output-parser.js +143 -0
  67. package/src/providers/opencode/cli-builder.js +42 -0
  68. package/src/providers/opencode/index.js +103 -0
  69. package/src/providers/opencode/models.js +55 -0
  70. package/src/providers/opencode/output-parser.js +122 -0
  71. package/src/schemas/sub-cluster.js +68 -39
  72. package/src/status-footer.js +94 -48
  73. package/src/sub-cluster-wrapper.js +92 -36
  74. package/src/task-runner.js +8 -6
  75. package/src/template-resolver.js +12 -9
  76. package/src/tui/data-poller.js +123 -99
  77. package/src/tui/formatters.js +4 -3
  78. package/src/tui/keybindings.js +259 -318
  79. package/src/tui/layout.js +20 -3
  80. package/src/tui/renderer.js +11 -21
  81. package/task-lib/attachable-watcher.js +150 -111
  82. package/task-lib/claude-recovery.js +156 -0
  83. package/task-lib/commands/episodes.js +105 -0
  84. package/task-lib/commands/list.js +3 -3
  85. package/task-lib/commands/logs.js +231 -189
  86. package/task-lib/commands/resume.js +3 -2
  87. package/task-lib/commands/run.js +12 -3
  88. package/task-lib/commands/schedules.js +111 -61
  89. package/task-lib/config.js +0 -2
  90. package/task-lib/runner.js +128 -50
  91. package/task-lib/scheduler.js +3 -3
  92. package/task-lib/store.js +464 -152
  93. package/task-lib/tui/formatters.js +94 -45
  94. package/task-lib/tui/renderer.js +13 -3
  95. package/task-lib/tui.js +73 -32
  96. package/task-lib/watcher.js +157 -100
  97. package/src/agents/git-pusher-agent.json +0 -20
  98. package/src/github.js +0 -103
@@ -272,25 +272,28 @@ class TemplateResolver {
272
272
  * @returns {any}
273
273
  */
274
274
  _parseValue(str) {
275
- str = str.trim();
275
+ const trimmed = str.trim();
276
276
 
277
277
  // String literals (single or double quotes)
278
- if ((str.startsWith('"') && str.endsWith('"')) || (str.startsWith("'") && str.endsWith("'"))) {
279
- return str.slice(1, -1);
278
+ if (
279
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
280
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
281
+ ) {
282
+ return trimmed.slice(1, -1);
280
283
  }
281
284
 
282
285
  // Boolean literals
283
- if (str === 'true') return true;
284
- if (str === 'false') return false;
285
- if (str === 'undefined' || str === 'null') return undefined;
286
+ if (trimmed === 'true') return true;
287
+ if (trimmed === 'false') return false;
288
+ if (trimmed === 'undefined' || trimmed === 'null') return undefined;
286
289
 
287
290
  // Numbers
288
- if (/^-?\d+(\.\d+)?$/.test(str)) {
289
- return parseFloat(str);
291
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
292
+ return parseFloat(trimmed);
290
293
  }
291
294
 
292
295
  // Return as-is for other cases
293
- return str;
296
+ return trimmed;
294
297
  }
295
298
 
296
299
  /**
@@ -10,6 +10,7 @@
10
10
 
11
11
  const pidusage = require('pidusage');
12
12
  const Ledger = require('../ledger');
13
+ const fs = require('fs');
13
14
  const path = require('path');
14
15
  const os = require('os');
15
16
 
@@ -138,47 +139,11 @@ class DataPoller {
138
139
  try {
139
140
  const clusters = this.orchestrator.listClusters();
140
141
  const stats = {};
142
+ const pids = this._collectClusterPids(clusters);
141
143
 
142
- // Collect all PIDs from all agents
143
- const pids = [];
144
- for (const cluster of clusters) {
145
- try {
146
- const status = this.orchestrator.getStatus(cluster.id);
147
- for (const agent of status.agents || []) {
148
- if (agent.pid) {
149
- pids.push(agent.pid);
150
- }
151
- }
152
- } catch {
153
- // Skip clusters that error
154
- continue;
155
- }
156
- }
157
-
158
- // Get stats for all PIDs
159
144
  if (pids.length > 0) {
160
- try {
161
- const pidStats = await pidusage(pids);
162
-
163
- // Convert to map format: pid -> { cpu, memory }
164
- for (const pid of pids) {
165
- if (pidStats[pid]) {
166
- stats[pid] = {
167
- cpu: pidStats[pid].cpu || 0,
168
- memory: pidStats[pid].memory || 0,
169
- };
170
- } else {
171
- // Process died - set to zero
172
- stats[pid] = { cpu: 0, memory: 0 };
173
- }
174
- }
175
- } catch {
176
- // pidusage throws if any process is dead
177
- // Set all to zero and continue
178
- for (const pid of pids) {
179
- stats[pid] = { cpu: 0, memory: 0 };
180
- }
181
- }
145
+ const pidStats = await this._safePidUsage(pids);
146
+ this._populatePidStats(stats, pids, pidStats);
182
147
  }
183
148
 
184
149
  this.onUpdate({
@@ -198,54 +163,36 @@ class DataPoller {
198
163
  */
199
164
  _watchForNewClusters() {
200
165
  this.watchForNewClustersStopFn = this.orchestrator.watchForNewClusters((cluster) => {
201
- try {
202
- // Lazy load ledger only when we need to stream messages
203
- // This avoids loading all ledgers on startup
204
- if (!this.ledgers.has(cluster.id)) {
205
- const storageDir = this.orchestrator.storageDir || path.join(os.homedir(), '.zeroshot');
206
- const dbPath = path.join(storageDir, `${cluster.id}.db`);
207
-
208
- // Only load if database file exists
209
- const fs = require('fs');
210
- if (!fs.existsSync(dbPath)) {
211
- return; // Skip non-existent ledgers
212
- }
213
-
214
- const ledger = new Ledger(dbPath);
215
- this.ledgers.set(cluster.id, ledger);
216
- }
217
-
218
- // Start streaming messages
219
- this._streamLedgerMessages(cluster.id);
166
+ // Lazy load ledger only when we need to stream messages
167
+ // This avoids loading all ledgers on startup
168
+ const ledger = this._loadLedgerForCluster(cluster, {
169
+ label: 'cluster',
170
+ requireExisting: true,
171
+ });
220
172
 
221
- // Emit update about new cluster
222
- this.onUpdate({
223
- type: 'new_cluster',
224
- cluster,
225
- });
226
- } catch (error) {
227
- console.error(
228
- `[DataPoller] Failed to load ledger for cluster ${cluster.id}:`,
229
- error.message
230
- );
173
+ if (!ledger) {
174
+ return;
231
175
  }
176
+
177
+ // Start streaming messages
178
+ this._streamLedgerMessages(cluster.id);
179
+
180
+ // Emit update about new cluster
181
+ this.onUpdate({
182
+ type: 'new_cluster',
183
+ cluster,
184
+ });
232
185
  }, 2000);
233
186
 
234
187
  // Also load ledgers for all existing clusters
235
188
  const existingClusters = this.orchestrator.listClusters();
236
189
  for (const cluster of existingClusters) {
237
- try {
238
- const storageDir = this.orchestrator.storageDir || path.join(os.homedir(), '.zeroshot');
239
- const dbPath = path.join(storageDir, `${cluster.id}.db`);
240
- const ledger = new Ledger(dbPath);
241
- this.ledgers.set(cluster.id, ledger);
242
- this._streamLedgerMessages(cluster.id);
243
- } catch (error) {
244
- console.error(
245
- `[DataPoller] Failed to load ledger for existing cluster ${cluster.id}:`,
246
- error.message
247
- );
190
+ const ledger = this._loadLedgerForCluster(cluster, { label: 'existing cluster' });
191
+ if (!ledger) {
192
+ continue;
248
193
  }
194
+
195
+ this._streamLedgerMessages(cluster.id);
249
196
  }
250
197
  }
251
198
 
@@ -295,31 +242,108 @@ class DataPoller {
295
242
  const clusters = this.orchestrator.listClusters();
296
243
 
297
244
  for (const cluster of clusters) {
298
- try {
299
- const status = this.orchestrator.getStatus(cluster.id);
300
-
301
- for (const agent of status.agents || []) {
302
- if (agent.pid) {
303
- try {
304
- const pidStat = await pidusage(agent.pid);
305
- stats[agent.pid] = {
306
- cpu: pidStat.cpu || 0,
307
- memory: pidStat.memory || 0,
308
- };
309
- } catch {
310
- // Process died - set to zero
311
- stats[agent.pid] = { cpu: 0, memory: 0 };
312
- }
313
- }
314
- }
315
- } catch {
316
- // Skip clusters that error
317
- continue;
245
+ const pids = this._getClusterAgentPids(cluster);
246
+ for (const pid of pids) {
247
+ stats[pid] = await this._getSinglePidStat(pid);
318
248
  }
319
249
  }
320
250
 
321
251
  return stats;
322
252
  }
253
+
254
+ _collectClusterPids(clusters) {
255
+ const pids = [];
256
+
257
+ for (const cluster of clusters) {
258
+ pids.push(...this._getClusterAgentPids(cluster));
259
+ }
260
+
261
+ return pids;
262
+ }
263
+
264
+ _getClusterAgentPids(cluster) {
265
+ try {
266
+ const status = this.orchestrator.getStatus(cluster.id);
267
+ const pids = [];
268
+
269
+ for (const agent of status.agents || []) {
270
+ if (agent.pid) {
271
+ pids.push(agent.pid);
272
+ }
273
+ }
274
+
275
+ return pids;
276
+ } catch {
277
+ // Skip clusters that error
278
+ return [];
279
+ }
280
+ }
281
+
282
+ async _getSinglePidStat(pid) {
283
+ try {
284
+ const pidStat = await pidusage(pid);
285
+ return {
286
+ cpu: pidStat.cpu || 0,
287
+ memory: pidStat.memory || 0,
288
+ };
289
+ } catch {
290
+ // Process died - set to zero
291
+ return { cpu: 0, memory: 0 };
292
+ }
293
+ }
294
+
295
+ async _safePidUsage(pids) {
296
+ try {
297
+ return await pidusage(pids);
298
+ } catch {
299
+ return null;
300
+ }
301
+ }
302
+
303
+ _populatePidStats(stats, pids, pidStats) {
304
+ for (const pid of pids) {
305
+ const entry = pidStats?.[pid];
306
+ stats[pid] = {
307
+ cpu: entry?.cpu || 0,
308
+ memory: entry?.memory || 0,
309
+ };
310
+ }
311
+ }
312
+
313
+ _loadLedgerForCluster(cluster, options) {
314
+ const { label, requireExisting = false } = options;
315
+
316
+ try {
317
+ return this._ensureLedger(cluster.id, { requireExisting });
318
+ } catch (error) {
319
+ console.error(
320
+ `[DataPoller] Failed to load ledger for ${label} ${cluster.id}:`,
321
+ error.message
322
+ );
323
+ return null;
324
+ }
325
+ }
326
+
327
+ _ensureLedger(clusterId, { requireExisting = false } = {}) {
328
+ const existing = this.ledgers.get(clusterId);
329
+ if (existing) {
330
+ return existing;
331
+ }
332
+
333
+ const dbPath = this._getLedgerPath(clusterId);
334
+ if (requireExisting && !fs.existsSync(dbPath)) {
335
+ return null;
336
+ }
337
+
338
+ const ledger = new Ledger(dbPath);
339
+ this.ledgers.set(clusterId, ledger);
340
+ return ledger;
341
+ }
342
+
343
+ _getLedgerPath(clusterId) {
344
+ const storageDir = this.orchestrator.storageDir || path.join(os.homedir(), '.zeroshot');
345
+ return path.join(storageDir, `${clusterId}.db`);
346
+ }
323
347
  }
324
348
 
325
349
  module.exports = DataPoller;
@@ -64,12 +64,13 @@ const formatCPU = (percent) => {
64
64
  if (typeof percent !== 'number' || percent < 0) return '0.0%';
65
65
 
66
66
  // Assert normalized range (should never exceed 100% after per-core normalization)
67
- if (percent > 100) {
67
+ let normalizedPercent = percent;
68
+ if (normalizedPercent > 100) {
68
69
  console.warn(`[formatCPU] CPU percent ${percent}% exceeds 100% - normalization bug?`);
69
- percent = 100;
70
+ normalizedPercent = 100;
70
71
  }
71
72
 
72
- return `${percent.toFixed(1)}%`;
73
+ return `${normalizedPercent.toFixed(1)}%`;
73
74
  };
74
75
 
75
76
  /**