@nowcrew/daemon 0.6.25 → 0.6.28

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,7 @@
1
+ import { awaitWithCancellation } from "./runtime-cancellation.js";
2
+ import { materializeDefaultCodexHome } from "./runtimes/codex-home.js";
3
+ export async function materializePrivateCodexHome(input) {
4
+ if (input.runtime !== "codex" || input.mode !== "private")
5
+ return;
6
+ await awaitWithCancellation((input.materialize ?? materializeDefaultCodexHome)(input.homeDir, input.sourceHome, input.sessionId), input.cancellation);
7
+ }
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ import { daemonHome, loadProfile, saveProfile } from "./computer-profile.js";
3
+ const MessageSchema = z.object({
4
+ type: z.literal("daemon:codex-home-mode"),
5
+ operationId: z.string().uuid(),
6
+ mode: z.enum(["shared", "private"]),
7
+ }).strict();
8
+ export function createCodexHomeModeController(input) {
9
+ let running = null;
10
+ const handled = new Set();
11
+ const fail = (operationId, errorCode) => {
12
+ input.sendStatus({ type: "daemon:codex-home-mode-status", operationId, status: "failed", errorCode });
13
+ };
14
+ const execute = async (message) => {
15
+ if (input.profileName === undefined) {
16
+ fail(message.operationId, "profile_unavailable");
17
+ return;
18
+ }
19
+ if (input.isBusy()) {
20
+ fail(message.operationId, "busy");
21
+ return;
22
+ }
23
+ input.sendStatus({ type: "daemon:codex-home-mode-status", operationId: message.operationId, status: "saving" });
24
+ try {
25
+ const home = input.profileHome ?? daemonHome();
26
+ const profile = await loadProfile(input.profileName, home);
27
+ await saveProfile({ ...profile, codexHomeMode: message.mode }, home);
28
+ }
29
+ catch {
30
+ fail(message.operationId, "save_failed");
31
+ return;
32
+ }
33
+ input.sendStatus({ type: "daemon:codex-home-mode-status", operationId: message.operationId, status: "restarting" });
34
+ try {
35
+ const serviceSpec = await input.getServiceSpec();
36
+ if (serviceSpec === null) {
37
+ fail(message.operationId, "restart_failed");
38
+ return;
39
+ }
40
+ const handoff = input.scheduleRestart(serviceSpec);
41
+ void handoff.failed.then(() => fail(message.operationId, "restart_failed"), () => fail(message.operationId, "restart_failed"));
42
+ }
43
+ catch {
44
+ fail(message.operationId, "restart_failed");
45
+ }
46
+ };
47
+ return {
48
+ handle: async (raw) => {
49
+ const parsed = MessageSchema.safeParse(raw);
50
+ if (!parsed.success)
51
+ return false;
52
+ if (handled.has(parsed.data.operationId))
53
+ return true;
54
+ if (running !== null) {
55
+ fail(parsed.data.operationId, "busy");
56
+ handled.add(parsed.data.operationId);
57
+ return true;
58
+ }
59
+ running = execute(parsed.data).finally(() => {
60
+ handled.add(parsed.data.operationId);
61
+ running = null;
62
+ });
63
+ await running;
64
+ return true;
65
+ },
66
+ drain: async () => { await running; },
67
+ };
68
+ }
@@ -21,6 +21,7 @@ const ProfileSchema = z.object({
21
21
  machineToken: MachineTokenSchema,
22
22
  agentsRoot: z.string().min(1).optional(),
23
23
  runtimePath: z.string().min(1).max(32768).refine((value) => !value.includes("\0") && !value.includes("\n") && !value.includes("\r"), "runtimePath must be a single line").optional(),
24
+ codexHomeMode: z.enum(["shared", "private"]).optional(),
24
25
  }).strict();
