@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.
- package/README.md +94 -14
- package/cli/commands/providers.js +8 -9
- package/cli/index.js +3032 -2409
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +1 -1
- package/cluster-templates/base-templates/full-workflow.json +72 -188
- package/cluster-templates/base-templates/worker-validator.json +59 -3
- package/cluster-templates/conductor-bootstrap.json +4 -4
- package/lib/docker-config.js +8 -0
- 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-names.js +2 -1
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +161 -63
- package/package.json +7 -2
- package/scripts/setup-merge-queue.sh +170 -0
- package/src/agent/agent-config.js +135 -82
- package/src/agent/agent-context-builder.js +297 -188
- package/src/agent/agent-hook-executor.js +310 -113
- package/src/agent/agent-lifecycle.js +385 -325
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +824 -565
- package/src/agent/output-extraction.js +41 -24
- package/src/agent/output-reformatter.js +1 -1
- package/src/agent/schema-utils.js +108 -73
- package/src/agent-wrapper.js +10 -1
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +85 -34
- package/src/config-validator.js +922 -657
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +289 -199
- 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 +22 -5
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1107 -811
- package/src/preflight.js +313 -159
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +53 -25
- package/src/providers/anthropic/index.js +72 -2
- package/src/providers/anthropic/output-parser.js +32 -14
- package/src/providers/base-provider.js +70 -0
- package/src/providers/capabilities.js +9 -0
- package/src/providers/google/output-parser.js +47 -38
- package/src/providers/index.js +19 -3
- package/src/providers/openai/cli-builder.js +14 -3
- package/src/providers/openai/index.js +1 -0
- package/src/providers/openai/output-parser.js +44 -30
- 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 +76 -35
- 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/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +118 -81
- package/task-lib/claude-recovery.js +61 -24
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +2 -2
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +84 -27
- package/task-lib/scheduler.js +1 -1
- package/task-lib/store.js +467 -168
- 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 +128 -90
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -139
|
@@ -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
|
@@ -44,61 +44,143 @@ let finalResultJson = null;
|
|
|
44
44
|
let streamingModeError = null;
|
|
45
45
|
|
|
46
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
|
+
}
|
|
47
163
|
|
|
48
164
|
child.stdout.on('data', (data) => {
|
|
49
165
|
const chunk = data.toString();
|
|
50
166
|
const timestamp = Date.now();
|
|
51
167
|
|
|
168
|
+
const { lines, remaining } = splitBufferLines(stdoutBuffer, chunk);
|
|
169
|
+
stdoutBuffer = remaining;
|
|
170
|
+
|
|
52
171
|
if (silentJsonMode) {
|
|
53
|
-
|
|
54
|
-
const lines = stdoutBuffer.split('\n');
|
|
55
|
-
stdoutBuffer = lines.pop() || '';
|
|
56
|
-
|
|
57
|
-
for (const line of lines) {
|
|
58
|
-
if (!line.trim()) continue;
|
|
59
|
-
if (enableRecovery) {
|
|
60
|
-
const detectedError = detectStreamingModeError(line);
|
|
61
|
-
if (detectedError) {
|
|
62
|
-
streamingModeError = { ...detectedError, timestamp };
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
try {
|
|
67
|
-
const json = JSON.parse(line);
|
|
68
|
-
if (json.structured_output) {
|
|
69
|
-
finalResultJson = line;
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
// Not JSON, skip
|
|
73
|
-
}
|
|
74
|
-
}
|
|
172
|
+
handleSilentJsonLines(lines, timestamp);
|
|
75
173
|
} else {
|
|
76
|
-
|
|
77
|
-
const lines = stdoutBuffer.split('\n');
|
|
78
|
-
stdoutBuffer = lines.pop() || '';
|
|
79
|
-
|
|
80
|
-
for (const line of lines) {
|
|
81
|
-
if (enableRecovery) {
|
|
82
|
-
const detectedError = detectStreamingModeError(line);
|
|
83
|
-
if (detectedError) {
|
|
84
|
-
streamingModeError = { ...detectedError, timestamp };
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
log(`[${timestamp}]${line}\n`);
|
|
89
|
-
}
|
|
174
|
+
handleStreamingLines(lines, timestamp);
|
|
90
175
|
}
|
|
91
176
|
});
|
|
92
177
|
|
|
93
|
-
let stderrBuffer = '';
|
|
94
|
-
|
|
95
178
|
child.stderr.on('data', (data) => {
|
|
96
179
|
const chunk = data.toString();
|
|
97
180
|
const timestamp = Date.now();
|
|
98
181
|
|
|
99
|
-
stderrBuffer
|
|
100
|
-
|
|
101
|
-
stderrBuffer = lines.pop() || '';
|
|
182
|
+
const { lines, remaining } = splitBufferLines(stderrBuffer, chunk);
|
|
183
|
+
stderrBuffer = remaining;
|
|
102
184
|
|
|
103
185
|
for (const line of lines) {
|
|
104
186
|
log(`[${timestamp}]${line}\n`);
|
|
@@ -108,60 +190,16 @@ child.stderr.on('data', (data) => {
|
|
|
108
190
|
child.on('close', async (code, signal) => {
|
|
109
191
|
const timestamp = Date.now();
|
|
110
192
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const detectedError = detectStreamingModeError(stdoutBuffer);
|
|
114
|
-
if (detectedError) {
|
|
115
|
-
streamingModeError = { ...detectedError, timestamp };
|
|
116
|
-
} else if (silentJsonMode) {
|
|
117
|
-
try {
|
|
118
|
-
const json = JSON.parse(stdoutBuffer);
|
|
119
|
-
if (json.structured_output) {
|
|
120
|
-
finalResultJson = stdoutBuffer;
|
|
121
|
-
}
|
|
122
|
-
} catch {
|
|
123
|
-
// Not valid JSON
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
log(`[${timestamp}]${stdoutBuffer}\n`);
|
|
127
|
-
}
|
|
128
|
-
} else if (!silentJsonMode) {
|
|
129
|
-
log(`[${timestamp}]${stdoutBuffer}\n`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (stderrBuffer.trim()) {
|
|
134
|
-
log(`[${timestamp}]${stderrBuffer}\n`);
|
|
135
|
-
}
|
|
193
|
+
flushStdoutBuffer(timestamp);
|
|
194
|
+
flushStderrBuffer(timestamp);
|
|
136
195
|
|
|
137
|
-
|
|
138
|
-
if (enableRecovery && code !== 0 && streamingModeError?.sessionId) {
|
|
139
|
-
recovered = recoverStructuredOutput(streamingModeError.sessionId);
|
|
140
|
-
if (recovered?.payload) {
|
|
141
|
-
const recoveredLine = JSON.stringify(recovered.payload);
|
|
142
|
-
if (silentJsonMode) {
|
|
143
|
-
finalResultJson = recoveredLine;
|
|
144
|
-
} else {
|
|
145
|
-
log(`[${timestamp}]${recoveredLine}\n`);
|
|
146
|
-
}
|
|
147
|
-
} else if (streamingModeError.line) {
|
|
148
|
-
if (silentJsonMode) {
|
|
149
|
-
log(streamingModeError.line + '\n');
|
|
150
|
-
} else {
|
|
151
|
-
log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
196
|
+
const recovered = attemptRecovery(code, timestamp);
|
|
155
197
|
|
|
156
198
|
if (silentJsonMode && finalResultJson) {
|
|
157
199
|
log(finalResultJson + '\n');
|
|
158
200
|
}
|
|
159
201
|
|
|
160
|
-
|
|
161
|
-
log(`\n${'='.repeat(50)}\n`);
|
|
162
|
-
log(`Finished: ${new Date().toISOString()}\n`);
|
|
163
|
-
log(`Exit code: ${code}, Signal: ${signal}\n`);
|
|
164
|
-
}
|
|
202
|
+
writeCompletionFooter(code, signal);
|
|
165
203
|
|
|
166
204
|
const resolvedCode = recovered?.payload ? 0 : code;
|
|
167
205
|
const status = resolvedCode === 0 ? 'completed' : 'failed';
|
|
@@ -169,7 +207,7 @@ child.on('close', async (code, signal) => {
|
|
|
169
207
|
await updateTask(taskId, {
|
|
170
208
|
status,
|
|
171
209
|
exitCode: resolvedCode,
|
|
172
|
-
error: resolvedCode
|
|
210
|
+
error: resolvedCode !== 0 && signal ? `Killed by ${signal}` : null,
|
|
173
211
|
});
|
|
174
212
|
} catch (updateError) {
|
|
175
213
|
log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "git-pusher",
|
|
3
|
-
"role": "completion-detector",
|
|
4
|
-
"modelLevel": "level2",
|
|
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
|
-
}
|