@crewx/cli 0.9.0-rc.145 → 0.9.0-rc.147

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 (45) hide show
  1. package/dist/bootstrap/codex-writable-roots.js +1 -113
  2. package/dist/bootstrap/crewx-cli.js +1 -84
  3. package/dist/builtin.js +5 -132
  4. package/dist/commands/agent.js +4 -118
  5. package/dist/commands/cal.js +2 -44
  6. package/dist/commands/db.js +4 -602
  7. package/dist/commands/detached.js +15 -620
  8. package/dist/commands/doctor.js +4 -649
  9. package/dist/commands/emit-trailer.js +2 -33
  10. package/dist/commands/execute.js +15 -294
  11. package/dist/commands/hook/command-marker.js +1 -5
  12. package/dist/commands/hook/install.js +8 -189
  13. package/dist/commands/hook/paths.js +1 -94
  14. package/dist/commands/hook/status.js +1 -86
  15. package/dist/commands/hook/uninstall.js +1 -71
  16. package/dist/commands/hook-dispatch.js +2 -180
  17. package/dist/commands/init.js +31 -437
  18. package/dist/commands/kill.js +1 -11
  19. package/dist/commands/log.js +3 -96
  20. package/dist/commands/parse-agent-message.js +1 -52
  21. package/dist/commands/parse-common-flags.js +3 -212
  22. package/dist/commands/ps.js +2 -117
  23. package/dist/commands/publish.js +7 -283
  24. package/dist/commands/query.js +16 -292
  25. package/dist/commands/registry.js +1 -33
  26. package/dist/commands/resolve-prompt.js +3 -76
  27. package/dist/commands/resolve-recipient.js +3 -47
  28. package/dist/commands/restart.js +18 -172
  29. package/dist/commands/result.js +1 -209
  30. package/dist/commands/shortcut.js +4 -253
  31. package/dist/commands/slack.js +16 -560
  32. package/dist/commands/stop.js +8 -119
  33. package/dist/commands/write-output.js +1 -24
  34. package/dist/index.js +1 -8
  35. package/dist/logging.js +10 -87
  36. package/dist/main.js +6 -289
  37. package/dist/register-builtin-tools.js +1 -9
  38. package/dist/slack/file-download.js +1 -134
  39. package/dist/slack/markdown.js +1 -33
  40. package/dist/utils/env-defaults.js +1 -15
  41. package/dist/utils/inherited-trace.js +1 -65
  42. package/dist/utils/product-package-root.js +1 -39
  43. package/dist/utils/sdk-compat.js +1 -120
  44. package/dist/utils/version.js +1 -28
  45. package/package.json +15 -15
