@crewx/cli 0.9.0-rc.109 → 0.9.0-rc.110

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.
@@ -6,7 +6,7 @@
6
6
  * trace context and are never repurposed as the runner assignment.
7
7
  */
8
8
  import { TaskRepository } from '@crewx/sdk/repository';
9
- import type { TaskActor } from '@crewx/sdk';
9
+ import type { DurableTaskAdmission, TaskActor } from '@crewx/sdk';
10
10
  export type ReceiptActorKind = 'human' | 'agent' | 'system' | 'unknown';
11
11
  export interface ReceiptActor {
12
12
  kind: ReceiptActorKind;
@@ -20,6 +20,14 @@ export declare function resolveCliTaskActor(metadata: Record<string, unknown>, a
20
20
  * cron runners cannot diverge after root context is stripped.
21
21
  */
22
22
  export declare function resolveReceiptActor(metadata: Record<string, unknown>, agentId?: string | undefined): ReceiptActor;
23
+ /**
24
+ * Report a blocked direct admission (WI-CTO-20260919-004 R2 finding #1) to
25
+ * the operator. Never called for an adopted (already pending) receipt — see
26
+ * admitDurableTask()'s own doc comment.
27
+ */
28
+ export declare function reportAdmissionBlocked(admission: Extract<DurableTaskAdmission, {
29
+ ok: false;
30
+ }>): void;
23
31
  /**
24
32
  * Remove receipt-selection flags, respecting the `--` literal-args sentinel.
25
33
  */
@@ -9,6 +9,7 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.resolveCliTaskActor = resolveCliTaskActor;
11
11
  exports.resolveReceiptActor = resolveReceiptActor;
12
+ exports.reportAdmissionBlocked = reportAdmissionBlocked;
12
13
  exports.extractReceiptFlags = extractReceiptFlags;
13
14
  exports.resolveReceiptMode = resolveReceiptMode;
14
15
  exports.adoptAssignedTask = adoptAssignedTask;
@@ -50,6 +51,14 @@ function resolveReceiptActor(metadata, agentId = process.env['CREWX_AGENT_ID'])
50
51
  const actor = resolveCliTaskActor(metadata, agentId);
51
52
  return { kind: actor.actorKind, id: actor.actorId };
52
53
  }
54
+ /**
55
+ * Report a blocked direct admission (WI-CTO-20260919-004 R2 finding #1) to
56
+ * the operator. Never called for an adopted (already pending) receipt — see
57
+ * admitDurableTask()'s own doc comment.
58
+ */
59
+ function reportAdmissionBlocked(admission) {
60
+ process.stderr.write(`${admission.message}\n`);
61
+ }
53
62
  const DETACHED_RUNNER_POLL_INTERVAL_MS = 200;
54
63
  const DETACHED_RUNNER_ADOPTION_TIMEOUT_MS = 30_000;
55
64
  const DETACHED_VALUE_FLAGS = [
@@ -421,7 +430,18 @@ async function runDetached(command, filteredArgs, reason) {
421
430
  process.exit(2);
422
431
  return;
423
432
  }
424
- const taskId = (0, sdk_1.generateId)('tsk');
433
+ // This process is a separate OS process from the embedded server and
434
+ // would otherwise never see a desktop update's admission gate at all
435
+ // (WI-CTO-20260919-004 R2 finding #1) — reserve-then-check before the
436
+ // pending receipt is written below, not after; see admitDurableTask()'s
437
+ // own doc comment for why that ordering closes the TOCTOU.
438
+ const admission = (0, sdk_1.admitDurableTask)(`direct-cli:${command}`);
439
+ if (!admission.ok) {
440
+ reportAdmissionBlocked(admission);
441
+ process.exit(1);
442
+ return;
443
+ }
444
+ const taskId = admission.taskId;
425
445
  const startedAt = new Date().toISOString();
426
446
  const workspacePath = process.cwd();
427
447
  const hasWorkspaceConfig = (0, fs_1.existsSync)((0, path_1.join)(workspacePath, 'crewx.yaml'))
@@ -455,10 +475,17 @@ async function runDetached(command, filteredArgs, reason) {
455
475
  });
456
476
  }
457
477
  catch (error) {
478
+ // The receipt's own pending row failed to persist — nothing durable was
479
+ // admitted, so the reservation's job is done regardless.
480
+ admission.release();
458
481
  process.stderr.write(`Failed to create detached task receipt: ${errorMessage(error)}\n`);
459
482
  process.exit(1);
460
483
  return;
461
484
  }
485
+ // The pending row above is now the durable, DB-visible signal of this
486
+ // work's admission — the reservation only ever existed to bridge the gap
487
+ // before it landed.
488
+ admission.release();
462
489
  const runnerArgs = buildDetachedRunnerArgs(filteredArgs, agentRef, finalMessage);