25
26
  const ProfileAgentsRootCandidateSchema = ProfileSchema.pick({
26
27
  name: true,
@@ -217,6 +218,7 @@ export async function saveProfile(input, home = daemonHome(), options = {}) {
217
218
  serverUrl: profile.serverUrl,
218
219
  ...(profile.agentsRoot ? { agentsRoot: profile.agentsRoot } : {}),
219
220
  ...(profile.runtimePath ? { runtimePath: profile.runtimePath } : {}),
221
+ ...(profile.codexHomeMode === undefined ? {} : { codexHomeMode: profile.codexHomeMode }),
220
222
  machineTokenProtected: { scheme: "dpapi-current-user", ciphertext },
221
223
  });
222
224
  await atomicPrivateWrite(profilePath(profile.name, home), `${JSON.stringify(stored, null, 2)}\n`, configured.harden);
@@ -353,4 +355,5 @@ export function applyProfileToEnv(profile, env) {
353
355
  delete env.CREW_AGENTS_ROOT;
354
356
  if (profile.runtimePath)
355
357
  env.PATH = profile.runtimePath;
358
+ env.CREW_CODEX_HOME_MODE = profile.codexHomeMode ?? "shared";
356
359
  }
package/dist/config.js CHANGED
@@ -57,6 +57,14 @@ function defaultCliPath() {
57
57
  }
58
58
  }
59
59
  export const DEFAULT_SERVER_URL = "http://127.0.0.1:3001";
