@covibes/zeroshot 1.0.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/LICENSE +21 -0
  3. package/README.md +364 -0
  4. package/cli/index.js +3990 -0
  5. package/cluster-templates/base-templates/debug-workflow.json +181 -0
  6. package/cluster-templates/base-templates/full-workflow.json +455 -0
  7. package/cluster-templates/base-templates/single-worker.json +48 -0
  8. package/cluster-templates/base-templates/worker-validator.json +131 -0
  9. package/cluster-templates/conductor-bootstrap.json +122 -0
  10. package/cluster-templates/conductor-junior-bootstrap.json +69 -0
  11. package/docker/zeroshot-cluster/Dockerfile +132 -0
  12. package/lib/completion.js +174 -0
  13. package/lib/id-detector.js +53 -0
  14. package/lib/settings.js +97 -0
  15. package/lib/stream-json-parser.js +236 -0
  16. package/package.json +121 -0
  17. package/src/agent/agent-config.js +121 -0
  18. package/src/agent/agent-context-builder.js +241 -0
  19. package/src/agent/agent-hook-executor.js +329 -0
  20. package/src/agent/agent-lifecycle.js +555 -0
  21. package/src/agent/agent-stuck-detector.js +256 -0
  22. package/src/agent/agent-task-executor.js +1034 -0
  23. package/src/agent/agent-trigger-evaluator.js +67 -0
  24. package/src/agent-wrapper.js +459 -0
  25. package/src/agents/git-pusher-agent.json +20 -0
  26. package/src/attach/attach-client.js +438 -0
  27. package/src/attach/attach-server.js +543 -0
  28. package/src/attach/index.js +35 -0
  29. package/src/attach/protocol.js +220 -0
  30. package/src/attach/ring-buffer.js +121 -0
  31. package/src/attach/socket-discovery.js +242 -0
  32. package/src/claude-task-runner.js +468 -0
  33. package/src/config-router.js +80 -0
  34. package/src/config-validator.js +598 -0
  35. package/src/github.js +103 -0
  36. package/src/isolation-manager.js +1042 -0
  37. package/src/ledger.js +429 -0
  38. package/src/logic-engine.js +223 -0
  39. package/src/message-bus-bridge.js +139 -0
  40. package/src/message-bus.js +202 -0
  41. package/src/name-generator.js +232 -0
  42. package/src/orchestrator.js +1938 -0
  43. package/src/schemas/sub-cluster.js +156 -0
  44. package/src/sub-cluster-wrapper.js +545 -0
  45. package/src/task-runner.js +28 -0
  46. package/src/template-resolver.js +347 -0
  47. package/src/tui/CHANGES.txt +133 -0
  48. package/src/tui/LAYOUT.md +261 -0
  49. package/src/tui/README.txt +192 -0
  50. package/src/tui/TWO-LEVEL-NAVIGATION.md +186 -0
  51. package/src/tui/data-poller.js +325 -0
  52. package/src/tui/demo.js +208 -0
  53. package/src/tui/formatters.js +123 -0
  54. package/src/tui/index.js +193 -0
  55. package/src/tui/keybindings.js +383 -0
  56. package/src/tui/layout.js +317 -0
  57. package/src/tui/renderer.js +194 -0
