@nowcrew/daemon 0.6.25 → 0.6.27

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";
@@ -348,9 +349,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
348
349
  const launchSessionId = resumeSessionId ?? (rotated
349
350
  ? await rotateAgentSession(workspace.sessionDir)
350
351
  : workspace.agentSessionId);
351
- if (runtime.name === "codex") {
352
- await awaitWithCancellation((dependencies.materializeDefaultCodexHome ?? materializeDefaultCodexHome)(workspace.homeDir, sourceCodexHome, launchSessionId), dependencies.cancellation);
353
- }
352
+ 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
353
  const promptContext = {
355
354
  workspace: executionWorkspace,
356
355
  resuming,
@@ -470,7 +469,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
470
469
  ? { KIMI_MODEL_THINKING_EFFORT: runtime.reasoning }
471
470
  : {}),
472
471
  };
473
- const providerEnv = applyProviderEnv(baseEnv, runtime.name, providerConfig, workspace.homeDir);
472
+ const providerEnv = applyProviderEnv(baseEnv, runtime.name, input.launch.codexHomeMode === undefined
473
+ ? providerConfig
474
+ : { ...providerConfig, codexHomeMode: input.launch.codexHomeMode }, workspace.homeDir);
474
475
  const childEnv = runtime.name === "deepseek-harness"
475
476
  ? applyDeepSeekHarnessMachineEnv(providerEnv, inheritedEnv)
476
477
  : providerEnv;
@@ -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,
package/dist/main.js CHANGED
File without changes
@@ -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" } : {}),
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.27",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",
@@ -16,6 +16,14 @@
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  },
19
+ "scripts": {
20
+ "daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
21
+ "build": "tsc -p tsconfig.json",
22
+ "codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
23
+ "prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
24
+ "test": "vitest run",
25
+ "typecheck": "tsc --noEmit"
26
+ },
19
27
  "dependencies": {
20
28
  "@agentclientprotocol/sdk": "1.2.1",
21
29
  "@nowcrew/cli": "^0.4.13",
@@ -34,12 +42,5 @@
34
42
  "tsx": "^4.19.0",
35
43
  "typescript": "^5.6.0",
36
44
  "vitest": "^2.1.0"
37
- },
38
- "scripts": {
39
- "daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
40
- "build": "tsc -p tsconfig.json",
41
- "codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
42
- "test": "vitest run",
43
- "typecheck": "tsc --noEmit"
44
45
  }
45
- }
46
+ }