463
490
  const commandForRunner = runnerCommand(command);
464
491
  const logDir = (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'logs');
@@ -178,63 +178,86 @@ async function handleExecute(args, command = 'execute', emitState = (0, emit_tra
178
178
  taskActor = (0, detached_1.readAssignedTaskActor)(taskRepo, adoption.taskId);
179
179
  }
180
180
  }
181
+ // A synchronous `crewx x` runs in this same process, but this process is
182
+ // still separate from the embedded server and never sees its in-memory
183
+ // UpdateGateService (WI-CTO-20260919-004 R2 finding #1) — only the durable
184
+ // marker it writes. Reserve-then-check before calling into the SDK below,
185
+ // not after; see admitDurableTask()'s own doc comment. Never checked for
186
+ // an adopted receipt (selfTaskId set): that work was already admitted by
187
+ // whichever process first created its pending DB row.
188
+ let admission;
189
+ if (selfTaskId === undefined) {
190
+ admission = (0, sdk_1.admitDurableTask)(`direct-cli:${command}`);
191
+ if (!admission.ok) {
192
+ (0, detached_1.reportAdmissionBlocked)(admission);
193
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
194
+ process.exit(1);
195
+ return;
196
+ }
197
+ }
181
198
  let exitCode = 0;
182
199
  let result;
