@crewx/cli 0.9.0-rc.30 → 0.9.0-rc.32

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/builtin.js CHANGED
@@ -55,6 +55,7 @@ const BUILTIN_MAP = {
55
55
  dreaming: () => Promise.resolve().then(() => __importStar(require('@crewx/dreaming/cli'))),
56
56
  wi: () => Promise.resolve().then(() => __importStar(require('@crewx/wi/cli'))),
57
57
  chromex: () => Promise.resolve().then(() => __importStar(require('@crewx/chromex/cli'))),
58
+ notify: () => Promise.resolve().then(() => __importStar(require('@crewx/notify/cli'))),
58
59
  };
59
60
  exports.BUILTIN_COMMANDS = new Set(Object.keys(BUILTIN_MAP));
60
61
  // Load skill-tracer for observability (graceful degradation if unavailable)
@@ -12,6 +12,9 @@
12
12
  * --output-format <fmt> Output format (json|text|stream-json)
13
13
  * --effort <level> Model effort (high|medium|low)
14
14
  * -f/--prompt-file <path> Read task body from file (bypasses cmd.exe argv truncation)
15
+ * --detach Re-spawn as a detached runner; print task-id and exit 0 immediately.
16
+ * Ignored when CREWX_TRACE_ID is already set (recursive-spawn guard) or
17
+ * on win32 (unsupported — exits with an error).
15
18
  *
16
19
  * Stdin support:
17
20
  * Pipe or redirect content into crewx x to supply the task body via stdin.
@@ -13,6 +13,9 @@
13
13
  * --output-format <fmt> Output format (json|text|stream-json)
14
14
  * --effort <level> Model effort (high|medium|low)
15
15
  * -f/--prompt-file <path> Read task body from file (bypasses cmd.exe argv truncation)
16
+ * --detach Re-spawn as a detached runner; print task-id and exit 0 immediately.
17
+ * Ignored when CREWX_TRACE_ID is already set (recursive-spawn guard) or
18
+ * on win32 (unsupported — exits with an error).
16
19
  *
17
20
  * Stdin support:
18
21
  * Pipe or redirect content into crewx x to supply the task body via stdin.
@@ -21,6 +24,10 @@
21
24
  */
22
25
  Object.defineProperty(exports, "__esModule", { value: true });
23
26
  exports.handleExecute = handleExecute;
27
+ const child_process_1 = require("child_process");
28
+ const fs_1 = require("fs");
29
+ const path_1 = require("path");
30
+ const os_1 = require("os");
24
31
  const sdk_1 = require("@crewx/sdk");
25
32
  const parse_agent_message_1 = require("./parse-agent-message");
26
33
  const parse_common_flags_1 = require("./parse-common-flags");
@@ -28,6 +35,61 @@ const resolve_prompt_1 = require("./resolve-prompt");
28
35
  const crewx_cli_1 = require("../bootstrap/crewx-cli");
29
36
  const inherited_trace_1 = require("../utils/inherited-trace");
30
37
  const write_output_1 = require("./write-output");