60
+ function codexHomeModeEnv(env) {
61
+ const value = env.CREW_CODEX_HOME_MODE;
62
+ if (value === undefined || value === "shared")
63
+ return "shared";
64
+ if (value === "private")
65
+ return "private";
66
+ throw new ConfigError("CREW_CODEX_HOME_MODE must be shared or private");
67
+ }
60
68
  export function loadConfig(env = process.env) {
61
69
  const serverUrl = (env.CREW_SERVER_URL ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
62
70
  const machineToken = env.CREW_MACHINE_TOKEN ?? "";
@@ -84,6 +92,7 @@ export function loadConfig(env = process.env) {
84
92
  agentsRoot: resolveAgentsRoot(env.CREW_AGENTS_ROOT, homedir()),
85
93
  cliPath: env.CREW_CLI_PATH ?? defaultCliPath(),
86
94
  runtimeBin: env.CREW_RUNTIME ?? "claude",
95
+ codexHomeMode: codexHomeModeEnv(env),
87
96
  dangerous: env.CREW_RUNTIME_SAFE !== "1", // 默认开启 (headless agent 在自有 workspace 内运行)
88
97
  resume: env.CREW_RESUME !== "off" && env.CREW_RESUME !== "0", // 默认开启;一键回退现状用 CREW_RESUME=off
89
98
  resumeWarmMs: env.CREW_RESUME_WARM_MS != null ? Number(env.CREW_RESUME_WARM_MS) : 3_600_000, // 默认 1h
@@ -716,6 +716,7 @@ export async function runExecution(config, input, dependencies) {
716
716
  agentsRoot: config.agentsRoot,
717
717
  cliPath: config.cliPath,
718
718
  providerConfig,
719
+ codexHomeMode: config.codexHomeMode,
719
720
  ...(providerConfig.description ? { description: providerConfig.description } : {}),
720
721
  ...(spec.reporting.allowBoundImDecision ? {
721
722
  systemEnv: {
@@ -6,6 +6,7 @@ import { CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV, isClaudeAdditionalDirecto
6
6
  import { spawnCodex } from "./runtimes/codex.js";
7
7
  import { DEEPSEEK_CODEX_MODEL, DEEPSEEK_CODEX_REASONING_LEVELS, materializeDeepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
8
8
  import { materializeDefaultCodexHome } from "./runtimes/codex-home.js";
9
+ import { materializePrivateCodexHome } from "./codex-home-launch.js";
9
10
  import { spawnKimi, KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
10
11
  import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint, } from "./provider-env.js";
11
12
  import { augmentedPath } from "./runtime-path.js";
@@ -27,6 +28,7 @@ import { projectSkillRuntimeDirectories, redactProjectSkillRuntimeRootText, reda
27
28
  import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPruneFilesWithinDeadline, parseMemoryPruneTraceId, } from "./memory-prune-diagnostics.js";
28
29
  import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
29
30
  import { CodexStartupStageParser } from "./codex-startup-stage.js";
31
+ import { formatRuntimeStartupError } from "./runtime-startup-error.js";
30
32
  function memoryPruneFileFactFields(fileFacts) {
31
33
  const fields = {};
32
34
  for (const [label, fact] of Object.entries(fileFacts)) {
@@ -348,9 +350,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
348
350
  const launchSessionId = resumeSessionId ?? (rotated
349
351
  ? await rotateAgentSession(workspace.sessionDir)
350
352
  : workspace.agentSessionId);
351
- if (runtime.name === "codex") {
352
- await awaitWithCancellation((dependencies.materializeDefaultCodexHome ?? materializeDefaultCodexHome)(workspace.homeDir, sourceCodexHome, launchSessionId), dependencies.cancellation);
353
- }
353
+ await materializePrivateCodexHome({ runtime: runtime.name, homeDir: workspace.homeDir, ...(input.launch.codexHomeMode === undefined ? {} : { mode: input.launch.codexHomeMode }), ...(sourceCodexHome === undefined ? {} : { sourceHome: sourceCodexHome }), ...(launchSessionId === undefined ? {} : { sessionId: launchSessionId }), ...(dependencies.cancellation === undefined ? {} : { cancellation: dependencies.cancellation }), ...(dependencies.materializeDefaultCodexHome === undefined ? {} : { materialize: dependencies.materializeDefaultCodexHome }) });
354
354
  const promptContext = {
355
355
  workspace: executionWorkspace,
356
356
  resuming,
@@ -470,7 +470,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
470
470
  ? { KIMI_MODEL_THINKING_EFFORT: runtime.reasoning }
471
471
  : {}),
472
472
  };
473
- const providerEnv = applyProviderEnv(baseEnv, runtime.name, providerConfig, workspace.homeDir);
473
+ const providerEnv = applyProviderEnv(baseEnv, runtime.name, input.launch.codexHomeMode === undefined
474
+ ? providerConfig
475
+ : { ...providerConfig, codexHomeMode: input.launch.codexHomeMode }, workspace.homeDir);
474
476
  const childEnv = runtime.name === "deepseek-harness"
475
477
  ? applyDeepSeekHarnessMachineEnv(providerEnv, inheritedEnv)
476
478
  : providerEnv;
@@ -755,9 +757,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
755
757
  await child.cancel?.();
756
758
  }
757
759
  startupReservation.release();
758
- throw new Error(startupOutcome.kind === "timeout"
759
- ? `${runtime.name} startup timed out`
760
- : `${runtime.name} exited before signaling ready (exit ${startupOutcome.exit.exitCode})`);
760
+ throw new Error(formatRuntimeStartupError(runtime.name, startupOutcome, stderrTail, codexStartupStageState.last));
761
761
  }
762
762
  await runtimeReadyNotification;
763
763
  }
@@ -183,6 +183,7 @@ export async function collectMachineHello(agentsRoot, executionLimits, runtimePl
183
183
  runtimes,
184
184
  executionRuntimes,
185
185
  capabilities,
186
+ ...(dependencies.codexHomeMode === undefined ? {} : { codexHomeMode: dependencies.codexHomeMode }),
186
187
  ...(dependencies.profileName === undefined ? {} : {
187
188
  layoutVersion: 1,
188
189
  profileName: dependencies.profileName,
@@ -50,7 +50,9 @@ export function applyProviderEnv(base, runtime, cfg, homeDir) {
50
50
  return env;
51
51
  }
52
52
  if (runtime === "codex") {
53
- return { ...base, CODEX_HOME: join(homeDir, ".codex") };
53
+ if (cfg.codexHomeMode === "private")
54
+ return { ...base, CODEX_HOME: join(homeDir, ".codex") };
55
+ return base;
54
56
  }
55
57
  if (runtime !== "claude" || cfg.provider !== "custom")
56
58
  return base;
package/dist/runner.js CHANGED
@@ -84,6 +84,7 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
84
84
  agentsRoot: config.agentsRoot,
85
85
  cliPath: config.cliPath,
86
86
  providerConfig,
87
+ codexHomeMode: config.codexHomeMode,
87
88
  ...(providerConfig.description ? { description: providerConfig.description } : {}),
88
89
  systemEnv: {
89
90
  ...(providerConfig.fastMode ? { CREW_FAST_MODE: "1" } : {}),
@@ -0,0 +1,18 @@
1
+ export function formatRuntimeStartupError(runtime, outcome, stderrTail, lastStage) {
2
+ if (outcome.kind === "timeout") {
3
+ const detail = [
4
+ lastStage === null
5
+ ? undefined
6
+ : `last_stage=${lastStage.stage} status=${lastStage.status} attempt=${lastStage.attempt}`,
7
+ stderrTail.trim() === "" ? undefined : `stderr: ${stderrTail.trim()}`,
8
+ ].filter(Boolean).join("; ");
9
+ return `${runtime} startup timed out${detail === "" ? "" : ` (${detail})`}`;
10
+ }
11
+ const detail = [
12
+ `exit ${outcome.exit.exitCode}`,
13
+ outcome.exit.spawnError === undefined ? undefined : `spawn_error: ${outcome.exit.spawnError}`,
14
+ outcome.exit.terminationSignal === undefined ? undefined : `signal: ${outcome.exit.terminationSignal}`,
15
+ stderrTail.trim() === "" ? undefined : `stderr: ${stderrTail.trim()}`,
16
+ ].filter(Boolean).join("; ");
17
+ return `${runtime} exited before signaling ready (${detail})`;
18
+ }
package/dist/serve.js CHANGED
@@ -49,6 +49,7 @@ import { parseMemoryPruneTraceId } from "./memory-prune-diagnostics.js";
49
49
  import { handleRuntimeProbeFrame, probeRuntimeHealth } from "./runtime-health.js";
50
50
  import { buildControlPlaneUrl } from "./control-plane-url.js";
51
51
  import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
52
+ import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
52
53
  export { buildControlPlaneUrl } from "./control-plane-url.js";
53
54
  // normalize.ts 的活动种类 → activity 枚举
54
55
  const ACTIVITY_MAP = {
@@ -121,6 +122,11 @@ export function serve(config, opts = {}) {
121
122
  }
122
123
  catch { /* reconnect */ } },
123
124
  });
125
+ const codexHomeModeController = createCodexHomeModeController({ ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }), scheduleRestart: opts.update?.scheduleRestart ?? scheduleServiceRestart, getServiceSpec: async () => { const eligibility = await updateEligibility(); return eligibility.eligible ? eligibility.serviceSpec : null; }, isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0, sendStatus: (frame) => { try {
126
+ if (ws?.readyState === WebSocket.OPEN)
127
+ ws.send(JSON.stringify(frame));
128
+ }
129
+ catch { /* reconnect */ } } });
124
130
  const projectionCoordinator = createAgentProjectionCoordinator();
125
131
  const agentAbilityRuntime = createAgentAbilityRuntime(config.agentsRoot, {
126
132
  ...opts.agentAbility,
@@ -299,7 +305,9 @@ export function serve(config, opts = {}) {
299
305
  ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
300
306
  // First hello must not wait for third-party handshakes; optional transports arrive in the refresh.
301
307
  detectExecutable: async (installed) => conservativeExecutionRuntimes(installed),
308
+ additionalCapabilities: async () => [...(await managedDaemonCapabilities(updateEligibility)), ...(opts.profileName === undefined ? [] : ["codex_home_mode_v1"])],
302
309
  capabilities: capabilities.machineHello,
310
+ codexHomeMode: config.codexHomeMode,
303
311
  });
304
312
  runtimeFacts = helloPromise
305
313
  .then(async (hello) => {
@@ -380,6 +388,10 @@ export function serve(config, opts = {}) {
380
388
  void handleMigrationControlMessage(migrationController, decoded).catch((error) => dslog("daemon.migration_failed", "daemon 布局迁移失败", { level: "ERROR", error_message: error.message }));
381
389
  return;
382
390
  }
391
+ if (rawType === "daemon:codex-home-mode") {
392
+ void codexHomeModeController.handle(decoded).catch((error) => dslog("daemon.codex_home_mode_failed", "Codex Home 模式切换失败", { level: "ERROR", error_message: error.message }));
393
+ return;
394
+ }
383
395
  if (rawType.startsWith("execution:")) {
384
396
  const parsedExecution = ServerToDaemonExecutionFrameSchema.safeParse(decoded);
385
397
  if (!parsedExecution.success) {
@@ -1147,6 +1159,7 @@ export function serve(config, opts = {}) {
1147
1159
  webSocketClosed,
1148
1160
  updateController.drain(),
1149
1161
  ...(migrationController === null ? [] : [migrationController.drain()]),
1162
+ codexHomeModeController.drain(),
1150
1163
  executionFrameQueue,
1151
1164
  ...executionRuns.values(),
1152
1165
  ...[...legacyRuns.values()].map((run) => run.done),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.25",
3
+ "version": "0.6.28",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",