@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.
- package/CHANGELOG.md +174 -189
- package/README.md +226 -195
- package/cli/commands/providers.js +149 -0
- package/cli/index.js +3145 -2366
- package/cli/lib/first-run.js +40 -3
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +24 -78
- package/cluster-templates/base-templates/full-workflow.json +99 -316
- package/cluster-templates/base-templates/single-worker.json +23 -15
- package/cluster-templates/base-templates/worker-validator.json +105 -36
- package/cluster-templates/conductor-bootstrap.json +9 -7
- package/lib/docker-config.js +14 -1
- 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-detection.js +59 -0
- package/lib/provider-names.js +57 -0
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +298 -15
- package/lib/stream-json-parser.js +4 -238
- package/package.json +27 -6
- package/scripts/setup-merge-queue.sh +170 -0
- package/scripts/validate-templates.js +100 -0
- package/src/agent/agent-config.js +140 -63
- package/src/agent/agent-context-builder.js +336 -165
- package/src/agent/agent-hook-executor.js +337 -67
- package/src/agent/agent-lifecycle.js +386 -287
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +944 -683
- package/src/agent/output-extraction.js +217 -0
- package/src/agent/output-reformatter.js +175 -0
- package/src/agent/schema-utils.js +146 -0
- package/src/agent-wrapper.js +112 -31
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +145 -44
- package/src/config-router.js +13 -13
- package/src/config-validator.js +1049 -563
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +499 -320
- 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 +50 -11
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1348 -757
- package/src/preflight.js +306 -149
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +73 -0
- package/src/providers/anthropic/index.js +204 -0
- package/src/providers/anthropic/models.js +23 -0
- package/src/providers/anthropic/output-parser.js +177 -0
- package/src/providers/base-provider.js +251 -0
- package/src/providers/capabilities.js +60 -0
- package/src/providers/google/cli-builder.js +55 -0
- package/src/providers/google/index.js +116 -0
- package/src/providers/google/models.js +24 -0
- package/src/providers/google/output-parser.js +101 -0
- package/src/providers/index.js +91 -0
- package/src/providers/openai/cli-builder.js +133 -0
- package/src/providers/openai/index.js +136 -0
- package/src/providers/openai/models.js +21 -0
- package/src/providers/openai/output-parser.js +143 -0
- 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 +92 -36
- package/src/task-runner.js +8 -6
- 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/layout.js +20 -3
- package/src/tui/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +150 -111
- package/task-lib/claude-recovery.js +156 -0
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +3 -3
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/resume.js +3 -2
- package/task-lib/commands/run.js +12 -3
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +128 -50
- package/task-lib/scheduler.js +3 -3
- package/task-lib/store.js +464 -152
- 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 +157 -100
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -103
|
@@ -100,6 +100,23 @@ function truncate(str, maxLen) {
|
|
|
100
100
|
* @returns {object|null} Parsed event with type, text, toolName, error
|
|
101
101
|
*/
|
|
102
102
|
function parseEvent(line) {
|
|
103
|
+
const { trimmed, timestamp } = extractTimestamp(line);
|
|
104
|
+
|
|
105
|
+
// Keep non-JSON lines as-is
|
|
106
|
+
if (!trimmed.startsWith('{')) {
|
|
107
|
+
return trimmed ? { type: 'raw', text: trimmed, timestamp } : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Parse JSON events
|
|
111
|
+
const event = safeParseEvent(trimmed);
|
|
112
|
+
if (!event) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return parseStreamEvent(event, timestamp);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function extractTimestamp(line) {
|
|
103
120
|
let trimmed = line.trim();
|
|
104
121
|
|
|
105
122
|
// Strip timestamp prefix if present: [1234567890]{...} -> {...}
|
|
@@ -110,57 +127,89 @@ function parseEvent(line) {
|
|
|
110
127
|
trimmed = timestampMatch[2];
|
|
111
128
|
}
|
|
112
129
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return trimmed ? { type: 'raw', text: trimmed, timestamp } : null;
|
|
116
|
-
}
|
|
130
|
+
return { trimmed, timestamp };
|
|
131
|
+
}
|
|
117
132
|
|
|
118
|
-
|
|
133
|
+
function safeParseEvent(trimmed) {
|
|
119
134
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// Text delta
|
|
123
|
-
if (event.type === 'stream_event' && event.event?.type === 'content_block_delta') {
|
|
124
|
-
return {
|
|
125
|
-
type: 'text',
|
|
126
|
-
text: event.event?.delta?.text || '',
|
|
127
|
-
timestamp,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
// Tool use
|
|
131
|
-
else if (event.type === 'stream_event' && event.event?.type === 'content_block_start') {
|
|
132
|
-
const block = event.event?.content_block;
|
|
133
|
-
if (block?.type === 'tool_use' && block?.name) {
|
|
134
|
-
return {
|
|
135
|
-
type: 'tool',
|
|
136
|
-
toolName: block.name,
|
|
137
|
-
timestamp,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// Assistant message
|
|
142
|
-
else if (event.type === 'assistant' && event.message?.content) {
|
|
143
|
-
let text = '';
|
|
144
|
-
for (const content of event.message.content) {
|
|
145
|
-
if (content.type === 'text') {
|
|
146
|
-
text += content.text;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return text ? { type: 'text', text, timestamp } : null;
|
|
150
|
-
}
|
|
151
|
-
// Error
|
|
152
|
-
else if (event.type === 'result' && event.is_error) {
|
|
153
|
-
return {
|
|
154
|
-
type: 'error',
|
|
155
|
-
text: event.result || 'Unknown error',
|
|
156
|
-
timestamp,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
135
|
+
return JSON.parse(trimmed);
|
|
159
136
|
} catch {
|
|
160
|
-
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseStreamEvent(event, timestamp) {
|
|
142
|
+
if (event.type === 'stream_event') {
|
|
143
|
+
return parseStreamDelta(event, timestamp);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (event.type === 'assistant') {
|
|
147
|
+
return parseAssistantMessage(event, timestamp);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (event.type === 'result') {
|
|
151
|
+
return parseResultEvent(event, timestamp);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseStreamDelta(event, timestamp) {
|
|
158
|
+
const eventType = event.event?.type;
|
|
159
|
+
|
|
160
|
+
if (eventType === 'content_block_delta') {
|
|
161
|
+
return {
|
|
162
|
+
type: 'text',
|
|
163
|
+
text: event.event?.delta?.text || '',
|
|
164
|
+
timestamp,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (eventType === 'content_block_start') {
|
|
169
|
+
return parseToolUseEvent(event.event?.content_block, timestamp);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function parseToolUseEvent(block, timestamp) {
|
|
176
|
+
if (block?.type === 'tool_use' && block?.name) {
|
|
177
|
+
return {
|
|
178
|
+
type: 'tool',
|
|
179
|
+
toolName: block.name,
|
|
180
|
+
timestamp,
|
|
181
|
+
};
|
|
161
182
|
}
|
|
162
183
|
|
|
163
184
|
return null;
|
|
164
185
|
}
|
|
165
186
|
|
|
187
|
+
function parseAssistantMessage(event, timestamp) {
|
|
188
|
+
const contentBlocks = event.message?.content;
|
|
189
|
+
if (!Array.isArray(contentBlocks)) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let text = '';
|
|
194
|
+
for (const content of contentBlocks) {
|
|
195
|
+
if (content.type === 'text') {
|
|
196
|
+
text += content.text;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return text ? { type: 'text', text, timestamp } : null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function parseResultEvent(event, timestamp) {
|
|
204
|
+
if (!event.is_error) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
type: 'error',
|
|
210
|
+
text: event.result || 'Unknown error',
|
|
211
|
+
timestamp,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
166
215
|
export { formatTimestamp, formatBytes, formatCPU, stateIcon, truncate, parseEvent };
|
package/task-lib/tui/renderer.js
CHANGED
|
@@ -23,6 +23,18 @@ class Renderer {
|
|
|
23
23
|
this.screen = screen;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Format task prompt for display (truncate if needed)
|
|
28
|
+
* @param {string|undefined} prompt - Task prompt
|
|
29
|
+
* @returns {string} Formatted prompt or empty string
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
_formatTaskPrompt(prompt) {
|
|
33
|
+
if (!prompt) return '';
|
|
34
|
+
const truncated = prompt.length > 80 ? prompt.substring(0, 80) + '...' : prompt;
|
|
35
|
+
return `{bold}Task:{/bold} {gray-fg}${truncated}{/}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
/**
|
|
27
39
|
* Render task info box
|
|
28
40
|
* @param {object} taskInfo - Task metadata
|
|
@@ -40,9 +52,7 @@ class Renderer {
|
|
|
40
52
|
`${icon} {bold}Status:{/bold} {white-fg}${taskInfo.status || 'unknown'}{/}`,
|
|
41
53
|
`{bold}Runtime:{/bold} {white-fg}${runtime}{/}`,
|
|
42
54
|
`{bold}CPU:{/bold} {white-fg}${cpu}{/} {bold}Memory:{/bold} {white-fg}${memory}{/}`,
|
|
43
|
-
taskInfo.prompt
|
|
44
|
-
? `{bold}Task:{/bold} {gray-fg}${taskInfo.prompt.substring(0, 80)}${taskInfo.prompt.length > 80 ? '...' : ''}{/}`
|
|
45
|
-
: '',
|
|
55
|
+
this._formatTaskPrompt(taskInfo.prompt),
|
|
46
56
|
]
|
|
47
57
|
.filter(Boolean)
|
|
48
58
|
.join(' ');
|
package/task-lib/tui.js
CHANGED
|
@@ -31,38 +31,80 @@ function parseLogLine(line) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Parse JSON and extract relevant info
|
|
34
|
+
const event = safeParseEvent(trimmed);
|
|
35
|
+
if (!event) {
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return extractEventText(event);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function safeParseEvent(trimmed) {
|
|
34
43
|
try {
|
|
35
|
-
|
|
44
|
+
return JSON.parse(trimmed);
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
36
49
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
function extractEventText(event) {
|
|
51
|
+
if (event.type === 'stream_event') {
|
|
52
|
+
return extractStreamEventText(event);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (event.type === 'assistant') {
|
|
56
|
+
return extractAssistantText(event);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (event.type === 'result') {
|
|
60
|
+
return extractResultText(event);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function extractStreamEventText(event) {
|
|
67
|
+
const eventType = event.event?.type;
|
|
68
|
+
|
|
69
|
+
if (eventType === 'content_block_delta') {
|
|
70
|
+
return event.event?.delta?.text || '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (eventType === 'content_block_start') {
|
|
74
|
+
return extractToolUseText(event.event?.content_block);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extractToolUseText(block) {
|
|
81
|
+
if (block?.type === 'tool_use' && block?.name) {
|
|
82
|
+
return `\n[Tool: ${block.name}]\n`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function extractAssistantText(event) {
|
|
89
|
+
const contentBlocks = event.message?.content;
|
|
90
|
+
|
|
91
|
+
if (!Array.isArray(contentBlocks)) {
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let output = '';
|
|
96
|
+
for (const content of contentBlocks) {
|
|
97
|
+
if (content.type === 'text') {
|
|
98
|
+
output += content.text;
|
|
63
99
|
}
|
|
64
|
-
}
|
|
65
|
-
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return output;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function extractResultText(event) {
|
|
106
|
+
if (event.is_error) {
|
|
107
|
+
return `\n[ERROR] ${event.result || 'Unknown error'}\n`;
|
|
66
108
|
}
|
|
67
109
|
|
|
68
110
|
return '';
|
|
@@ -260,8 +302,8 @@ class TaskTUI {
|
|
|
260
302
|
const tasks = loadTasks();
|
|
261
303
|
this.tasks = Object.values(tasks);
|
|
262
304
|
|
|
263
|
-
// Sort by creation date,
|
|
264
|
-
this.tasks.sort((a, b) => new Date(
|
|
305
|
+
// Sort by creation date, oldest first (chronological)
|
|
306
|
+
this.tasks.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
|
|
265
307
|
|
|
266
308
|
// Verify running status
|
|
267
309
|
for (const task of this.tasks) {
|
|
@@ -334,7 +376,6 @@ class TaskTUI {
|
|
|
334
376
|
}
|
|
335
377
|
|
|
336
378
|
// Strip ANSI codes for clean display
|
|
337
|
-
// eslint-disable-next-line no-control-regex
|
|
338
379
|
content = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
339
380
|
} catch (error) {
|
|
340
381
|
content = `Error reading log: ${error.message}`;
|
package/task-lib/watcher.js
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Watcher process - spawns and monitors a
|
|
4
|
+
* Watcher process - spawns and monitors a CLI process
|
|
5
5
|
* Runs detached from parent, updates task status on completion
|
|
6
|
-
*
|
|
7
|
-
* Uses regular spawn (not PTY) - Claude CLI with --print is non-interactive
|
|
8
|
-
* PTY causes EIO errors when processes are killed/OOM'd
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
8
|
import { spawn } from 'child_process';
|
|
12
9
|
import { appendFileSync } from 'fs';
|
|
13
|
-
import { dirname } from 'path';
|
|
14
|
-
import { fileURLToPath } from 'url';
|
|
15
10
|
import { updateTask } from './store.js';
|
|
11
|
+
import { detectStreamingModeError, recoverStructuredOutput } from './claude-recovery.js';
|
|
16
12
|
import { createRequire } from 'module';
|
|
17
13
|
|
|
18
14
|
const require = createRequire(import.meta.url);
|
|
19
|
-
const {
|
|
20
|
-
|
|
21
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const { normalizeProviderName } = require('../lib/provider-names');
|
|
22
16
|
|
|
23
17
|
const [, , taskId, cwd, logFile, argsJson, configJson] = process.argv;
|
|
24
18
|
const args = JSON.parse(argsJson);
|
|
@@ -28,142 +22,205 @@ function log(msg) {
|
|
|
28
22
|
appendFileSync(logFile, msg);
|
|
29
23
|
}
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
// Add model flag - priority: config.model > ANTHROPIC_MODEL env var
|
|
35
|
-
const claudeArgs = [...args];
|
|
36
|
-
const model = config.model || env.ANTHROPIC_MODEL;
|
|
37
|
-
if (model && !claudeArgs.includes('--model')) {
|
|
38
|
-
claudeArgs.unshift('--model', model);
|
|
39
|
-
}
|
|
25
|
+
const providerName = normalizeProviderName(config.provider || 'claude');
|
|
26
|
+
const enableRecovery = providerName === 'claude';
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const finalArgs = [...
|
|
28
|
+
const env = { ...process.env, ...(config.env || {}) };
|
|
29
|
+
const command = config.command || 'claude';
|
|
30
|
+
const finalArgs = [...args];
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
// --print mode is non-interactive, PTY adds overhead and causes EIO on OOM
|
|
47
|
-
const child = spawn(claudeCommand, finalArgs, {
|
|
32
|
+
const child = spawn(command, finalArgs, {
|
|
48
33
|
cwd,
|
|
49
34
|
env,
|
|
50
35
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
51
36
|
});
|
|
52
37
|
|
|
53
|
-
// Update task with PID
|
|
54
38
|
updateTask(taskId, { pid: child.pid });
|
|
55
39
|
|
|
56
|
-
// For JSON schema output with silent mode, capture ONLY the structured_output JSON
|
|
57
40
|
const silentJsonMode =
|
|
58
|
-
config.outputFormat === 'json' && config.jsonSchema && config.silentJsonOutput;
|
|
41
|
+
config.outputFormat === 'json' && config.jsonSchema && config.silentJsonOutput && enableRecovery;
|
|
42
|
+
|
|
59
43
|
let finalResultJson = null;
|
|
44
|
+
let streamingModeError = null;
|
|
60
45
|
|
|
61
|
-
// Buffer for incomplete lines (need complete lines to add timestamps)
|
|
62
46
|
let stdoutBuffer = '';
|
|
47
|
+
let stderrBuffer = '';
|
|
48
|
+
|
|
49
|
+
function splitBufferLines(buffer, chunk) {
|
|
50
|
+
const nextBuffer = buffer + chunk;
|
|
51
|
+
const lines = nextBuffer.split('\n');
|
|
52
|
+
const remaining = lines.pop() || '';
|
|
53
|
+
return { lines, remaining };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function captureStreamingError(line, timestamp) {
|
|
57
|
+
if (!enableRecovery) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const detectedError = detectStreamingModeError(line);
|
|
62
|
+
if (!detectedError) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
streamingModeError = { ...detectedError, timestamp };
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function maybeCaptureStructuredOutput(line) {
|
|
71
|
+
try {
|
|
72
|
+
const json = JSON.parse(line);
|
|
73
|
+
if (json.structured_output) {
|
|
74
|
+
finalResultJson = line;
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Not JSON, skip
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleSilentJsonLines(lines, timestamp) {
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (!line.trim()) continue;
|
|
84
|
+
if (captureStreamingError(line, timestamp)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
maybeCaptureStructuredOutput(line);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function handleStreamingLines(lines, timestamp) {
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
if (captureStreamingError(line, timestamp)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
log(`[${timestamp}]${line}\n`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function flushStdoutBuffer(timestamp) {
|
|
101
|
+
if (!stdoutBuffer.trim()) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!enableRecovery) {
|
|
106
|
+
if (!silentJsonMode) {
|
|
107
|
+
log(`[${timestamp}]${stdoutBuffer}\n`);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (captureStreamingError(stdoutBuffer, timestamp)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (silentJsonMode) {
|
|
117
|
+
maybeCaptureStructuredOutput(stdoutBuffer);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
log(`[${timestamp}]${stdoutBuffer}\n`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function flushStderrBuffer(timestamp) {
|
|
125
|
+
if (stderrBuffer.trim()) {
|
|
126
|
+
log(`[${timestamp}]${stderrBuffer}\n`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function attemptRecovery(code, timestamp) {
|
|
131
|
+
if (!(enableRecovery && code !== 0 && streamingModeError?.sessionId)) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const recovered = recoverStructuredOutput(streamingModeError.sessionId);
|
|
136
|
+
if (recovered?.payload) {
|
|
137
|
+
const recoveredLine = JSON.stringify(recovered.payload);
|
|
138
|
+
if (silentJsonMode) {
|
|
139
|
+
finalResultJson = recoveredLine;
|
|
140
|
+
} else {
|
|
141
|
+
log(`[${timestamp}]${recoveredLine}\n`);
|
|
142
|
+
}
|
|
143
|
+
} else if (streamingModeError.line) {
|
|
144
|
+
if (silentJsonMode) {
|
|
145
|
+
log(streamingModeError.line + '\n');
|
|
146
|
+
} else {
|
|
147
|
+
log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return recovered;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function writeCompletionFooter(code, signal) {
|
|
155
|
+
if (config.outputFormat === 'json') {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
log(`\n${'='.repeat(50)}\n`);
|
|
160
|
+
log(`Finished: ${new Date().toISOString()}\n`);
|
|
161
|
+
log(`Exit code: ${code}, Signal: ${signal}\n`);
|
|
162
|
+
}
|
|
63
163
|
|
|
64
|
-
// Process stdout data
|
|
65
|
-
// CRITICAL: Prepend timestamp to each line for real-time tracking in cluster
|
|
66
|
-
// Format: [1733301234567]{json...} - consumers parse timestamp for accurate timing
|
|
67
164
|
child.stdout.on('data', (data) => {
|
|
68
165
|
const chunk = data.toString();
|
|
69
166
|
const timestamp = Date.now();
|
|
70
167
|
|
|
168
|
+
const { lines, remaining } = splitBufferLines(stdoutBuffer, chunk);
|
|
169
|
+
stdoutBuffer = remaining;
|
|
170
|
+
|
|
71
171
|
if (silentJsonMode) {
|
|
72
|
-
|
|
73
|
-
stdoutBuffer += chunk;
|
|
74
|
-
const lines = stdoutBuffer.split('\n');
|
|
75
|
-
stdoutBuffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
76
|
-
|
|
77
|
-
for (const line of lines) {
|
|
78
|
-
if (!line.trim()) continue;
|
|
79
|
-
try {
|
|
80
|
-
const json = JSON.parse(line);
|
|
81
|
-
if (json.structured_output) {
|
|
82
|
-
finalResultJson = line;
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
// Not JSON or incomplete, skip
|
|
86
|
-
}
|
|
87
|
-
}
|
|
172
|
+
handleSilentJsonLines(lines, timestamp);
|
|
88
173
|
} else {
|
|
89
|
-
|
|
90
|
-
stdoutBuffer += chunk;
|
|
91
|
-
const lines = stdoutBuffer.split('\n');
|
|
92
|
-
stdoutBuffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
93
|
-
|
|
94
|
-
for (const line of lines) {
|
|
95
|
-
// Timestamp each line: [epochMs]originalContent
|
|
96
|
-
log(`[${timestamp}]${line}\n`);
|
|
97
|
-
}
|
|
174
|
+
handleStreamingLines(lines, timestamp);
|
|
98
175
|
}
|
|
99
176
|
});
|
|
100
177
|
|
|
101
|
-
// Buffer for stderr incomplete lines
|
|
102
|
-
let stderrBuffer = '';
|
|
103
|
-
|
|
104
|
-
// Stream stderr to log with timestamps
|
|
105
178
|
child.stderr.on('data', (data) => {
|
|
106
179
|
const chunk = data.toString();
|
|
107
180
|
const timestamp = Date.now();
|
|
108
181
|
|
|
109
|
-
stderrBuffer
|
|
110
|
-
|
|
111
|
-
stderrBuffer = lines.pop() || '';
|
|
182
|
+
const { lines, remaining } = splitBufferLines(stderrBuffer, chunk);
|
|
183
|
+
stderrBuffer = remaining;
|
|
112
184
|
|
|
113
185
|
for (const line of lines) {
|
|
114
186
|
log(`[${timestamp}]${line}\n`);
|
|
115
187
|
}
|
|
116
188
|
});
|
|
117
189
|
|
|
118
|
-
|
|
119
|
-
child.on('close', (code, signal) => {
|
|
190
|
+
child.on('close', async (code, signal) => {
|
|
120
191
|
const timestamp = Date.now();
|
|
121
192
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (silentJsonMode) {
|
|
125
|
-
try {
|
|
126
|
-
const json = JSON.parse(stdoutBuffer);
|
|
127
|
-
if (json.structured_output) {
|
|
128
|
-
finalResultJson = stdoutBuffer;
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
// Not valid JSON
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
log(`[${timestamp}]${stdoutBuffer}\n`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
193
|
+
flushStdoutBuffer(timestamp);
|
|
194
|
+
flushStderrBuffer(timestamp);
|
|
137
195
|
|
|
138
|
-
|
|
139
|
-
if (stderrBuffer.trim()) {
|
|
140
|
-
log(`[${timestamp}]${stderrBuffer}\n`);
|
|
141
|
-
}
|
|
196
|
+
const recovered = attemptRecovery(code, timestamp);
|
|
142
197
|
|
|
143
|
-
// In silent JSON mode, log ONLY the final structured_output JSON
|
|
144
198
|
if (silentJsonMode && finalResultJson) {
|
|
145
199
|
log(finalResultJson + '\n');
|
|
146
200
|
}
|
|
147
201
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
202
|
+
writeCompletionFooter(code, signal);
|
|
203
|
+
|
|
204
|
+
const resolvedCode = recovered?.payload ? 0 : code;
|
|
205
|
+
const status = resolvedCode === 0 ? 'completed' : 'failed';
|
|
206
|
+
try {
|
|
207
|
+
await updateTask(taskId, {
|
|
208
|
+
status,
|
|
209
|
+
exitCode: resolvedCode,
|
|
210
|
+
error: resolvedCode !== 0 && signal ? `Killed by ${signal}` : null,
|
|
211
|
+
});
|
|
212
|
+
} catch (updateError) {
|
|
213
|
+
log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
|
|
153
214
|
}
|
|
154
|
-
|
|
155
|
-
// Simple status: completed if exit 0, failed otherwise
|
|
156
|
-
const status = code === 0 ? 'completed' : 'failed';
|
|
157
|
-
updateTask(taskId, {
|
|
158
|
-
status,
|
|
159
|
-
exitCode: code,
|
|
160
|
-
error: signal ? `Killed by ${signal}` : null,
|
|
161
|
-
});
|
|
162
215
|
process.exit(0);
|
|
163
216
|
});
|
|
164
217
|
|
|
165
|
-
child.on('error', (err) => {
|
|
218
|
+
child.on('error', async (err) => {
|
|
166
219
|
log(`\nError: ${err.message}\n`);
|
|
167
|
-
|
|
220
|
+
try {
|
|
221
|
+
await updateTask(taskId, { status: 'failed', error: err.message });
|
|
222
|
+
} catch (updateError) {
|
|
223
|
+
log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
|
|
224
|
+
}
|
|
168
225
|
process.exit(1);
|
|
169
226
|
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "git-pusher",
|
|
3
|
-
"role": "completion-detector",
|
|
4
|
-
"model": "sonnet",
|
|
5
|
-
"triggers": [
|
|
6
|
-
{
|
|
7
|
-
"topic": "VALIDATION_RESULT",
|
|
8
|
-
"logic": {
|
|
9
|
-
"engine": "javascript",
|
|
10
|
-
"script": "const validators = cluster.getAgentsByRole('validator'); const lastPush = ledger.findLast({ topic: 'IMPLEMENTATION_READY' }); if (!lastPush) return false; if (validators.length === 0) return true; const results = ledger.query({ topic: 'VALIDATION_RESULT', since: lastPush.timestamp }); if (results.length < validators.length) return false; const allApproved = results.every(r => r.content?.data?.approved === 'true' || r.content?.data?.approved === true); if (!allApproved) return false; const hasRealEvidence = results.every(r => { const criteria = r.content?.data?.criteriaResults || []; return criteria.every(c => { return c.evidence?.command && typeof c.evidence?.exitCode === 'number' && c.evidence?.output?.length > 10; }); }); return hasRealEvidence;"
|
|
11
|
-
},
|
|
12
|
-
"action": "execute_task"
|
|
13
|
-
}
|
|
14
|
-
],
|
|
15
|
-
"prompt": "\ud83d\udea8 CRITICAL: ALL VALIDATORS APPROVED. YOU MUST CREATE A PR AND GET IT MERGED. DO NOT STOP UNTIL THE PR IS MERGED. \ud83d\udea8\n\n## MANDATORY STEPS - EXECUTE EACH ONE IN ORDER - DO NOT SKIP ANY STEP\n\n### STEP 1: Stage ALL changes (MANDATORY)\n```bash\ngit add -A\n```\nRun this command. Do not skip it.\n\n### STEP 2: Check what's staged\n```bash\ngit status\n```\nRun this. If nothing to commit, output JSON with pr_url: null and stop.\n\n### STEP 3: Commit the changes (MANDATORY if there are changes)\n```bash\ngit commit -m \"feat: implement #{{issue_number}} - {{issue_title}}\"\n```\nRun this command. Do not skip it.\n\n### STEP 4: Push to origin (MANDATORY)\n```bash\ngit push -u origin HEAD\n```\nRun this. If it fails, check the error and fix it.\n\n\u26a0\ufe0f AFTER PUSH YOU ARE NOT DONE! CONTINUE TO STEP 5! \u26a0\ufe0f\n\n### STEP 5: CREATE THE PR (MANDATORY - YOU MUST RUN THIS COMMAND)\n```bash\ngh pr create --title \"feat: {{issue_title}}\" --body \"Closes #{{issue_number}}\"\n```\n\ud83d\udea8 YOU MUST RUN `gh pr create`! Outputting a link is NOT creating a PR! \ud83d\udea8\nThe push output shows a \"Create a pull request\" link - IGNORE IT.\nYou MUST run the `gh pr create` command above. Save the actual PR URL from the output.\n\n\u26a0\ufe0f AFTER PR CREATION YOU ARE NOT DONE! CONTINUE TO STEP 6! \u26a0\ufe0f\n\n### STEP 6: MERGE THE PR (MANDATORY - THIS IS NOT OPTIONAL)\n```bash\ngh pr merge --merge --auto\n```\nThis sets auto-merge. If it fails (e.g., no auto-merge enabled), try:\n```bash\ngh pr merge --merge\n```\n\n\ud83d\udea8 IF MERGE FAILS DUE TO CONFLICTS - YOU MUST RESOLVE THEM:\na) Pull latest main and rebase:\n ```bash\n git fetch origin main\n git rebase origin/main\n ```\nb) If conflicts appear - RESOLVE THEM IMMEDIATELY:\n - Read the conflicting files\n - Make intelligent decisions about what code to keep\n - Edit the files to resolve conflicts\n - `git add <resolved-files>`\n - `git rebase --continue`\nc) Force push the resolved branch:\n ```bash\n git push --force-with-lease\n ```\nd) Retry merge:\n ```bash\n gh pr merge --merge\n ```\n\nREPEAT UNTIL MERGED. DO NOT GIVE UP. DO NOT SKIP. THE PR MUST BE MERGED.\nIf merge is blocked by CI, wait and retry. If blocked by reviews, set auto-merge.\n\n## CRITICAL RULES\n- Execute EVERY step in order (1, 2, 3, 4, 5, 6)\n- Do NOT skip git add -A\n- Do NOT skip git commit\n- Do NOT skip gh pr create - THE TASK IS NOT DONE UNTIL PR EXISTS\n- Do NOT skip gh pr merge - THE TASK IS NOT DONE UNTIL PR IS MERGED\n- If push fails, debug and fix it\n- If PR creation fails, debug and fix it\n- If merge fails, debug and fix it\n- DO NOT OUTPUT JSON UNTIL PR IS ACTUALLY MERGED\n- A link from git push is NOT a PR - you must run gh pr create\n\n## Final Output\nONLY after the PR is MERGED, output:\n```json\n{\"pr_url\": \"https://github.com/owner/repo/pull/123\", \"pr_number\": 123, \"merged\": true}\n```\n\nIf truly no changes exist, output:\n```json\n{\"pr_url\": null, \"pr_number\": null, \"merged\": false}\n```",
|
|
16
|
-
"output": {
|
|
17
|
-
"topic": "PR_CREATED",
|
|
18
|
-
"publishAfter": "CLUSTER_COMPLETE"
|
|
19
|
-
}
|
|
20
|
-
}
|