@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,236 @@
1
+ /**
2
+ * Stream JSON Parser for Claude Code output
3
+ *
4
+ * Parses NDJSON (newline-delimited JSON) streaming output from Claude Code.
5
+ * Extracts: text output, tool calls, tool results, thinking, errors.
6
+ *
7
+ * Event types from Claude Code stream-json format:
8
+ * - system: Session initialization
9
+ * - stream_event: Real-time streaming (content_block_start, content_block_delta, etc.)
10
+ * - assistant: Complete assistant message with content array
11
+ * - user: Tool results
12
+ * - result: Final task result
13
+ */
14
+
15
+ /**
16
+ * Parse a single JSON line and extract displayable content
17
+ * @param {string} line - Single line of NDJSON
18
+ * @returns {Object|null} Parsed event with type and content, or null if not displayable
19
+ */
20
+ function parseStreamLine(line) {
21
+ const trimmed = line.trim();
22
+ if (!trimmed || !trimmed.startsWith('{') || !trimmed.endsWith('}')) {
23
+ return null;
24
+ }
25
+
26
+ let event;
27
+ try {
28
+ event = JSON.parse(trimmed);
29
+ } catch {
30
+ return null;
31
+ }
32
+
33
+ // stream_event - real-time streaming updates
34
+ if (event.type === 'stream_event' && event.event) {
35
+ return parseStreamEvent(event.event);
36
+ }
37
+
38
+ // assistant - complete message with content blocks
39
+ if (event.type === 'assistant' && event.message?.content) {
40
+ return parseAssistantMessage(event.message);
41
+ }
42
+
43
+ // user - tool result
44
+ if (event.type === 'user' && event.message?.content) {
45
+ return parseUserMessage(event.message);
46
+ }
47
+
48
+ // result - final task result
49
+ if (event.type === 'result') {
50
+ return {
51
+ type: 'result',
52
+ success: event.subtype === 'success',
53
+ result: event.result,
54
+ error: event.is_error ? event.result : null,
55
+ cost: event.total_cost_usd,
56
+ duration: event.duration_ms,
57
+ };
58
+ }
59
+
60
+ // system - session init (skip, not user-facing)
61
+ if (event.type === 'system') {
62
+ return null;
63
+ }
64
+
65
+ return null;
66
+ }
67
+
68
+ /**
69
+ * Parse stream_event inner event
70
+ */
71
+ function parseStreamEvent(inner) {
72
+ // content_block_start - tool use or text block starting
73
+ if (inner.type === 'content_block_start' && inner.content_block) {
74
+ const block = inner.content_block;
75
+
76
+ if (block.type === 'tool_use') {
77
+ return {
78
+ type: 'tool_start',
79
+ toolName: block.name,
80
+ toolId: block.id,
81
+ };
82
+ }
83
+
84
+ if (block.type === 'thinking') {
85
+ return {
86
+ type: 'thinking_start',
87
+ };
88
+ }
89
+
90
+ // text block start - usually empty, skip
91
+ return null;
92
+ }
93
+
94
+ // content_block_delta - incremental content
95
+ if (inner.type === 'content_block_delta' && inner.delta) {
96
+ const delta = inner.delta;
97
+
98
+ if (delta.type === 'text_delta' && delta.text) {
99
+ return {
100
+ type: 'text',
101
+ text: delta.text,
102
+ };
103
+ }
104
+
105
+ if (delta.type === 'thinking_delta' && delta.thinking) {
106
+ return {
107
+ type: 'thinking',
108
+ text: delta.thinking,
109
+ };
110
+ }
111
+
112
+ if (delta.type === 'input_json_delta' && delta.partial_json) {
113
+ return {
114
+ type: 'tool_input',
115
+ json: delta.partial_json,
116
+ };
117
+ }
118
+ }
119
+
120
+ // content_block_stop - block ended
121
+ if (inner.type === 'content_block_stop') {
122
+ return {
123
+ type: 'block_end',
124
+ index: inner.index,
125
+ };
126
+ }
127
+
128
+ // message_start - skip text extraction here since it will come via text_delta events
129
+ // (extracting here causes duplicate text output)
130
+
131
+ return null;
132
+ }
133
+
134
+ /**
135
+ * Parse assistant message content blocks
136
+ * NOTE: Skip text blocks here since they were already streamed via text_delta events.
137
+ * Extracting text here would cause duplicate output.
138
+ */
139
+ function parseAssistantMessage(message) {
140
+ const results = [];
141
+
142
+ for (const block of message.content) {
143
+ // Skip text blocks - already streamed via text_delta events
144
+ // Extracting here causes duplicate output
145
+
146
+ if (block.type === 'tool_use') {
147
+ results.push({
148
+ type: 'tool_call',
149
+ toolName: block.name,
150
+ toolId: block.id,
151
+ input: block.input,
152
+ });
153
+ }
154
+
155
+ if (block.type === 'thinking' && block.thinking) {
156
+ // Skip thinking blocks too - already streamed via thinking_delta
157
+ // But keep for non-streaming contexts (direct API responses)
158
+ // Only emit if we haven't seen streaming deltas (detected by having results already)
159
+ }
160
+ }
161
+
162
+ if (results.length === 1) {
163
+ return results[0];
164
+ }
165
+
166
+ if (results.length > 1) {
167
+ return {
168
+ type: 'multi',
169
+ events: results,
170
+ };
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ /**
177
+ * Parse user message (tool results)
178
+ */
179
+ function parseUserMessage(message) {
180
+ const results = [];
181
+
182
+ for (const block of message.content) {
183
+ if (block.type === 'tool_result') {
184
+ results.push({
185
+ type: 'tool_result',
186
+ toolId: block.tool_use_id,
187
+ content: typeof block.content === 'string' ? block.content : JSON.stringify(block.content),
188
+ isError: block.is_error || false,
189
+ });
190
+ }
191
+ }
192
+
193
+ if (results.length === 1) {
194
+ return results[0];
195
+ }
196
+
197
+ if (results.length > 1) {
198
+ return {
199
+ type: 'multi',
200
+ events: results,
201
+ };
202
+ }
203
+
204
+ return null;
205
+ }
206
+
207
+ /**
208
+ * Parse multiple lines of NDJSON
209
+ * @param {string} chunk - Chunk of text potentially containing multiple JSON lines
210
+ * @returns {Array} Array of parsed events
211
+ */
212
+ function parseChunk(chunk) {
213
+ const events = [];
214
+ const lines = chunk.split('\n');
215
+
216
+ for (const line of lines) {
217
+ const event = parseStreamLine(line);
218
+ if (event) {
219
+ if (event.type === 'multi') {
220
+ events.push(...event.events);
221
+ } else {
222
+ events.push(event);
223
+ }
224
+ }
225
+ }
226
+
227
+ return events;
228
+ }
229
+
230
+ module.exports = {
231
+ parseStreamLine,
232
+ parseChunk,
233
+ parseStreamEvent,
234
+ parseAssistantMessage,
235
+ parseUserMessage,
236
+ };
package/package.json ADDED
@@ -0,0 +1,121 @@
1
+ {
2
+ "name": "@covibes/zeroshot",
3
+ "version": "1.0.1",
4
+ "description": "Multi-agent orchestration engine for Claude - cluster coordinator and CLI",
5
+ "main": "src/orchestrator.js",
6
+ "bin": {
7
+ "zeroshot": "./cli/index.js"
8
+ },
9
+ "directories": {
10
+ "example": "examples",
11
+ "test": "tests"
12
+ },
13
+ "scripts": {
14
+ "test": "mocha 'tests/**/*.test.js'",
15
+ "test:coverage": "c8 npm test",
16
+ "test:coverage:report": "c8 --reporter=html npm test && echo 'Coverage report generated at coverage/index.html'",
17
+ "start": "node cli/index.js",
18
+ "typecheck": "tsc --noEmit",
19
+ "lint": "eslint .",
20
+ "lint:fix": "eslint . --fix",
21
+ "format": "prettier --write .",
22
+ "format:check": "prettier --check .",
23
+ "deadcode": "ts-prune --skip node_modules",
24
+ "deadcode:files": "unimported",
25
+ "deadcode:deps": "depcheck",
26
+ "deadcode:all": "npm run deadcode && npm run deadcode:files && npm run deadcode:deps",
27
+ "check": "npm run typecheck && npm run lint",
28
+ "check:all": "npm run check && npm run deadcode:all",
29
+ "release": "semantic-release",
30
+ "prepublishOnly": "npm run lint && npm run typecheck && npm test"
31
+ },
32
+ "c8": {
33
+ "reporter": [
34
+ "text",
35
+ "lcov",
36
+ "html"
37
+ ],
38
+ "include": [
39
+ "src/**/*.js",
40
+ "lib/**/*.js",
41
+ "cli/**/*.js"
42
+ ],
43
+ "exclude": [
44
+ "**/*.test.js",
45
+ "**/tests/**"
46
+ ],
47
+ "all": true
48
+ },
49
+ "keywords": [
50
+ "ai",
51
+ "agents",
52
+ "multi-agent",
53
+ "orchestration",
54
+ "claude",
55
+ "automation"
56
+ ],
57
+ "author": "Covibes",
58
+ "license": "MIT",
59
+ "homepage": "https://github.com/covibes/zeroshot",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://github.com/covibes/zeroshot.git"
63
+ },
64
+ "bugs": {
65
+ "url": "https://github.com/covibes/zeroshot/issues"
66
+ },
67
+ "engines": {
68
+ "node": ">=18.0.0"
69
+ },
70
+ "publishConfig": {
71
+ "access": "public",
72
+ "registry": "https://registry.npmjs.org/"
73
+ },
74
+ "files": [
75
+ "src/",
76
+ "lib/",
77
+ "cli/",
78
+ "cluster-templates/",
79
+ "hooks/",
80
+ "docker/",
81
+ "README.md",
82
+ "LICENSE",
83
+ "CHANGELOG.md"
84
+ ],
85
+ "dependencies": {
86
+ "ajv": "^8.17.1",
87
+ "ansi-to-html": "^0.7.2",
88
+ "better-sqlite3": "^12.5.0",
89
+ "blessed": "^0.1.81",
90
+ "blessed-contrib": "^4.11.0",
91
+ "chalk": "^4.1.2",
92
+ "commander": "^14.0.2",
93
+ "md-to-pdf": "^5.2.5",
94
+ "node-pty": "^1.0.0",
95
+ "omelette": "^0.4.17",
96
+ "pidusage": "^4.0.1",
97
+ "proper-lockfile": "^4.1.2"
98
+ },
99
+ "devDependencies": {
100
+ "@semantic-release/changelog": "^6.0.3",
101
+ "@semantic-release/git": "^10.0.1",
102
+ "@semantic-release/github": "^11.0.6",
103
+ "@types/better-sqlite3": "^7.6.13",
104
+ "@types/node": "^25.0.3",
105
+ "c8": "^10.1.3",
106
+ "chai": "^6.2.1",
107
+ "depcheck": "^1.4.7",
108
+ "eslint": "^9.39.1",
109
+ "eslint-config-prettier": "^10.1.8",
110
+ "eslint-plugin-unused-imports": "^4.3.0",
111
+ "mocha": "^11.7.5",
112
+ "semantic-release": "^25.0.2",
113
+ "sinon": "^21.0.0",
114
+ "ts-prune": "^0.10.3",
115
+ "typescript": "^5.9.3",
116
+ "unimported": "^1.31.0"
117
+ },
118
+ "overrides": {
119
+ "xml2js": "^0.5.0"
120
+ }
121
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * AgentConfig - Agent configuration validation and defaults
3
+ *
4
+ * Provides:
5
+ * - Config validation and normalization
6
+ * - Default values for optional fields
7
+ * - Model configuration setup
8
+ * - Safety checks for test mode
9
+ */
10
+
11
+ // Default max iterations (high limit - let the user decide when to give up)
12
+ const DEFAULT_MAX_ITERATIONS = 100;
13
+
14
+ // Task timeout - DISABLED (tasks run until completion or explicit kill)
15
+ // Originally: 2 hours - caused premature termination of long-running tasks
16
+ // Now: Infinity - tasks only stop on completion, explicit kill, or external error
17
+ const DEFAULT_TASK_TIMEOUT_MS = Infinity;
18
+
19
+ // Stale detection - ENABLED by default using multi-indicator analysis (safe from false positives)
20
+ // Multi-indicator approach checks: process state, CPU usage, context switches, network I/O
21
+ // Only flags as stuck when ALL indicators show inactivity (score >= 3.5)
22
+ // Single-indicator detection (just output freshness) was too risky - this is safe.
23
+ const DEFAULT_STALE_DURATION_MS = 30 * 60 * 1000; // 30 minutes before triggering analysis
24
+ const DEFAULT_LIVENESS_CHECK_ENABLED = true; // Safe with multi-indicator detection
25
+
26
+ /**
27
+ * Validate and normalize agent configuration
28
+ * @param {Object} config - Raw agent configuration
29
+ * @param {Object} options - Agent wrapper options
30
+ * @returns {Object} Normalized configuration
31
+ */
32
+ function validateAgentConfig(config, options = {}) {
33
+ // CRITICAL: Enforce JSON schema output by default to prevent parse failures and crashes
34
+ // Agents MUST return structured output so hooks can safely use {{result.*}} templates
35
+ if (!config.outputFormat) {
36
+ config.outputFormat = 'json';
37
+ }
38
+
39
+ // If outputFormat is json but no schema defined, use a minimal default schema
40
+ // This prevents uncaught exceptions when parsing agent output
41
+ if (config.outputFormat === 'json' && !config.jsonSchema) {
42
+ config.jsonSchema = {
43
+ type: 'object',
44
+ properties: {
45
+ summary: {
46
+ type: 'string',
47
+ description: 'Brief summary of what was done',
48
+ },
49
+ result: {
50
+ type: 'string',
51
+ description: 'Detailed result or output',
52
+ },
53
+ },
54
+ required: ['summary', 'result'],
55
+ };
56
+ }
57
+
58
+ // Model configuration: support both static model and dynamic rules
59
+ let modelConfig;
60
+ if (config.modelRules) {
61
+ modelConfig = { type: 'rules', rules: config.modelRules };
62
+ } else {
63
+ modelConfig = { type: 'static', model: config.model || 'sonnet' };
64
+ }
65
+
66
+ // Prompt configuration: support static prompt OR iteration-based rules
67
+ // Formats:
68
+ // prompt: "string" -> static
69
+ // prompt: { system: "string" } -> static
70
+ // prompt: { initial: "...", subsequent: "..." } -> iteration 1 vs 2+
71
+ // prompt: { iterations: [...] } -> full control
72
+ let promptConfig = null;
73
+ if (config.prompt?.iterations) {
74
+ promptConfig = { type: 'rules', rules: config.prompt.iterations };
75
+ } else if (config.prompt?.initial || config.prompt?.subsequent) {
76
+ const rules = [];
77
+ if (config.prompt.initial) rules.push({ match: '1', system: config.prompt.initial });
78
+ if (config.prompt.subsequent) rules.push({ match: '2+', system: config.prompt.subsequent });
79
+ promptConfig = { type: 'rules', rules };
80
+ } else if (typeof config.prompt === 'string') {
81
+ promptConfig = { type: 'static', system: config.prompt };
82
+ } else if (config.prompt?.system) {
83
+ promptConfig = { type: 'static', system: config.prompt.system };
84
+ } else if (config.prompt) {
85
+ throw new Error(`Agent "${config.id}": invalid prompt format`);
86
+ }
87
+
88
+ // Build normalized config
89
+ const normalizedConfig = {
90
+ ...config,
91
+ modelConfig,
92
+ promptConfig,
93
+ maxIterations: config.maxIterations || DEFAULT_MAX_ITERATIONS,
94
+ timeout: config.timeout || DEFAULT_TASK_TIMEOUT_MS,
95
+ staleDuration: config.staleDuration || DEFAULT_STALE_DURATION_MS,
96
+ enableLivenessCheck: config.enableLivenessCheck ?? DEFAULT_LIVENESS_CHECK_ENABLED, // On by default, opt-out with false
97
+ };
98
+
99
+ // SAFETY: In test mode, verify mock is provided for agents that execute tasks
100
+ // Check if this agent executes tasks (vs orchestrator agents that only publish messages)
101
+ const executesTask = config.triggers?.some(
102
+ (trigger) => !trigger.action || trigger.action === 'execute_task'
103
+ );
104
+
105
+ if (options.testMode && !options.mockSpawnFn && executesTask) {
106
+ throw new Error(
107
+ `AgentWrapper: testMode=true but no mockSpawnFn provided for agent '${config.id}'. ` +
108
+ `This would cause real Claude API calls. ABORTING.`
109
+ );
110
+ }
111
+
112
+ return normalizedConfig;
113
+ }
114
+
115
+ module.exports = {
116
+ validateAgentConfig,
117
+ DEFAULT_MAX_ITERATIONS,
118
+ DEFAULT_TASK_TIMEOUT_MS,
119
+ DEFAULT_STALE_DURATION_MS,
120
+ DEFAULT_LIVENESS_CHECK_ENABLED,
121
+ };