183
200
  try {
184
- result = await crewx.execute(agentRef, finalMessage, {
185
- provider,
186
- model,
187
- effort: effort || undefined,
188
- overdrive: overdrive || undefined,
189
- threadId: thread,
190
- taskId: selfTaskId,
191
- actor: assignedTaskId === undefined ? actor : taskActor,
192
- metadata: Object.keys(parsedMetadata).length > 0 ? parsedMetadata : undefined,
193
- vars: Object.keys(vars).length > 0 ? vars : undefined,
194
- trace: inheritedTrace,
195
- });
196
- if (!result.ok) {
197
- const errMsg = result.error?.message ?? 'Execute failed';
198
- console.error(errMsg);
199
- (0, write_output_1.appendError)(out, errMsg);
201
+ try {
202
+ result = await crewx.execute(agentRef, finalMessage, {
203
+ provider,
204
+ model,
205
+ effort: effort || undefined,
206
+ overdrive: overdrive || undefined,
207
+ threadId: thread,
208
+ taskId: selfTaskId,
209
+ actor: assignedTaskId === undefined ? actor : taskActor,
210
+ metadata: Object.keys(parsedMetadata).length > 0 ? parsedMetadata : undefined,
211
+ vars: Object.keys(vars).length > 0 ? vars : undefined,
212
+ trace: inheritedTrace,
213
+ });
214
+ if (!result.ok) {
215
+ const errMsg = result.error?.message ?? 'Execute failed';
216
+ console.error(errMsg);
217
+ (0, write_output_1.appendError)(out, errMsg);
218
+ exitCode = 1;
219
+ }
220
+ else {
221
+ if (verbose) {
222
+ process.stderr.write(`\n📊 Results from ${agentRef}:\n`);
223
+ process.stderr.write('═'.repeat(60) + '\n');
224
+ process.stderr.write('🟢 Status: Success\n');
225
+ process.stderr.write(`🤖 Provider: ${result.meta.provider}\n`);
226
+ process.stderr.write(`⏱️ Duration: ${result.meta.durationMs}ms\n`);
227
+ process.stderr.write('\n📄 Response:\n');
228
+ process.stderr.write('─'.repeat(40) + '\n');
229
+ }
230
+ (0, write_output_1.writeResult)(out, result.data);
231
+ if (verbose)
232
+ process.stderr.write('\n✅ Execute completed successfully\n');
233
+ }
234
+ }
235
+ catch (err) {
236
+ const errMsg = err instanceof Error ? err.message : String(err);
237
+ console.error(`Error: ${errMsg}`);
238
+ (0, write_output_1.appendError)(out, `Error: ${errMsg}`);
200
239
  exitCode = 1;
201
240
  }
202
- else {
203
- if (verbose) {
204
- process.stderr.write(`\n📊 Results from ${agentRef}:\n`);
205
- process.stderr.write('═'.repeat(60) + '\n');
206
- process.stderr.write('🟢 Status: Success\n');
207
- process.stderr.write(`🤖 Provider: ${result.meta.provider}\n`);
208
- process.stderr.write(`⏱️ Duration: ${result.meta.durationMs}ms\n`);
209
- process.stderr.write('\n📄 Response:\n');
210
- process.stderr.write('─'.repeat(40) + '\n');
241
+ finally {
242
+ try {
243
+ await crewx.close();
244
+ }
245
+ catch (err) {
246
+ (0, emit_trailer_1.emitDelegationResult)({
247
+ command,
248
+ trace: inheritedTrace,
249
+ taskId: result?.meta.taskId,
250
+ agentId: result?.meta.agentId,
251
+ isError: true,
252
+ state: emitState,
253
+ });
254
+ throw err;
211
255
  }
212
- (0, write_output_1.writeResult)(out, result.data);
213
- if (verbose)
214
- process.stderr.write('\n✅ Execute completed successfully\n');
215
256
  }
216
257
  }
217
- catch (err) {
218
- const errMsg = err instanceof Error ? err.message : String(err);
219
- console.error(`Error: ${errMsg}`);
220
- (0, write_output_1.appendError)(out, `Error: ${errMsg}`);
221
- exitCode = 1;
222
- }
223
258
  finally {
224
- try {
225
- await crewx.close();
226
- }
227
- catch (err) {
228
- (0, emit_trailer_1.emitDelegationResult)({
229
- command,
230
- trace: inheritedTrace,
231
- taskId: result?.meta.taskId,
232
- agentId: result?.meta.agentId,
233
- isError: true,
234
- state: emitState,
235
- });
236
- throw err;
237
- }
259
+ if (admission?.ok)
260
+ admission.release();
238
261
  }
239
262
  (0, emit_trailer_1.emitDelegationResult)({
240
263
  command,
@@ -175,64 +175,87 @@ async function handleQuery(args, command = 'query', emitState = (0, emit_trailer
175
175
  taskActor = (0, detached_1.readAssignedTaskActor)(taskRepo, adoption.taskId);
176
176
  }
177
177
  }
178
+ // A synchronous `crewx q` runs in this same process, but this process is
179
+ // still separate from the embedded server and never sees its in-memory
180
+ // UpdateGateService (WI-CTO-20260919-004 R2 finding #1) — only the durable
181
+ // marker it writes. Reserve-then-check before calling into the SDK below,
182
+ // not after; see admitDurableTask()'s own doc comment. Never checked for
183
+ // an adopted receipt (selfTaskId set): that work was already admitted by
184
+ // whichever process first created its pending DB row.
185
+ let admission;
186
+ if (selfTaskId === undefined) {
187
+ admission = (0, sdk_1.admitDurableTask)(`direct-cli:${command}`);
188
+ if (!admission.ok) {
189
+ (0, detached_1.reportAdmissionBlocked)(admission);
190
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
191
+ process.exit(1);
192
+ return;
193
+ }
194
+ }
178
195
  let exitCode = 0;
179
196
  let result;
180
197
  try {
181
- result = await crewx.query(agentRef, finalMessage, {
182
- provider,
183
- model,
184
- effort: effort || undefined,
185
- overdrive: overdrive || undefined,
186
- threadId: thread,
187
- taskId: selfTaskId,
188
- actor: assignedTaskId === undefined ? actor : taskActor,
189
- metadata: Object.keys(parsedMetadata).length > 0 ? parsedMetadata : undefined,
190
- vars: Object.keys(vars).length > 0 ? vars : undefined,
191
- trace: inheritedTrace,
192
- });
193
- if (!result.ok) {
194
- const errMsg = result.error?.message ?? 'Query failed';
195
- console.error(errMsg);
196
- (0, write_output_1.appendError)(out, errMsg);
198
+ try {
199
+ result = await crewx.query(agentRef, finalMessage, {
200
+ provider,
201
+ model,
202
+ effort: effort || undefined,
203
+ overdrive: overdrive || undefined,
204
+ threadId: thread,
205
+ taskId: selfTaskId,
206
+ actor: assignedTaskId === undefined ? actor : taskActor,
207
+ metadata: Object.keys(parsedMetadata).length > 0 ? parsedMetadata : undefined,
208
+ vars: Object.keys(vars).length > 0 ? vars : undefined,
209
+ trace: inheritedTrace,
210
+ });
211
+ if (!result.ok) {
212
+ const errMsg = result.error?.message ?? 'Query failed';
213
+ console.error(errMsg);
214
+ (0, write_output_1.appendError)(out, errMsg);
215
+ exitCode = 1;
216
+ }
217
+ else {
218
+ if (verbose) {
219
+ process.stderr.write(`\n📊 Results from ${agentRef}:\n`);
220
+ process.stderr.write('═'.repeat(60) + '\n');
221
+ process.stderr.write('🟢 Status: Success\n');
222
+ process.stderr.write(`🤖 Provider: ${result.meta.provider}\n`);
223
+ process.stderr.write(`⏱️ Duration: ${result.meta.durationMs}ms\n`);
224
+ process.stderr.write('\n📄 Response:\n');
225
+ process.stderr.write('─'.repeat(40) + '\n');
226
+ }
227
+ (0, write_output_1.writeResult)(out, result.data);
228
+ if (verbose) {
229
+ process.stderr.write('\n✅ Query completed successfully\n');
230
+ }
231
+ }
232
+ }
233
+ catch (err) {
234
+ const errMsg = err instanceof Error ? err.message : String(err);
235
+ console.error(`Error: ${errMsg}`);
236
+ (0, write_output_1.appendError)(out, `Error: ${errMsg}`);
197
237
  exitCode = 1;
198
238
  }
199
- else {
200
- if (verbose) {
201
- process.stderr.write(`\n📊 Results from ${agentRef}:\n`);
202
- process.stderr.write('═'.repeat(60) + '\n');
203
- process.stderr.write('🟢 Status: Success\n');
204
- process.stderr.write(`🤖 Provider: ${result.meta.provider}\n`);
205
- process.stderr.write(`⏱️ Duration: ${result.meta.durationMs}ms\n`);
206
- process.stderr.write('\n📄 Response:\n');
207
- process.stderr.write('─'.repeat(40) + '\n');
239
+ finally {
240
+ try {
241
+ await crewx.close();
208
242
  }
209
- (0, write_output_1.writeResult)(out, result.data);
210
- if (verbose) {
211
- process.stderr.write('\n✅ Query completed successfully\n');
243
+ catch (err) {
244
+ (0, emit_trailer_1.emitDelegationResult)({
245
+ command,
246
+ trace: inheritedTrace,
247
+ taskId: result?.meta.taskId,
248
+ agentId: result?.meta.agentId,
249
+ isError: true,
250
+ state: emitState,
251
+ });
252
+ throw err;
212
253
  }
213
254
  }
214
255
  }
215
- catch (err) {
216
- const errMsg = err instanceof Error ? err.message : String(err);
217
- console.error(`Error: ${errMsg}`);
218
- (0, write_output_1.appendError)(out, `Error: ${errMsg}`);
219
- exitCode = 1;
220
- }
221
256
  finally {
222
- try {
223
- await crewx.close();
224
- }
225
- catch (err) {
226
- (0, emit_trailer_1.emitDelegationResult)({
227
- command,
228
- trace: inheritedTrace,
229
- taskId: result?.meta.taskId,
230
- agentId: result?.meta.agentId,
231
- isError: true,
232
- state: emitState,
233
- });
234
- throw err;
235
- }
257
+ if (admission?.ok)
258
+ admission.release();
236
259
  }
237
260
  (0, emit_trailer_1.emitDelegationResult)({
238
261
  command,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewx/cli",
3
- "version": "0.9.0-rc.109",
3
+ "version": "0.9.0-rc.110",
4
4
  "license": "UNLICENSED",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"
@@ -25,19 +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.109",
29
- "@crewx/knowledge-core": "0.1.17-rc.119",
30
- "@crewx/search": "0.1.10-rc.105",
31
- "@crewx/doc": "0.1.9-rc.102",
32
- "@crewx/memory": "0.1.23-rc.126",
33
- "@crewx/wbs": "0.1.10-rc.135",
34
- "@crewx/cron": "0.1.10-rc.144",
35
- "@crewx/workflow": "0.3.22-rc.155",
36
- "@crewx/skill": "0.1.20-rc.3",
37
- "@crewx/notify": "0.1.0-rc.80",
38
- "@crewx/chromex": "0.1.0-rc.142",
39
- "@crewx/shared": "0.0.6-rc.24",
40
- "@crewx/wi": "0.1.10-rc.129"
28
+ "@crewx/sdk": "0.9.0-rc.110",
29
+ "@crewx/knowledge-core": "0.1.17-rc.120",
30
+ "@crewx/memory": "0.1.23-rc.127",
31
+ "@crewx/doc": "0.1.9-rc.103",
32
+ "@crewx/search": "0.1.10-rc.106",
33
+ "@crewx/wbs": "0.1.10-rc.136",
34
+ "@crewx/cron": "0.1.10-rc.145",
35
+ "@crewx/skill": "0.1.20-rc.4",
36
+ "@crewx/workflow": "0.3.22-rc.156",
37
+ "@crewx/chromex": "0.1.0-rc.143",
38
+ "@crewx/notify": "0.1.0-rc.81",
39
+ "@crewx/wi": "0.1.10-rc.130",
40
+ "@crewx/shared": "0.0.6-rc.25"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/better-sqlite3": "*",