@the-open-engine/zeroshot 6.10.0 → 6.10.2
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/cli/index.js +16 -0
- package/cluster-hooks/block-ask-user-question.py +2 -3
- package/cluster-hooks/block-dangerous-git.py +2 -3
- package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +42 -1
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-options.js +2 -0
- package/lib/agent-cli-provider/contract-options.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +6 -2
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/package.json +1 -1
- package/src/agent/agent-lifecycle.js +13 -6
- package/src/agent/agent-task-executor.js +527 -313
- package/src/agent-cli-provider/adapters/claude.ts +46 -1
- package/src/agent-cli-provider/contract-options.ts +6 -0
- package/src/agent-cli-provider/types.ts +9 -6
- package/src/claude-task-runner.js +168 -44
- package/src/darwin-keychain-boundary.js +174 -0
- package/src/task-spawn-cleanup-ownership.js +82 -0
- package/src/worktree-claude-config.js +193 -85
- package/src/worktree-tooling-env.js +3 -0
- package/task-lib/attachable-watcher.js +93 -267
- package/task-lib/command-spec-cleanup.js +219 -0
- package/task-lib/commands/clean.js +35 -5
- package/task-lib/commands/get-task-id-by-spawn-token.js +10 -0
- package/task-lib/commands/kill.js +117 -4
- package/task-lib/commands/status.js +1 -0
- package/task-lib/runner.js +61 -4
- package/task-lib/store.js +68 -6
- package/task-lib/watcher-output-runtime.js +284 -0
- package/task-lib/watcher.js +124 -257
package/task-lib/watcher.js
CHANGED
|
@@ -5,17 +5,19 @@
|
|
|
5
5
|
* Runs detached from parent, updates task status on completion
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { appendFileSync, unlinkSync } from 'fs';
|
|
10
|
-
import { unlink } from 'fs/promises';
|
|
8
|
+
import { appendFileSync } from 'fs';
|
|
11
9
|
import { getTask, updateTask } from './store.js';
|
|
12
|
-
import {
|
|
13
|
-
detectProviderFatalError,
|
|
14
|
-
detectProviderStreamingModeError,
|
|
15
|
-
recoverProviderStructuredOutput,
|
|
16
|
-
supportsProviderStructuredOutputRecovery,
|
|
17
|
-
} from './provider-helper-runtime.js';
|
|
10
|
+
import { createCommandSpecCleanup } from './command-spec-cleanup.js';
|
|
18
11
|
import { createProviderSessionCapture } from './provider-session-capture.js';
|
|
12
|
+
import {
|
|
13
|
+
completeWatcherFailure,
|
|
14
|
+
completePendingWatcherCancellation,
|
|
15
|
+
completeWatcherTask,
|
|
16
|
+
createWatcherOutputRuntime,
|
|
17
|
+
resolveWatcherCommand,
|
|
18
|
+
spawnWatcherProvider,
|
|
19
|
+
terminateWatcherProvider,
|
|
20
|
+
} from './watcher-output-runtime.js';
|
|
19
21
|
import { createRequire } from 'module';
|
|
20
22
|
|
|
21
23
|
const require = createRequire(import.meta.url);
|
|
@@ -35,8 +37,24 @@ function log(msg) {
|
|
|
35
37
|
appendFileSync(logFile, msg);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
function emergencyLog(msg) {
|
|
41
|
+
try {
|
|
42
|
+
log(msg);
|
|
43
|
+
} catch {
|
|
44
|
+
process.stderr.write(msg);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const commandCleanup = createCommandSpecCleanup(commandSpec, (cleanupPath, error) => {
|
|
49
|
+
emergencyLog(`[${Date.now()}][CLEANUP] Failed to delete ${cleanupPath}: ${error.message}\n`);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const { providerName, env, command, finalArgs } = resolveWatcherCommand(
|
|
53
|
+
config,
|
|
54
|
+
commandSpec,
|
|
55
|
+
args,
|
|
56
|
+
normalizeProviderName
|
|
57
|
+
);
|
|
40
58
|
const storedTask = getTask(taskId);
|
|
41
59
|
const providerSessionCapture = createProviderSessionCapture({
|
|
42
60
|
providerName,
|
|
@@ -47,64 +65,14 @@ const providerSessionCapture = createProviderSessionCapture({
|
|
|
47
65
|
initialSessionId: storedTask?.sessionId || null,
|
|
48
66
|
initialSessionIdConflict: storedTask?.sessionIdConflict === true,
|
|
49
67
|
});
|
|
50
|
-
const maybeCaptureProviderSession = providerSessionCapture.captureLine;
|
|
51
|
-
|
|
52
|
-
const env = { ...process.env, ...(commandSpec.env || {}) };
|
|
53
|
-
const command = commandSpec.binary;
|
|
54
|
-
const finalArgs = [...(commandSpec.args || args)];
|
|
55
|
-
|
|
56
|
-
const child = spawn(command, finalArgs, {
|
|
57
|
-
cwd: commandSpec.cwd || cwd,
|
|
58
|
-
env,
|
|
59
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
60
|
-
detached: process.platform !== 'win32',
|
|
61
|
-
windowsHide: true,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
updateTask(taskId, {
|
|
65
|
-
pid: child.pid,
|
|
66
|
-
processGroupId: process.platform === 'win32' ? null : child.pid,
|
|
67
|
-
terminationStrategy: process.platform === 'win32' ? 'process-tree' : 'process-group',
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const silentJsonMode =
|
|
71
|
-
config.outputFormat === 'json' &&
|
|
72
|
-
config.jsonSchema &&
|
|
73
|
-
config.silentJsonOutput &&
|
|
74
|
-
supportsProviderStructuredOutputRecovery(providerName);
|
|
75
|
-
|
|
76
|
-
let finalResultJson = null;
|
|
77
|
-
let streamingModeError = null;
|
|
78
|
-
let fatalError = null;
|
|
79
|
-
let cleanupStarted = false;
|
|
80
68
|
|
|
69
|
+
let crashStarted = false;
|
|
70
|
+
let child = null;
|
|
81
71
|
let stdoutBuffer = '';
|
|
82
72
|
let stderrBuffer = '';
|
|
83
73
|
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
const lines = nextBuffer.split('\n');
|
|
87
|
-
const remaining = lines.pop() || '';
|
|
88
|
-
return { lines, remaining };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function maybeHandleFatalError(line, timestamp) {
|
|
92
|
-
if (fatalError) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const detected = detectProviderFatalError(providerName, line);
|
|
97
|
-
if (!detected) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
fatalError = detected;
|
|
102
|
-
|
|
103
|
-
if (silentJsonMode) {
|
|
104
|
-
log(`[${timestamp}]${line}\n`);
|
|
105
|
-
}
|
|
106
|
-
log(`[${timestamp}][FATAL] ${detected}\n`);
|
|
107
|
-
|
|
74
|
+
function stopProviderAfterFatalOutput() {
|
|
75
|
+
if (!child) return;
|
|
108
76
|
try {
|
|
109
77
|
child.kill('SIGTERM');
|
|
110
78
|
} catch {
|
|
@@ -120,221 +88,120 @@ function maybeHandleFatalError(line, timestamp) {
|
|
|
120
88
|
}
|
|
121
89
|
}
|
|
122
90
|
}, 5000);
|
|
123
|
-
|
|
124
|
-
return true;
|
|
125
91
|
}
|
|
126
92
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function maybeCaptureStructuredOutput(line) {
|
|
138
|
-
try {
|
|
139
|
-
const json = JSON.parse(line);
|
|
140
|
-
if (json.structured_output) {
|
|
141
|
-
finalResultJson = line;
|
|
142
|
-
}
|
|
143
|
-
} catch {
|
|
144
|
-
// Not JSON, skip
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function handleSilentJsonLines(lines, timestamp) {
|
|
149
|
-
for (const line of lines) {
|
|
150
|
-
if (!line.trim()) continue;
|
|
151
|
-
maybeCaptureProviderSession(line);
|
|
152
|
-
maybeHandleFatalError(line, timestamp);
|
|
153
|
-
if (captureStreamingError(line, timestamp)) {
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
maybeCaptureStructuredOutput(line);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function handleStreamingLines(lines, timestamp) {
|
|
161
|
-
for (const line of lines) {
|
|
162
|
-
maybeCaptureProviderSession(line);
|
|
163
|
-
maybeHandleFatalError(line, timestamp);
|
|
164
|
-
if (captureStreamingError(line, timestamp)) {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
log(`[${timestamp}]${line}\n`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function flushStdoutBuffer(timestamp) {
|
|
172
|
-
if (!stdoutBuffer.trim()) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
maybeCaptureProviderSession(stdoutBuffer);
|
|
177
|
-
if (!enableRecovery) {
|
|
178
|
-
if (!silentJsonMode) {
|
|
179
|
-
log(`[${timestamp}]${stdoutBuffer}\n`);
|
|
180
|
-
}
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
maybeHandleFatalError(stdoutBuffer, timestamp);
|
|
185
|
-
if (captureStreamingError(stdoutBuffer, timestamp)) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (silentJsonMode) {
|
|
190
|
-
maybeCaptureStructuredOutput(stdoutBuffer);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
93
|
+
const outputRuntime = createWatcherOutputRuntime({
|
|
94
|
+
config,
|
|
95
|
+
providerName,
|
|
96
|
+
log,
|
|
97
|
+
stopProvider: stopProviderAfterFatalOutput,
|
|
98
|
+
providerSessionCapture,
|
|
99
|
+
});
|
|
193
100
|
|
|
194
|
-
|
|
101
|
+
function terminateOwnedProviderBoundary(exitObserved = false) {
|
|
102
|
+
return terminateWatcherProvider(child, { exitObserved });
|
|
195
103
|
}
|
|
196
104
|
|
|
197
|
-
function
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
105
|
+
async function crashWithError(error, source) {
|
|
106
|
+
if (crashStarted) return;
|
|
107
|
+
crashStarted = true;
|
|
108
|
+
await completeWatcherFailure({
|
|
109
|
+
taskId,
|
|
110
|
+
error,
|
|
111
|
+
source,
|
|
112
|
+
commandCleanup,
|
|
113
|
+
terminateProvider: terminateOwnedProviderBoundary,
|
|
114
|
+
updateTask,
|
|
115
|
+
emergencyLog,
|
|
116
|
+
});
|
|
117
|
+
process.exit(1);
|
|
202
118
|
}
|
|
203
119
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const recovered = recoverProviderStructuredOutput(providerName, streamingModeError.sessionId);
|
|
210
|
-
if (recovered?.payload) {
|
|
211
|
-
const recoveredLine = JSON.stringify(recovered.payload);
|
|
212
|
-
if (silentJsonMode) {
|
|
213
|
-
finalResultJson = recoveredLine;
|
|
214
|
-
} else {
|
|
215
|
-
log(`[${timestamp}]${recoveredLine}\n`);
|
|
216
|
-
}
|
|
217
|
-
} else if (streamingModeError.line) {
|
|
218
|
-
if (silentJsonMode) {
|
|
219
|
-
log(streamingModeError.line + '\n');
|
|
220
|
-
} else {
|
|
221
|
-
log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return recovered;
|
|
226
|
-
}
|
|
120
|
+
process.on('uncaughtException', (error) => {
|
|
121
|
+
void crashWithError(error, 'uncaughtException');
|
|
122
|
+
});
|
|
227
123
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
for (const file of commandSpec.cleanup || []) {
|
|
232
|
-
try {
|
|
233
|
-
await unlink(file);
|
|
234
|
-
} catch (error) {
|
|
235
|
-
log(`[${Date.now()}][CLEANUP] Failed to delete ${file}: ${error.message}\n`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
124
|
+
process.on('unhandledRejection', (reason) => {
|
|
125
|
+
void crashWithError(reason, 'unhandledRejection');
|
|
126
|
+
});
|
|
239
127
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
128
|
+
if (
|
|
129
|
+
await completePendingWatcherCancellation({
|
|
130
|
+
taskId,
|
|
131
|
+
getTask,
|
|
132
|
+
commandCleanup,
|
|
133
|
+
terminateProvider: () => true,
|
|
134
|
+
updateTask,
|
|
135
|
+
emergencyLog,
|
|
136
|
+
})
|
|
137
|
+
) {
|
|
138
|
+
process.exit(0);
|
|
250
139
|
}
|
|
251
140
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
log(`Finished: ${new Date().toISOString()}\n`);
|
|
259
|
-
log(`Exit code: ${code}, Signal: ${signal}\n`);
|
|
260
|
-
}
|
|
141
|
+
child = spawnWatcherProvider(command, finalArgs, {
|
|
142
|
+
cwd: commandSpec.cwd || cwd,
|
|
143
|
+
env,
|
|
144
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
145
|
+
detached: process.platform !== 'win32',
|
|
146
|
+
});
|
|
261
147
|
|
|
262
148
|
child.stdout.on('data', (data) => {
|
|
263
|
-
|
|
264
|
-
const timestamp = Date.now();
|
|
265
|
-
|
|
266
|
-
const { lines, remaining } = splitBufferLines(stdoutBuffer, chunk);
|
|
267
|
-
stdoutBuffer = remaining;
|
|
268
|
-
|
|
269
|
-
if (silentJsonMode) {
|
|
270
|
-
handleSilentJsonLines(lines, timestamp);
|
|
271
|
-
} else {
|
|
272
|
-
handleStreamingLines(lines, timestamp);
|
|
273
|
-
}
|
|
149
|
+
stdoutBuffer = outputRuntime.consumeOutput(stdoutBuffer, data);
|
|
274
150
|
});
|
|
275
151
|
|
|
276
152
|
child.stderr.on('data', (data) => {
|
|
277
|
-
|
|
278
|
-
const timestamp = Date.now();
|
|
279
|
-
|
|
280
|
-
const { lines, remaining } = splitBufferLines(stderrBuffer, chunk);
|
|
281
|
-
stderrBuffer = remaining;
|
|
282
|
-
|
|
283
|
-
for (const line of lines) {
|
|
284
|
-
log(`[${timestamp}]${line}\n`);
|
|
285
|
-
}
|
|
153
|
+
stderrBuffer = outputRuntime.consumeStderr(stderrBuffer, data);
|
|
286
154
|
});
|
|
287
155
|
|
|
288
156
|
child.on('close', async (code, signal) => {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
writeCompletionFooter(resolvedCode, signal);
|
|
306
|
-
await cleanupCommandSpec();
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
await updateTask(taskId, {
|
|
310
|
-
status,
|
|
311
|
-
pid: null,
|
|
312
|
-
processGroupId: null,
|
|
313
|
-
exitCode: resolvedCode,
|
|
314
|
-
...providerSessionCapture.getCompletionUpdate(resolvedCode),
|
|
315
|
-
error:
|
|
316
|
-
fatalError ||
|
|
317
|
-
sessionIdentityError ||
|
|
318
|
-
(resolvedCode !== 0 && signal ? `Killed by ${signal}` : null),
|
|
319
|
-
});
|
|
320
|
-
} catch (updateError) {
|
|
321
|
-
log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
|
|
322
|
-
}
|
|
157
|
+
if (crashStarted) return;
|
|
158
|
+
const completion = outputRuntime.complete({
|
|
159
|
+
code,
|
|
160
|
+
signal,
|
|
161
|
+
outputBuffer: stdoutBuffer,
|
|
162
|
+
stderrBuffer,
|
|
163
|
+
});
|
|
164
|
+
await completeWatcherTask({
|
|
165
|
+
taskId,
|
|
166
|
+
completion,
|
|
167
|
+
commandCleanup,
|
|
168
|
+
terminateProvider: () => terminateOwnedProviderBoundary(true),
|
|
169
|
+
updateTask,
|
|
170
|
+
emergencyLog,
|
|
171
|
+
});
|
|
323
172
|
process.exit(0);
|
|
324
173
|
});
|
|
325
174
|
|
|
326
175
|
child.on('error', async (err) => {
|
|
176
|
+
if (crashStarted) return;
|
|
177
|
+
crashStarted = true;
|
|
327
178
|
log(`\nError: ${err.message}\n`);
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
} catch (updateError) {
|
|
337
|
-
log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
|
|
338
|
-
}
|
|
179
|
+
await completeWatcherTask({
|
|
180
|
+
taskId,
|
|
181
|
+
completion: { status: 'failed', resolvedCode: 1, error: err.message },
|
|
182
|
+
commandCleanup,
|
|
183
|
+
terminateProvider: terminateOwnedProviderBoundary,
|
|
184
|
+
updateTask,
|
|
185
|
+
emergencyLog,
|
|
186
|
+
});
|
|
339
187
|
process.exit(1);
|
|
340
188
|
});
|
|
189
|
+
|
|
190
|
+
updateTask(taskId, {
|
|
191
|
+
pid: child.pid,
|
|
192
|
+
processGroupId: process.platform === 'win32' ? null : child.pid,
|
|
193
|
+
terminationStrategy: process.platform === 'win32' ? 'process-tree' : 'process-group',
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (getTask(taskId)?.cancelRequested) {
|
|
197
|
+
crashStarted = true;
|
|
198
|
+
await completePendingWatcherCancellation({
|
|
199
|
+
taskId,
|
|
200
|
+
getTask,
|
|
201
|
+
commandCleanup,
|
|
202
|
+
terminateProvider: terminateOwnedProviderBoundary,
|
|
203
|
+
updateTask,
|
|
204
|
+
emergencyLog,
|
|
205
|
+
});
|
|
206
|
+
process.exit(0);
|
|
207
|
+
}
|