@the-open-engine/zeroshot 6.10.0 → 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 (31) hide show
  1. package/cli/index.js +16 -0
  2. package/cluster-hooks/block-ask-user-question.py +2 -3
  3. package/cluster-hooks/block-dangerous-git.py +2 -3
  4. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
  5. package/lib/agent-cli-provider/adapters/claude.js +42 -1
  6. package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
  7. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
  8. package/lib/agent-cli-provider/contract-options.js +2 -0
  9. package/lib/agent-cli-provider/contract-options.js.map +1 -1
  10. package/lib/agent-cli-provider/types.d.ts +6 -2
  11. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  12. package/lib/agent-cli-provider/types.js.map +1 -1
  13. package/package.json +1 -1
  14. package/src/agent/agent-lifecycle.js +13 -6
  15. package/src/agent/agent-task-executor.js +526 -313
  16. package/src/agent-cli-provider/adapters/claude.ts +46 -1
  17. package/src/agent-cli-provider/contract-options.ts +6 -0
  18. package/src/agent-cli-provider/types.ts +9 -6
  19. package/src/claude-task-runner.js +153 -44
  20. package/src/task-spawn-cleanup-ownership.js +82 -0
  21. package/src/worktree-claude-config.js +193 -85
  22. package/task-lib/attachable-watcher.js +93 -267
  23. package/task-lib/command-spec-cleanup.js +219 -0
  24. package/task-lib/commands/clean.js +35 -5
  25. package/task-lib/commands/get-task-id-by-spawn-token.js +10 -0
  26. package/task-lib/commands/kill.js +117 -4
  27. package/task-lib/commands/status.js +1 -0
  28. package/task-lib/runner.js +61 -4
  29. package/task-lib/store.js +68 -6
  30. package/task-lib/watcher-output-runtime.js +284 -0
  31. package/task-lib/watcher.js +124 -257