38
+ /**
39
+ * Split `--detach` out of argv, respecting the `--` literal-args sentinel
40
+ * (a `--detach` appearing after `--` is message text, not the flag).
41
+ */
42
+ function extractDetachFlag(args) {
43
+ const rest = [];
44
+ let detach = false;
45
+ let escapeMode = false;
46
+ for (const arg of args) {
47
+ if (!escapeMode && arg === '--') {
48
+ escapeMode = true;
49
+ rest.push(arg);
50
+ continue;
51
+ }
52
+ if (!escapeMode && arg === '--detach') {
53
+ detach = true;
54
+ continue;
55
+ }
56
+ rest.push(arg);
57
+ }
58
+ return { detach, rest };
59
+ }
60
+ /**
61
+ * Double-detach: re-spawn this same CLI entry (minus --detach) as a detached
62
+ * process so it survives the parent's exit. The task-id is pre-generated here
63
+ * and injected as CREWX_TRACE_ID so the runner's own task row is created under
64
+ * this id (see handleExecute's `selfTaskId` derivation below) — it doubles as
65
+ * both this task's row id and the root of any further delegation it spawns.
66
+ *
67
+ * stdout contract (script-parseable): task-id only, on the first line.
68
+ * Everything else goes to stderr.
69
+ */
70
+ function runDetached(filteredArgs) {
71
+ const taskId = (0, sdk_1.generateId)('tsk');
72
+ const logDir = (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'logs');
73
+ if (!(0, fs_1.existsSync)(logDir))
74
+ (0, fs_1.mkdirSync)(logDir, { recursive: true });
75
+ const logPath = (0, path_1.join)(logDir, `${taskId}.log`);
76
+ const logFd = (0, fs_1.openSync)(logPath, 'a');
77
+ const entry = process.argv[1];
78
+ const child = (0, child_process_1.spawn)(process.execPath, [entry, 'x', ...filteredArgs], {
79
+ detached: true,
80
+ stdio: ['ignore', logFd, logFd],
81
+ env: { ...process.env, CREWX_TRACE_ID: taskId },
82
+ });
83
+ (0, fs_1.closeSync)(logFd);
84
+ child.on('error', (err) => {
85
+ process.stderr.write(`Failed to spawn detached runner: ${err.message}\n`);
86
+ });
87
+ child.unref();
88
+ console.log(taskId);
89
+ process.stderr.write(`Detached task ${taskId} started (log: ${logPath}).\n`);
90
+ process.stderr.write(`Use \`crewx result ${taskId} --wait=N\` to wait for completion.\n`);
91
+ process.exit(0);
92
+ }
31
93
  /**
32
94
  * Handle `crewx execute <agentRef> <message>` command.
33
95
  *
@@ -35,7 +97,26 @@ const write_output_1 = require("./write-output");
35
97
  * --verbose: debug info written to stderr, response to stdout.
36
98
  */
