@crewx/cli 0.9.0-rc.94 → 0.9.0-rc.96
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/dist/bootstrap/crewx-cli.js +5 -1
- package/dist/commands/cal.d.ts +15 -0
- package/dist/commands/cal.js +44 -0
- package/dist/commands/db.d.ts +1 -0
- package/dist/commands/db.js +191 -1
- package/dist/commands/detached.d.ts +65 -0
- package/dist/commands/detached.js +507 -0
- package/dist/commands/execute.d.ts +4 -6
- package/dist/commands/execute.js +46 -96
- package/dist/commands/kill.d.ts +2 -8
- package/dist/commands/kill.js +6 -47
- package/dist/commands/ps.d.ts +2 -2
- package/dist/commands/ps.js +18 -9
- package/dist/commands/query.d.ts +2 -0
- package/dist/commands/query.js +39 -7
- package/dist/commands/registry.js +1 -1
- package/dist/commands/result.d.ts +3 -2
- package/dist/commands/result.js +104 -7
- package/dist/commands/stop.d.ts +8 -0
- package/dist/commands/stop.js +119 -0
- package/dist/main.js +17 -9
- package/dist/utils/inherited-trace.d.ts +24 -0
- package/dist/utils/inherited-trace.js +40 -0
- package/package.json +14 -13
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared receipt-mode implementation for the query and execute commands.
|
|
4
|
+
*
|
|
5
|
+
* A receipt is persisted before a detached runner is started. The runner gets
|
|
6
|
+
* its own identity variables, while the trace variables remain the caller's
|
|
7
|
+
* trace context and are never repurposed as the runner assignment.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.extractReceiptFlags = extractReceiptFlags;
|
|
11
|
+
exports.resolveReceiptMode = resolveReceiptMode;
|
|
12
|
+
exports.adoptAssignedTask = adoptAssignedTask;
|
|
13
|
+
exports.readDetachedMiddleHop = readDetachedMiddleHop;
|
|
14
|
+
exports.runDetachedMiddle = runDetachedMiddle;
|
|
15
|
+
exports.runDetached = runDetached;
|
|
16
|
+
const child_process_1 = require("child_process");
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
const path_1 = require("path");
|
|
19
|
+
const os_1 = require("os");
|
|
20
|
+
const sdk_1 = require("@crewx/sdk");
|
|
21
|
+
const repository_1 = require("@crewx/sdk/repository");
|
|
22
|
+
const parse_agent_message_1 = require("./parse-agent-message");
|
|
23
|
+
const parse_common_flags_1 = require("./parse-common-flags");
|
|
24
|
+
const resolve_prompt_1 = require("./resolve-prompt");
|
|
25
|
+
const version_1 = require("../utils/version");
|
|
26
|
+
const inherited_trace_1 = require("../utils/inherited-trace");
|
|
27
|
+
const cal_1 = require("./cal");
|
|
28
|
+
const DETACHED_RUNNER_POLL_INTERVAL_MS = 200;
|
|
29
|
+
const DETACHED_RUNNER_ADOPTION_TIMEOUT_MS = 30_000;
|
|
30
|
+
const DETACHED_VALUE_FLAGS = [
|
|
31
|
+
'--thread',
|
|
32
|
+
'--provider',
|
|
33
|
+
'--model',
|
|
34
|
+
'--metadata',
|
|
35
|
+
'--config',
|
|
36
|
+
'-c',
|
|
37
|
+
'--output-format',
|
|
38
|
+
'--effort',
|
|
39
|
+
'--prompt-file',
|
|
40
|
+
'-f',
|
|
41
|
+
'--out',
|
|
42
|
+
'-o',
|
|
43
|
+
];
|
|
44
|
+
const INTERNAL_MIDDLE_FLAG = '--__crewx-detached-middle';
|
|
45
|
+
const INTERNAL_TASK_FLAG = '--__crewx-detached-task';
|
|
46
|
+
const INTERNAL_LOG_FLAG = '--__crewx-detached-log';
|
|
47
|
+
const INTERNAL_TOKEN_FLAG = '--__crewx-detached-token';
|
|
48
|
+
const INTERNAL_TOKEN_ENV = 'CREWX_DETACHED_HOP_TOKEN';
|
|
49
|
+
function isDetachedValueFlag(arg) {
|
|
50
|
+
return DETACHED_VALUE_FLAGS.some((name) => arg === name || arg.startsWith(`${name}=`));
|
|
51
|
+
}
|
|
52
|
+
function isDetachedPromptFileFlag(arg) {
|
|
53
|
+
return arg === '--prompt-file'
|
|
54
|
+
|| arg === '-f'
|
|
55
|
+
|| arg.startsWith('--prompt-file=')
|
|
56
|
+
|| arg.startsWith('-f=');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Remove receipt-selection flags, respecting the `--` literal-args sentinel.
|
|
60
|
+
*/
|
|
61
|
+
function extractReceiptFlags(args) {
|
|
62
|
+
const rest = [];
|
|
63
|
+
let detach = false;
|
|
64
|
+
let wait = false;
|
|
65
|
+
let escapeMode = false;
|
|
66
|
+
for (const arg of args) {
|
|
67
|
+
if (!escapeMode && arg === '--') {
|
|
68
|
+
escapeMode = true;
|
|
69
|
+
rest.push(arg);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!escapeMode && (arg === '--detach' || arg === '--async')) {
|
|
73
|
+
detach = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!escapeMode && (arg === '--wait' || arg.startsWith('--wait='))) {
|
|
77
|
+
wait = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
rest.push(arg);
|
|
81
|
+
}
|
|
82
|
+
return { detach, wait, rest };
|
|
83
|
+
}
|
|
84
|
+
/** Resolve the receipt/synchronous mode contract shared by q and x. */
|
|
85
|
+
function resolveReceiptMode(options) {
|
|
86
|
+
if (options.wait)
|
|
87
|
+
return { receipt: false, reason: 'wait' };
|
|
88
|
+
// The runner guard is deliberately independent from trace propagation.
|
|
89
|
+
if (options.detachedRunner)
|
|
90
|
+
return { receipt: false, reason: 'detached-runner' };
|
|
91
|
+
if (options.detach)
|
|
92
|
+
return { receipt: true, reason: 'explicit-detach' };
|
|
93
|
+
if (options.agentContext)
|
|
94
|
+
return { receipt: true, reason: 'agent-context' };
|
|
95
|
+
return { receipt: false, reason: 'default' };
|
|
96
|
+
}
|
|
97
|
+
function errorMessage(error) {
|
|
98
|
+
return error instanceof Error ? error.message : String(error);
|
|
99
|
+
}
|
|
100
|
+
/** Wait for the pending receipt to be adopted, or for the runner to die. */
|
|
101
|
+
function waitForDetachedRunner(taskRepo, taskId, child, options = {}) {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
let settled = false;
|
|
104
|
+
let pollTimer;
|
|
105
|
+
let timeoutTimer;
|
|
106
|
+
const cleanup = () => {
|
|
107
|
+
if (pollTimer !== undefined)
|
|
108
|
+
clearInterval(pollTimer);
|
|
109
|
+
if (timeoutTimer !== undefined)
|
|
110
|
+
clearTimeout(timeoutTimer);
|
|
111
|
+
};
|
|
112
|
+
const finish = (outcome) => {
|
|
113
|
+
if (settled)
|
|
114
|
+
return;
|
|
115
|
+
settled = true;
|
|
116
|
+
cleanup();
|
|
117
|
+
resolve(outcome);
|
|
118
|
+
};
|
|
119
|
+
const inspect = (terminalReason, timedOut = false) => {
|
|
120
|
+
try {
|
|
121
|
+
const task = taskRepo.getTask(taskId);
|
|
122
|
+
if (task && task.status !== 'pending') {
|
|
123
|
+
finish({ kind: 'adopted' });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (terminalReason !== undefined) {
|
|
127
|
+
finish({ kind: 'exited', reason: terminalReason });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (timedOut)
|
|
131
|
+
finish({ kind: 'timeout' });
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (settled)
|
|
135
|
+
return;
|
|
136
|
+
settled = true;
|
|
137
|
+
cleanup();
|
|
138
|
+
reject(error);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
child.on('error', (error) => {
|
|
142
|
+
inspect(`Runner failed before adopting task: ${errorMessage(error)}`);
|
|
143
|
+
});
|
|
144
|
+
child.on('exit', (code, signal) => {
|
|
145
|
+
if (options.ignoreSuccessfulExit && code === 0 && signal === null)
|
|
146
|
+
return;
|
|
147
|
+
const detail = signal ? `signal ${signal}` : `exit ${code ?? 'unknown'}`;
|
|
148
|
+
inspect(`Runner exited before adopting task (${detail})`);
|
|
149
|
+
});
|
|
150
|
+
pollTimer = setInterval(() => inspect(), DETACHED_RUNNER_POLL_INTERVAL_MS);
|
|
151
|
+
timeoutTimer = setTimeout(() => inspect(undefined, true), DETACHED_RUNNER_ADOPTION_TIMEOUT_MS);
|
|
152
|
+
inspect();
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** Mark a failed receipt only while the parent still owns its pending row. */
|
|
156
|
+
function failDetachedTask(taskRepo, taskId, reason) {
|
|
157
|
+
try {
|
|
158
|
+
const transitioned = taskRepo.failPendingTask(taskId, reason);
|
|
159
|
+
if (transitioned) {
|
|
160
|
+
process.stderr.write(`Detached task ${taskId} failed: ${reason}\n`);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const task = taskRepo.getTask(taskId);
|
|
164
|
+
if (task && task.status !== 'pending')
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
reason += `; could not persist failure: ${errorMessage(error)}`;
|
|
169
|
+
}
|
|
170
|
+
process.stderr.write(`Detached task ${taskId} failed: ${reason}\n`);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
function announceDetachedTask(taskRepo, taskId, logPath, reason, verbose, outputFormat, warning) {
|
|
174
|
+
let status = 'pending';
|
|
175
|
+
try {
|
|
176
|
+
const task = taskRepo.getTask(taskId);
|
|
177
|
+
status = task?.status === 'pending' ? 'pending' : 'running';
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// The receipt was already persisted. Keep the output contract stable if a
|
|
181
|
+
// follow-up status read is temporarily unavailable.
|
|
182
|
+
}
|
|
183
|
+
if (outputFormat === 'json') {
|
|
184
|
+
process.stdout.write(`${JSON.stringify({ taskId, status, mode: 'receipt', logPath })}\n`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (verbose)
|
|
188
|
+
process.stderr.write(`mode=receipt reason=${reason}\n`);
|
|
189
|
+
process.stdout.write(`${taskId}\n`);
|
|
190
|
+
process.stderr.write(`Task ${taskId} accepted. This ID is the handle (not a PID).\n`);
|
|
191
|
+
if (warning && verbose)
|
|
192
|
+
process.stderr.write(`${warning}\n`);
|
|
193
|
+
const waitSeconds = (0, cal_1.suggestWaitSeconds)({ startedAt: null });
|
|
194
|
+
(0, cal_1.writeCalFooter)({
|
|
195
|
+
commandId: 'crewx.receipt',
|
|
196
|
+
params: { taskId, wait: String(waitSeconds) },
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Rebuild runner argv from the parsed request. The resolved prompt is passed
|
|
201
|
+
* as one argv value after `--`, so stdin is never needed by the detached
|
|
202
|
+
* child and prompt text beginning with a dash remains positional text.
|
|
203
|
+
*/
|
|
204
|
+
function buildDetachedRunnerArgs(filteredArgs, agentRef, finalMessage) {
|
|
205
|
+
const forwarded = [];
|
|
206
|
+
for (let i = 0; i < filteredArgs.length; i += 1) {
|
|
207
|
+
const arg = filteredArgs[i];
|
|
208
|
+
if (arg === '--')
|
|
209
|
+
break;
|
|
210
|
+
if (arg === '--verbose' || arg === '--overdrive') {
|
|
211
|
+
forwarded.push(arg);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (arg === '--var') {
|
|
215
|
+
const value = filteredArgs[i + 1];
|
|
216
|
+
if (value !== undefined) {
|
|
217
|
+
forwarded.push(arg, value);
|
|
218
|
+
i += 1;
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (arg.startsWith('--var=')) {
|
|
223
|
+
forwarded.push(arg);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (!isDetachedValueFlag(arg))
|
|
227
|
+
continue;
|
|
228
|
+
if (isDetachedPromptFileFlag(arg)) {
|
|
229
|
+
// The parent already resolved this file into finalMessage. Forwarding
|
|
230
|
+
// the original path would make a later child read a mutable file again.
|
|
231
|
+
if (!arg.includes('='))
|
|
232
|
+
i += 1;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
forwarded.push(arg);
|
|
236
|
+
if (!arg.includes('=')) {
|
|
237
|
+
const value = filteredArgs[i + 1];
|
|
238
|
+
if (value !== undefined) {
|
|
239
|
+
forwarded.push(value);
|
|
240
|
+
i += 1;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return [
|
|
245
|
+
...forwarded,
|
|
246
|
+
...(agentRef ? [agentRef] : []),
|
|
247
|
+
'--',
|
|
248
|
+
finalMessage,
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
function runnerCommand(command) {
|
|
252
|
+
if (command === 'execute')
|
|
253
|
+
return 'x';
|
|
254
|
+
if (command === 'query')
|
|
255
|
+
return 'q';
|
|
256
|
+
return command;
|
|
257
|
+
}
|
|
258
|
+
/** Build the only environment contract that identifies a detached runner. */
|
|
259
|
+
function buildRunnerEnvironment(taskId) {
|
|
260
|
+
const env = { ...process.env };
|
|
261
|
+
delete env[INTERNAL_TOKEN_ENV];
|
|
262
|
+
env.CREWX_DETACHED_RUNNER = '1';
|
|
263
|
+
env.CREWX_ASSIGNED_TASK_ID = taskId;
|
|
264
|
+
return env;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Claim a pre-issued receipt without overwriting an already-owned row.
|
|
268
|
+
* Assignment-only leakage is recoverable: discard the stale id and let the
|
|
269
|
+
* normal SDK path create a fresh task. A real detached runner must fail loud
|
|
270
|
+
* because its receipt would otherwise point at no execution row.
|
|
271
|
+
*/
|
|
272
|
+
function adoptAssignedTask(taskRepo, taskId, detachedRunner) {
|
|
273
|
+
if (taskRepo.adoptPendingTask(taskId)) {
|
|
274
|
+
return { kind: 'adopted', taskId };
|
|
275
|
+
}
|
|
276
|
+
const reason = `stale assigned id ${taskId}: expected pending task with pid NULL`;
|
|
277
|
+
if (detachedRunner) {
|
|
278
|
+
process.stderr.write(`Detached runner failed to adopt task ${taskId}: ${reason}\n`);
|
|
279
|
+
return { kind: 'failed' };
|
|
280
|
+
}
|
|
281
|
+
process.stderr.write(`${reason}\n`);
|
|
282
|
+
return { kind: 'stale' };
|
|
283
|
+
}
|
|
284
|
+
function buildMiddleEnvironment(token) {
|
|
285
|
+
const env = { ...process.env };
|
|
286
|
+
env[INTERNAL_TOKEN_ENV] = token;
|
|
287
|
+
return env;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Read the private Windows middle-hop contract. Invalid direct invocations
|
|
291
|
+
* are left untouched and therefore fail through the normal unknown-option
|
|
292
|
+
* path instead of becoming an undocumented public command.
|
|
293
|
+
*/
|
|
294
|
+
function readDetachedMiddleHop(args) {
|
|
295
|
+
let marker = false;
|
|
296
|
+
let taskId;
|
|
297
|
+
let logPath;
|
|
298
|
+
let token;
|
|
299
|
+
let escapeMode = false;
|
|
300
|
+
const runnerArgs = [];
|
|
301
|
+
for (const arg of args) {
|
|
302
|
+
if (!escapeMode && arg === '--') {
|
|
303
|
+
escapeMode = true;
|
|
304
|
+
runnerArgs.push(arg);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (!escapeMode && arg === INTERNAL_MIDDLE_FLAG) {
|
|
308
|
+
marker = true;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (!escapeMode && arg.startsWith(`${INTERNAL_TASK_FLAG}=`)) {
|
|
312
|
+
taskId = arg.slice(`${INTERNAL_TASK_FLAG}=`.length);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (!escapeMode && arg.startsWith(`${INTERNAL_LOG_FLAG}=`)) {
|
|
316
|
+
logPath = arg.slice(`${INTERNAL_LOG_FLAG}=`.length);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (!escapeMode && arg.startsWith(`${INTERNAL_TOKEN_FLAG}=`)) {
|
|
320
|
+
token = arg.slice(`${INTERNAL_TOKEN_FLAG}=`.length);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
runnerArgs.push(arg);
|
|
324
|
+
}
|
|
325
|
+
if (!marker || !taskId || !logPath || !token || token !== process.env[INTERNAL_TOKEN_ENV]) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
return { taskId, logPath, runnerArgs };
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Windows middle hop: transfer a transient log handle to the final runner,
|
|
332
|
+
* close the middle process's copy, and exit immediately. The parent treats a
|
|
333
|
+
* successful middle-hop exit as normal and makes adoption a database decision.
|
|
334
|
+
*/
|
|
335
|
+
async function runDetachedMiddle(command, hop) {
|
|
336
|
+
let logFd;
|
|
337
|
+
try {
|
|
338
|
+
const logDir = (0, path_1.dirname)(hop.logPath);
|
|
339
|
+
if (!(0, fs_1.existsSync)(logDir))
|
|
340
|
+
(0, fs_1.mkdirSync)(logDir, { recursive: true });
|
|
341
|
+
logFd = (0, fs_1.openSync)(hop.logPath, 'a');
|
|
342
|
+
const entry = process.argv[1];
|
|
343
|
+
const child = (0, child_process_1.spawn)(process.execPath, [entry, runnerCommand(command), ...hop.runnerArgs], {
|
|
344
|
+
detached: true,
|
|
345
|
+
windowsHide: true,
|
|
346
|
+
stdio: ['ignore', logFd, logFd],
|
|
347
|
+
env: buildRunnerEnvironment(hop.taskId),
|
|
348
|
+
});
|
|
349
|
+
(0, fs_1.closeSync)(logFd);
|
|
350
|
+
logFd = undefined;
|
|
351
|
+
child.unref();
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
if (logFd !== undefined)
|
|
355
|
+
(0, fs_1.closeSync)(logFd);
|
|
356
|
+
process.stderr.write(`Runner failed before adopting task: ${errorMessage(error)}\n`);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
process.exit(0);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Persist a receipt and launch its detached runner. Unix uses one hop. Windows
|
|
364
|
+
* uses a short-lived middle hop so the final runner has no live parent link.
|
|
365
|
+
*/
|
|
366
|
+
async function runDetached(command, filteredArgs, reason) {
|
|
367
|
+
const parsedFlags = (0, parse_common_flags_1.parseCommonFlags)(filteredArgs);
|
|
368
|
+
const { thread, metadata, promptFile, rest } = parsedFlags;
|
|
369
|
+
const { agentRef: parsedAgentRef, message } = (0, parse_agent_message_1.parseAgentMessage)(rest);
|
|
370
|
+
const finalMessage = await (0, resolve_prompt_1.resolvePrompt)(message, promptFile);
|
|
371
|
+
const agentRef = parsedAgentRef || '@crewx';
|
|
372
|
+
if (!finalMessage) {
|
|
373
|
+
console.error('Usage: crewx query/execute [@agent] <message> [options]');
|
|
374
|
+
console.error(' @agent is optional. Defaults to @crewx when omitted.');
|
|
375
|
+
console.error(' Task body can also be supplied via stdin pipe or --prompt-file.');
|
|
376
|
+
process.exit(1);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
let parsedMetadata;
|
|
380
|
+
try {
|
|
381
|
+
parsedMetadata = (0, parse_common_flags_1.parseMetadata)(metadata);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
process.stderr.write(`Error: ${errorMessage(error)}\n`);
|
|
385
|
+
process.exit(2);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const taskId = (0, sdk_1.generateId)('tsk');
|
|
389
|
+
const startedAt = new Date().toISOString();
|
|
390
|
+
const workspacePath = process.cwd();
|
|
391
|
+
const hasWorkspaceConfig = (0, fs_1.existsSync)((0, path_1.join)(workspacePath, 'crewx.yaml'))
|
|
392
|
+
|| (0, fs_1.existsSync)((0, path_1.join)(workspacePath, 'crewx.yml'));
|
|
393
|
+
const inheritedTrace = (0, inherited_trace_1.readInheritedTrace)();
|
|
394
|
+
const taskTrace = inheritedTrace?.rootTraceId || taskId;
|
|
395
|
+
const taskRepo = new repository_1.TaskRepository();
|
|
396
|
+
try {
|
|
397
|
+
taskRepo.startTask({
|
|
398
|
+
id: taskId,
|
|
399
|
+
agentId: agentRef.replace(/^@/, ''),
|
|
400
|
+
prompt: finalMessage,
|
|
401
|
+
mode: command === 'q' || command === 'query' ? 'query' : 'execute',
|
|
402
|
+
status: 'pending',
|
|
403
|
+
startedAt,
|
|
404
|
+
pid: null,
|
|
405
|
+
parentTaskId: inheritedTrace?.parentTaskId ?? null,
|
|
406
|
+
callerAgentId: inheritedTrace?.callerAgentId ?? null,
|
|
407
|
+
traceId: taskTrace,
|
|
408
|
+
command: process.argv.join(' '),
|
|
409
|
+
metadata: Object.keys(parsedMetadata).length > 0 ? JSON.stringify(parsedMetadata) : null,
|
|
410
|
+
workspaceId: hasWorkspaceConfig ? (0, sdk_1.hashWorkspaceId)(workspacePath) : null,
|
|
411
|
+
projectId: null,
|
|
412
|
+
platform: 'cli',
|
|
413
|
+
crewxVersion: version_1.CLI_VERSION,
|
|
414
|
+
threadId: thread ?? null,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
process.stderr.write(`Failed to create detached task receipt: ${errorMessage(error)}\n`);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const runnerArgs = buildDetachedRunnerArgs(filteredArgs, agentRef, finalMessage);
|
|
423
|
+
const commandForRunner = runnerCommand(command);
|
|
424
|
+
const logDir = (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'logs');
|
|
425
|
+
const logPath = (0, path_1.join)(logDir, `${taskId}.log`);
|
|
426
|
+
let logFd;
|
|
427
|
+
let child;
|
|
428
|
+
let middleHop = false;
|
|
429
|
+
try {
|
|
430
|
+
if (!(0, fs_1.existsSync)(logDir))
|
|
431
|
+
(0, fs_1.mkdirSync)(logDir, { recursive: true });
|
|
432
|
+
if (process.platform === 'win32') {
|
|
433
|
+
middleHop = true;
|
|
434
|
+
const token = (0, sdk_1.generateId)('req');
|
|
435
|
+
child = (0, child_process_1.spawn)(process.execPath, [
|
|
436
|
+
process.argv[1],
|
|
437
|
+
commandForRunner,
|
|
438
|
+
INTERNAL_MIDDLE_FLAG,
|
|
439
|
+
`${INTERNAL_TASK_FLAG}=${taskId}`,
|
|
440
|
+
`${INTERNAL_LOG_FLAG}=${logPath}`,
|
|
441
|
+
`${INTERNAL_TOKEN_FLAG}=${token}`,
|
|
442
|
+
...runnerArgs,
|
|
443
|
+
], {
|
|
444
|
+
detached: true,
|
|
445
|
+
windowsHide: true,
|
|
446
|
+
stdio: 'ignore',
|
|
447
|
+
env: buildMiddleEnvironment(token),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
logFd = (0, fs_1.openSync)(logPath, 'a');
|
|
452
|
+
child = (0, child_process_1.spawn)(process.execPath, [process.argv[1], commandForRunner, ...runnerArgs], {
|
|
453
|
+
detached: true,
|
|
454
|
+
windowsHide: true,
|
|
455
|
+
stdio: ['ignore', logFd, logFd],
|
|
456
|
+
env: buildRunnerEnvironment(taskId),
|
|
457
|
+
});
|
|
458
|
+
(0, fs_1.closeSync)(logFd);
|
|
459
|
+
logFd = undefined;
|
|
460
|
+
}
|
|
461
|
+
child.unref();
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
if (logFd !== undefined)
|
|
465
|
+
(0, fs_1.closeSync)(logFd);
|
|
466
|
+
const reasonMessage = `Runner failed before adopting task: ${errorMessage(error)}`;
|
|
467
|
+
if (!failDetachedTask(taskRepo, taskId, reasonMessage))
|
|
468
|
+
process.exit(1);
|
|
469
|
+
else {
|
|
470
|
+
announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat);
|
|
471
|
+
process.exit(0);
|
|
472
|
+
}
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
let outcome;
|
|
476
|
+
try {
|
|
477
|
+
outcome = await waitForDetachedRunner(taskRepo, taskId, child, {
|
|
478
|
+
ignoreSuccessfulExit: middleHop,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
const reasonMessage = `Runner adoption check failed: ${errorMessage(error)}`;
|
|
483
|
+
if (!failDetachedTask(taskRepo, taskId, reasonMessage))
|
|
484
|
+
process.exit(1);
|
|
485
|
+
else {
|
|
486
|
+
announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat);
|
|
487
|
+
process.exit(0);
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (outcome.kind === 'adopted') {
|
|
492
|
+
announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat);
|
|
493
|
+
process.exit(0);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (outcome.kind === 'timeout') {
|
|
497
|
+
announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat, `Warning: runner alive, row not yet adopted after ${DETACHED_RUNNER_ADOPTION_TIMEOUT_MS / 1000}s.`);
|
|
498
|
+
process.exit(0);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (!failDetachedTask(taskRepo, taskId, outcome.reason))
|
|
502
|
+
process.exit(1);
|
|
503
|
+
else {
|
|
504
|
+
announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat);
|
|
505
|
+
process.exit(0);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
@@ -7,22 +7,20 @@
|
|
|
7
7
|
* --provider <cli/xxx> Provider override
|
|
8
8
|
* --model <name> Model override (e.g. claude-sonnet-5)
|
|
9
9
|
* --metadata <json> Extra metadata JSON (double-quoted object). Propagated to events/hooks/tracing.
|
|
10
|
-
* e.g. --metadata='{"workflow_id":"wf-1"}'
|
|
11
10
|
* --verbose Debug output mode (default: raw agent response only)
|
|
12
11
|
* --config/-c <path> Config file path override
|
|
13
12
|
* --output-format <fmt> Output format (json|text|stream-json)
|
|
14
13
|
* --effort <level> Model effort (high|medium|low)
|
|
15
|
-
* -f/--prompt-file <path> Read task body from file
|
|
16
|
-
* --detach
|
|
17
|
-
*
|
|
18
|
-
* on win32 (unsupported — exits with an error).
|
|
14
|
+
* -f/--prompt-file <path> Read task body from file
|
|
15
|
+
* --detach/--async Persist a receipt and launch a detached runner
|
|
16
|
+
* --wait Force synchronous execution
|
|
19
17
|
*
|
|
20
18
|
* Stdin support:
|
|
21
19
|
* Pipe or redirect content into crewx x to supply the task body via stdin.
|
|
22
|
-
* e.g. cat task.md | crewx x "@agent label"
|
|
23
20
|
* Stdin is ignored when running in an interactive TTY.
|
|
24
21
|
*/
|
|
25
22
|
import { type DelegationEmitState } from './emit-trailer';
|
|
23
|
+
export { runDetachedMiddle } from './detached';
|
|
26
24
|
/**
|
|
27
25
|
* Handle `crewx execute <agentRef> <message>` command.
|
|
28
26
|
*
|