crewx 0.9.0-rc.111 → 0.9.0-rc.112

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/bin/crewx-ui.js CHANGED
@@ -53,12 +53,6 @@ if (isElectron || isOverlay) {
53
53
  ? join(__dirname, '..', 'dist-electron', 'overlay.js')
54
54
  : join(__dirname, '..', 'dist-electron', 'main.js');
55
55
 
56
- if (process.env.ELECTRON_RUN_AS_NODE) {
57
- console.warn(
58
- '[crewx-ui] Ignoring ELECTRON_RUN_AS_NODE for Electron UI launch (it forces Electron into Node mode).',
59
- );
60
- }
61
-
62
56
  const child = spawn(String(electronPath), [entryFile], {
63
57
  stdio: 'inherit',
64
58
  env: getElectronSpawnEnv(),
package/bin/crewx.js CHANGED
@@ -55,12 +55,6 @@ function launchElectron(entryFile, routeArgs = []) {
55
55
  return false; // Electron not installed
56
56
  }
57
57
 
58
- if (process.env.ELECTRON_RUN_AS_NODE) {
59
- console.warn(
60
- '[crewx] Ignoring ELECTRON_RUN_AS_NODE for Electron UI launch (it forces Electron into Node mode).',
61
- );
62
- }
63
-
64
58
  const child = spawn(String(electronPath), [entryFile, ...routeArgs], {
65
59
  stdio: 'inherit',
66
60
  env: getElectronSpawnEnv(),
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OUTPUT_UNAVAILABLE = void 0;
4
4
  exports.isProviderProtocolOnlyText = isProviderProtocolOnlyText;
5
+ exports.parsePublicStdoutLogEntries = parsePublicStdoutLogEntries;
5
6
  exports.isUnsupportedStructuredInfoLog = isUnsupportedStructuredInfoLog;
6
7
  exports.createOutputUnavailableLogEntry = createOutputUnavailableLogEntry;
7
8
  exports.projectPublicTaskLogEntry = projectPublicTaskLogEntry;
@@ -25,6 +26,34 @@ const STRUCTURED_INFO_LOG_TYPES = new Set([
25
26
  function isProviderProtocolOnlyText(message, adapterKey) {
26
27
  return (0, sdk_1.isProtocolOnlyText)(message, adapterKey);
27
28
  }
29
+ /**
30
+ * Classify one persisted stdout row before the shared log pipeline runs.
31
+ *
32
+ * Displayable adapter output always wins. A confirmed Claude metadata line is
33
+ * omitted before merge, truncation, and log-tail slicing so protocol chatter
34
+ * cannot consume the public log budget. All other protocol-only rows retain
35
+ * the existing content-free placeholder and uncertain input remains raw text.
36
+ */
37
+ function parsePublicStdoutLogEntries(timestamp, message, adapterKey) {
38
+ let parsed = [];
39
+ try {
40
+ parsed = (0, sdk_1.parseStdoutEvent)(timestamp, message, adapterKey);
41
+ }
42
+ catch {
43
+ // Preserve the historical raw-text fallback when an adapter parser fails.
44
+ }
45
+ if (parsed.length > 0)
46
+ return parsed;
47
+ // Metadata classification is intentionally opt-in. Without a registered
48
+ // adapter key, do not infer that an otherwise protocol-shaped line is safe
49
+ // to drop.
50
+ if (adapterKey && (0, sdk_1.isMetadataOnlyProtocolLine)(message, adapterKey))
51
+ return [];
52
+ if ((0, sdk_1.isProtocolOnlyText)(message, adapterKey)) {
53
+ return [createOutputUnavailableLogEntry(timestamp)];
54
+ }
55
+ return [{ timestamp, type: 'text', content: message }];
56
+ }
28
57
  /**
29
58
  * Return true for a JSON ACP/info message whose type is outside the accepted
30
59
  * structured activity allowlist. Plain info text and untyped JSON remain text.
@@ -64,8 +64,10 @@ const DEFAULT_LEGACY_TERM_TIMEOUT_MS = 1_000;
64
64
  const DEFAULT_LEGACY_POLL_INTERVAL_MS = 50;
65
65
  // macOS `ps lstart` is second-granularity while the plain legacy PID marker
66
66
  // has millisecond mtime. This is only a coarse PID-reuse guard, not proof of
67
- // process creation identity; keep its tolerance bounded to one second.
68
- const PROCESS_START_IDENTITY_TOLERANCE_MS = 1_000;
67
+ // process creation identity. `ps lstart` is quantized down to whole seconds,
68
+ // so only the marker-write-after-start direction is valid. Keep the window
69
+ // bounded to one quantization second plus one marker-write second.
70
+ const PROCESS_START_IDENTITY_MAX_DELTA_MS = 2_000;
69
71
  const EXPECTED_COMMAND_UNAVAILABLE_REASON = 'expected full process command is unavailable';
70
72
  /** Nest token for injectable host seams used by tests and embedders. */
71
73
  var cron_runtime_sync_port_js_2 = require("./cron-runtime-sync.port.js");
@@ -423,7 +425,8 @@ function startIdentitiesMatch(marker, processStart) {
423
425
  const markerNumber = typeof marker === 'number' ? marker : Number(marker);
424
426
  const processNumber = typeof processStart === 'number' ? processStart : Number(processStart);
425
427
  if (Number.isFinite(markerNumber) && Number.isFinite(processNumber)) {
426
- return Math.abs(markerNumber - processNumber) <= PROCESS_START_IDENTITY_TOLERANCE_MS;
428
+ const delta = markerNumber - processNumber;
429
+ return delta >= 0 && delta <= PROCESS_START_IDENTITY_MAX_DELTA_MS;
427
430
  }
428
431
  return String(marker) === String(processStart);
429
432
  }
@@ -729,6 +732,12 @@ let CronBootstrapService = CronBootstrapService_1 = class CronBootstrapService {
729
732
  alert: 'expected-command-unavailable',
730
733
  };
731
734
  }
735
+ if (process.platform === 'win32') {
736
+ // Win32_Process does not provide a trusted cwd in the default
737
+ // inspector. Do not apply the Unix ps/lstart numeric window to an
738
+ // injected observation that happens to include a cwd.
739
+ return { kind: 'unknown', reason: 'process cwd is not trustworthy on Windows' };
740
+ }
732
741
  if (!observation.commandLine)
733
742
  return { kind: 'unknown', reason: 'process command line is unknown' };
734
743
  const daemonKind = classifyCronDaemonCommand(observation.commandLine);
@@ -1247,23 +1247,7 @@ let TaskService = TaskService_1 = class TaskService {
1247
1247
  const entries = [];
1248
1248
  for (const log of rawLogs) {
1249
1249
  if (log.level === 'stdout') {
1250
- let parsed = [];
1251
- try {
1252
- parsed = (0, sdk_1.parseStdoutEvent)(log.timestamp, log.message, providerId);
1253
- }
1254
- catch { /* adapter parse failure — classify the raw row below */ }
1255
- if (parsed.length > 0) {
1256
- entries.push(...parsed);
1257
- continue;
1258
- }
1259
- // Parsed successfully but no displayable entries (e.g. thinking-only assistant message).
1260
- // Keep a safe row for confirmed provider protocol envelopes instead of
1261
- // exposing the raw JSON fallback.
1262
- if ((0, public_log_projection_js_1.isProviderProtocolOnlyText)(log.message, providerId)) {
1263
- entries.push((0, public_log_projection_js_1.createOutputUnavailableLogEntry)(log.timestamp));
1264
- continue;
1265
- }
1266
- entries.push({ timestamp: log.timestamp, type: 'text', content: log.message });
1250
+ entries.push(...(0, public_log_projection_js_1.parsePublicStdoutLogEntries)(log.timestamp, log.message, providerId));
1267
1251
  }
1268
1252
  else if (log.level === 'info') {
1269
1253
  const infoEntries = (0, sdk_1.defaultParseEvent)(log.message, log.timestamp);
@@ -1123,23 +1123,7 @@ let ThreadService = ThreadService_1 = class ThreadService {
1123
1123
  const entries = [];
1124
1124
  for (const log of rawLogs) {
1125
1125
  if (log.level === 'stdout') {
1126
- let parsed = [];
1127
- try {
1128
- parsed = (0, sdk_1.parseStdoutEvent)(log.timestamp, log.message, providerId);
1129
- }
1130
- catch { /* adapter parse failure — classify the raw row below */ }
1131
- if (parsed.length > 0) {
1132
- entries.push(...parsed);
1133
- continue;
1134
- }
1135
- // Parsed successfully but no displayable entries (e.g. thinking-only assistant message).
1136
- // Keep a safe row for confirmed provider protocol envelopes instead of
1137
- // exposing the raw JSON fallback.
1138
- if ((0, public_log_projection_js_1.isProviderProtocolOnlyText)(log.message, providerId)) {
1139
- entries.push((0, public_log_projection_js_1.createOutputUnavailableLogEntry)(log.timestamp));
1140
- continue;
1141
- }
1142
- entries.push({ timestamp: log.timestamp, type: 'text', content: log.message });
1126
+ entries.push(...(0, public_log_projection_js_1.parsePublicStdoutLogEntries)(log.timestamp, log.message, providerId));
1143
1127
  }
1144
1128
  else if (log.level === 'info') {
1145
1129
  const infoEntries = (0, sdk_1.defaultParseEvent)(log.message, log.timestamp);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crewx",
3
- "version": "0.9.0-rc.111",
3
+ "version": "0.9.0-rc.112",
4
4
  "description": "CrewX — AI agent team dashboard with Electron UI and CLI (Web + Electron + Global CLI)",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -150,20 +150,20 @@
150
150
  "ws": "8.20.0",
151
151
  "yargs": "17.7.0",
152
152
  "zod": "3.25.76",
153
- "@crewx/chromex": "0.1.0-rc.144",
154
- "@crewx/cli": "0.9.0-rc.111",
155
- "@crewx/cron": "0.1.10-rc.146",
156
- "@crewx/doc": "0.1.9-rc.104",
157
- "@crewx/dreaming": "0.1.0-rc.137",
158
- "@crewx/memory": "0.1.23-rc.128",
159
- "@crewx/knowledge-core": "0.1.17-rc.121",
160
- "@crewx/search": "0.1.10-rc.107",
161
- "@crewx/sdk": "0.9.0-rc.111",
162
- "@crewx/shared": "0.0.6-rc.26",
163
- "@crewx/skill": "0.1.20-rc.5",
164
- "@crewx/wbs": "0.1.10-rc.137",
165
- "@crewx/workflow": "0.3.22-rc.157",
166
- "@crewx/wi": "0.1.10-rc.131"
153
+ "@crewx/chromex": "0.1.0-rc.145",
154
+ "@crewx/cli": "0.9.0-rc.112",
155
+ "@crewx/cron": "0.1.10-rc.147",
156
+ "@crewx/dreaming": "0.1.0-rc.138",
157
+ "@crewx/knowledge-core": "0.1.17-rc.122",
158
+ "@crewx/doc": "0.1.9-rc.105",
159
+ "@crewx/memory": "0.1.23-rc.129",
160
+ "@crewx/shared": "0.0.6-rc.27",
161
+ "@crewx/search": "0.1.10-rc.108",
162
+ "@crewx/skill": "0.1.20-rc.6",
163
+ "@crewx/sdk": "0.9.0-rc.112",
164
+ "@crewx/workflow": "0.3.22-rc.158",
165
+ "@crewx/wi": "0.1.10-rc.132",
166
+ "@crewx/wbs": "0.1.10-rc.138"
167
167
  },
168
168
  "devDependencies": {
169
169
  "@anthropic-ai/claude-agent-sdk": "0.2.132",
@@ -293,6 +293,9 @@ function runnerCommand(command) {
293
293
  function buildRunnerEnvironment(taskId) {
294
294
  const env = { ...process.env };
295
295
  delete env[INTERNAL_TOKEN_ENV];
296
+ if (process.versions.electron) {
297
+ env.ELECTRON_RUN_AS_NODE = '1';
298
+ }
296
299
  env.CREWX_DETACHED_RUNNER = '1';
297
300
  env.CREWX_ASSIGNED_TASK_ID = taskId;
298
301
  return env;
@@ -328,6 +331,9 @@ function readAssignedTaskActor(taskRepo, taskId) {
328
331
  }
329
332
  function buildMiddleEnvironment(token) {
330
333
  const env = { ...process.env };
334
+ if (process.versions.electron) {
335
+ env.ELECTRON_RUN_AS_NODE = '1';
336
+ }
331
337
  env[INTERNAL_TOKEN_ENV] = token;
332
338
  return env;
333
339
  }
@@ -6,6 +6,7 @@
6
6
  * crewx doctor Run full diagnosis
7
7
  * crewx doctor --config <path> Use specific config file
8
8
  */
9
+ import type { CrewxExecutableResolution, CrewxExecutableStage } from '@crewx/sdk';
9
10
  interface DiagnosticResult {
10
11
  name: string;
11
12
  status: 'success' | 'warning' | 'error';
@@ -44,6 +45,25 @@ export interface CrewxShimReport {
44
45
  fingerprint: string;
45
46
  }>;
46
47
  }
48
+ export interface CrewxExecutableReport {
49
+ platform: NodeJS.Platform;
50
+ pathCandidates: string[];
51
+ stage?: CrewxExecutableStage;
52
+ argv?: string[];
53
+ electron: boolean;
54
+ requiredEnvKeys: string[];
55
+ binResolution: 'global_first' | 'local_first';
56
+ adoptedPath?: string;
57
+ resolutionError?: string;
58
+ }
59
+ export interface CrewxExecutableInspectionOptions {
60
+ platform?: NodeJS.Platform;
61
+ cwd?: string;
62
+ env?: NodeJS.ProcessEnv;
63
+ binResolution?: 'global_first' | 'local_first';
64
+ pathCandidates?: readonly string[];
65
+ resolution?: CrewxExecutableResolution;
66
+ }
47
67
  /** CLI diagnostics keyed by the bare provider name used in PROVIDER_ORDER. */
48
68
  export declare const CLI_PROVIDER_INFO: Record<string, CliProviderInfo>;
49
69
  /** Format a byte count for diagnostic output. */
@@ -52,6 +72,10 @@ export declare function formatBytes(bytes: number): string;
52
72
  export declare function inspectNpxCache(cacheRoot?: string): NpxCacheReport;
53
73
  /** Inspect installed CrewX hooks without rewriting provider settings. */
54
74
  export declare function inspectHookCommands(projectRoot: string): HookCommandReport;
75
+ /** Inspect the SDK-selected CrewX executable without printing environment values. */
76
+ export declare function inspectCrewxExecutable(options?: CrewxExecutableInspectionOptions & {
77
+ configPath?: string;
78
+ }): CrewxExecutableReport;
55
79
  /** Inspect orphaned CrewX PATH shim directories without modifying them. */
56
80
  export declare function inspectCrewxShims(crewxHome?: string, resolvedFingerprint?: string | undefined): CrewxShimReport;
57
81
  /**
@@ -12,10 +12,10 @@ exports.CLI_PROVIDER_INFO = void 0;
12
12
  exports.formatBytes = formatBytes;
13
13
  exports.inspectNpxCache = inspectNpxCache;
14
14
  exports.inspectHookCommands = inspectHookCommands;
15
+ exports.inspectCrewxExecutable = inspectCrewxExecutable;
15
16
  exports.inspectCrewxShims = inspectCrewxShims;
16
17
  exports.checkCliProviders = checkCliProviders;
17
18
  exports.handleDoctor = handleDoctor;
18
- const node_crypto_1 = require("node:crypto");
19
19
  const node_child_process_1 = require("node:child_process");
20
20
  const node_fs_1 = require("node:fs");
21
21
  const node_os_1 = require("node:os");
@@ -255,17 +255,98 @@ function inspectHookCommands(projectRoot) {
255
255
  }
256
256
  return { inspectedFiles, issues };
257
257
  }
258
- function currentShimFingerprint() {
259
- const resolution = (0, sdk_1.resolveCrewxExecutable)();
260
- if (!resolution.ok)
261
- return undefined;
262
- return (0, node_crypto_1.createHash)('sha256')
263
- .update(JSON.stringify(resolution.argv))
264
- .digest('hex')
265
- .slice(0, 16);
258
+ function configuredBinResolution(configPath, fallback = 'global_first') {
259
+ try {
260
+ const config = (0, sdk_1.loadYamlFile)(configPath);
261
+ const value = config.settings?.bin_resolution;
262
+ return value === 'local_first' || value === 'global_first' ? value : fallback;
263
+ }
264
+ catch {
265
+ return fallback;
266
+ }
267
+ }
268
+ function readWindowsCrewxPathCandidates(cwd, env, platform) {
269
+ if (platform !== 'win32')
270
+ return [];
271
+ try {
272
+ (0, sdk_1.primeWindowsConsoleCodePage)();
273
+ const output = (0, node_child_process_1.execFileSync)('where.exe', ['crewx'], {
274
+ cwd,
275
+ env,
276
+ encoding: null,
277
+ stdio: ['ignore', 'pipe', 'ignore'],
278
+ });
279
+ return (0, sdk_1.decodeConsoleBuffer)(output, { platform })
280
+ .split(/\r?\n/)
281
+ .map((line) => line.trim())
282
+ .filter((line) => line.length > 0);
283
+ }
284
+ catch {
285
+ return [];
286
+ }
287
+ }
288
+ /** Inspect the SDK-selected CrewX executable without printing environment values. */
289
+ function inspectCrewxExecutable(options = {}) {
290
+ const platform = options.platform ?? process.platform;
291
+ const cwd = (0, node_path_1.resolve)(options.cwd ?? process.cwd());
292
+ const env = options.env ?? process.env;
293
+ const configPath = options.configPath ?? (0, node_path_1.join)(cwd, 'crewx.yaml');
294
+ const resolution = options.resolution ?? (0, sdk_1.resolveCrewxExecutable)({ env, cwd, platform });
295
+ const pathCandidates = options.pathCandidates
296
+ ? [...options.pathCandidates]
297
+ : readWindowsCrewxPathCandidates(cwd, env, platform);
298
+ const requiredEnvKeys = resolution.ok
299
+ ? Object.keys(resolution.requiredEnv ?? {})
300
+ .filter((key) => key === 'ELECTRON_RUN_AS_NODE')
301
+ : [];
302
+ return {
303
+ platform,
304
+ pathCandidates,
305
+ ...(resolution.ok
306
+ ? {
307
+ stage: resolution.stage,
308
+ argv: [...resolution.argv],
309
+ adoptedPath: resolution.argv[0],
310
+ }
311
+ : { resolutionError: resolution.reason }),
312
+ electron: requiredEnvKeys.includes('ELECTRON_RUN_AS_NODE'),
313
+ requiredEnvKeys,
314
+ binResolution: options.binResolution ?? configuredBinResolution(configPath),
315
+ };
316
+ }
317
+ function checkCrewxExecutable(report) {
318
+ const details = [
319
+ `platform: ${report.platform}`,
320
+ `stage: ${report.stage ?? '(unresolved)'}`,
321
+ `argv: ${report.argv ? (0, sdk_1.formatCrewxExecutableArgv)(report.argv) : '(unresolved)'}`,
322
+ `adopted path: ${report.adoptedPath ?? '(none)'}`,
323
+ `Electron runtime: ${report.electron ? 'yes' : 'no'}`,
324
+ `required environment keys: ${report.requiredEnvKeys.length > 0 ? report.requiredEnvKeys.join(', ') : '(none)'}`,
325
+ `bin_resolution setting: ${report.binResolution}`,
326
+ ...(report.platform === 'win32'
327
+ ? [`where.exe crewx candidates: ${report.pathCandidates.length > 0 ? report.pathCandidates.join(', ') : '(none)'}`]
328
+ : []),
329
+ ];
330
+ if (report.resolutionError) {
331
+ return {
332
+ name: 'CrewX Executable',
333
+ status: 'warning',
334
+ message: 'Could not resolve the CrewX executable',
335
+ details: `${details.join('\n')}\nResolution failed; no environment values were printed.`,
336
+ };
337
+ }
338
+ return {
339
+ name: 'CrewX Executable',
340
+ status: 'success',
341
+ message: 'SDK child executable contract is available',
342
+ details: details.join('\n'),
343
+ };
266
344
  }
267
345
  /** Inspect orphaned CrewX PATH shim directories without modifying them. */
268
- function inspectCrewxShims(crewxHome = (0, sdk_1.getCrewxHome)(), resolvedFingerprint = currentShimFingerprint()) {
346
+ function inspectCrewxShims(crewxHome = (0, sdk_1.getCrewxHome)(), resolvedFingerprint = (() => {
347
+ const resolution = (0, sdk_1.resolveCrewxExecutable)();
348
+ return resolution.ok ? (0, sdk_1.crewxCliShimFingerprint)(resolution) : undefined;
349
+ })()) {
269
350
  const shimRoot = (0, node_path_1.join)((0, node_path_1.resolve)(crewxHome), 'bin-shim');
270
351
  const staleDirectories = [];
271
352
  if (resolvedFingerprint) {
@@ -526,6 +607,7 @@ async function handleDoctor(args) {
526
607
  let shims = inspectCrewxShims();
527
608
  const projectRoot = (0, node_path_1.dirname)((0, node_path_1.resolve)(configPath));
528
609
  const hooks = inspectHookCommands(projectRoot);
610
+ const executable = inspectCrewxExecutable({ configPath, cwd: process.cwd() });
529
611
  if (cleanNpxCache) {
530
612
  cleanDoctorArtifacts(npxCache, shims);
531
613
  npxCache = inspectNpxCache();
@@ -535,6 +617,7 @@ async function handleDoctor(args) {
535
617
  checkConfig(configPath),
536
618
  checkLogsDir(),
537
619
  checkEnvVars(),
620
+ checkCrewxExecutable(executable),
538
621
  ...checkCliProviders(),
539
622
  checkNpxCache(npxCache),
540
623
  checkHookCommands(hooks),
@@ -32,9 +32,26 @@ function resolveCrewxBinary() {
32
32
  if (!resolution.ok) {
33
33
  throw new Error((0, sdk_1.formatCrewxExecutableFailure)(resolution));
34
34
  }
35
- const stableEntrypoint = (0, sdk_1.materializeCrewxStableEntrypoint)(resolution);
36
- const useStable = stableEntrypoint && resolution.argv.some(sdk_1.isRotatingInstallPath);
37
- return (0, sdk_1.formatCrewxExecutableArgv)(useStable ? [stableEntrypoint] : resolution.argv);
35
+ let stableEntrypoint;
36
+ try {
37
+ stableEntrypoint = (0, sdk_1.materializeCrewxStableEntrypoint)(resolution, { allowEnvStage: true });
38
+ }
39
+ catch (error) {
40
+ if (resolution.requiredEnv && Object.keys(resolution.requiredEnv).length > 0) {
41
+ throw new Error(`CrewX hook entrypoint could not preserve required environment: ${error instanceof Error ? error.message : String(error)}`);
42
+ }
43
+ throw error;
44
+ }
45
+ if (stableEntrypoint) {
46
+ return (0, sdk_1.formatCrewxExecutableArgv)([stableEntrypoint]);
47
+ }
48
+ // A required runtime environment cannot be represented by a raw argv string,
49
+ // and the fingerprint shim directory is doctor-GC managed. Refuse to write
50
+ // either representation when the stable hook path was not eligible.
51
+ if (resolution.requiredEnv && Object.keys(resolution.requiredEnv).length > 0) {
52
+ throw new Error('CrewX hook entrypoint could not preserve required environment: stable entrypoint is not eligible for this executable');
53
+ }
54
+ return (0, sdk_1.formatCrewxExecutableArgv)(resolution.argv);
38
55
  }
39
56
  function readSettingsTyped(settingsPath) {
40
57
  return (0, paths_1.readSettings)(settingsPath);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewx/cli",
3
- "version": "0.9.0-rc.111",
3
+ "version": "0.9.0-rc.112",
4
4
  "license": "UNLICENSED",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"