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