@nowcrew/daemon 0.6.91 → 0.6.92

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.
@@ -1,4 +1,4 @@
1
- import { delimiter, join } from "node:path";
1
+ import { delimiter, join, resolve } from "node:path";
2
2
  import { environmentForExecution } from "./agent-environments.js";
3
3
  import { augmentedPath } from "./runtime-path.js";
4
4
  import { sanitizeEnvVars } from "./runtime-env.js";
@@ -10,6 +10,7 @@ export function buildLocalRuntimeEnvironment(context) {
10
10
  const { input, workspace, executionWorkspace, inheritedEnv, providerConfig, crewHome, contextThreadId, nativeThreadContextPath, reportContext, runtime } = context;
11
11
  const agentEnvironment = sanitizeEnvVars(providerConfig.environmentPlan === undefined ? providerConfig.envVars
12
12
  : environmentForExecution(providerConfig.environmentPlan, workspace.dir, input.projectContext?.primary?.root ?? executionWorkspace.runDir, input.projectContext?.primary?.projectId));
13
+ const boundImDecisionFile = input.launch.systemEnv?.CREW_BOUND_IM_DECISION_FILE;
13
14
  return mergeRuntimeEnvironments([
14
15
  inheritedEnv, agentEnvironment,
15
16
  input.trainingProjectContext === undefined ? {} : machinePackageManagerEnv(inheritedEnv),
@@ -35,6 +36,10 @@ export function buildLocalRuntimeEnvironment(context) {
35
36
  QA_REPORT_DELIVERY_CONTEXT: JSON.stringify(reportContext),
36
37
  CREW_TASK_DIR: executionWorkspace.runDir,
37
38
  CREW_TASK_LOG: executionWorkspace.workLogPath,
39
+ // CLI 可以从 Agent 主目录或项目目录调用,标记始终写回完成阶段读取的任务目录。
40
+ ...(boundImDecisionFile ? {
41
+ CREW_BOUND_IM_DECISION_FILE: resolve(executionWorkspace.runDir, boundImDecisionFile),
42
+ } : {}),
38
43
  ...(input.wakeMessageId ? { CREW_WAKE_MESSAGE_ID: input.wakeMessageId } : {}),
39
44
  XDG_CONFIG_HOME: join(workspace.homeDir, ".config"),
40
45
  XDG_DATA_HOME: join(workspace.homeDir, ".local", "share"),
@@ -31,6 +31,7 @@ export function createMachineEnvironmentProvider(options = {}) {
31
31
  let lastFailureTime = -Infinity;
32
32
  let stopped = false;
33
33
  let unsafeCleanup = false;
34
+ let isCleanupComplete;
34
35
  let flight;
35
36
  let controller;
36
37
  let captureEpoch = 0;
@@ -50,8 +51,19 @@ export function createMachineEnvironmentProvider(options = {}) {
50
51
  return module.captureWindowsMachineEnvironment(request);
51
52
  });
52
53
  const refresh = async (force, signal) => {
53
- if (!selected.source || stopped || unsafeCleanup)
54
+ if (!selected.source || stopped)
54
55
  return snapshot;
56
+ if (unsafeCleanup) {
57
+ try {
58
+ if (!isCleanupComplete?.())
59
+ return snapshot;
60
+ }
61
+ catch {
62
+ return snapshot;
63
+ }
64
+ unsafeCleanup = false;
65
+ isCleanupComplete = undefined;
66
+ }
55
67
  if (!force && now() - lastFailureTime < (options.backoffMs ?? 30_000))
56
68
  return snapshot;
57
69
  const attemptedAt = new Date(now()).toISOString();
@@ -64,7 +76,7 @@ export function createMachineEnvironmentProvider(options = {}) {
64
76
  return snapshot;
65
77
  publish(snapshot.env, { state: "loading", attemptedAt, errorCode: null });
66
78
  capturing = true;
67
- const deadline = Date.now() + (options.captureTimeoutMs ?? (selected.source === "windows-registry" ? 8_000 : 5_000));
79
+ const deadline = Date.now() + (options.captureTimeoutMs ?? (selected.source === "windows-registry" ? 8_000 : 10_000));
68
80
  const capture = selected.source === "windows-registry" ? captureWindows : options.captureShell ?? captureShellMachineEnvironment;
69
81
  for (let attempt = 0; attempt < 2; attempt++) {
70
82
  try {
@@ -111,8 +123,10 @@ export function createMachineEnvironmentProvider(options = {}) {
111
123
  }
112
124
  catch (error) {
113
125
  const errorCode = error instanceof MachineEnvironmentCaptureError ? error.code : selected.source === "windows-registry" ? "windows_environment_unavailable" : "shell_capture_failed";
114
- if (errorCode === "shell_cleanup_failed")
126
+ if (errorCode === "shell_cleanup_failed") {
115
127
  unsafeCleanup = true;
128
+ isCleanupComplete = error instanceof MachineEnvironmentCaptureError ? error.isCleanupComplete : undefined;
129
+ }
116
130
  lastFailureTime = now();
117
131
  return publish(lastGood?.env ?? baseline, { state: lastGood ? "stale" : "fallback", errorCode, attemptedAt });
118
132
  }
@@ -129,7 +143,7 @@ export function createMachineEnvironmentProvider(options = {}) {
129
143
  }
130
144
  return flight;
131
145
  }
132
- if (stopped || unsafeCleanup || selected.source === null)
146
+ if (stopped || selected.source === null)
133
147
  return Promise.resolve(snapshot);
134
148
  controller = new AbortController();
135
149
  flight = refresh(acquireOptions?.force === true, controller.signal).finally(() => { flight = undefined; controller = undefined; });
@@ -1,8 +1,10 @@
1
1
  export class MachineEnvironmentCaptureError extends Error {
2
2
  code;
3
- constructor(code) {
3
+ isCleanupComplete;
4
+ constructor(code, isCleanupComplete) {
4
5
  super(code);
5
6
  this.code = code;
7
+ this.isCleanupComplete = isCleanupComplete;
6
8
  this.name = "MachineEnvironmentCaptureError";
7
9
  }
8
10
  }
@@ -44,7 +44,11 @@ export async function runMachineEnvironmentProcess(options) {
44
44
  stdio[collectFd] = "pipe";
45
45
  if (options.metadataFd !== undefined)
46
46
  stdio[options.metadataFd] = "pipe";
47
- const child = spawn(options.file, [...options.args], { cwd: options.cwd, env: options.env, detached: true, stdio });
47
+ // launchd Background 的 I/O 策略会让 shell 初始化停滞;只调整短命采集子进程。
48
+ // taskpolicy 原地 exec,保持同一 PID/进程组、环境和私有管道。
49
+ const file = process.platform === "darwin" ? "/usr/sbin/taskpolicy" : options.file;
50
+ const args = process.platform === "darwin" ? ["-a", "--", options.file, ...options.args] : [...options.args];
51
+ const child = spawn(file, args, { cwd: options.cwd, env: options.env, detached: true, stdio });
48
52
  const chunks = [];
49
53
  let size = 0;
50
54
  const metadata = [];
@@ -102,6 +106,22 @@ export async function runMachineEnvironmentProcess(options) {
102
106
  try {
103
107
  await cleanupOnce();
104
108
  }
109
+ catch (error) {
110
+ const pid = child.pid;
111
+ if (error instanceof MachineEnvironmentCaptureError && error.code === "shell_cleanup_failed" && pid !== undefined) {
112
+ // 后续只验证旧进程组消失,不对可能复用的 PID 再发送终止信号。
113
+ throw new MachineEnvironmentCaptureError("shell_cleanup_failed", () => {
114
+ try {
115
+ process.kill(-pid, 0);
116
+ return false;
117
+ }
118
+ catch (cause) {
119
+ return cause.code === "ESRCH";
120
+ }
121
+ });
122
+ }
123
+ throw error;
124
+ }
105
125
  finally {
106
126
  for (const stream of child.stdio)
107
127
  stream?.destroy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.91",
3
+ "version": "0.6.92",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",