@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.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Stop a running task through the SDK's cooperative-stop pipeline.
3
+ *
4
+ * `kill` is kept as a compatibility alias; both entry points use this same
5
+ * handler so server, CLI, and future adapters share one stop contract.
6
+ */
7
+ /** Handle `crewx stop` and the compatibility `crewx kill` alias. */
8
+ export declare function handleStop(args: string[], invokedAs?: 'stop' | 'kill'): Promise<void>;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ /**
3
+ * Stop a running task through the SDK's cooperative-stop pipeline.
4
+ *
5
+ * `kill` is kept as a compatibility alias; both entry points use this same
6
+ * handler so server, CLI, and future adapters share one stop contract.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.handleStop = handleStop;
10
+ const sdk_1 = require("@crewx/sdk");
11
+ const repository_1 = require("@crewx/sdk/repository");
12
+ const cal_1 = require("./cal");
13
+ function parseGrace(args) {
14
+ const flag = args.find((arg) => arg === '--grace' || arg.startsWith('--grace='));
15
+ if (!flag)
16
+ return { graceMs: 5_000 };
17
+ const raw = flag === '--grace'
18
+ ? args[args.indexOf(flag) + 1]
19
+ : flag.slice('--grace='.length);
20
+ const value = Number(raw);
21
+ if (!raw || !Number.isFinite(value) || value < 0) {
22
+ return { graceMs: 5_000, error: `Invalid --grace value: ${raw ?? '(missing)'}` };
23
+ }
24
+ return { graceMs: Math.trunc(value) };
25
+ }
26
+ function taskIdFromArgs(args) {
27
+ return args.find((arg, index) => {
28
+ if (arg === '--grace')
29
+ return false;
30
+ if (index > 0 && args[index - 1] === '--grace')
31
+ return false;
32
+ return !arg.startsWith('--');
33
+ });
34
+ }
35
+ function writeUsage() {
36
+ process.stderr.write('Error: Please provide a task ID or use --all to stop all running tasks.\n');
37
+ process.stderr.write('\nUsage:\n');
38
+ process.stderr.write(' crewx stop <task-id> Stop a running task\n');
39
+ process.stderr.write(' crewx stop --all Stop all running tasks\n');
40
+ process.stderr.write(' crewx stop <task-id> --grace=<ms>\n');
41
+ }
42
+ function formatGrace(graceMs) {
43
+ return `${graceMs / 1000}s`;
44
+ }
45
+ function reportResult(taskId, result, elapsedMs, graceMs) {
46
+ if (result.status === 'stopped') {
47
+ if (result.method === 'forced') {
48
+ process.stdout.write(`Stopped ${taskId} (forced after ${formatGrace(graceMs)} grace)\n`);
49
+ }
50
+ else {
51
+ process.stdout.write(`Stopped ${taskId} (cooperative, ${(elapsedMs / 1000).toFixed(1)}s)\n`);
52
+ }
53
+ return 0;
54
+ }
55
+ if (result.status === 'not_running') {
56
+ process.stdout.write(`Task ${taskId} is not running (${result.finalStatus ?? 'unknown'})\n`);
57
+ return 0;
58
+ }
59
+ if (result.status === 'already_requested') {
60
+ process.stderr.write(`Task ${taskId} already has a stop request; it remains ${result.finalStatus ?? 'running'}.\n`);
61
+ return 2;
62
+ }
63
+ if (result.status === 'ownership_unverified') {
64
+ const pid = result.pid ? ` (pid ${result.pid})` : '';
65
+ process.stderr.write(`Could not verify the process owner of ${taskId}; not terminated${pid}. ` +
66
+ 'Row stays running — the reaper will settle it.\n');
67
+ return 2;
68
+ }
69
+ process.stderr.write(`Could not stop ${taskId}: ${result.error ?? 'unknown error'}\n`);
70
+ return 1;
71
+ }
72
+ /** Handle `crewx stop` and the compatibility `crewx kill` alias. */
73
+ async function handleStop(args, invokedAs = 'stop') {
74
+ if (invokedAs === 'kill')
75
+ process.stderr.write('kill is an alias of stop\n');
76
+ const stopAll = args.includes('--all');
77
+ const { graceMs, error } = parseGrace(args);
78
+ if (error) {
79
+ process.stderr.write(`Error: ${error}\n`);
80
+ process.exitCode = 1;
81
+ return;
82
+ }
83
+ const taskId = taskIdFromArgs(args);
84
+ if (!taskId && !stopAll) {
85
+ writeUsage();
86
+ process.exitCode = 1;
87
+ return;
88
+ }
89
+ const repo = new repository_1.TaskRepository();
90
+ const stopOne = async (id, withFooter) => {
91
+ const startedAt = Date.now();
92
+ const result = await (0, sdk_1.stopTask)(repo, id, { graceMs });
93
+ const exitCode = reportResult(id, result, Date.now() - startedAt, graceMs);
94
+ if (withFooter && (result.status === 'stopped' || result.status === 'not_running')) {
95
+ (0, cal_1.writeCalFooter)({
96
+ commandId: 'crewx.stop.done',
97
+ params: { taskId: id },
98
+ });
99
+ }
100
+ return exitCode;
101
+ };
102
+ if (stopAll) {
103
+ const running = repo.getRunningTasks();
104
+ if (running.length === 0) {
105
+ process.stdout.write('No running tasks found.\n');
106
+ return;
107
+ }
108
+ let exitCode = 0;
109
+ for (const task of running) {
110
+ exitCode = Math.max(exitCode, await stopOne(task.id, false));
111
+ }
112
+ if (exitCode > 0)
113
+ process.exitCode = exitCode;
114
+ return;
115
+ }
116
+ const exitCode = await stopOne(taskId, true);
117
+ if (exitCode > 0)
118
+ process.exitCode = exitCode;
119
+ }
package/dist/main.js CHANGED
@@ -48,11 +48,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
48
48
  // Must run before any other code that might use process.env.CREWX_CLI.