@@ -1,620 +1,15 @@
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.resolveCliTaskActor = resolveCliTaskActor;
11
- exports.resolveReceiptActor = resolveReceiptActor;
12
- exports.reportAdmissionBlocked = reportAdmissionBlocked;
13
- exports.extractReceiptFlags = extractReceiptFlags;
14
- exports.resolveReceiptMode = resolveReceiptMode;
15
- exports.buildDetachedRunnerArgs = buildDetachedRunnerArgs;
16
- exports.adoptAssignedTask = adoptAssignedTask;
17
- exports.readAssignedTaskActor = readAssignedTaskActor;
18
- exports.readDetachedMiddleHop = readDetachedMiddleHop;
19
- exports.runDetachedMiddle = runDetachedMiddle;
20
- exports.runDetached = runDetached;
21
- const child_process_1 = require("child_process");
22
- const fs_1 = require("fs");
23
- const path_1 = require("path");
24
- const os_1 = require("os");
25
- const sdk_1 = require("@crewx/sdk");
26
- const node_1 = require("@crewx/sdk/node");
27
- const repository_1 = require("@crewx/sdk/repository");
28
- const parse_agent_message_1 = require("./parse-agent-message");
29
- const parse_common_flags_1 = require("./parse-common-flags");
30
- const resolve_recipient_1 = require("./resolve-recipient");
31
- const resolve_prompt_1 = require("./resolve-prompt");
32
- const version_1 = require("../utils/version");
33
- const inherited_trace_1 = require("../utils/inherited-trace");
34
- const cal_1 = require("./cal");
35
- const emit_trailer_1 = require("./emit-trailer");
36
- function nonEmptyString(value) {
37
- return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
38
- }
39
- /** Resolve the CLI actor from metadata and the caller's current environment. */
40
- function resolveCliTaskActor(metadata, agentId = process.env['CREWX_AGENT_ID']) {
41
- const scheduleId = nonEmptyString(metadata.schedule_id);
42
- if (scheduleId)
43
- return { actorKind: 'system', actorId: `cron:${scheduleId}` };
44
- const workflow = metadata.workflow;
45
- const workflowMetadata = workflow !== null && typeof workflow === 'object' && !Array.isArray(workflow)
46
- ? workflow
47
- : undefined;
48
- const workflowExecId = nonEmptyString(workflowMetadata?.exec_id);
49
- if (workflowExecId)
50
- return { actorKind: 'system', actorId: `workflow:${workflowExecId}` };
51
- const normalizedAgentId = nonEmptyString(agentId);
52
- if (normalizedAgentId)
53
- return { actorKind: 'agent', actorId: normalizedAgentId };
54
- if (process.stdin.isTTY === true || process.stderr.isTTY === true) {
55
- return { actorKind: 'human', actorId: (0, node_1.getLocalActorId)() };
56
- }
57
- return { actorKind: 'unknown', actorId: null };
58
- }
59
- /**
60
- * Resolve the actor at the receipt boundary while the caller's environment is
61
- * still available. The receipt and synchronous paths share one predicate so
62
- * cron runners cannot diverge after root context is stripped.
63
- */
64
- function resolveReceiptActor(metadata, agentId = process.env['CREWX_AGENT_ID']) {
65
- const actor = resolveCliTaskActor(metadata, agentId);
66
- return { kind: actor.actorKind, id: actor.actorId };
67
- }
68
- /**
69
- * Report a blocked direct admission (WI-CTO-20260919-004 R2 finding #1) to
70
- * the operator. Never called for an adopted (already pending) receipt — see
71
- * admitDurableTask()'s own doc comment.
72
- */
73
- function reportAdmissionBlocked(admission) {
74
- process.stderr.write(`${admission.message}\n`);
75
- }
76
- const DETACHED_RUNNER_POLL_INTERVAL_MS = 200;
77
- const DETACHED_RUNNER_ADOPTION_TIMEOUT_MS = 30_000;
78
- const DETACHED_VALUE_FLAGS = [
79
- '--to',
80
- '--thread',
81
- '--provider',
82
- '--model',
83
- '--metadata',
84
- '--config',
85
- '-c',
86
- '--output-format',
87
- '--effort',
88
- '--prompt-file',
89
- '-f',
90
- '--out',
91
- '-o',
92
- ];
93
- const INTERNAL_MIDDLE_FLAG = '--__crewx-detached-middle';
94
- const INTERNAL_TASK_FLAG = '--__crewx-detached-task';
95
- const INTERNAL_LOG_FLAG = '--__crewx-detached-log';
96
- const INTERNAL_TOKEN_FLAG = '--__crewx-detached-token';
97
- const INTERNAL_TOKEN_ENV = 'CREWX_DETACHED_HOP_TOKEN';
98
- function isDetachedValueFlag(arg) {
99
- return DETACHED_VALUE_FLAGS.some((name) => arg === name || arg.startsWith(`${name}=`));
100
- }
101
- function isDetachedPromptFileFlag(arg) {
102
- return arg === '--prompt-file'
103
- || arg === '-f'
104
- || arg.startsWith('--prompt-file=')
105
- || arg.startsWith('-f=');
106
- }
107
- /**
108
- * Remove receipt-selection flags, respecting the `--` literal-args sentinel.
109
- */
110
- function extractReceiptFlags(args) {
111
- const rest = [];
112
- let detach = false;
113
- let wait = false;
114
- let escapeMode = false;
115
- for (const arg of args) {
116
- if (!escapeMode && arg === '--') {
117
- escapeMode = true;
118
- rest.push(arg);
119
- continue;
120
- }
121
- if (!escapeMode && (arg === '--detach' || arg === '--async')) {
122
- detach = true;
123
- continue;
124
- }
125
- if (!escapeMode && (arg === '--wait' || arg.startsWith('--wait='))) {
126
- wait = true;
127
- continue;
128
- }
129
- rest.push(arg);
130
- }
131
- return { detach, wait, rest };
132
- }
133
- /** Resolve the receipt/synchronous mode contract shared by q and x. */
134
- function resolveReceiptMode(options) {
135
- if (options.wait)
136
- return { receipt: false, reason: 'wait' };
137
- // The runner guard is deliberately independent from trace propagation.
138
- if (options.detachedRunner)
139
- return { receipt: false, reason: 'detached-runner' };
140
- if (options.detach)
141
- return { receipt: true, reason: 'explicit-detach' };
142
- if (options.agentContext)
143
- return { receipt: true, reason: 'agent-context' };
144
- return { receipt: false, reason: 'default' };
145
- }
146
- function errorMessage(error) {
147
- return error instanceof Error ? error.message : String(error);
148
- }
149
- /** Wait for the pending receipt to be adopted, or for the runner to die. */
150
- function waitForDetachedRunner(taskRepo, taskId, child, options = {}) {
151
- return new Promise((resolve, reject) => {
152
- let settled = false;
153
- let pollTimer;
154
- let timeoutTimer;
155
- const cleanup = () => {
156
- if (pollTimer !== undefined)
157
- clearInterval(pollTimer);
158
- if (timeoutTimer !== undefined)
159
- clearTimeout(timeoutTimer);
160
- };
161
- const finish = (outcome) => {
162
- if (settled)
163
- return;
164
- settled = true;
165
- cleanup();
166
- resolve(outcome);
167
- };
168
- const inspect = (terminalReason, timedOut = false) => {
169
- try {
170
- const task = taskRepo.getTask(taskId);
171
- if (task && task.status !== 'pending') {
172
- finish({ kind: 'adopted' });
173
- return;
174
- }
175
- if (terminalReason !== undefined) {
176
- finish({ kind: 'exited', reason: terminalReason });
177
- return;
178
- }
179
- if (timedOut)
180
- finish({ kind: 'timeout' });
181
- }
182
- catch (error) {
183
- if (settled)
184
- return;
185
- settled = true;
186
- cleanup();
187
- reject(error);
188
- }
189
- };
190
- child.on('error', (error) => {
191
- inspect(`Runner failed before adopting task: ${errorMessage(error)}`);
192
- });
193
- child.on('exit', (code, signal) => {
194
- if (options.ignoreSuccessfulExit && code === 0 && signal === null)
195
- return;
196
- const detail = signal ? `signal ${signal}` : `exit ${code ?? 'unknown'}`;
197
- inspect(`Runner exited before adopting task (${detail})`);
198
- });
199
- pollTimer = setInterval(() => inspect(), DETACHED_RUNNER_POLL_INTERVAL_MS);
200
- timeoutTimer = setTimeout(() => inspect(undefined, true), DETACHED_RUNNER_ADOPTION_TIMEOUT_MS);
201
- inspect();
202
- });
203
- }
204
- /** Mark a failed receipt only while the parent still owns its pending row. */
205
- function failDetachedTask(taskRepo, taskId, reason) {
206
- try {
207
- const transitioned = taskRepo.failPendingTask(taskId, reason);
208
- if (transitioned) {
209
- process.stderr.write(`Detached task ${taskId} failed: ${reason}\n`);
210
- return false;
211
- }
212
- const task = taskRepo.getTask(taskId);
213
- if (task && task.status !== 'pending')
214
- return true;
215
- }
216
- catch (error) {
217
- reason += `; could not persist failure: ${errorMessage(error)}`;
218
- }
219
- process.stderr.write(`Detached task ${taskId} failed: ${reason}\n`);
220
- return false;
221
- }
222
- function announceDetachedTask(taskRepo, taskId, logPath, reason, verbose, outputFormat, warning) {
223
- let status = 'pending';
224
- try {
225
- const task = taskRepo.getTask(taskId);
226
- status = task?.status === 'pending' ? 'pending' : 'running';
227
- }
228
- catch {
229
- // The receipt was already persisted. Keep the output contract stable if a
230
- // follow-up status read is temporarily unavailable.
231
- }
232
- if (outputFormat === 'json') {
233
- process.stdout.write(`${JSON.stringify({ taskId, status, mode: 'receipt', logPath })}\n`);
234
- return;
235
- }
236
- if (verbose)
237
- process.stderr.write(`mode=receipt reason=${reason}\n`);
238
- process.stdout.write(`${taskId}\n`);
239
- process.stderr.write(`Task ${taskId} accepted. This ID is the handle (not a PID).\n`);
240
- if (warning && verbose)
241
- process.stderr.write(`${warning}\n`);
242
- const waitSeconds = (0, cal_1.suggestWaitSeconds)({ startedAt: null });
243
- (0, cal_1.writeCalFooter)({
244
- commandId: 'crewx.receipt',
245
- params: { taskId, wait: String(waitSeconds) },
246
- });
247
- }
248
- /**
249
- * Rebuild runner argv from the parsed request. The resolved prompt is passed
250
- * as one argv value after `--`, so stdin is never needed by the detached
251
- * child and prompt text beginning with a dash remains positional text.
252
- */
253
- function buildDetachedRunnerArgs(filteredArgs, agentRef, finalMessage) {
254
- const forwarded = [];
255
- for (let i = 0; i < filteredArgs.length; i += 1) {
256
- const arg = filteredArgs[i];
257
- if (arg === '--')
258
- break;
259
- if (arg === '--verbose' || arg === '--overdrive') {
260
- forwarded.push(arg);
261
- continue;
262
- }
263
- if (arg === '--var') {
264
- const value = filteredArgs[i + 1];
265
- if (value !== undefined) {
266
- forwarded.push(arg, value);
267
- i += 1;
268
- }
269
- continue;
270
- }
271
- if (arg.startsWith('--var=')) {
272
- forwarded.push(arg);
273
- continue;
274
- }
275
- if (arg === '--to') {
276
- // The child receives the resolved recipient as a positional @agent.
277
- i += 1;
278
- continue;
279
- }
280
- if (arg.startsWith('--to='))
281
- continue;
282
- if (!isDetachedValueFlag(arg))
283
- continue;
284
- if (isDetachedPromptFileFlag(arg)) {
285
- // The parent already resolved this file into finalMessage. Forwarding
286
- // the original path would make a later child read a mutable file again.
287
- if (!arg.includes('='))
288
- i += 1;
289
- continue;
290
- }
291
- forwarded.push(arg);
292
- if (!arg.includes('=')) {
293
- const value = filteredArgs[i + 1];
294
- if (value !== undefined) {
295
- forwarded.push(value);
296
- i += 1;
297
- }
298
- }
299
- }
300
- return [
301
- ...forwarded,
302
- ...(agentRef ? [agentRef] : []),
303
- '--',
304
- finalMessage,
305
- ];
306
- }
307
- function runnerCommand(command) {
308
- if (command === 'execute')
309
- return 'x';
310
- if (command === 'query')
311
- return 'q';
312
- return command;
313
- }
314
- /** Build the only environment contract that identifies a detached runner. */
315
- function buildRunnerEnvironment(taskId) {
316
- const env = { ...process.env };
317
- delete env[INTERNAL_TOKEN_ENV];
318
- if (process.versions.electron) {
319
- env.ELECTRON_RUN_AS_NODE = '1';
320
- }
321
- env.CREWX_DETACHED_RUNNER = '1';
322
- env.CREWX_ASSIGNED_TASK_ID = taskId;
323
- return env;
324
- }
325
- /**
326
- * Claim a pre-issued receipt without overwriting an already-owned row.
327
- * Assignment-only leakage is recoverable: discard the stale id and let the
328
- * normal SDK path create a fresh task. A real detached runner must fail loud
329
- * because its receipt would otherwise point at no execution row.
330
- */
331
- function adoptAssignedTask(taskRepo, taskId, detachedRunner) {
332
- if (taskRepo.adoptPendingTask(taskId)) {
333
- return { kind: 'adopted', taskId };
334
- }
335
- const reason = `stale assigned id ${taskId}: expected pending task with pid NULL`;
336
- if (detachedRunner) {
337
- process.stderr.write(`Detached runner failed to adopt task ${taskId}: ${reason}\n`);
338
- return { kind: 'failed' };
339
- }
340
- process.stderr.write(`${reason}\n`);
341
- return { kind: 'stale' };
342
- }
343
- /** Read the actor persisted on the receipt after the runner adopts it. */
344
- function readAssignedTaskActor(taskRepo, taskId) {
345
- const task = taskRepo.getTask(taskId);
346
- const actorKind = task?.actor_kind;
347
- if (actorKind === null || actorKind === undefined) {
348
- return { actorKind: 'unknown', actorId: null };
349
- }
350
- if (actorKind === 'human' || actorKind === 'agent' || actorKind === 'system' || actorKind === 'unknown') {
351
- return { actorKind, actorId: task?.actor_id ?? null };
352
- }
353
- return { actorKind: 'unknown', actorId: task?.actor_id ?? null };
354
- }
355
- function buildMiddleEnvironment(token) {
356
- const env = { ...process.env };
357
- if (process.versions.electron) {
358
- env.ELECTRON_RUN_AS_NODE = '1';
359
- }
360
- env[INTERNAL_TOKEN_ENV] = token;
361
- return env;
362
- }
363
- /**
364
- * Read the private Windows middle-hop contract. Invalid direct invocations
365
- * are left untouched and therefore fail through the normal unknown-option
366
- * path instead of becoming an undocumented public command.
367
- */
368
- function readDetachedMiddleHop(args) {
369
- let marker = false;
370
- let taskId;
371
- let logPath;
372
- let token;
373
- let escapeMode = false;
374
- const runnerArgs = [];
375
- for (const arg of args) {
376
- if (!escapeMode && arg === '--') {
377
- escapeMode = true;
378
- runnerArgs.push(arg);
379
- continue;
380
- }
381
- if (!escapeMode && arg === INTERNAL_MIDDLE_FLAG) {
382
- marker = true;
383
- continue;
384
- }
385
- if (!escapeMode && arg.startsWith(`${INTERNAL_TASK_FLAG}=`)) {
386
- taskId = arg.slice(`${INTERNAL_TASK_FLAG}=`.length);
387
- continue;
388
- }
389
- if (!escapeMode && arg.startsWith(`${INTERNAL_LOG_FLAG}=`)) {
390
- logPath = arg.slice(`${INTERNAL_LOG_FLAG}=`.length);
391
- continue;
392
- }
393
- if (!escapeMode && arg.startsWith(`${INTERNAL_TOKEN_FLAG}=`)) {
394
- token = arg.slice(`${INTERNAL_TOKEN_FLAG}=`.length);
395
- continue;
396
- }
397
- runnerArgs.push(arg);
398
- }
399
- if (!marker || !taskId || !logPath || !token || token !== process.env[INTERNAL_TOKEN_ENV]) {
400
- return undefined;
401
- }
402
- return { taskId, logPath, runnerArgs };
403
- }
404
- /**
405
- * Windows middle hop: transfer a transient log handle to the final runner,
406
- * close the middle process's copy, and exit immediately. The parent treats a
407
- * successful middle-hop exit as normal and makes adoption a database decision.
408
- */
409
- async function runDetachedMiddle(command, hop) {
410
- let logFd;
411
- try {
412
- const logDir = (0, path_1.dirname)(hop.logPath);
413
- if (!(0, fs_1.existsSync)(logDir))
414
- (0, fs_1.mkdirSync)(logDir, { recursive: true });
415
- logFd = (0, fs_1.openSync)(hop.logPath, 'a');
416
- const entry = process.argv[1];
417
- const child = (0, child_process_1.spawn)(process.execPath, [entry, runnerCommand(command), ...hop.runnerArgs], {
418
- detached: true,
419
- windowsHide: true,
420
- stdio: ['ignore', logFd, logFd],
421
- env: buildRunnerEnvironment(hop.taskId),
422
- });
423
- (0, fs_1.closeSync)(logFd);
424
- logFd = undefined;
425
- child.unref();
426
- }
427
- catch (error) {
428
- if (logFd !== undefined)
429
- (0, fs_1.closeSync)(logFd);
430
- process.stderr.write(`Runner failed before adopting task: ${errorMessage(error)}\n`);
431
- process.exit(1);
432
- return;
433
- }
434
- process.exit(0);
435
- }
436
- /**
437
- * Persist a receipt and launch its detached runner. Unix uses one hop. Windows
438
- * uses a short-lived middle hop so the final runner has no live parent link.
439
- */
440
- async function runDetached(command, filteredArgs, reason, invocationArgs = filteredArgs) {
441
- const parsedFlags = (0, parse_common_flags_1.parseCommonFlags)(filteredArgs);
442
- const { thread, metadata, promptFile, rest, to } = parsedFlags;
443
- const { agentRef: parsedAgentRef, message } = (0, parse_agent_message_1.parseAgentMessage)(rest);
444
- const finalMessage = await (0, resolve_prompt_1.resolvePrompt)(message, promptFile);
445
- let agentRef;
446
- try {
447
- ({ agentRef } = (0, resolve_recipient_1.resolveRecipient)({
448
- positionalRef: parsedAgentRef,
449
- toFlag: to,
450
- agentContext: Boolean(process.env['CREWX_AGENT_ID']),
451
- bodyForHint: finalMessage,
452
- command,
453
- invocationArgs,
454
- }));
455
- }
456
- catch (error) {
457
- const messageText = errorMessage(error);
458
- process.stderr.write(`Error: ${messageText}\n`);
459
- (0, emit_trailer_1.emitDelegationResult)({ command, trace: (0, inherited_trace_1.readInheritedTrace)(), isError: true });
460
- process.exit(2);
461
- return;
462
- }
463
- if (!finalMessage) {
464
- console.error('Usage: crewx q|x --to <agent> <message> [options]');
465
- console.error(' @agent is a positional shorthand for selecting a recipient.');
466
- console.error(' Task body can also be supplied via stdin pipe or --prompt-file.');
467
- process.exit(1);
468
- return;
469
- }
470
- let parsedMetadata;
471
- try {
472
- parsedMetadata = (0, parse_common_flags_1.parseMetadata)(metadata);
473
- }
474
- catch (error) {
475
- process.stderr.write(`Error: ${errorMessage(error)}\n`);
476
- process.exit(2);
477
- return;
478
- }
479
- // This process is a separate OS process from the embedded server and
480
- // would otherwise never see a desktop update's admission gate at all
481
- // (WI-CTO-20260919-004 R2 finding #1) — reserve-then-check before the
482
- // pending receipt is written below, not after; see admitDurableTask()'s
483
- // own doc comment for why that ordering closes the TOCTOU.
484
- const admission = (0, sdk_1.admitDurableTask)(`direct-cli:${command}`);
485
- if (!admission.ok) {
486
- reportAdmissionBlocked(admission);
487
- process.exit(1);
488
- return;
489
- }
490
- const taskId = admission.taskId;
491
- const startedAt = new Date().toISOString();
492
- const workspacePath = process.cwd();
493
- const hasWorkspaceConfig = (0, fs_1.existsSync)((0, path_1.join)(workspacePath, 'crewx.yaml'))
494
- || (0, fs_1.existsSync)((0, path_1.join)(workspacePath, 'crewx.yml'));
495
- const inheritedTrace = (0, inherited_trace_1.readInheritedTrace)();
496
- const taskTrace = inheritedTrace?.rootTraceId || taskId;
497
- const taskRepo = new repository_1.TaskRepository();
498
- const actor = resolveReceiptActor(parsedMetadata);
499
- try {
500
- taskRepo.startTask({
501
- id: taskId,
502
- agentId: agentRef.replace(/^@/, ''),
503
- actorKind: actor.kind,
504
- actorId: actor.id,
505
- prompt: finalMessage,
506
- mode: command === 'q' || command === 'query' ? 'query' : 'execute',
507
- status: 'pending',
508
- startedAt,
509
- pid: null,
510
- parentTaskId: inheritedTrace?.parentTaskId ?? null,
511
- callerAgentId: inheritedTrace?.callerAgentId ?? null,
512
- traceId: taskTrace,
513
- command: process.argv.join(' '),
514
- metadata: Object.keys(parsedMetadata).length > 0 ? JSON.stringify(parsedMetadata) : null,
515
- workspaceId: hasWorkspaceConfig ? (0, sdk_1.hashWorkspaceId)((0, sdk_1.resolveWorkspaceOwnerPath)(workspacePath)) : null,
516
- cwd: (0, sdk_1.normalizeWorkspacePath)(workspacePath),
517
- projectId: null,
518
- platform: 'cli',
519
- crewxVersion: version_1.CLI_VERSION,
520
- threadId: thread ?? null,
521
- });
522
- }
523
- catch (error) {
524
- // The receipt's own pending row failed to persist — nothing durable was
525
- // admitted, so the reservation's job is done regardless.
526
- admission.release();
527
- process.stderr.write(`Failed to create detached task receipt: ${errorMessage(error)}\n`);
528
- process.exit(1);
529
- return;
530
- }
531
- // The pending row above is now the durable, DB-visible signal of this
532
- // work's admission — the reservation only ever existed to bridge the gap
533
- // before it landed.
534
- admission.release();
535
- const runnerArgs = buildDetachedRunnerArgs(filteredArgs, agentRef, finalMessage);
536
- const commandForRunner = runnerCommand(command);
537
- const logDir = (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'logs');
538
- const logPath = (0, path_1.join)(logDir, `${taskId}.log`);
539
- let logFd;
540
- let child;
541
- let middleHop = false;
542
- try {
543
- if (!(0, fs_1.existsSync)(logDir))
544
- (0, fs_1.mkdirSync)(logDir, { recursive: true });
545
- if (process.platform === 'win32') {
546
- middleHop = true;
547
- const token = (0, sdk_1.generateId)('req');
548
- child = (0, child_process_1.spawn)(process.execPath, [
549
- process.argv[1],
550
- commandForRunner,
551
- INTERNAL_MIDDLE_FLAG,
552
- `${INTERNAL_TASK_FLAG}=${taskId}`,
553
- `${INTERNAL_LOG_FLAG}=${logPath}`,
554
- `${INTERNAL_TOKEN_FLAG}=${token}`,
555
- ...runnerArgs,
556
- ], {
557
- detached: true,
558
- windowsHide: true,
559
- stdio: 'ignore',
560
- env: buildMiddleEnvironment(token),
561
- });
562
- }
563
- else {
564
- logFd = (0, fs_1.openSync)(logPath, 'a');
565
- child = (0, child_process_1.spawn)(process.execPath, [process.argv[1], commandForRunner, ...runnerArgs], {
566
- detached: true,
567
- windowsHide: true,
568
- stdio: ['ignore', logFd, logFd],
569
- env: buildRunnerEnvironment(taskId),
570
- });
571
- (0, fs_1.closeSync)(logFd);
572
- logFd = undefined;
573
- }
574
- child.unref();
575
- }
576
- catch (error) {
577
- if (logFd !== undefined)
578
- (0, fs_1.closeSync)(logFd);
579
- const reasonMessage = `Runner failed before adopting task: ${errorMessage(error)}`;
580
- if (!failDetachedTask(taskRepo, taskId, reasonMessage))
581
- process.exit(1);
582
- else {
583
- announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat);
584
- process.exit(0);
585
- }
586
- return;
587
- }
588
- let outcome;
589
- try {
590
- outcome = await waitForDetachedRunner(taskRepo, taskId, child, {
591
- ignoreSuccessfulExit: middleHop,
592
- });
593
- }
594
- catch (error) {
595
- const reasonMessage = `Runner adoption check failed: ${errorMessage(error)}`;
596
- if (!failDetachedTask(taskRepo, taskId, reasonMessage))
597
- process.exit(1);
598
- else {
599
- announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat);
600
- process.exit(0);
601
- }
602
- return;
603
- }
604
- if (outcome.kind === 'adopted') {
605
- announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat);
606
- process.exit(0);
607
- return;
608
- }
609
- if (outcome.kind === 'timeout') {
610
- announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat, `Warning: runner alive, row not yet adopted after ${DETACHED_RUNNER_ADOPTION_TIMEOUT_MS / 1000}s.`);
611
- process.exit(0);
612
- return;
613
- }
614
- if (!failDetachedTask(taskRepo, taskId, outcome.reason))
615
- process.exit(1);
616
- else {
617
- announceDetachedTask(taskRepo, taskId, logPath, reason, parsedFlags.verbose, parsedFlags.outputFormat);
618
- process.exit(0);
619
- }
620
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.resolveCliTaskActor=V;exports.resolveReceiptActor=X;exports.reportAdmissionBlocked=B;exports.extractReceiptFlags=he;exports.resolveReceiptMode=_e;exports.buildDetachedRunnerArgs=Y;exports.adoptAssignedTask=we;exports.readAssignedTaskActor=me;exports.readDetachedMiddleHop=Ee;exports.runDetachedMiddle=xe;exports.runDetached=ve;const F=require("child_process");const l=require("fs");const A=require("path");const ne=require("os");const y=require("@crewx/sdk");const se=require("@crewx/sdk/node");const oe=require("@crewx/sdk/repository");const ie=require("./parse-agent-message");const j=require("./parse-common-flags");const ce=require("./resolve-recipient");const ae=require("./resolve-prompt");const de=require("../utils/version");const G=require("../utils/inherited-trace");const U=require("./cal");const ue=require("./emit-trailer");function q(e){return typeof e==="string"&&e.trim().length>0?e.trim():void 0}function V(e,t=process.env["CREWX_AGENT_ID"]){const r=q(e.schedule_id);if(r)return{actorKind:"system",actorId:`cron:${r}`};const n=e.workflow;const s=n!==null&&typeof n==="object"&&!Array.isArray(n)?n:void 0;const o=q(s?.exec_id);if(o)return{actorKind:"system",actorId:`workflow:${o}`};const i=q(t);if(i)return{actorKind:"agent",actorId:i};if(process.stdin.isTTY===true||process.stderr.isTTY===true){return{actorKind:"human",actorId:(0,se.getLocalActorId)()}}return{actorKind:"unknown",actorId:null}}function X(e,t=process.env["CREWX_AGENT_ID"]){const r=V(e,t);return{kind:r.actorKind,id:r.actorId}}function B(e){process.stderr.write(`${e.message}
2
+ `)}const le=200;const z=3e4;const pe=["--to","--thread","--provider","--model","--metadata","--config","-c","--output-format","--effort","--prompt-file","-f","--out","-o"];const J="--__crewx-detached-middle";const C="--__crewx-detached-task";const L="--__crewx-detached-log";const M="--__crewx-detached-token";const b="CREWX_DETACHED_HOP_TOKEN";function fe(e){return pe.some(t=>e===t||e.startsWith(`${t}=`))}function ge(e){return e==="--prompt-file"||e==="-f"||e.startsWith("--prompt-file=")||e.startsWith("-f=")}function he(e){const t=[];let r=false;let n=false;let s=false;for(const o of e){if(!s&&o==="--"){s=true;t.push(o);continue}if(!s&&(o==="--detach"||o==="--async")){r=true;continue}if(!s&&(o==="--wait"||o.startsWith("--wait="))){n=true;continue}t.push(o)}return{detach:r,wait:n,rest:t}}function _e(e){if(e.wait)return{receipt:false,reason:"wait"};if(e.detachedRunner)return{receipt:false,reason:"detached-runner"};if(e.detach)return{receipt:true,reason:"explicit-detach"};if(e.agentContext)return{receipt:true,reason:"agent-context"};return{receipt:false,reason:"default"}}function h(e){return e instanceof Error?e.message:String(e)}function ke(e,t,r,n={}){return new Promise((s,o)=>{let i=false;let c;let _;const T=()=>{if(c!==void 0)clearInterval(c);if(_!==void 0)clearTimeout(_)};const E=a=>{if(i)return;i=true;T();s(a)};const k=(a,g=false)=>{try{const p=e.getTask(t);if(p&&p.status!=="pending"){E({kind:"adopted"});return}if(a!==void 0){E({kind:"exited",reason:a});return}if(g)E({kind:"timeout"})}catch(p){if(i)return;i=true;T();o(p)}};r.on("error",a=>{k(`Runner failed before adopting task: ${h(a)}`)});r.on("exit",(a,g)=>{if(n.ignoreSuccessfulExit&&a===0&&g===null)return;const p=g?`signal ${g}`:`exit ${a??"unknown"}`;k(`Runner exited before adopting task (${p})`)});c=setInterval(()=>k(),le);_=setTimeout(()=>k(void 0,true),z);k()})}function W(e,t,r){try{const n=e.failPendingTask(t,r);if(n){process.stderr.write(`Detached task ${t} failed: ${r}
3
+ `);return false}const s=e.getTask(t);if(s&&s.status!=="pending")return true}catch(n){r+=`; could not persist failure: ${h(n)}`}process.stderr.write(`Detached task ${t} failed: ${r}
4
+ `);return false}function R(e,t,r,n,s,o,i){let c="pending";try{const T=e.getTask(t);c=T?.status==="pending"?"pending":"running"}catch{}if(o==="json"){process.stdout.write(`${JSON.stringify({taskId:t,status:c,mode:"receipt",logPath:r})}
5
+ `);return}if(s)process.stderr.write(`mode=receipt reason=${n}
6
+ `);process.stdout.write(`${t}
7
+ `);process.stderr.write(`Task ${t} accepted. This ID is the handle (not a PID).
8
+ `);if(i&&s)process.stderr.write(`${i}
9
+ `);const _=(0,U.suggestWaitSeconds)({startedAt:null});(0,U.writeCalFooter)({commandId:"crewx.receipt",params:{taskId:t,wait:String(_)}})}function Y(e,t,r){const n=[];for(let s=0;s<e.length;s+=1){const o=e[s];if(o==="--")break;if(o==="--verbose"||o==="--overdrive"){n.push(o);continue}if(o==="--var"){const i=e[s+1];if(i!==void 0){n.push(o,i);s+=1}continue}if(o.startsWith("--var=")){n.push(o);continue}if(o==="--to"){s+=1;continue}if(o.startsWith("--to="))continue;if(!fe(o))continue;if(ge(o)){if(!o.includes("="))s+=1;continue}n.push(o);if(!o.includes("=")){const i=e[s+1];if(i!==void 0){n.push(i);s+=1}}}return[...n,...t?[t]:[],"--",r]}function Q(e){if(e==="execute")return"x";if(e==="query")return"q";return e}function Z(e){const t={...process.env};delete t[b];if(process.versions.electron){t.ELECTRON_RUN_AS_NODE="1"}t.CREWX_DETACHED_RUNNER="1";t.CREWX_ASSIGNED_TASK_ID=e;return t}function we(e,t,r){if(e.adoptPendingTask(t)){return{kind:"adopted",taskId:t}}const n=`stale assigned id ${t}: expected pending task with pid NULL`;if(r){process.stderr.write(`Detached runner failed to adopt task ${t}: ${n}
10
+ `);return{kind:"failed"}}process.stderr.write(`${n}
11
+ `);return{kind:"stale"}}function me(e,t){const r=e.getTask(t);const n=r?.actor_kind;if(n===null||n===void 0){return{actorKind:"unknown",actorId:null}}if(n==="human"||n==="agent"||n==="system"||n==="unknown"){return{actorKind:n,actorId:r?.actor_id??null}}return{actorKind:"unknown",actorId:r?.actor_id??null}}function Te(e){const t={...process.env};if(process.versions.electron){t.ELECTRON_RUN_AS_NODE="1"}t[b]=e;return t}function Ee(e){let t=false;let r;let n;let s;let o=false;const i=[];for(const c of e){if(!o&&c==="--"){o=true;i.push(c);continue}if(!o&&c===J){t=true;continue}if(!o&&c.startsWith(`${C}=`)){r=c.slice(`${C}=`.length);continue}if(!o&&c.startsWith(`${L}=`)){n=c.slice(`${L}=`.length);continue}if(!o&&c.startsWith(`${M}=`)){s=c.slice(`${M}=`.length);continue}i.push(c)}if(!t||!r||!n||!s||s!==process.env[b]){return void 0}return{taskId:r,logPath:n,runnerArgs:i}}async function xe(e,t){let r;try{const n=(0,A.dirname)(t.logPath);if(!(0,l.existsSync)(n))(0,l.mkdirSync)(n,{recursive:true});r=(0,l.openSync)(t.logPath,"a");const s=process.argv[1];const o=(0,F.spawn)(process.execPath,[s,Q(e),...t.runnerArgs],{detached:true,windowsHide:true,stdio:["ignore",r,r],env:Z(t.taskId)});(0,l.closeSync)(r);r=void 0;o.unref()}catch(n){if(r!==void 0)(0,l.closeSync)(r);process.stderr.write(`Runner failed before adopting task: ${h(n)}
12
+ `);process.exit(1);return}process.exit(0)}async function ve(e,t,r,n=t){const s=(0,j.parseCommonFlags)(t);const{thread:o,metadata:i,promptFile:c,rest:_,to:T}=s;const{agentRef:E,message:k}=(0,ie.parseAgentMessage)(_);const a=await(0,ae.resolvePrompt)(k,c);let g;try{({agentRef:g}=(0,ce.resolveRecipient)({positionalRef:E,toFlag:T,agentContext:Boolean(process.env["CREWX_AGENT_ID"]),bodyForHint:a,command:e,invocationArgs:n}))}catch(u){const v=h(u);process.stderr.write(`Error: ${v}
13
+ `);(0,ue.emitDelegationResult)({command:e,trace:(0,G.readInheritedTrace)(),isError:true});process.exit(2);return}if(!a){console.error("Usage: crewx q|x --to <agent> <message> [options]");console.error(" @agent is a positional shorthand for selecting a recipient.");console.error(" Task body can also be supplied via stdin pipe or --prompt-file.");process.exit(1);return}let p;try{p=(0,j.parseMetadata)(i)}catch(u){process.stderr.write(`Error: ${h(u)}
14
+ `);process.exit(2);return}const x=(0,y.admitDurableTask)(`direct-cli:${e}`);if(!x.ok){B(x);process.exit(1);return}const d=x.taskId;const ee=new Date().toISOString();const I=process.cwd();const te=(0,l.existsSync)((0,A.join)(I,"crewx.yaml"))||(0,l.existsSync)((0,A.join)(I,"crewx.yml"));const N=(0,G.readInheritedTrace)();const re=N?.rootTraceId||d;const f=new oe.TaskRepository;const O=X(p);try{f.startTask({id:d,agentId:g.replace(/^@/,""),actorKind:O.kind,actorId:O.id,prompt:a,mode:e==="q"||e==="query"?"query":"execute",status:"pending",startedAt:ee,pid:null,parentTaskId:N?.parentTaskId??null,callerAgentId:N?.callerAgentId??null,traceId:re,command:process.argv.join(" "),metadata:Object.keys(p).length>0?JSON.stringify(p):null,workspaceId:te?(0,y.hashWorkspaceId)((0,y.resolveWorkspaceOwnerPath)(I)):null,cwd:(0,y.normalizeWorkspacePath)(I),projectId:null,platform:"cli",crewxVersion:de.CLI_VERSION,threadId:o??null})}catch(u){x.release();process.stderr.write(`Failed to create detached task receipt: ${h(u)}
15
+ `);process.exit(1);return}x.release();const P=Y(t,g,a);const K=Q(e);const S=(0,A.join)((0,ne.homedir)(),".crewx","logs");const w=(0,A.join)(S,`${d}.log`);let m;let D;let H=false;try{if(!(0,l.existsSync)(S))(0,l.mkdirSync)(S,{recursive:true});if(process.platform==="win32"){H=true;const u=(0,y.generateId)("req");D=(0,F.spawn)(process.execPath,[process.argv[1],K,J,`${C}=${d}`,`${L}=${w}`,`${M}=${u}`,...P],{detached:true,windowsHide:true,stdio:"ignore",env:Te(u)})}else{m=(0,l.openSync)(w,"a");D=(0,F.spawn)(process.execPath,[process.argv[1],K,...P],{detached:true,windowsHide:true,stdio:["ignore",m,m],env:Z(d)});(0,l.closeSync)(m);m=void 0}D.unref()}catch(u){if(m!==void 0)(0,l.closeSync)(m);const v=`Runner failed before adopting task: ${h(u)}`;if(!W(f,d,v))process.exit(1);else{R(f,d,w,r,s.verbose,s.outputFormat);process.exit(0)}return}let $;try{$=await ke(f,d,D,{ignoreSuccessfulExit:H})}catch(u){const v=`Runner adoption check failed: ${h(u)}`;if(!W(f,d,v))process.exit(1);else{R(f,d,w,r,s.verbose,s.outputFormat);process.exit(0)}return}if($.kind==="adopted"){R(f,d,w,r,s.verbose,s.outputFormat);process.exit(0);return}if($.kind==="timeout"){R(f,d,w,r,s.verbose,s.outputFormat,`Warning: runner alive, row not yet adopted after ${z/1e3}s.`);process.exit(0);return}if(!W(f,d,$.reason))process.exit(1);else{R(f,d,w,r,s.verbose,s.outputFormat);process.exit(0)}}