@@ -0,0 +1,28 @@
1
+ /**
2
+ * TaskRunner - Strategy Pattern interface for executing Claude tasks
3
+ *
4
+ * Implementations must provide a `run()` method that executes a Claude task
5
+ * with the given context and options. Different runners can implement various
6
+ * execution strategies (Claude CLI, mock responses, etc).
7
+ */
8
+ class TaskRunner {
9
+ /**
10
+ * Execute a Claude task with the given context and options
11
+ *
12
+ * @param {string} _context - Full prompt/context for Claude to process
13
+ * @param {Object} _options - Execution options
14
+ * @param {string} _options.agentId - Identifier for this agent/task
15
+ * @param {string} _options.model - Model to use (e.g., 'opus', 'sonnet', 'haiku')
16
+ * @param {string} [_options.outputFormat] - Output format ('text', 'json', 'stream-json')
17
+ * @param {Object} [_options.jsonSchema] - JSON schema for structured output validation
18
+ * @param {string} [_options.cwd] - Working directory for task execution
19
+ * @param {boolean} [_options.isolation] - Whether to run in isolated container
20
+ *
21
+ * @returns {Promise<{success: boolean, output: string, error: string|null, taskId?: string}>} Result object with success status, output, error message, and optional taskId
22
+ */
23
+ run(_context, _options) {
24
+ throw new Error('TaskRunner.run() not implemented');
25
+ }
26
+ }
27
+
28
+ module.exports = TaskRunner;
@@ -0,0 +1,347 @@
1
+ /**
2
+ * TemplateResolver - Resolves parameterized cluster templates
3
+ *
4
+ * Takes a base template with {{param}} placeholders and resolves them
5
+ * with provided parameter values. Pure data transformation, no magic.
6
+ *
7
+ * Resolution rules:
8
+ * 1. Load base template JSON
9
+ * 2. Deep clone
10
+ * 3. Walk all values, replace {{param}} with params[param]
11
+ * 4. Handle conditional agents via "condition" field
12
+ * 5. Fail hard if any {{param}} remains unresolved
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ class TemplateResolver {
19
+ /**
20
+ * @param {string} templatesDir
21
+ */
22
+ constructor(templatesDir) {
23
+ this.templatesDir = templatesDir;
24
+ this.baseTemplatesDir = path.join(templatesDir, 'base-templates');
25
+ }
26
+
27
+ /**
28
+ * Resolve a template with parameters
29
+ * @param {string} baseName - Name of base template (without .json)
30
+ * @param {Object} params - Parameter values to substitute
31
+ * @returns {Object} Resolved cluster config
32
+ */
33
+ resolve(baseName, params) {
34
+ // Load base template
35
+ const templatePath = path.join(this.baseTemplatesDir, `${baseName}.json`);
36
+ if (!fs.existsSync(templatePath)) {
37
+ throw new Error(`Base template not found: ${baseName} (looked in ${templatePath})`);
38
+ }
39
+
40
+ const templateJson = fs.readFileSync(templatePath, 'utf8');
41
+ const template = JSON.parse(templateJson);
42
+
43
+ // Validate required params
44
+ this._validateParams(template, params);
45
+
46
+ // Deep clone and resolve
47
+ const resolved = this._resolveObject(JSON.parse(JSON.stringify(template)), params);
48
+
49
+ // Filter out conditional agents that don't meet their condition
50
+ if (resolved.agents) {
51
+ resolved.agents = resolved.agents.filter((/** @type {any} */ agent) => {
52
+ if (!agent.condition) return true;
53
+ const conditionMet = this._evaluateCondition(agent.condition, params);
54
+ delete agent.condition; // Remove condition field from final output
55
+ return conditionMet;
56
+ });
57
+ }
58
+
59
+ // Verify no unresolved placeholders remain
60
+ this._verifyResolved(resolved);
61
+
62
+ // Remove params schema from output (it's metadata, not config)
63
+ delete resolved.params;
64
+
65
+ return resolved;
66
+ }
67
+
68
+ /**
69
+ * Validate that required params are provided
70
+ * @private
71
+ * @param {any} template
72
+ * @param {any} params
73
+ */
74
+ _validateParams(template, params) {
75
+ if (!template.params) return;
76
+
77
+ const missing = [];
78
+ for (const [name, schema] of Object.entries(template.params)) {
79
+ if (params[name] === undefined && schema.default === undefined) {
80
+ missing.push(name);
81
+ }
82
+ }
83
+
84
+ if (missing.length > 0) {
85
+ throw new Error(`Missing required params: ${missing.join(', ')}`);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Recursively resolve placeholders in an object
91
+ * @private
92
+ * @param {any} obj
93
+ * @param {any} params
94
+ * @returns {any}
95
+ */
96
+ _resolveObject(obj, params) {
97
+ if (obj === null || obj === undefined) {
98
+ return obj;
99
+ }
100
+
101
+ if (typeof obj === 'string') {
102
+ return this._resolveString(obj, params);
103
+ }
104
+
105
+ if (Array.isArray(obj)) {
106
+ return obj.map((item) => this._resolveObject(item, params));
107
+ }
108
+
109
+ if (typeof obj === 'object') {
110
+ /** @type {any} */
111
+ const result = {};
112
+ for (const [key, value] of Object.entries(obj)) {
113
+ result[key] = this._resolveObject(value, params);
114
+ }
115
+ return result;
116
+ }
117
+
118
+ return obj;
119
+ }
120
+
121
+ /**
122
+ * Resolve placeholders in a string
123
+ * Supports: {{param}} and {{#if condition}}...{{/if}}
124
+ * @private
125
+ * @param {any} str
126
+ * @param {any} params
127
+ * @returns {any}
128
+ */
129
+ _resolveString(str, params) {
130
+ // Handle simple {{param}} substitutions
131
+ let result = str.replace(
132
+ /\{\{(\w+)\}\}/g,
133
+ (/** @type {any} */ _match, /** @type {any} */ paramName) => {
134
+ if (params[paramName] !== undefined) {
135
+ return params[paramName];
136
+ }
137
+ // Return match unchanged if param not found (will be caught by verify)
138
+ return _match;
139
+ }
140
+ );
141
+
142
+ // Handle {{#if condition}}...{{/if}} blocks
143
+ result = result.replace(
144
+ /\{\{#if\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g,
145
+ (/** @type {any} */ _match, /** @type {any} */ condition, /** @type {any} */ content) => {
146
+ const conditionMet = this._evaluateCondition(condition, params);
147
+ return conditionMet ? content : '';
148
+ }
149
+ );
150
+
151
+ // Clean up multiple newlines from removed conditionals
152
+ result = result.replace(/\n{3,}/g, '\n\n');
153
+
154
+ return result;
155
+ }
156
+
157
+ /**
158
+ * Evaluate a simple condition expression
159
+ * Supports: param == 'value', param != 'value', {{param}} >= N
160
+ * @private
161
+ * @param {any} condition
162
+ * @param {any} params
163
+ * @returns {boolean}
164
+ */
165
+ _evaluateCondition(condition, params) {
166
+ // Replace {{param}} with actual values first
167
+ let expr = condition.trim();
168
+
169
+ // Replace {{param}} placeholders
170
+ expr = expr.replace(
171
+ /\{\{(\w+)\}\}/g,
172
+ (/** @type {any} */ _match, /** @type {any} */ paramName) => {
173
+ const value = params[paramName];
174
+ if (value === undefined) return 'undefined';
175
+ if (typeof value === 'string') return `"${value}"`;
176
+ return String(value);
177
+ }
178
+ );
179
+
180
+ // Replace bare param names
181
+ for (const [name, value] of Object.entries(params)) {
182
+ const regex = new RegExp(`\\b${name}\\b`, 'g');
183
+ if (typeof value === 'string') {
184
+ expr = expr.replace(regex, `"${value}"`);
185
+ } else {
186
+ expr = expr.replace(regex, String(value));
187
+ }
188
+ }
189
+
190
+ try {
191
+ // Parse and evaluate simple comparison expressions without eval
192
+ return this._evaluateSimpleExpression(expr);
193
+ } catch {
194
+ console.error(`Failed to evaluate condition: ${condition} (resolved: ${expr})`);
195
+ return false;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Evaluate simple comparison expressions without eval
201
+ * Supports: ==, !=, <, >, <=, >=, &&, ||
202
+ * @private
203
+ * @param {string} expr
204
+ * @returns {boolean}
205
+ */
206
+ _evaluateSimpleExpression(expr) {
207
+ // Handle logical operators (&&, ||) by splitting and recursing
208
+ if (expr.includes('||')) {
209
+ const parts = expr.split('||');
210
+ return parts.some((part) => this._evaluateSimpleExpression(part.trim()));
211
+ }
212
+ if (expr.includes('&&')) {
213
+ const parts = expr.split('&&');
214
+ return parts.every((part) => this._evaluateSimpleExpression(part.trim()));
215
+ }
216
+
217
+ // Handle comparison operators
218
+ const comparisonOps = ['==', '!=', '<=', '>=', '<', '>'];
219
+ for (const op of comparisonOps) {
220
+ if (expr.includes(op)) {
221
+ const [left, right] = expr.split(op).map((s) => s.trim());
222
+ const leftVal = this._parseValue(left);
223
+ const rightVal = this._parseValue(right);
224
+
225
+ switch (op) {
226
+ case '==':
227
+ return leftVal === rightVal;
228
+ case '!=':
229
+ return leftVal !== rightVal;
230
+ case '<':
231
+ return leftVal < rightVal;
232
+ case '>':
233
+ return leftVal > rightVal;
234
+ case '<=':
235
+ return leftVal <= rightVal;
236
+ case '>=':
237
+ return leftVal >= rightVal;
238
+ }
239
+ }
240
+ }
241
+
242
+ // If no operator found, treat as boolean literal or truthy value
243
+ return this._parseValue(expr) ? true : false;
244
+ }
245
+
246
+ /**
247
+ * Parse a value from string (number, boolean, string literal)
248
+ * @private
249
+ * @param {string} str
250
+ * @returns {any}
251
+ */
252
+ _parseValue(str) {
253
+ str = str.trim();
254
+
255
+ // String literals (single or double quotes)
256
+ if ((str.startsWith('"') && str.endsWith('"')) || (str.startsWith("'") && str.endsWith("'"))) {
257
+ return str.slice(1, -1);
258
+ }
259
+
260
+ // Boolean literals
261
+ if (str === 'true') return true;
262
+ if (str === 'false') return false;
263
+ if (str === 'undefined' || str === 'null') return undefined;
264
+
265
+ // Numbers
266
+ if (/^-?\d+(\.\d+)?$/.test(str)) {
267
+ return parseFloat(str);
268
+ }
269
+
270
+ // Return as-is for other cases
271
+ return str;
272
+ }
273
+
274
+ /**
275
+ * Verify no unresolved placeholders remain
276
+ * @private
277
+ * @param {any} obj
278
+ */
279
+ _verifyResolved(obj) {
280
+ const unresolved = this._findUnresolved(obj);
281
+ if (unresolved.length > 0) {
282
+ throw new Error(`Unresolved template placeholders: ${unresolved.join(', ')}`);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Find all unresolved {{param}} placeholders
288
+ * @private
289
+ * @param {any} obj
290
+ * @param {string} pathPrefix
291
+ * @returns {string[]}
292
+ */
293
+ _findUnresolved(obj, pathPrefix = '') {
294
+ const unresolved = [];
295
+
296
+ if (typeof obj === 'string') {
297
+ const matches = obj.match(/\{\{(\w+)\}\}/g);
298
+ if (matches) {
299
+ unresolved.push(...matches.map((m) => `${pathPrefix}: ${m}`));
300
+ }
301
+ } else if (Array.isArray(obj)) {
302
+ obj.forEach((item, i) => {
303
+ unresolved.push(...this._findUnresolved(item, `${pathPrefix}[${i}]`));
304
+ });
305
+ } else if (obj && typeof obj === 'object') {
306
+ for (const [key, value] of Object.entries(obj)) {
307
+ unresolved.push(...this._findUnresolved(value, `${pathPrefix}.${key}`));
308
+ }
309
+ }
310
+
311
+ return unresolved;
312
+ }
313
+
314
+ /**
315
+ * List available base templates
316
+ * @returns {string[]}
317
+ */
318
+ listTemplates() {
319
+ if (!fs.existsSync(this.baseTemplatesDir)) {
320
+ return [];
321
+ }
322
+ return fs
323
+ .readdirSync(this.baseTemplatesDir)
324
+ .filter((f) => f.endsWith('.json'))
325
+ .map((f) => f.replace('.json', ''));
326
+ }
327
+
328
+ /**
329
+ * Get template metadata (name, description, params)
330
+ * @param {any} baseName
331
+ * @returns {any}
332
+ */
333
+ getTemplateInfo(baseName) {
334
+ const templatePath = path.join(this.baseTemplatesDir, `${baseName}.json`);
335
+ if (!fs.existsSync(templatePath)) {
336
+ return null;
337
+ }
338
+ const template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
339
+ return {
340
+ name: template.name,
341
+ description: template.description,
342
+ params: template.params || {},
343
+ };
344
+ }
345
+ }
346
+
347
+ module.exports = TemplateResolver;
@@ -0,0 +1,133 @@
1
+ TUI Performance & UX Improvements
2
+ ==================================
3
+
4
+ ## New Features
5
+
6
+ ### 4. Two-Level Navigation 🎯
7
+ **Feature:** Completely separate layouts for overview vs detail
8
+ **Implementation:**
9
+ - Overview mode: ONLY shows clusters table + stats (agents/logs hidden)
10
+ - Detail mode: ONLY shows agents + logs (clusters/stats hidden)
11
+ - Enter key to drill into detail, Escape to return
12
+ - Help text updates dynamically based on current view
13
+ - Widgets physically shown/hidden (not just empty data)
14
+
15
+ ## Fixed Issues
16
+
17
+ ### 1. Slow Startup ⚡
18
+ **Problem:** TUI took 5-10 seconds to start due to synchronous cluster loading
19
+ **Solution:**
20
+ - Deferred initial polls by 50-100ms to let UI render first
21
+ - Shows "Loading..." message immediately
22
+ - Lazy-loads cluster ledgers only when needed
23
+ - Startup now instant (<100ms)
24
+
25
+ ### 2. Default Filter 🎯
26
+ **Problem:** Showed all clusters (including stopped) by default
27
+ **Solution:**
28
+ - Changed default filter from "all" to "running"
29
+ - User only sees active clusters
30
+ - Can still use `--filter all` to see everything
31
+
32
+ ### 3. Cluster Selection 📍
33
+ **Problem:** Agents and logs weren't properly filtered by selected cluster
34
+ **Solution:**
35
+ - Renderer now tracks selectedClusterId
36
+ - Agents shown are for the selected cluster only
37
+ - Logs filtered to show only messages from selected cluster
38
+ - Messages cleared when switching between clusters
39
+ - Navigate with ↑↓ or jk keys
40
+
41
+ ## Performance Improvements
42
+
43
+ **Before:**
44
+ - Startup: 5-10 seconds
45
+ - All clusters loaded synchronously
46
+ - All ledgers opened on startup
47
+ - Unfiltered logs from all clusters
48
+
49
+ **After:**
50
+ - Startup: <100ms instant
51
+ - Clusters loaded async after UI renders
52
+ - Ledgers lazy-loaded when needed
53
+ - Logs filtered to selected cluster only
54
+
55
+ ## Usage
56
+
57
+ ```bash
58
+ # Shows only running clusters (default)
59
+ vibe watch
60
+
61
+ # Show all clusters (including stopped)
62
+ vibe watch --filter all
63
+
64
+ # Show only stopped clusters
65
+ vibe watch --filter stopped
66
+ ```
67
+
68
+ ## Keyboard Navigation
69
+
70
+ ### Two-Level Navigation
71
+ - **Overview Mode** (default): ONLY clusters + stats visible
72
+ - Large clusters table (16 rows) with system stats sidebar
73
+ - No agents or logs shown
74
+ - `↑` / `k` - Select previous cluster
75
+ - `↓` / `j` - Select next cluster
76
+ - `Enter` - Switch to detail view for selected cluster
77
+
78
+ - **Detail Mode**: ONLY agents + logs visible
79
+ - Full-width agents table (9 rows)
80
+ - Full-width live logs (9 rows)
81
+ - Clusters table and stats hidden
82
+ - `Escape` - Switch back to overview mode
83
+
84
+ Agents and logs auto-update in real-time when in detail view
85
+
86
+ ## Technical Changes
87
+
88
+ ### data-poller.js
89
+ - Line 45-53: Deferred initial polls with setTimeout
90
+ - Line 196-208: Added lazy loading for cluster ledgers
91
+ - Line 201-203: Check if ledger DB exists before loading
92
+
93
+ ### index.js (TUI)
94
+ - Line 21: Changed default filter to 'running'
95
+ - Line 34-36: Added viewMode state ('overview' or 'detail') and detailClusterId
96
+ - Line 48-49: Show "Loading..." message on startup
97
+ - Line 107-119: Conditional rendering - agents only shown in detail view
98
+ - Line 130-137: Conditional rendering for resource_stats case
99
+
100
+ ### keybindings.js
101
+ - Line 14-37: Enter key handler to switch to detail view
102
+ - Line 39-59: Escape key handler to switch to overview view
103
+ - Line 22, 45: Clear messages when switching views
104
+ - Line 29-34: Detail mode - hide clusters/stats, show agents/logs
105
+ - Line 52-57: Overview mode - show clusters/stats, hide agents/logs
106
+ - Line 24-27, 47-50: Update help text based on view mode
107
+
108
+ ### layout.js
109
+ - Line 36: Expanded clusters table to 16 rows (from 6)
110
+ - Line 66: Expanded stats box to 16 rows (from 6)
111
+ - Line 85: Repositioned agent table to row 0, 9 rows, full width
112
+ - Line 119: Repositioned logs to row 9, 9 rows, full width
113
+ - Line 165-167: Initially hide agent table and logs (overview mode default)
114
+
115
+ ### cli/index.js
116
+ - Line 1161: Changed default filter to 'running' in CLI option
117
+
118
+ ## Testing
119
+
120
+ Run integration tests:
121
+ ```bash
122
+ node tests/tui-integration.test.js # Basic TUI startup and data loading
123
+ node tests/tui-navigation-test.js # Two-level navigation functionality
124
+ ```
125
+
126
+ Expected: All tests pass, TUI starts instantly
127
+
128
+ ## Notes
129
+
130
+ - Messages are cluster-scoped (only show for selected cluster)
131
+ - Selection persists across refreshes
132
+ - Empty clusters (no agents) still show in list
133
+ - Logs clear when switching clusters to avoid confusion