49
49
  const sdk_compat_1 = require("./utils/sdk-compat");
50
50
  const account_1 = require("@crewx/sdk/account");
51
+ const inherited_trace_1 = require("./utils/inherited-trace");
51
52
  process.env.CREWX_CLI ??= sdk_compat_1.sdkCompat.resolveCrewxCli();
52
53
  process.env.CREWX_WORKSPACE ??= sdk_compat_1.sdkCompat.resolveCrewxWorkspace();
53
54
  // Load workspace .env before ~/.crewx/.env. The SDK-owned loader keeps CLI and
54
55
  // server bootstrap on the same process > workspace > home precedence contract.
55
56
  (0, account_1.loadCrewxEnvFiles)();
57
+ // Receipt runner identity is a one-hop capability. Consume it before any
58
+ // command/provider code can spawn a child process.
59
+ (0, inherited_trace_1.consumeInheritedRunnerIdentity)();
56
60
  // ─── Pricing remote override (WI-20260701-002) ───────────────────────────────
57
61
  // Best-effort, non-blocking: fetch remote model registry so new models get
58
62
  // accurate pricing without a CLI republish. Falls back to bundled table on
@@ -63,7 +67,7 @@ const query_1 = require("./commands/query");
63
67
  const execute_1 = require("./commands/execute");
64
68
  const agent_1 = require("./commands/agent");
65
69
  const ps_1 = require("./commands/ps");
66
- const kill_1 = require("./commands/kill");
70
+ const stop_1 = require("./commands/stop");
67
71
  const result_1 = require("./commands/result");
68
72
  const restart_1 = require("./commands/restart");
69
73
  const log_1 = require("./commands/log");
