@covibes/zeroshot 5.2.1 → 5.3.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 +199 -248
- package/cli/commands/providers.js +150 -0
- package/cli/index.js +214 -58
- package/cli/lib/first-run.js +40 -3
- package/cluster-templates/base-templates/debug-workflow.json +24 -78
- package/cluster-templates/base-templates/full-workflow.json +44 -145
- package/cluster-templates/base-templates/single-worker.json +23 -15
- package/cluster-templates/base-templates/worker-validator.json +47 -34
- package/cluster-templates/conductor-bootstrap.json +7 -5
- package/lib/docker-config.js +6 -1
- package/lib/provider-detection.js +59 -0
- package/lib/provider-names.js +56 -0
- package/lib/settings.js +191 -6
- package/lib/stream-json-parser.js +4 -238
- package/package.json +21 -5
- package/scripts/validate-templates.js +100 -0
- package/src/agent/agent-config.js +37 -13
- package/src/agent/agent-context-builder.js +64 -2
- package/src/agent/agent-hook-executor.js +82 -9
- package/src/agent/agent-lifecycle.js +53 -14
- package/src/agent/agent-task-executor.js +196 -194
- package/src/agent/output-extraction.js +200 -0
- package/src/agent/output-reformatter.js +175 -0
- package/src/agent/schema-utils.js +111 -0
- package/src/agent-wrapper.js +102 -30
- package/src/agents/git-pusher-agent.json +1 -1
- package/src/claude-task-runner.js +80 -30
- package/src/config-router.js +13 -13
- package/src/config-validator.js +231 -10
- package/src/github.js +36 -0
- package/src/isolation-manager.js +243 -154
- package/src/ledger.js +28 -6
- package/src/orchestrator.js +391 -96
- package/src/preflight.js +85 -82
- package/src/providers/anthropic/cli-builder.js +45 -0
- package/src/providers/anthropic/index.js +134 -0
- package/src/providers/anthropic/models.js +23 -0
- package/src/providers/anthropic/output-parser.js +159 -0
- package/src/providers/base-provider.js +181 -0
- package/src/providers/capabilities.js +51 -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 +92 -0
- package/src/providers/index.js +75 -0
- package/src/providers/openai/cli-builder.js +122 -0
- package/src/providers/openai/index.js +135 -0
- package/src/providers/openai/models.js +21 -0
- package/src/providers/openai/output-parser.js +129 -0
- package/src/sub-cluster-wrapper.js +18 -3
- package/src/task-runner.js +8 -6
- package/src/tui/layout.js +20 -3
- package/task-lib/attachable-watcher.js +80 -78
- package/task-lib/claude-recovery.js +119 -0
- package/task-lib/commands/list.js +1 -1
- package/task-lib/commands/resume.js +3 -2
- package/task-lib/commands/run.js +12 -3
- package/task-lib/runner.js +59 -38
- package/task-lib/scheduler.js +2 -2
- package/task-lib/store.js +43 -30
- package/task-lib/watcher.js +81 -62
|
@@ -1,42 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Attachable Watcher - spawns
|
|
5
|
-
*
|
|
4
|
+
* Attachable Watcher - spawns a CLI process with PTY for attach/detach support
|
|
6
5
|
* Runs detached from parent, provides Unix socket for attach clients.
|
|
7
|
-
* Uses node-pty for proper terminal emulation.
|
|
8
|
-
*
|
|
9
|
-
* Key differences from legacy watcher.js:
|
|
10
|
-
* - Uses AttachServer (node-pty) instead of child_process.spawn
|
|
11
|
-
* - Creates Unix socket at ~/.zeroshot/sockets/task-<id>.sock
|
|
12
|
-
* - Supports multiple attached clients
|
|
13
|
-
* - Still writes to log file for backward compatibility
|
|
14
|
-
*
|
|
15
|
-
* CRITICAL: Global error handlers installed FIRST to catch silent crashes
|
|
16
6
|
*/
|
|
17
7
|
|
|
18
8
|
import { appendFileSync, existsSync, mkdirSync } from 'fs';
|
|
19
9
|
import { join } from 'path';
|
|
20
10
|
import { homedir } from 'os';
|
|
21
11
|
import { updateTask } from './store.js';
|
|
12
|
+
import { detectStreamingModeError, recoverStructuredOutput } from './claude-recovery.js';
|
|
13
|
+
import { createRequire } from 'module';
|
|
22
14
|
|
|
23
15
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
24
16
|
// 🔴 CRITICAL: Global error handlers - MUST be installed BEFORE any async ops
|
|
25
17
|
// Without these, uncaught errors cause SILENT process death (no logs, no status)
|
|
26
18
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
19
|
|
|
28
|
-
// Parse args early so we can log errors to the correct file
|
|
29
20
|
const [, , taskIdArg, cwdArg, logFileArg, argsJsonArg, configJsonArg] = process.argv;
|
|
30
21
|
|
|
31
|
-
/**
|
|
32
|
-
* Emergency logger - works even if main log function isn't ready
|
|
33
|
-
*/
|
|
34
22
|
function emergencyLog(msg) {
|
|
35
23
|
if (logFileArg) {
|
|
36
24
|
try {
|
|
37
25
|
appendFileSync(logFileArg, msg);
|
|
38
26
|
} catch {
|
|
39
|
-
// Last resort - stderr
|
|
40
27
|
process.stderr.write(msg);
|
|
41
28
|
}
|
|
42
29
|
} else {
|
|
@@ -44,9 +31,6 @@ function emergencyLog(msg) {
|
|
|
44
31
|
}
|
|
45
32
|
}
|
|
46
33
|
|
|
47
|
-
/**
|
|
48
|
-
* Mark task as failed and exit
|
|
49
|
-
*/
|
|
50
34
|
function crashWithError(error, source) {
|
|
51
35
|
const timestamp = Date.now();
|
|
52
36
|
const errorMsg = error instanceof Error ? error.stack || error.message : String(error);
|
|
@@ -54,7 +38,6 @@ function crashWithError(error, source) {
|
|
|
54
38
|
emergencyLog(`\n[${timestamp}][CRASH] ${source}: ${errorMsg}\n`);
|
|
55
39
|
emergencyLog(`[${timestamp}][CRASH] Process terminating due to unhandled error\n`);
|
|
56
40
|
|
|
57
|
-
// Try to update task status - may fail if error is in store.js itself
|
|
58
41
|
if (taskIdArg) {
|
|
59
42
|
try {
|
|
60
43
|
updateTask(taskIdArg, {
|
|
@@ -67,11 +50,9 @@ function crashWithError(error, source) {
|
|
|
67
50
|
}
|
|
68
51
|
}
|
|
69
52
|
|
|
70
|
-
// Exit with error code
|
|
71
53
|
process.exit(1);
|
|
72
54
|
}
|
|
73
55
|
|
|
74
|
-
// Install handlers IMMEDIATELY
|
|
75
56
|
process.on('uncaughtException', (error) => {
|
|
76
57
|
crashWithError(error, 'uncaughtException');
|
|
77
58
|
});
|
|
@@ -80,24 +61,19 @@ process.on('unhandledRejection', (reason) => {
|
|
|
80
61
|
crashWithError(reason, 'unhandledRejection');
|
|
81
62
|
});
|
|
82
63
|
|
|
83
|
-
// Import attach infrastructure from src package (CommonJS)
|
|
84
|
-
import { createRequire } from 'module';
|
|
85
64
|
const require = createRequire(import.meta.url);
|
|
86
65
|
const { AttachServer } = require('../src/attach');
|
|
87
|
-
const {
|
|
66
|
+
const { normalizeProviderName } = require('../lib/provider-names');
|
|
88
67
|
|
|
89
|
-
// Use the args parsed earlier (during error handler setup)
|
|
90
68
|
const taskId = taskIdArg;
|
|
91
69
|
const cwd = cwdArg;
|
|
92
70
|
const logFile = logFileArg;
|
|
93
71
|
const args = JSON.parse(argsJsonArg);
|
|
94
72
|
const config = configJsonArg ? JSON.parse(configJsonArg) : {};
|
|
95
73
|
|
|
96
|
-
// Socket path for attach
|
|
97
74
|
const SOCKET_DIR = join(homedir(), '.zeroshot', 'sockets');
|
|
98
75
|
const socketPath = join(SOCKET_DIR, `${taskId}.sock`);
|
|
99
76
|
|
|
100
|
-
// Ensure socket directory exists
|
|
101
77
|
if (!existsSync(SOCKET_DIR)) {
|
|
102
78
|
mkdirSync(SOCKET_DIR, { recursive: true });
|
|
103
79
|
}
|
|
@@ -106,33 +82,24 @@ function log(msg) {
|
|
|
106
82
|
appendFileSync(logFile, msg);
|
|
107
83
|
}
|
|
108
84
|
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
// Add model flag - priority: config.model > ANTHROPIC_MODEL env var
|
|
113
|
-
const claudeArgs = [...args];
|
|
114
|
-
const model = config.model || env.ANTHROPIC_MODEL;
|
|
115
|
-
if (model && !claudeArgs.includes('--model')) {
|
|
116
|
-
claudeArgs.unshift('--model', model);
|
|
117
|
-
}
|
|
85
|
+
const providerName = normalizeProviderName(config.provider || 'claude');
|
|
86
|
+
const enableRecovery = providerName === 'claude';
|
|
118
87
|
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
const finalArgs = [...
|
|
88
|
+
const env = { ...process.env, ...(config.env || {}) };
|
|
89
|
+
const command = config.command || 'claude';
|
|
90
|
+
const finalArgs = [...args];
|
|
122
91
|
|
|
123
|
-
// For JSON schema output with silent mode, track final result
|
|
124
92
|
const silentJsonMode =
|
|
125
|
-
config.outputFormat === 'json' && config.jsonSchema && config.silentJsonOutput;
|
|
126
|
-
let finalResultJson = null;
|
|
93
|
+
config.outputFormat === 'json' && config.jsonSchema && config.silentJsonOutput && enableRecovery;
|
|
127
94
|
|
|
128
|
-
|
|
95
|
+
let finalResultJson = null;
|
|
129
96
|
let outputBuffer = '';
|
|
97
|
+
let streamingModeError = null;
|
|
130
98
|
|
|
131
|
-
// Create AttachServer to spawn Claude with PTY
|
|
132
99
|
const server = new AttachServer({
|
|
133
100
|
id: taskId,
|
|
134
101
|
socketPath,
|
|
135
|
-
command
|
|
102
|
+
command,
|
|
136
103
|
args: finalArgs,
|
|
137
104
|
cwd,
|
|
138
105
|
env,
|
|
@@ -140,19 +107,24 @@ const server = new AttachServer({
|
|
|
140
107
|
rows: 30,
|
|
141
108
|
});
|
|
142
109
|
|
|
143
|
-
// Handle output from PTY
|
|
144
110
|
server.on('output', (data) => {
|
|
145
111
|
const chunk = data.toString();
|
|
146
112
|
const timestamp = Date.now();
|
|
147
113
|
|
|
148
114
|
if (silentJsonMode) {
|
|
149
|
-
// Parse each line to find structured_output
|
|
150
115
|
outputBuffer += chunk;
|
|
151
116
|
const lines = outputBuffer.split('\n');
|
|
152
117
|
outputBuffer = lines.pop() || '';
|
|
153
118
|
|
|
154
119
|
for (const line of lines) {
|
|
155
120
|
if (!line.trim()) continue;
|
|
121
|
+
if (enableRecovery) {
|
|
122
|
+
const detectedError = detectStreamingModeError(line);
|
|
123
|
+
if (detectedError) {
|
|
124
|
+
streamingModeError = { ...detectedError, timestamp };
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
156
128
|
try {
|
|
157
129
|
const json = JSON.parse(line);
|
|
158
130
|
if (json.structured_output) {
|
|
@@ -163,73 +135,106 @@ server.on('output', (data) => {
|
|
|
163
135
|
}
|
|
164
136
|
}
|
|
165
137
|
} else {
|
|
166
|
-
// Normal mode - stream with timestamps
|
|
167
138
|
outputBuffer += chunk;
|
|
168
139
|
const lines = outputBuffer.split('\n');
|
|
169
140
|
outputBuffer = lines.pop() || '';
|
|
170
141
|
|
|
171
142
|
for (const line of lines) {
|
|
143
|
+
if (enableRecovery) {
|
|
144
|
+
const detectedError = detectStreamingModeError(line);
|
|
145
|
+
if (detectedError) {
|
|
146
|
+
streamingModeError = { ...detectedError, timestamp };
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
172
150
|
log(`[${timestamp}]${line}\n`);
|
|
173
151
|
}
|
|
174
152
|
}
|
|
175
153
|
});
|
|
176
154
|
|
|
177
|
-
|
|
178
|
-
server.on('exit', ({ exitCode, signal }) => {
|
|
155
|
+
server.on('exit', async ({ exitCode, signal }) => {
|
|
179
156
|
const timestamp = Date.now();
|
|
180
157
|
const code = exitCode;
|
|
181
158
|
|
|
182
|
-
// Flush remaining buffered output
|
|
183
159
|
if (outputBuffer.trim()) {
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
160
|
+
if (enableRecovery) {
|
|
161
|
+
const detectedError = detectStreamingModeError(outputBuffer);
|
|
162
|
+
if (detectedError) {
|
|
163
|
+
streamingModeError = { ...detectedError, timestamp };
|
|
164
|
+
} else if (silentJsonMode) {
|
|
165
|
+
try {
|
|
166
|
+
const json = JSON.parse(outputBuffer);
|
|
167
|
+
if (json.structured_output) {
|
|
168
|
+
finalResultJson = outputBuffer;
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
// Not valid JSON
|
|
189
172
|
}
|
|
190
|
-
}
|
|
191
|
-
|
|
173
|
+
} else {
|
|
174
|
+
log(`[${timestamp}]${outputBuffer}\n`);
|
|
192
175
|
}
|
|
193
|
-
} else {
|
|
176
|
+
} else if (!silentJsonMode) {
|
|
194
177
|
log(`[${timestamp}]${outputBuffer}\n`);
|
|
195
178
|
}
|
|
196
179
|
}
|
|
197
180
|
|
|
198
|
-
|
|
181
|
+
let recovered = null;
|
|
182
|
+
if (enableRecovery && code !== 0 && streamingModeError?.sessionId) {
|
|
183
|
+
recovered = recoverStructuredOutput(streamingModeError.sessionId);
|
|
184
|
+
if (recovered?.payload) {
|
|
185
|
+
const recoveredLine = JSON.stringify(recovered.payload);
|
|
186
|
+
if (silentJsonMode) {
|
|
187
|
+
finalResultJson = recoveredLine;
|
|
188
|
+
} else {
|
|
189
|
+
log(`[${timestamp}]${recoveredLine}\n`);
|
|
190
|
+
}
|
|
191
|
+
} else if (streamingModeError.line) {
|
|
192
|
+
if (silentJsonMode) {
|
|
193
|
+
log(streamingModeError.line + '\n');
|
|
194
|
+
} else {
|
|
195
|
+
log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
199
200
|
if (silentJsonMode && finalResultJson) {
|
|
200
201
|
log(finalResultJson + '\n');
|
|
201
202
|
}
|
|
202
203
|
|
|
203
|
-
// Skip footer for pure JSON output
|
|
204
204
|
if (config.outputFormat !== 'json') {
|
|
205
205
|
log(`\n${'='.repeat(50)}\n`);
|
|
206
206
|
log(`Finished: ${new Date().toISOString()}\n`);
|
|
207
207
|
log(`Exit code: ${code}, Signal: ${signal}\n`);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
const status =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
const resolvedCode = recovered?.payload ? 0 : code;
|
|
211
|
+
const status = resolvedCode === 0 ? 'completed' : 'failed';
|
|
212
|
+
try {
|
|
213
|
+
await updateTask(taskId, {
|
|
214
|
+
status,
|
|
215
|
+
exitCode: resolvedCode,
|
|
216
|
+
error: resolvedCode === 0 ? null : signal ? `Killed by ${signal}` : null,
|
|
217
|
+
socketPath: null,
|
|
218
|
+
});
|
|
219
|
+
} catch (updateError) {
|
|
220
|
+
log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
|
|
221
|
+
}
|
|
218
222
|
|
|
219
|
-
// Give clients time to receive exit message before exiting
|
|
220
223
|
setTimeout(() => {
|
|
221
224
|
process.exit(0);
|
|
222
225
|
}, 500);
|
|
223
226
|
});
|
|
224
227
|
|
|
225
|
-
|
|
226
|
-
server.on('error', (err) => {
|
|
228
|
+
server.on('error', async (err) => {
|
|
227
229
|
log(`\nError: ${err.message}\n`);
|
|
228
|
-
|
|
230
|
+
try {
|
|
231
|
+
await updateTask(taskId, { status: 'failed', error: err.message });
|
|
232
|
+
} catch (updateError) {
|
|
233
|
+
log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
|
|
234
|
+
}
|
|
229
235
|
process.exit(1);
|
|
230
236
|
});
|
|
231
237
|
|
|
232
|
-
// Handle client attach/detach for logging
|
|
233
238
|
server.on('clientAttach', ({ clientId }) => {
|
|
234
239
|
log(`[${Date.now()}][ATTACH] Client attached: ${clientId.slice(0, 8)}...\n`);
|
|
235
240
|
});
|
|
@@ -238,11 +243,9 @@ server.on('clientDetach', ({ clientId }) => {
|
|
|
238
243
|
log(`[${Date.now()}][DETACH] Client detached: ${clientId.slice(0, 8)}...\n`);
|
|
239
244
|
});
|
|
240
245
|
|
|
241
|
-
// Start the server
|
|
242
246
|
try {
|
|
243
247
|
await server.start();
|
|
244
248
|
|
|
245
|
-
// Update task with PID and socket path
|
|
246
249
|
updateTask(taskId, {
|
|
247
250
|
pid: server.pid,
|
|
248
251
|
socketPath,
|
|
@@ -258,7 +261,6 @@ try {
|
|
|
258
261
|
process.exit(1);
|
|
259
262
|
}
|
|
260
263
|
|
|
261
|
-
// Handle process signals for cleanup
|
|
262
264
|
process.on('SIGTERM', async () => {
|
|
263
265
|
log(`[${Date.now()}][SYSTEM] Received SIGTERM, stopping...\n`);
|
|
264
266
|
await server.stop('SIGTERM');
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
|
|
5
|
+
export const STREAMING_MODE_ERROR = 'only prompt commands are supported in streaming mode';
|
|
6
|
+
|
|
7
|
+
export function detectStreamingModeError(line) {
|
|
8
|
+
const trimmed = typeof line === 'string' ? line.trim() : '';
|
|
9
|
+
if (!trimmed.startsWith('{')) return null;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(trimmed);
|
|
13
|
+
if (
|
|
14
|
+
parsed &&
|
|
15
|
+
parsed.type === 'result' &&
|
|
16
|
+
parsed.is_error === true &&
|
|
17
|
+
Array.isArray(parsed.errors) &&
|
|
18
|
+
parsed.errors.includes(STREAMING_MODE_ERROR) &&
|
|
19
|
+
typeof parsed.session_id === 'string'
|
|
20
|
+
) {
|
|
21
|
+
return {
|
|
22
|
+
sessionId: parsed.session_id,
|
|
23
|
+
line: trimmed,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// Ignore parse errors - not JSON
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function findSessionJsonlPath(sessionId) {
|
|
34
|
+
const claudeDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
|
35
|
+
const projectsDir = join(claudeDir, 'projects');
|
|
36
|
+
if (!existsSync(projectsDir)) return null;
|
|
37
|
+
|
|
38
|
+
const target = `${sessionId}.jsonl`;
|
|
39
|
+
const queue = [projectsDir];
|
|
40
|
+
|
|
41
|
+
while (queue.length > 0) {
|
|
42
|
+
const dir = queue.pop();
|
|
43
|
+
if (!dir) continue;
|
|
44
|
+
|
|
45
|
+
let entries;
|
|
46
|
+
try {
|
|
47
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
48
|
+
} catch {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
if (entry.isFile() && entry.name === target) {
|
|
54
|
+
return join(dir, entry.name);
|
|
55
|
+
}
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
queue.push(join(dir, entry.name));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function recoverStructuredOutput(sessionId) {
|
|
66
|
+
const jsonlPath = findSessionJsonlPath(sessionId);
|
|
67
|
+
if (!jsonlPath) return null;
|
|
68
|
+
|
|
69
|
+
let fileContents;
|
|
70
|
+
try {
|
|
71
|
+
fileContents = readFileSync(jsonlPath, 'utf8');
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const lines = fileContents.split('\n');
|
|
77
|
+
let structuredOutput = null;
|
|
78
|
+
let usage = null;
|
|
79
|
+
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
if (!line.trim()) continue;
|
|
82
|
+
try {
|
|
83
|
+
const entry = JSON.parse(line);
|
|
84
|
+
const message = entry?.message;
|
|
85
|
+
const content = message?.content;
|
|
86
|
+
if (!Array.isArray(content)) continue;
|
|
87
|
+
|
|
88
|
+
for (const block of content) {
|
|
89
|
+
if (block?.type === 'tool_use' && block?.name === 'StructuredOutput' && block?.input) {
|
|
90
|
+
structuredOutput = block.input;
|
|
91
|
+
if (message?.usage && typeof message.usage === 'object') {
|
|
92
|
+
usage = message.usage;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// Skip invalid JSON lines
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!structuredOutput) return null;
|
|
102
|
+
|
|
103
|
+
const payload = {
|
|
104
|
+
type: 'result',
|
|
105
|
+
subtype: 'success',
|
|
106
|
+
is_error: false,
|
|
107
|
+
structured_output: structuredOutput,
|
|
108
|
+
session_id: sessionId,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (usage) {
|
|
112
|
+
payload.usage = usage;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
payload,
|
|
117
|
+
sourcePath: jsonlPath,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -27,7 +27,7 @@ export function listTasks(options = {}) {
|
|
|
27
27
|
// Table format (default) or verbose format
|
|
28
28
|
if (options.verbose) {
|
|
29
29
|
// Verbose format (old behavior)
|
|
30
|
-
console.log(chalk.bold(`\
|
|
30
|
+
console.log(chalk.bold(`\nTasks (${filtered.length}/${taskList.length})\n`));
|
|
31
31
|
|
|
32
32
|
for (const task of filtered) {
|
|
33
33
|
// Verify running status
|
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { getTask } from '../store.js';
|
|
3
3
|
import { spawnTask } from '../runner.js';
|
|
4
4
|
|
|
5
|
-
export function resumeTask(taskId, newPrompt) {
|
|
5
|
+
export async function resumeTask(taskId, newPrompt) {
|
|
6
6
|
const task = getTask(taskId);
|
|
7
7
|
|
|
8
8
|
if (!task) {
|
|
@@ -23,10 +23,11 @@ export function resumeTask(taskId, newPrompt) {
|
|
|
23
23
|
console.log(chalk.dim(`Original prompt: ${task.prompt}`));
|
|
24
24
|
console.log(chalk.dim(`Resume prompt: ${prompt}`));
|
|
25
25
|
|
|
26
|
-
const newTask = spawnTask(prompt, {
|
|
26
|
+
const newTask = await spawnTask(prompt, {
|
|
27
27
|
cwd: task.cwd,
|
|
28
28
|
continue: true, // Use --continue to load most recent session in that directory
|
|
29
29
|
sessionId: task.sessionId,
|
|
30
|
+
provider: task.provider,
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
console.log(chalk.green(`\n✓ Resumed as new task: ${chalk.cyan(newTask.id)}`));
|
package/task-lib/commands/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { spawnTask } from '../runner.js';
|
|
3
3
|
|
|
4
|
-
export function runTask(prompt, options = {}) {
|
|
4
|
+
export async function runTask(prompt, options = {}) {
|
|
5
5
|
if (!prompt || prompt.trim().length === 0) {
|
|
6
6
|
console.log(chalk.red('Error: Prompt is required'));
|
|
7
7
|
process.exit(1);
|
|
@@ -11,10 +11,16 @@ export function runTask(prompt, options = {}) {
|
|
|
11
11
|
const jsonSchema = options.jsonSchema;
|
|
12
12
|
const silentJsonOutput = options.silentJsonOutput || false;
|
|
13
13
|
|
|
14
|
-
console.log(chalk.dim('Spawning
|
|
14
|
+
console.log(chalk.dim('Spawning task...'));
|
|
15
|
+
if (options.provider) {
|
|
16
|
+
console.log(chalk.dim(` Provider: ${options.provider}`));
|
|
17
|
+
}
|
|
15
18
|
if (options.model) {
|
|
16
19
|
console.log(chalk.dim(` Model: ${options.model}`));
|
|
17
20
|
}
|
|
21
|
+
if (options.modelLevel) {
|
|
22
|
+
console.log(chalk.dim(` Level: ${options.modelLevel}`));
|
|
23
|
+
}
|
|
18
24
|
if (jsonSchema && outputFormat === 'json') {
|
|
19
25
|
console.log(chalk.dim(` JSON Schema: enforced`));
|
|
20
26
|
if (silentJsonOutput) {
|
|
@@ -22,9 +28,12 @@ export function runTask(prompt, options = {}) {
|
|
|
22
28
|
}
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
const task = spawnTask(prompt, {
|
|
31
|
+
const task = await spawnTask(prompt, {
|
|
26
32
|
cwd: options.cwd || process.cwd(),
|
|
27
33
|
model: options.model,
|
|
34
|
+
modelLevel: options.modelLevel,
|
|
35
|
+
reasoningEffort: options.reasoningEffort,
|
|
36
|
+
provider: options.provider,
|
|
28
37
|
resume: options.resume,
|
|
29
38
|
continue: options.continue,
|
|
30
39
|
outputFormat,
|
package/task-lib/runner.js
CHANGED
|
@@ -1,55 +1,76 @@
|
|
|
1
1
|
import { fork } from 'child_process';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { LOGS_DIR
|
|
4
|
+
import { LOGS_DIR } from './config.js';
|
|
5
5
|
import { addTask, generateId, ensureDirs } from './store.js';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const { loadSettings } = require('../lib/settings.js');
|
|
10
|
+
const { normalizeProviderName } = require('../lib/provider-names');
|
|
11
|
+
const { getProvider } = require('../src/providers');
|
|
6
12
|
|
|
7
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
14
|
|
|
9
|
-
export function spawnTask(prompt, options = {}) {
|
|
15
|
+
export async function spawnTask(prompt, options = {}) {
|
|
10
16
|
ensureDirs();
|
|
11
17
|
|
|
12
18
|
const id = generateId();
|
|
13
19
|
const logFile = join(LOGS_DIR, `${id}.log`);
|
|
14
20
|
const cwd = options.cwd || process.cwd();
|
|
15
|
-
const model = options.model || process.env.ANTHROPIC_MODEL || DEFAULT_MODEL;
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
const settings = loadSettings();
|
|
23
|
+
const providerName = normalizeProviderName(
|
|
24
|
+
options.provider || settings.defaultProvider || 'claude'
|
|
25
|
+
);
|
|
26
|
+
const provider = getProvider(providerName);
|
|
27
|
+
const providerSettings = settings.providerSettings?.[providerName] || {};
|
|
28
|
+
const levelOverrides = providerSettings.levelOverrides || {};
|
|
29
|
+
|
|
21
30
|
const outputFormat = options.outputFormat || 'stream-json';
|
|
22
|
-
const args = ['--print', '--dangerously-skip-permissions', '--output-format', outputFormat];
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
if (outputFormat
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
args.push('--include-partial-messages');
|
|
32
|
+
let jsonSchema = options.jsonSchema || null;
|
|
33
|
+
if (jsonSchema && outputFormat !== 'json') {
|
|
34
|
+
console.warn('Warning: --json-schema requires --output-format json, ignoring schema');
|
|
35
|
+
jsonSchema = null;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
if (options.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
let modelSpec;
|
|
39
|
+
if (options.model) {
|
|
40
|
+
modelSpec = {
|
|
41
|
+
model: options.model,
|
|
42
|
+
reasoningEffort: options.reasoningEffort,
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
const level = options.modelLevel || providerSettings.defaultLevel || provider.getDefaultLevel();
|
|
46
|
+
modelSpec = provider.resolveModelSpec(level, levelOverrides);
|
|
47
|
+
if (options.reasoningEffort) {
|
|
48
|
+
modelSpec = { ...modelSpec, reasoningEffort: options.reasoningEffort };
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
const cliFeatures = await provider.getCliFeatures();
|
|
53
|
+
const commandSpec = provider.buildCommand(prompt, {
|
|
54
|
+
modelSpec,
|
|
55
|
+
outputFormat,
|
|
56
|
+
jsonSchema,
|
|
57
|
+
cwd,
|
|
58
|
+
autoApprove: true,
|
|
59
|
+
cliFeatures,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const finalArgs = [...commandSpec.args];
|
|
63
|
+
if (providerName === 'claude') {
|
|
64
|
+
const promptIndex = finalArgs.length - 1;
|
|
65
|
+
if (options.resume) {
|
|
66
|
+
finalArgs.splice(promptIndex, 0, '--resume', options.resume);
|
|
67
|
+
} else if (options.continue) {
|
|
68
|
+
finalArgs.splice(promptIndex, 0, '--continue');
|
|
69
|
+
}
|
|
70
|
+
} else if (options.resume || options.continue) {
|
|
71
|
+
console.warn('Warning: resume/continue is only supported for Claude CLI; ignoring.');
|
|
49
72
|
}
|
|
50
73
|
|
|
51
|
-
args.push(prompt);
|
|
52
|
-
|
|
53
74
|
const task = {
|
|
54
75
|
id,
|
|
55
76
|
prompt: prompt.slice(0, 200) + (prompt.length > 200 ? '...' : ''),
|
|
@@ -63,6 +84,8 @@ export function spawnTask(prompt, options = {}) {
|
|
|
63
84
|
updatedAt: new Date().toISOString(),
|
|
64
85
|
exitCode: null,
|
|
65
86
|
error: null,
|
|
87
|
+
provider: providerName,
|
|
88
|
+
model: modelSpec?.model || null,
|
|
66
89
|
// Schedule reference (if spawned by scheduler)
|
|
67
90
|
scheduleId: options.scheduleId || null,
|
|
68
91
|
// Attach support
|
|
@@ -72,24 +95,23 @@ export function spawnTask(prompt, options = {}) {
|
|
|
72
95
|
|
|
73
96
|
addTask(task);
|
|
74
97
|
|
|
75
|
-
// Fork a watcher process that will manage the claude process
|
|
76
98
|
const watcherConfig = {
|
|
77
99
|
outputFormat,
|
|
78
|
-
jsonSchema
|
|
100
|
+
jsonSchema,
|
|
79
101
|
silentJsonOutput: options.silentJsonOutput || false,
|
|
80
|
-
|
|
102
|
+
provider: providerName,
|
|
103
|
+
command: commandSpec.binary,
|
|
104
|
+
env: commandSpec.env || {},
|
|
81
105
|
};
|
|
82
106
|
|
|
83
|
-
|
|
84
|
-
// Attachable watcher uses node-pty and creates a Unix socket for attach/detach
|
|
85
|
-
const useAttachable = options.attachable !== false;
|
|
107
|
+
const useAttachable = options.attachable !== false && !options.jsonSchema;
|
|
86
108
|
const watcherScript = useAttachable
|
|
87
109
|
? join(__dirname, 'attachable-watcher.js')
|
|
88
110
|
: join(__dirname, 'watcher.js');
|
|
89
111
|
|
|
90
112
|
const watcher = fork(
|
|
91
113
|
watcherScript,
|
|
92
|
-
[id, cwd, logFile, JSON.stringify(
|
|
114
|
+
[id, cwd, logFile, JSON.stringify(finalArgs), JSON.stringify(watcherConfig)],
|
|
93
115
|
{
|
|
94
116
|
detached: true,
|
|
95
117
|
stdio: 'ignore',
|
|
@@ -98,7 +120,6 @@ export function spawnTask(prompt, options = {}) {
|
|
|
98
120
|
|
|
99
121
|
watcher.unref();
|
|
100
122
|
|
|
101
|
-
// Return task immediately - watcher will update PID async
|
|
102
123
|
return task;
|
|
103
124
|
}
|
|
104
125
|
|
package/task-lib/scheduler.js
CHANGED
|
@@ -108,7 +108,7 @@ function log(msg) {
|
|
|
108
108
|
/**
|
|
109
109
|
* Check and run due schedules
|
|
110
110
|
*/
|
|
111
|
-
function checkSchedules() {
|
|
111
|
+
async function checkSchedules() {
|
|
112
112
|
const schedules = loadSchedules();
|
|
113
113
|
const now = new Date();
|
|
114
114
|
|
|
@@ -122,7 +122,7 @@ function checkSchedules() {
|
|
|
122
122
|
log(`Running scheduled task: ${schedule.id} - "${schedule.prompt.slice(0, 50)}..."`);
|
|
123
123
|
|
|
124
124
|
try {
|
|
125
|
-
const task = spawnTask(schedule.prompt, {
|
|
125
|
+
const task = await spawnTask(schedule.prompt, {
|
|
126
126
|
cwd: schedule.cwd,
|
|
127
127
|
scheduleId: schedule.id,
|
|
128
128
|
});
|