@@ -0,0 +1,284 @@
1
+ import { spawn } from 'child_process';
2
+ import {
3
+ detectProviderFatalError,
4
+ detectProviderStreamingModeError,
5
+ recoverProviderStructuredOutput,
6
+ supportsProviderStructuredOutputRecovery,
7
+ } from './provider-helper-runtime.js';
8
+ import { terminateProcess } from './process-termination.js';
9
+
10
+ export const COMMAND_CLEANUP_UNINITIALIZED = Symbol('command-cleanup-uninitialized');
11
+
12
+ export function spawnWatcherProvider(command, finalArgs, options) {
13
+ return spawn(command, finalArgs, {
14
+ ...options,
15
+ windowsHide: true,
16
+ });
17
+ }
18
+
19
+ export async function terminateWatcherProvider(providerProcess, options = {}) {
20
+ const pid = providerProcess?.pid;
21
+ if (!pid) return true;
22
+ const platform = options.platform || process.platform;
23
+ const terminate = options.terminateProcessFn || terminateProcess;
24
+ const terminationStrategy = platform === 'win32' ? 'process-tree' : 'process-group';
25
+ const result = await terminate(pid, {
26
+ processGroupId: platform === 'win32' ? null : pid,
27
+ terminationStrategy,
28
+ });
29
+ if (terminationStrategy === 'process-tree' && result.alreadyDead && !options.exitObserved) {
30
+ return false;
31
+ }
32
+ return result.terminated;
33
+ }
34
+
35
+ function splitBufferLines(buffer, chunk) {
36
+ const nextBuffer = buffer + chunk;
37
+ const lines = nextBuffer.split('\n');
38
+ return { lines: lines.slice(0, -1), remaining: lines.at(-1) || '' };
39
+ }
40
+
41
+ export function resolveWatcherCommand(config, commandSpec, fallbackArgs, normalizeProviderName) {
42
+ return {
43
+ providerName: normalizeProviderName(config.provider || 'claude'),
44
+ env: { ...process.env, ...(commandSpec.env || {}) },
45
+ command: commandSpec.binary,
46
+ finalArgs: [...(commandSpec.args || fallbackArgs)],
47
+ };
48
+ }
49
+
50
+ export async function completeWatcherTask({
51
+ taskId,
52
+ completion,
53
+ commandCleanup,
54
+ terminateProvider,
55
+ updateTask,
56
+ emergencyLog,
57
+ terminalUpdates = {},
58
+ }) {
59
+ let providerTerminal = false;
60
+ try {
61
+ providerTerminal = await terminateProvider();
62
+ } catch (error) {
63
+ emergencyLog(`[${Date.now()}][CLEANUP] Provider termination check failed: ${error.message}\n`);
64
+ }
65
+ if (!providerTerminal) {
66
+ emergencyLog(
67
+ `[${Date.now()}][CLEANUP] Provider termination boundary is still live; preserving command cleanup paths.\n`
68
+ );
69
+ try {
70
+ await updateTask(taskId, {
71
+ status: 'running',
72
+ error: completion.error
73
+ ? `${completion.error}; provider termination could not be confirmed`
74
+ : 'Provider termination could not be confirmed; retry and cleanup remain blocked',
75
+ });
76
+ } catch (error) {
77
+ emergencyLog(`[${Date.now()}][ERROR] Failed to preserve task ownership: ${error.message}\n`);
78
+ }
79
+ return false;
80
+ }
81
+
82
+ let cleanupSucceeded = false;
83
+ if (commandCleanup === COMMAND_CLEANUP_UNINITIALIZED) {
84
+ emergencyLog(
85
+ `[${Date.now()}][CLEANUP] Command cleanup ownership was not initialized; preserving the persisted receipt.\n`
86
+ );
87
+ } else if (commandCleanup?.run) {
88
+ try {
89
+ cleanupSucceeded = await commandCleanup.run();
90
+ } catch (error) {
91
+ emergencyLog(`[${Date.now()}][CLEANUP] Command cleanup failed: ${error.message}\n`);
92
+ }
93
+ }
94
+ try {
95
+ await updateTask(taskId, {
96
+ status: completion.status,
97
+ pid: null,
98
+ processGroupId: null,
99
+ exitCode: completion.resolvedCode,
100
+ error: completion.error,
101
+ cancelRequested: false,
102
+ ...terminalUpdates,
103
+ ...(completion.terminalUpdates || {}),
104
+ ...(cleanupSucceeded ? { commandCleanup: null } : {}),
105
+ });
106
+ } catch (error) {
107
+ emergencyLog(`[${Date.now()}][ERROR] Failed to update task status: ${error.message}\n`);
108
+ }
109
+ return true;
110
+ }
111
+
112
+ export async function completePendingWatcherCancellation({
113
+ taskId,
114
+ getTask,
115
+ ...completionOptions
116
+ }) {
117
+ if (!getTask(taskId)?.cancelRequested) return false;
118
+ await completeWatcherTask({
119
+ taskId,
120
+ ...completionOptions,
121
+ completion: {
122
+ status: 'killed',
123
+ resolvedCode: 143,
124
+ error: 'Cancellation requested before provider startup completed',
125
+ },
126
+ });
127
+ return true;
128
+ }
129
+
130
+ export function completeWatcherFailure({ error, source, ...completionOptions }) {
131
+ const errorMessage = error instanceof Error ? error.stack || error.message : String(error);
132
+ completionOptions.emergencyLog(`\n[${Date.now()}][CRASH] ${source}: ${errorMessage}\n`);
133
+ return completeWatcherTask({
134
+ ...completionOptions,
135
+ completion: {
136
+ status: 'failed',
137
+ resolvedCode: 1,
138
+ error: `${source}: ${errorMessage}`,
139
+ },
140
+ });
141
+ }
142
+
143
+ export function createWatcherOutputRuntime({
144
+ config,
145
+ providerName,
146
+ log,
147
+ stopProvider,
148
+ providerSessionCapture = null,
149
+ }) {
150
+ const enableRecovery = supportsProviderStructuredOutputRecovery(providerName);
151
+ const silentJsonMode =
152
+ config.outputFormat === 'json' &&
153
+ config.jsonSchema &&
154
+ config.silentJsonOutput &&
155
+ enableRecovery;
156
+ let finalResultJson = null;
157
+ let streamingModeError = null;
158
+ let fatalError = null;
159
+ const captureProviderSession = providerSessionCapture?.captureLine || (() => {});
160
+
161
+ function maybeHandleFatalError(line, timestamp) {
162
+ if (fatalError) return false;
163
+ const detected = detectProviderFatalError(providerName, line);
164
+ if (!detected) return false;
165
+ fatalError = detected;
166
+ if (silentJsonMode) log(`[${timestamp}]${line}\n`);
167
+ log(`[${timestamp}][FATAL] ${detected}\n`);
168
+ stopProvider(timestamp);
169
+ return true;
170
+ }
171
+
172
+ function captureStreamingError(line, timestamp) {
173
+ const detectedError = detectProviderStreamingModeError(providerName, line);
174
+ if (!detectedError) return false;
175
+ streamingModeError = { ...detectedError, timestamp };
176
+ return true;
177
+ }
178
+
179
+ function maybeCaptureStructuredOutput(line) {
180
+ try {
181
+ const json = JSON.parse(line);
182
+ if (json.structured_output) finalResultJson = line;
183
+ } catch {
184
+ // Not JSON, skip.
185
+ }
186
+ }
187
+
188
+ function handleOutputLine(line, timestamp) {
189
+ captureProviderSession(line);
190
+ if (silentJsonMode && !line.trim()) return;
191
+ maybeHandleFatalError(line, timestamp);
192
+ if (captureStreamingError(line, timestamp)) return;
193
+ if (silentJsonMode) {
194
+ maybeCaptureStructuredOutput(line);
195
+ } else {
196
+ log(`[${timestamp}]${line}\n`);
197
+ }
198
+ }
199
+
200
+ function consumeOutput(buffer, chunk) {
201
+ const timestamp = Date.now();
202
+ const { lines, remaining } = splitBufferLines(buffer, chunk.toString());
203
+ for (const line of lines) handleOutputLine(line, timestamp);
204
+ return remaining;
205
+ }
206
+
207
+ function consumeStderr(buffer, chunk) {
208
+ const timestamp = Date.now();
209
+ const { lines, remaining } = splitBufferLines(buffer, chunk.toString());
210
+ for (const line of lines) log(`[${timestamp}]${line}\n`);
211
+ return remaining;
212
+ }
213
+
214
+ function flushOutput(buffer, timestamp) {
215
+ if (!buffer.trim()) return;
216
+ captureProviderSession(buffer);
217
+ if (!enableRecovery) {
218
+ if (!silentJsonMode) log(`[${timestamp}]${buffer}\n`);
219
+ return;
220
+ }
221
+ maybeHandleFatalError(buffer, timestamp);
222
+ if (captureStreamingError(buffer, timestamp)) return;
223
+ if (silentJsonMode) {
224
+ maybeCaptureStructuredOutput(buffer);
225
+ } else {
226
+ log(`[${timestamp}]${buffer}\n`);
227
+ }
228
+ }
229
+
230
+ function flushStderr(buffer, timestamp) {
231
+ if (!buffer.trim()) return;
232
+ maybeHandleFatalError(buffer, timestamp);
233
+ log(`[${timestamp}]${buffer}\n`);
234
+ }
235
+
236
+ function attemptRecovery(code, timestamp) {
237
+ if (!(code !== 0 && streamingModeError?.sessionId)) return null;
238
+ const recovered = recoverProviderStructuredOutput(providerName, streamingModeError.sessionId);
239
+ if (recovered?.payload) {
240
+ const recoveredLine = JSON.stringify(recovered.payload);
241
+ if (silentJsonMode) {
242
+ finalResultJson = recoveredLine;
243
+ } else {
244
+ log(`[${timestamp}]${recoveredLine}\n`);
245
+ }
246
+ } else if (streamingModeError.line) {
247
+ const prefix = silentJsonMode ? '' : `[${streamingModeError.timestamp}]`;
248
+ log(`${prefix}${streamingModeError.line}\n`);
249
+ }
250
+ return recovered;
251
+ }
252
+
253
+ function complete({ code, signal, outputBuffer, stderrBuffer = null }) {
254
+ const timestamp = Date.now();
255
+ flushOutput(outputBuffer, timestamp);
256
+ if (stderrBuffer !== null) flushStderr(stderrBuffer, timestamp);
257
+ const recovered = attemptRecovery(code, timestamp);
258
+ const sessionIdentityError = providerSessionCapture?.getCompletionError() || null;
259
+ if (silentJsonMode && finalResultJson) log(`${finalResultJson}\n`);
260
+ if (config.outputFormat !== 'json') {
261
+ log(`\n${'='.repeat(50)}\n`);
262
+ log(`Finished: ${new Date().toISOString()}\n`);
263
+ log(`Exit code: ${code}, Signal: ${signal}\n`);
264
+ }
265
+ let resolvedCode = code;
266
+ if (recovered?.payload) {
267
+ resolvedCode = 0;
268
+ }
269
+ if (fatalError || sessionIdentityError) {
270
+ resolvedCode = 1;
271
+ }
272
+ return {
273
+ resolvedCode,
274
+ status: resolvedCode === 0 ? 'completed' : 'failed',
275
+ error:
276
+ fatalError ||
277
+ sessionIdentityError ||
278
+ (resolvedCode !== 0 && signal ? `Killed by ${signal}` : null),
279
+ terminalUpdates: providerSessionCapture?.getCompletionUpdate(resolvedCode) || {},
280
+ };
281
+ }
282
+
283
+ return { complete, consumeOutput, consumeStderr };
284
+ }