@crewx/cli 0.9.0-rc.93 → 0.9.0-rc.95

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.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Boot sequence:
7
7
  * 1. Inject CREWX_CLI / CREWX_WORKSPACE env defaults (must be first)
8
- * 2. Load workspace .env (does not override existing process.env values)
8
+ * 2. Load workspace and ~/.crewx .env files (does not override process.env)
9
9
  * 3. Parse command
10
10
  * 4. Dispatch to handler
11
11
  */
package/dist/main.js CHANGED
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Boot sequence:
8
8
  * 1. Inject CREWX_CLI / CREWX_WORKSPACE env defaults (must be first)
9
- * 2. Load workspace .env (does not override existing process.env values)
9
+ * 2. Load workspace and ~/.crewx .env files (does not override process.env)
10
10
  * 3. Parse command
11
11
  * 4. Dispatch to handler
12
12
  */
@@ -47,15 +47,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
47
47
  // ─── P0-1: Env Bootstrap ─────────────────────────────────────────────────────
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
- const dotenv = __importStar(require("dotenv"));
51
- const path_1 = require("path");
50
+ const account_1 = require("@crewx/sdk/account");
51
+ const inherited_trace_1 = require("./utils/inherited-trace");
52
52
  process.env.CREWX_CLI ??= sdk_compat_1.sdkCompat.resolveCrewxCli();
53
53
  process.env.CREWX_WORKSPACE ??= sdk_compat_1.sdkCompat.resolveCrewxWorkspace();
54
- // Load the workspace .env (WI-20260725-010): keeps CLI (crewx q/x) in parity with
55
- // the server's ConfigModule, which already reads cwd/.env. override:false so a
56
- // value already present in the shell/environment always wins over the file.
57
- // quiet:true suppresses dotenv's injected-keys log and tip banner on stdout.
58
- dotenv.config({ path: (0, path_1.join)(process.env.CREWX_WORKSPACE, '.env'), override: false, quiet: true });
54
+ // Load workspace .env before ~/.crewx/.env. The SDK-owned loader keeps CLI and
55
+ // server bootstrap on the same process > workspace > home precedence contract.
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)();
59
60
  // ─── Pricing remote override (WI-20260701-002) ───────────────────────────────
60
61
  // Best-effort, non-blocking: fetch remote model registry so new models get
61
62
  // accurate pricing without a CLI republish. Falls back to bundled table on
@@ -66,7 +67,7 @@ const query_1 = require("./commands/query");
66
67
  const execute_1 = require("./commands/execute");
67
68
  const agent_1 = require("./commands/agent");
68
69
  const ps_1 = require("./commands/ps");
69
- const kill_1 = require("./commands/kill");
70
+ const stop_1 = require("./commands/stop");
70
71
  const result_1 = require("./commands/result");
71
72
  const restart_1 = require("./commands/restart");
72
73
  const log_1 = require("./commands/log");
@@ -142,9 +143,12 @@ async function main(emitState) {
142
143
  case 'ps':
143
144
  await (0, ps_1.handlePs)(args.slice(1));
144
145
  return;
145
- // 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;
146
150
  case 'kill':
147
- await (0, kill_1.handleKill)(args.slice(1));
151
+ await (0, stop_1.handleStop)(args.slice(1), 'kill');
148
152
  return;
149
153
  // P0-6: result
150
154
  case 'result':
@@ -287,10 +291,10 @@ Query / Execute:
287
291
  -- End of flags; remaining tokens treated as message text
288
292
  e.g. crewx q "@agent label" -- --flag-in-message
289
293
 
290
- x/execute only:
291
- --detach Re-spawn as a detached background runner; print task-id
292
- and exit 0 immediately. Ignored if CREWX_TRACE_ID is
293
- 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.
294
298
  e.g. crewx x "@agent label" --detach
295
299
 
296
300
  Agent Management:
@@ -302,8 +306,9 @@ Agent Management:
302
306
 
303
307
  Task Management:
304
308
  ps List running tasks
305
- kill <task-id> Kill a running task
306
- 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
307
312
  result [task-id] Get task result (or list recent tasks)
308
313
  --wait=N Poll (1s interval) up to N seconds for the task to
309
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.93",
3
+ "version": "0.9.0-rc.95",
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/sdk": "0.9.0-rc.93",
29
- "@crewx/memory": "0.1.23-rc.110",
30
- "@crewx/search": "0.1.10-rc.89",
31
- "@crewx/wbs": "0.1.10-rc.119",
32
- "@crewx/doc": "0.1.9-rc.86",
33
- "@crewx/cron": "0.1.10-rc.128",
28
+ "@crewx/sdk": "0.9.0-rc.95",
29
+ "@crewx/memory": "0.1.23-rc.112",
30
+ "@crewx/search": "0.1.10-rc.91",
31
+ "@crewx/doc": "0.1.9-rc.88",
32
+ "@crewx/knowledge-core": "0.1.17-rc.105",
33
+ "@crewx/wbs": "0.1.10-rc.121",
34
+ "@crewx/workflow": "0.3.22-rc.141",
34
35
  "@crewx/skill": "0.1.20",
35
- "@crewx/workflow": "0.3.22-rc.139",
36
- "@crewx/wi": "0.1.10-rc.113",
37
- "@crewx/chromex": "0.1.0-rc.126",
38
- "@crewx/notify": "0.1.0-rc.64",
39
- "@crewx/shared": "0.0.6"
36
+ "@crewx/wi": "0.1.10-rc.115",
37
+ "@crewx/cron": "0.1.10-rc.130",
38
+ "@crewx/chromex": "0.1.0-rc.128",
39
+ "@crewx/shared": "0.0.6",
40
+ "@crewx/notify": "0.1.0-rc.66"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/better-sqlite3": "*",