37
99
  async function handleExecute(args) {
38
- const { thread, provider, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, out, rest } = (0, parse_common_flags_1.parseCommonFlags)(args);
100
+ const { detach, rest: detachFilteredArgs } = extractDetachFlag(args);
101
+ if (detach) {
102
+ // Recursive-spawn guard: a CREWX_TRACE_ID already present means this
103
+ // process is itself running inside a traced context (either the
104
+ // respawned runner, or a delegated sub-call) — never chain a second
105
+ // detach off of it. Silently fall through to normal (synchronous) execution.
106
+ if (process.env['CREWX_TRACE_ID']) {
107
+ process.stderr.write('Note: --detach ignored (already running inside a traced context; CREWX_TRACE_ID is set).\n');
108
+ }
109
+ else if (process.platform === 'win32') {
110
+ console.error('Error: --detach is not supported on win32.');
111
+ process.exit(1);
112
+ return;
113
+ }
114
+ else {
115
+ runDetached(detachFilteredArgs);
116
+ return;
117
+ }
118
+ }
119
+ const { thread, provider, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, out, rest } = (0, parse_common_flags_1.parseCommonFlags)(detachFilteredArgs);
39
120
  const { agentRef: parsedAgentRef, message } = (0, parse_agent_message_1.parseAgentMessage)(rest);
40
121
  // No @mention → default to @crewx agent (matches cli-bak behaviour)
41
122
  const agentRef = parsedAgentRef || '@crewx';
@@ -99,6 +180,15 @@ async function handleExecute(args) {
99
180
  process.stderr.write(`Error: ${msg}\n`);
100
181
  process.exit(2);
101
182
  }
183
+ const inheritedTrace = (0, inherited_trace_1.readInheritedTrace)();
184
+ // A trace with a rootTraceId but no parentTaskId means the id was pre-assigned
185
+ // to *this* task itself (the detach runner's parent injects only CREWX_TRACE_ID,
186
+ // never CREWX_PARENT_TASK_ID — see execute.ts's runDetached), not inherited from
187
+ // an ancestor task in a delegation chain (which always carries both). It doubles
188
+ // as this task's own row id so `crewx result <task-id>` can find it.
189
+ const selfTaskId = inheritedTrace && !inheritedTrace.parentTaskId
190
+ ? (inheritedTrace.rootTraceId || undefined)
191
+ : undefined;
102
192
  let exitCode = 0;
103
193
  try {
104
194
  const result = await crewx.execute(agentRef, finalMessage, {
@@ -106,9 +196,10 @@ async function handleExecute(args) {
106
196
  effort: effort || undefined,
107
197
  overdrive: overdrive || undefined,
108
198
  threadId: thread,
199
+ taskId: selfTaskId,
109
200
  metadata: Object.keys(parsedMetadata).length > 0 ? parsedMetadata : undefined,
110
201
  vars: Object.keys(vars).length > 0 ? vars : undefined,
111
- trace: (0, inherited_trace_1.readInheritedTrace)(),
202
+ trace: inheritedTrace,
112
203
  });
113
204
  if (!result.ok) {
114
205
  const errMsg = result.error?.message ?? 'Execute failed';
@@ -23,7 +23,7 @@ exports.KNOWN_COMMANDS = new Set([
23
23
  /** Built-in tool commands routed via handleBuiltin(). */
24
24
  exports.BUILTIN_COMMAND_NAMES = new Set([
25
25
  'memory', 'search', 'doc', 'wbs', 'cron', 'workflow', 'skill', 'dreaming',
26
- 'wi', 'chromex',
26
+ 'wi', 'chromex', 'notify',
27
27
  ]);
28
28
  /** Commands not yet migrated from cli-bak — show a migration message. */
29
29
  exports.NOT_YET_MIGRATED = new Set([
@@ -3,8 +3,12 @@
3
3
  * Retrieves the result of a completed task by its ID.
4
4
  *
5
5
  * Usage:
6
- * crewx result <task-id> Print raw result
7
- * crewx result <task-id> --json Print full task record as JSON
8
- * crewx result List recent tasks (latest 10)
6
+ * crewx result <task-id> Print raw result
7
+ * crewx result <task-id> --json Print full task record as JSON
8
+ * crewx result <task-id> --wait=N Poll (1s interval) up to N seconds for
9
+ * the task to leave 'running'. Exit 124 on
10
+ * timeout. --wait=0 behaves like no --wait
11
+ * (single immediate check).
12
+ * crewx result List recent tasks (latest 10)
9
13
  */
10
14
  export declare function handleResult(args: string[]): Promise<void>;
@@ -4,13 +4,18 @@
4
4
  * Retrieves the result of a completed task by its ID.
5
5
  *
6
6
  * Usage:
7
- * crewx result <task-id> Print raw result
8
- * crewx result <task-id> --json Print full task record as JSON
9
- * crewx result List recent tasks (latest 10)
7
+ * crewx result <task-id> Print raw result
8
+ * crewx result <task-id> --json Print full task record as JSON
9
+ * crewx result <task-id> --wait=N Poll (1s interval) up to N seconds for
10
+ * the task to leave 'running'. Exit 124 on
11
+ * timeout. --wait=0 behaves like no --wait
12
+ * (single immediate check).
13
+ * crewx result List recent tasks (latest 10)
10
14
  */
11
15
  Object.defineProperty(exports, "__esModule", { value: true });
12
16
  exports.handleResult = handleResult;
13
17
  const repository_1 = require("@crewx/sdk/repository");
18
+ const POLL_INTERVAL_MS = 1000;
14
19
  function statusIcon(status) {
15
20
  switch (status) {
16
21
  case 'running': return '⏳';
@@ -19,8 +24,20 @@ function statusIcon(status) {
19
24
  default: return '❓';
20
25
  }
21
26
  }
27
+ function sleep(ms) {
28
+ return new Promise((resolve) => setTimeout(resolve, ms));
29
+ }
30
+ /** Parses `--wait=N` (seconds). Returns undefined when the flag is absent. */
31
+ function parseWaitSeconds(args) {
32
+ const arg = args.find(a => a.startsWith('--wait='));
33
+ if (arg === undefined)
34
+ return undefined;
35
+ const n = Number(arg.slice('--wait='.length));
36
+ return Number.isFinite(n) && n >= 0 ? n : 0;
37
+ }
22
38
  async function handleResult(args) {
23
39
  const jsonMode = args.includes('--json');
40
+ const waitSeconds = parseWaitSeconds(args);
24
41
  const taskId = args.find(a => !a.startsWith('--'));
25
42
  const repo = new repository_1.TaskRepository();
26
43
  if (!taskId) {
@@ -45,12 +62,29 @@ async function handleResult(args) {
45
62
  console.log('Tip: Run `crewx result <task-id>` to see full output.');
46
63
  return;
47
64
  }
48
- const task = repo.getTask(taskId);
65
+ let task = repo.getTask(taskId);
49
66
  if (!task) {
50
67
  console.error(`Error: Task not found: ${taskId}`);
51
68
  process.exit(1);
52
69
  return;
53
70
  }
71
+ if (waitSeconds !== undefined && waitSeconds > 0 && task.status === 'running') {
72
+ const deadline = Date.now() + waitSeconds * 1000;
73
+ while (task && task.status === 'running') {
74
+ if (Date.now() >= deadline) {
75
+ console.error(`Task ${taskId} did not complete within --wait=${waitSeconds}s (status: running).`);
76
+ process.exit(124);
77
+ return;
78
+ }
79
+ await sleep(Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
80
+ task = repo.getTask(taskId);
81
+ if (!task) {
82
+ console.error(`Error: Task not found: ${taskId}`);
83
+ process.exit(1);
84
+ return;
85
+ }
86
+ }
87
+ }
54
88
  if (jsonMode) {
55
89
  console.log(JSON.stringify(task, null, 2));
56
90
  return;
package/dist/main.js CHANGED
@@ -269,6 +269,12 @@ Query / Execute:
269
269
  -- End of flags; remaining tokens treated as message text
270
270
  e.g. crewx q "@agent label" -- --flag-in-message
271
271
 
272
+ x/execute only:
273
+ --detach Re-spawn as a detached background runner; print task-id
274
+ and exit 0 immediately. Ignored if CREWX_TRACE_ID is
275
+ already set (recursive-spawn guard). Unsupported on win32.
276
+ e.g. crewx x "@agent label" --detach
277
+
272
278
  Agent Management:
273
279
  agent ls [options] List configured agents
274
280
  --role <value> Filter by role (comma-separated for OR match)
@@ -281,6 +287,8 @@ Task Management:
281
287
  kill <task-id> Kill a running task
282
288
  kill --all Kill all running tasks
283
289
  result [task-id] Get task result (or list recent tasks)
290
+ --wait=N Poll (1s interval) up to N seconds for the task to
291
+ finish. Exit 124 on timeout. --wait=0 = single check.
284
292
  restart <task-id> Restart a failed task as a new task
285
293
 
286
294
  Logs & Diagnostics:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewx/cli",
3
- "version": "0.9.0-rc.30",
3
+ "version": "0.9.0-rc.32",
4
4
  "license": "UNLICENSED",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"
@@ -24,16 +24,17 @@
24
24
  "@crewx/adapter-slack": "0.1.4",
25
25
  "better-sqlite3": "*",
26
26
  "isomorphic-git": "1.37.1",
27
- "@crewx/search": "0.1.10",
28
- "@crewx/sdk": "0.9.0-rc.30",
27
+ "@crewx/sdk": "0.9.0-rc.32",
29
28
  "@crewx/memory": "0.1.23",
30
- "@crewx/cron": "0.1.10",
31
- "@crewx/wbs": "0.1.10",
32
29
  "@crewx/doc": "0.1.9",
33
- "@crewx/workflow": "0.3.22-rc.76",
34
- "@crewx/wi": "0.1.10-rc.50",
35
- "@crewx/chromex": "0.1.0",
30
+ "@crewx/wbs": "0.1.10",
31
+ "@crewx/search": "0.1.10",
32
+ "@crewx/cron": "0.1.10",
36
33
  "@crewx/skill": "0.1.20",
34
+ "@crewx/workflow": "0.3.22-rc.78",
35
+ "@crewx/wi": "0.1.10-rc.52",
36
+ "@crewx/notify": "0.1.0-rc.2",
37
+ "@crewx/chromex": "0.1.0",
37
38
  "@crewx/shared": "0.0.6"
38
39
  },
39
40
  "devDependencies": {