@@ -139,9 +143,12 @@ async function main(emitState) {
139
143
  case 'ps':
140
144
  await (0, ps_1.handlePs)(args.slice(1));
141
145
  return;
142
- // P0-5: kill
146
+ // Task stop (kill is a compatibility alias)
147
+ case 'stop':
148
+ await (0, stop_1.handleStop)(args.slice(1), 'stop');
149
+ return;
143
150
  case 'kill':
144
- await (0, kill_1.handleKill)(args.slice(1));
151
+ await (0, stop_1.handleStop)(args.slice(1), 'kill');
145
152
  return;
146
153
  // P0-6: result
147
154
  case 'result':
@@ -284,10 +291,10 @@ Query / Execute:
284
291
  -- End of flags; remaining tokens treated as message text
285
292
  e.g. crewx q "@agent label" -- --flag-in-message
286
293
 
287
- x/execute only:
288
- --detach Re-spawn as a detached background runner; print task-id
289
- and exit 0 immediately. Ignored if CREWX_TRACE_ID is
290
- already set (recursive-spawn guard). Unsupported on win32.
294
+ q/query/x/execute:
295
+ --detach/--async Persist a receipt, launch a detached runner, and print task-id
296
+ (automatic inside an agent process; q/x stay synchronous elsewhere).
297
+ --wait Force synchronous execution, including inside an agent process.
291
298
  e.g. crewx x "@agent label" --detach
292
299
 
293
300
  Agent Management:
@@ -299,8 +306,9 @@ Agent Management:
299
306
 
300
307
  Task Management:
301
308
  ps List running tasks
302
- kill <task-id> Kill a running task
303
- kill --all Kill all running tasks
309
+ stop <task-id> Stop a running task (cooperative, then forced)
310
+ stop --all Stop all running tasks
311
+ kill <task-id> Alias for stop
304
312
  result [task-id] Get task result (or list recent tasks)
305
313
  --wait=N Poll (1s interval) up to N seconds for the task to
306
314
  finish. Exit 124 on timeout. --wait=0 = single check.
@@ -1,4 +1,19 @@
1
1
  import type { TraceContext } from '@crewx/sdk';
2
+ export interface InheritedRunnerIdentity {
3
+ assignedTaskId?: string;
4
+ detachedRunner: boolean;
5
+ }
6
+ /**
7
+ * Consume the runner-only identity exactly once.
8
+ *
9
+ * These variables are a one-hop capability, not trace context. Keeping the
10
+ * values in this module lets the current CLI process use them while deleting
11
+ * them from process.env prevents later child processes from inheriting the
12
+ * receipt runner identity.
13
+ */
14
+ export declare function consumeInheritedRunnerIdentity(): InheritedRunnerIdentity;
15
+ /** Test-only reset for the module-level one-shot cache. */
16
+ export declare function resetInheritedRunnerIdentityForTests(): void;
2
17
  /**
3
18
  * Read cross-process trace context from inherited environment variables.
4
19
  *
@@ -10,3 +25,12 @@ import type { TraceContext } from '@crewx/sdk';
10
25
  * Returns undefined when none of the three vars are set (root invocation).
11
26
  */
12
27
  export declare function readInheritedTrace(): TraceContext | undefined;
28
+ /**
29
+ * Read the pre-assigned task id carried only by a detached runner.
30
+ *
31
+ * This is deliberately separate from readInheritedTrace(): trace variables
32
+ * describe the caller's chain and must not be used as this task's identity.
33
+ */
34
+ export declare function readRunnerAssignment(): string | undefined;
35
+ /** Return true only for the final detached runner process. */
36
+ export declare function isDetachedRunner(): boolean;
@@ -1,6 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.consumeInheritedRunnerIdentity = consumeInheritedRunnerIdentity;
4
+ exports.resetInheritedRunnerIdentityForTests = resetInheritedRunnerIdentityForTests;
3
5
  exports.readInheritedTrace = readInheritedTrace;
6
+ exports.readRunnerAssignment = readRunnerAssignment;
7
+ exports.isDetachedRunner = isDetachedRunner;
8
+ let inheritedRunnerIdentity;
9
+ /**
10
+ * Consume the runner-only identity exactly once.
11
+ *
12
+ * These variables are a one-hop capability, not trace context. Keeping the
13
+ * values in this module lets the current CLI process use them while deleting
14
+ * them from process.env prevents later child processes from inheriting the
15
+ * receipt runner identity.
16
+ */
17
+ function consumeInheritedRunnerIdentity() {
18
+ if (inheritedRunnerIdentity !== undefined)
19
+ return inheritedRunnerIdentity;
20
+ const assignedTaskId = process.env['CREWX_ASSIGNED_TASK_ID']?.trim() || undefined;
21
+ const detachedRunner = process.env['CREWX_DETACHED_RUNNER'] === '1';
22
+ delete process.env['CREWX_ASSIGNED_TASK_ID'];
23
+ delete process.env['CREWX_DETACHED_RUNNER'];
24
+ inheritedRunnerIdentity = { assignedTaskId, detachedRunner };
25
+ return inheritedRunnerIdentity;
26
+ }
27
+ /** Test-only reset for the module-level one-shot cache. */
28
+ function resetInheritedRunnerIdentityForTests() {
29
+ inheritedRunnerIdentity = undefined;
30
+ }
4
31
  /**
5
32
  * Read cross-process trace context from inherited environment variables.
6
33
  *
@@ -23,3 +50,16 @@ function readInheritedTrace() {
23
50
  callerAgentId,
24
51
  };
25
52
  }
53
+ /**
54
+ * Read the pre-assigned task id carried only by a detached runner.
55
+ *
56
+ * This is deliberately separate from readInheritedTrace(): trace variables
57
+ * describe the caller's chain and must not be used as this task's identity.
58
+ */
59
+ function readRunnerAssignment() {
60
+ return consumeInheritedRunnerIdentity().assignedTaskId;
61
+ }
62
+ /** Return true only for the final detached runner process. */
63
+ function isDetachedRunner() {
64
+ return consumeInheritedRunnerIdentity().detachedRunner;
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewx/cli",
3
- "version": "0.9.0-rc.94",
3
+ "version": "0.9.0-rc.96",
4
4
  "license": "UNLICENSED",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"
@@ -25,18 +25,19 @@
25
25
  "better-sqlite3": "*",
26
26
  "dotenv": "17.2.3",
27
27
  "isomorphic-git": "1.37.1",
28
- "@crewx/memory": "0.1.23-rc.111",
29
- "@crewx/sdk": "0.9.0-rc.94",
30
- "@crewx/doc": "0.1.9-rc.87",
31
- "@crewx/wbs": "0.1.10-rc.120",
32
- "@crewx/search": "0.1.10-rc.90",
33
- "@crewx/workflow": "0.3.22-rc.140",
34
- "@crewx/cron": "0.1.10-rc.129",
35
- "@crewx/notify": "0.1.0-rc.65",
36
- "@crewx/shared": "0.0.6",
37
- "@crewx/wi": "0.1.10-rc.114",
38
- "@crewx/chromex": "0.1.0-rc.127",
39
- "@crewx/skill": "0.1.20"
28
+ "@crewx/sdk": "0.9.0-rc.96",
29
+ "@crewx/knowledge-core": "0.1.17-rc.106",
30
+ "@crewx/search": "0.1.10-rc.92",
31
+ "@crewx/memory": "0.1.23-rc.113",
32
+ "@crewx/wbs": "0.1.10-rc.122",
33
+ "@crewx/doc": "0.1.9-rc.89",
34
+ "@crewx/cron": "0.1.10-rc.131",
35
+ "@crewx/wi": "0.1.10-rc.116",
36
+ "@crewx/skill": "0.1.20",
37
+ "@crewx/notify": "0.1.0-rc.67",
38
+ "@crewx/chromex": "0.1.0-rc.129",
39
+ "@crewx/workflow": "0.3.22-rc.142",
40
+ "@crewx/shared": "0.0.6"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/better-sqlite3": "*",