@nowcrew/daemon 0.6.67 → 0.6.68

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.
Files changed (61) hide show
  1. package/dist/agent-ability/materializer.js +1 -20
  2. package/dist/agent-ability/resolver.js +10 -7
  3. package/dist/agent-ability/runtime.js +0 -3
  4. package/dist/automatic-install-target.js +10 -15
  5. package/dist/computer-cli.js +1 -10
  6. package/dist/computer-service.js +1 -41
  7. package/dist/config.js +0 -44
  8. package/dist/daemon-migration-controller.js +18 -16
  9. package/dist/execution-protocol.js +1 -0
  10. package/dist/execution-runner.js +8 -5
  11. package/dist/local-execution-prompt.js +11 -15
  12. package/dist/local-executor.js +53 -93
  13. package/dist/machine-info.js +4 -3
  14. package/dist/main.js +0 -83
  15. package/dist/managed-service-lifecycle.js +2 -47
  16. package/dist/managed-service-startup.js +0 -2
  17. package/dist/profile-layout.js +4 -10
  18. package/dist/project-skills/controller.js +0 -2
  19. package/dist/prompt.js +1 -19
  20. package/dist/provider-env.js +21 -3
  21. package/dist/runner.js +2 -4
  22. package/dist/runtimes/codex-app-server-runner.js +23 -55
  23. package/dist/runtimes/codex-contract.js +5 -20
  24. package/dist/runtimes/codex-deepseek-config.js +17 -7
  25. package/dist/runtimes/codex.js +3 -4
  26. package/dist/self-training-workspace.js +15 -0
  27. package/dist/serve.js +3 -68
  28. package/dist/supervised-runtime.js +1 -3
  29. package/dist/workspace.js +14 -34
  30. package/package.json +1 -1
  31. package/dist/local-memory/index-budget.js +0 -74
  32. package/dist/local-memory/pending.js +0 -111
  33. package/dist/local-memory/runtime.js +0 -87
  34. package/dist/local-memory/types.js +0 -1
  35. package/dist/local-memory/write.js +0 -448
  36. package/dist/runtimes/codex-command-safety.js +0 -75
  37. package/dist/runtimes/codex-turn-retry.js +0 -69
  38. package/dist/storage-config.js +0 -175
  39. package/dist/storage-control.js +0 -193
  40. package/dist/storage-copy.js +0 -609
  41. package/dist/storage-credential-guard.js +0 -21
  42. package/dist/storage-doctor.js +0 -228
  43. package/dist/storage-identity.js +0 -29
  44. package/dist/storage-layout.js +0 -143
  45. package/dist/storage-manifest.js +0 -855
  46. package/dist/storage-migration-controller.js +0 -417
  47. package/dist/storage-migration-manifest.js +0 -105
  48. package/dist/storage-migration-policy.js +0 -100
  49. package/dist/storage-runtime-controller.js +0 -339
  50. package/dist/storage-runtime-readiness.js +0 -184
  51. package/dist/storage-runtime.js +0 -39
  52. package/dist/storage-safe-fs.js +0 -302
  53. package/dist/storage-serve-adapter.js +0 -65
  54. package/dist/storage-status-delivery.js +0 -209
  55. package/dist/storage-status-replay.js +0 -125
  56. package/dist/storage-target-confirmation.js +0 -129
  57. package/dist/storage-worker-command.js +0 -27
  58. package/dist/storage-worker-executor.js +0 -384
  59. package/dist/storage-worker-handoff.js +0 -151
  60. package/dist/storage-worker-operation.js +0 -284
  61. package/dist/storage-worker-status.js +0 -232
@@ -11,6 +11,24 @@
11
11
  import { join } from "node:path";
12
12
  import { DEEPSEEK_CODEX_KEY_ENV, deepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
13
13
  import { DEEPSEEK_HARNESS_CONFIG_ENV } from "./runtimes/deepseek-harness.js";
14
+ const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
15
+ const RESERVED_ENV = new Set([
16
+ "PATH",
17
+ "XDG_CONFIG_HOME",
18
+ "XDG_DATA_HOME",
19
+ "XDG_CACHE_HOME",
20
+ "GH_CONFIG_DIR",
21
+ "CLOUDSDK_CONFIG",
22
+ ]);
23
+ /** Keep user-provided variables away from daemon-owned runtime and credential fields. */
24
+ export function sanitizeEnvVars(raw) {
25
+ if (!raw)
26
+ return {};
27
+ return Object.fromEntries(Object.entries(raw).filter(([key, value]) => typeof value === "string"
28
+ && ENV_KEY_RE.test(key)
29
+ && !key.toUpperCase().startsWith("CREW_")
30
+ && !RESERVED_ENV.has(key.toUpperCase())));
31
+ }
14
32
  /** custom 模式下必须从继承 env 中剔除的全局路由/凭证变量(避免与注入值叠加或抢优先级)。 */
15
33
  const CONFLICTING_ENV = [
16
34
  "ANTHROPIC_BASE_URL",
@@ -39,12 +57,12 @@ const CONFLICTING_CODEX_ENV = [
39
57
  DEEPSEEK_CODEX_KEY_ENV,
40
58
  ];
41
59
  /** 返回叠加了 provider 隔离的新 env(不改入参);不支持的 runtime/provider 组合原样返回。 */
42
- export function applyProviderEnv(base, runtime, cfg, homeDir) {
60
+ export function applyProviderEnv(base, runtime, cfg, homeDir, deepSeekHomeOverride) {
43
61
  if (runtime === "codex" && cfg.provider === "deepseek") {
44
62
  const env = { ...base };
45
63
  for (const key of CONFLICTING_CODEX_ENV)
46
64
  delete env[key];
47
- env.CODEX_HOME = deepSeekCodexHome(homeDir);
65
+ env.CODEX_HOME = deepSeekHomeOverride ?? deepSeekCodexHome(homeDir);
48
66
  if (cfg.providerApiKey)
49
67
  env[DEEPSEEK_CODEX_KEY_ENV] = cfg.providerApiKey;
50
68
  return env;
@@ -100,7 +118,7 @@ export function applyDeepSeekHarnessMachineEnv(base, machineEnv) {
100
118
  */
101
119
  export function providerFingerprint(runtime, cfg) {
102
120
  if (runtime === "codex" && cfg.provider === "deepseek") {
103
- return `deepseek|${cfg.providerApiKey ? "key" : "nokey"}`;
121
+ return `deepseek|${cfg.providerBaseUrl ?? "default"}|${cfg.providerApiKey ? "key" : "nokey"}`;
104
122
  }
105
123
  if (runtime !== "claude" || cfg.provider !== "custom")
106
124
  return null;
package/dist/runner.js CHANGED
@@ -10,7 +10,8 @@ import { readOriginDecisionFile, resetOriginDecisionFile } from "./origin-decisi
10
10
  import { readBoundImDecisionFile, resetBoundImDecisionFile } from "./bound-im-decision.js";
11
11
  import { launchSupervisedRuntime } from "./supervised-runtime.js";
12
12
  import { awaitWithCancellation, } from "./runtime-cancellation.js";
13
- export { awaitExit, exitActivity, sanitizeEnvVars } from "./local-executor.js";
13
+ export { awaitExit, exitActivity } from "./local-executor.js";
14
+ export { sanitizeEnvVars } from "./provider-env.js";
14
15
  const ICON = {
15
16
  init: "🟢", text: "💬", reading: "📖", sending: "📨", checking: "🔎",
16
17
  claiming: "📌", crew: "⚙️", tool: "🛠️", tool_result: "↩️", done: "✅", error: "❌",
@@ -58,7 +59,6 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
58
59
  ...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
59
60
  memory: capMemoryForInject(workspace.memory),
60
61
  ...(resuming ? {} : { workLog: capWorkLogForInject(workspace.workLog) }),
61
- collaborationToolsEnabled: config.codexCollaborationTools && runtime === "codex",
62
62
  }),
63
63
  wakePrompt: ({ resuming, rotatedForBudget, nearBudget }) => {
64
64
  const distillHint = nearBudget
@@ -75,7 +75,6 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
75
75
  name: runtime,
76
76
  ...(providerConfig.model ? { model: providerConfig.model } : {}),
77
77
  ...(reasoning.success ? { reasoning: reasoning.data } : {}),
78
- codexCollaborationTools: config.codexCollaborationTools,
79
78
  },
80
79
  effectivePermission: config.dangerous ? "full_access" : "workspace_write",
81
80
  captureFinal: true,
@@ -107,7 +106,6 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
107
106
  softTokens: config.sessionSoftTokens,
108
107
  maxTurns: config.sessionMaxTurns,
109
108
  },
110
- localMemoryV2Enabled: config.localMemoryV2Handles?.has(input.handle) === true,
111
109
  }, {
112
110
  onActivity,
113
111
  onConsole,
@@ -6,8 +6,7 @@ import { pathToFileURL } from "node:url";
6
6
  import spawn from "cross-spawn";
7
7
  import { z } from "zod";
8
8
  import { signalSupervisorTree } from "../execution-supervisor.js";
9
- import { assertCodexCollaborationVersion, assertCodexNativeWorkspaceVersion, assertCodexProjectWorkspaceResponse, assertCodexSkillsListResponse, boundedCodexPlan, codexProjectWorkspaceUnsupported, } from "./codex-contract.js";
10
- import { decideTurnRetry, itemMakesTurnUnsafe } from "./codex-turn-retry.js";
9
+ import { assertCodexNativeWorkspaceVersion, assertCodexProjectWorkspaceResponse, assertCodexSkillsListResponse, boundedCodexPlan, codexProjectWorkspaceUnsupported, } from "./codex-contract.js";
11
10
  import { startFirstProgressWatchdog } from "./progress-watchdog.js";
12
11
  const RunnerInputSchema = z.object({
13
12
  systemPrompt: z.string().min(1),
@@ -21,9 +20,13 @@ const RunnerInputSchema = z.object({
21
20
  cwd: z.string().min(1).optional(),
22
21
  workspaceRoots: z.array(z.string().min(1)).optional(),
23
22
  skillRoots: z.array(z.string().min(1)).optional(),
24
- codexCollaborationTools: z.boolean().optional(),
23
+ networkAccess: z.boolean().optional(),
25
24
  resume: z.boolean(),
26
- }).strict();
25
+ }).strict().superRefine((input, ctx) => {
26
+ if (input.networkAccess === true && input.effectivePermission !== "workspace_write") {
27
+ ctx.addIssue({ code: "custom", message: "network_access_requires_workspace_write" });
28
+ }
29
+ });
27
30
  const RPC_TIMEOUT_MS = 30_000;
28
31
  const INITIALIZE_RPC_TIMEOUT_MS = 60_000;
29
32
  const ERROR_MESSAGE_CAP = 2_000;
@@ -39,17 +42,12 @@ const MAX_TRANSIENT_TURN_RETRIES = 2;
39
42
  const TRANSIENT_TURN_RETRY_DELAY_MS = 15_000;
40
43
  const TRANSIENT_TURN_RETRY_BACKOFF_FACTOR = 3;
41
44
  const PROCESS_TREE_STOP_TIMEOUT_MS = 1_000;
42
- /** 进程级:一旦某个 collabAgentToolCall 的 item/completed 没有对应 item/started,本执行后续永不重试(spec R4-3)。 */
43
- let collabStartedMissing = false;
44
45
  const USER_INPUT_UNAVAILABLE_MESSAGE = "NowWork cannot collect interactive user input in this headless run; ask the user in the final response instead of assuming an answer.";
45
- export function codexAppServerArgs(input) {
46
+ export function codexAppServerArgs(projectRootMarkers) {
46
47
  return [
47
- ...(input.projectRootMarkers === undefined
48
+ ...(projectRootMarkers === undefined
48
49
  ? []
49
- : ["-c", `project_root_markers=${JSON.stringify(input.projectRootMarkers)}`]),
50
- // multi_agent 按开关;multi_agent_v2 无条件关,钉死已验证的 v1 契约(spec 2b)。
51
- "-c", `features.multi_agent=${input.collaborationTools === true}`,
52
- "-c", "features.multi_agent_v2=false",
50
+ : ["-c", `project_root_markers=${JSON.stringify(projectRootMarkers)}`]),
53
51
  "app-server", "--listen", "stdio://",
54
52
  ];
55
53
  }
@@ -60,8 +58,7 @@ const TRANSIENT_TURN_ERROR_PATTERNS = [
60
58
  /too many requests/i,
61
59
  ];
62
60
  /** 模型侧瞬态失败(网关过载/限流)→ 换个时间整轮重试大概率成功。
63
- * interrupted 单独归为 collab_interrupted:2026-08-10 实锤是模型自己调 collaboration.interrupt_agent
64
- * 连带把父 turn 置为 interrupted。两类原因都只在本 turn 无白名单外工具活动时重试(codex-turn-retry.ts)。 */
61
+ * interrupted 不算:那是取消信号或 codex collab 连带中断,重跑语义不明确。 */
65
62
  export function isTransientTurnFailure(detail) {
66
63
  return TRANSIENT_TURN_ERROR_PATTERNS.some((pattern) => pattern.test(detail));
67
64
  }
@@ -421,10 +418,7 @@ async function stopChildTree(child) {
421
418
  }
422
419
  async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs) {
423
420
  const attemptStartedAt = Date.now();
424
- const child = spawn(bin, codexAppServerArgs({
425
- ...(input.projectRootMarkers === undefined ? {} : { projectRootMarkers: input.projectRootMarkers }),
426
- ...(input.codexCollaborationTools === undefined ? {} : { collaborationTools: input.codexCollaborationTools }),
427
- }), {
421
+ const child = spawn(bin, codexAppServerArgs(input.projectRootMarkers), {
428
422
  cwd: process.cwd(),
429
423
  env: process.env,
430
424
  stdio: ["pipe", "pipe", "pipe"],
@@ -445,8 +439,6 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
445
439
  let threadId = null;
446
440
  let announcedThreadId = null;
447
441
  let turnId = null;
448
- let turnHasUnsafeSideEffect = false;
449
- const startedItemIds = new Set();
450
442
  let lastUsage;
451
443
  let completionResolve;
452
444
  let completionReject;
@@ -465,20 +457,6 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
465
457
  || method === "item/agentMessage/delta" || method === "item/completed") {
466
458
  firstProgress.observe();
467
459
  }
468
- if (method === "item/started" || method === "item/completed") {
469
- const item = params !== null && typeof params === "object"
470
- ? params.item
471
- : undefined;
472
- if (method === "item/started" && typeof item?.id === "string")
473
- startedItemIds.add(item.id);
474
- if (method === "item/completed" && item?.type === "collabAgentToolCall"
475
- && (typeof item.id !== "string" || !startedItemIds.has(item.id)) && !collabStartedMissing) {
476
- collabStartedMissing = true;
477
- process.stderr.write("[codex-app-server] collab_item_started_missing\n");
478
- }
479
- if (itemMakesTurnUnsafe(item))
480
- turnHasUnsafeSideEffect = true;
481
- }
482
460
  for (const event of mapCodexNotification(method, params)) {
483
461
  const eventThreadId = typeof event.thread_id === "string" ? event.thread_id : null;
484
462
  if (event.type === "thread.started" && eventThreadId === announcedThreadId)
@@ -523,8 +501,6 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
523
501
  if (input.workspaceRoots !== undefined || input.skillRoots !== undefined) {
524
502
  assertCodexNativeWorkspaceVersion(initialized);
525
503
  }
526
- if (input.codexCollaborationTools === true)
527
- assertCodexCollaborationVersion(initialized);
528
504
  logStage(activeStage, "ok", attempt, stageStartedAt);
529
505
  rpc.notify("initialized");
530
506
  activeStage = input.resume ? "thread_resume" : "thread_start";
@@ -589,6 +565,9 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
589
565
  ...(input.reasoning === undefined || input.reasoning === "default"
590
566
  ? {}
591
567
  : { effort: input.reasoning }),
568
+ ...(input.networkAccess === true ? {
569
+ sandboxPolicy: { type: "workspaceWrite", networkAccess: true },
570
+ } : {}),
592
571
  });
593
572
  logStage(activeStage, "ok", attempt, stageStartedAt);
594
573
  turnId = started.turn.id;
@@ -609,19 +588,15 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
609
588
  }),
610
589
  });
611
590
  if (completed.turn?.status === "completed")
612
- return { code: 0, initializeTimedOut: false, unsafeToRetry: false };
591
+ return { code: 0, initializeTimedOut: false };
613
592
  const detail = completed.turn?.error?.message ?? `turn status ${completed.turn?.status ?? "unknown"}`;
614
593
  process.stderr.write(sanitizeCodexDiagnostic(`Codex turn failed: ${detail.slice(0, ERROR_MESSAGE_CAP)}\n`, secrets));
615
- const status = completed.turn?.status;
616
- const retryReason = cancelling ? undefined
617
- : status === "interrupted" ? "collab_interrupted"
618
- : status === "failed" && isTransientTurnFailure(detail) ? "transient_turn_failure"
619
- : undefined;
620
594
  return {
621
- code: status === "interrupted" ? 130 : 1,
595
+ code: completed.turn?.status === "interrupted" ? 130 : 1,
622
596
  initializeTimedOut: false,
623
- ...(retryReason === undefined ? {} : { retryReason }),
624
- unsafeToRetry: turnHasUnsafeSideEffect || collabStartedMissing,
597
+ transientTurnFailure: !cancelling
598
+ && completed.turn?.status === "failed"
599
+ && isTransientTurnFailure(detail),
625
600
  };
626
601
  }
627
602
  catch (error) {
@@ -634,7 +609,7 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
634
609
  logStage(activeStage, "error", attempt, stageStartedAt);
635
610
  }
636
611
  process.stderr.write(`Codex app-server execution failed: ${safeErrorMessage(error, secrets)}\n`);
637
- return { code: 1, initializeTimedOut, unsafeToRetry: false };
612
+ return { code: 1, initializeTimedOut };
638
613
  }
639
614
  finally {
640
615
  firstProgress.stop();
@@ -666,20 +641,13 @@ export async function runCodexAppServer(bin, options = {}) {
666
641
  process.stderr.write(`[codex-app-server] stage=retry status=start attempt=${attempt + 1} elapsed_ms=0 reason=initialize_timeout\n`);
667
642
  continue;
668
643
  }
669
- const decision = decideTurnRetry(result, turnRetries, MAX_TRANSIENT_TURN_RETRIES);
670
- if (decision.kind === "retry") {
644
+ if (result.transientTurnFailure && turnRetries < MAX_TRANSIENT_TURN_RETRIES) {
671
645
  const delayMs = turnRetryDelayMs * TRANSIENT_TURN_RETRY_BACKOFF_FACTOR ** turnRetries;
672
646
  turnRetries += 1;
673
- process.stderr.write(`[codex-app-server] stage=retry status=start attempt=${attempt + 1} elapsed_ms=0 reason=${decision.reason}\n`);
647
+ process.stderr.write(`[codex-app-server] stage=retry status=start attempt=${attempt + 1} elapsed_ms=0 reason=transient_turn_failure\n`);
674
648
  await new Promise((resolve) => setTimeout(resolve, delayMs));
675
649
  continue;
676
650
  }
677
- if (decision.kind === "skipped") {
678
- process.stderr.write(`[codex-app-server] stage=retry status=skipped attempt=${attempt} reason=${decision.reason}_unsafe\n`);
679
- }
680
- else if (decision.kind === "exhausted") {
681
- process.stderr.write(`[codex-app-server] stage=retry status=exhausted attempt=${attempt} reason=${decision.reason}\n`);
682
- }
683
651
  return result.code;
684
652
  }
685
653
  }
@@ -1,7 +1,6 @@
1
- import { CODEX_COLLABORATION_MIN_VERSION, CODEX_NATIVE_WORKSPACE_MIN_VERSION } from "./codex.js";
1
+ import { CODEX_NATIVE_WORKSPACE_MIN_VERSION } from "./codex.js";
2
2
  import { z } from "zod";
3
3
  export const CODEX_PROJECT_WORKSPACE_UNSUPPORTED = "codex_project_workspace_unsupported";
4
- export const CODEX_COLLABORATION_UNSUPPORTED = "codex_collaboration_unsupported";
5
4
  export const CODEX_PLAN_ENTRY_LIMIT = 16;
6
5
  // 16 steps remain inside the server's 4,000 UTF-16-unit plan payload cap even
7
6
  // when every retained code point is a surrogate pair (for example, emoji).
@@ -52,32 +51,18 @@ const versionTuple = (version) => {
52
51
  const versionAtLeast = (actual, minimum) => actual[0] > minimum[0]
53
52
  || (actual[0] === minimum[0] && (actual[1] > minimum[1]
54
53
  || (actual[1] === minimum[1] && actual[2] >= minimum[2])));
55
- function initializeVersion(response) {
54
+ /** Native roots require the app-server version returned by initialize, not a separate shell probe. */
55
+ export function assertCodexNativeWorkspaceVersion(response) {
56
56
  const parsed = NativeInitializeResponseSchema.safeParse(response);
57
57
  if (!parsed.success)
58
- return null;
58
+ throw codexProjectWorkspaceUnsupported();
59
59
  const match = /(?:^|[/\s])(\d+\.\d+\.\d+)(?=$|[\s(])/u.exec(parsed.data.userAgent);
60
- return match === null ? null : versionTuple(match[1] ?? "");
61
- }
62
- /** Native roots require the app-server version returned by initialize, not a separate shell probe. */
63
- export function assertCodexNativeWorkspaceVersion(response) {
64
- const actual = initializeVersion(response);
60
+ const actual = match === null ? null : versionTuple(match[1] ?? "");
65
61
  const minimum = versionTuple(CODEX_NATIVE_WORKSPACE_MIN_VERSION);
66
62
  if (actual === null || minimum === null || !versionAtLeast(actual, minimum)) {
67
63
  throw codexProjectWorkspaceUnsupported();
68
64
  }
69
65
  }
70
- /** Collaboration tools are exposed only on an app-server whose contract was verified (spec 2b). */
71
- export function assertCodexCollaborationVersion(response) {
72
- const actual = initializeVersion(response);
73
- const minimum = versionTuple(CODEX_COLLABORATION_MIN_VERSION);
74
- if (actual === null || minimum === null || !versionAtLeast(actual, minimum)) {
75
- throw codexCollaborationUnsupported();
76
- }
77
- }
78
- export function codexCollaborationUnsupported() {
79
- return new Error(CODEX_COLLABORATION_UNSUPPORTED);
80
- }
81
66
  const PLAN_STATUSES = new Set(["pending", "inProgress", "completed"]);
82
67
  const clipCharacters = (value, limit) => [...value.trim().replace(/\s+/gu, " ")].slice(0, limit).join("");
83
68
  /** Treat plan updates as bounded user-visible data; any malformed retained step rejects the event. */
@@ -7,10 +7,20 @@ export const DEEPSEEK_CODEX_BASE_URL = "https://api.deepseek.com/";
7
7
  export const DEEPSEEK_CODEX_KEY_ENV = "NOWCREW_DEEPSEEK_API_KEY";
8
8
  export const DEEPSEEK_CODEX_REASONING_LEVELS = ["low", "high", "max"];
9
9
  const PRIVATE_CODEX_HOME = ".codex-deepseek";
10
- export function deepSeekCodexHome(homeDir) {
11
- return resolve(homeDir, PRIVATE_CODEX_HOME);
10
+ const SELF_TRAINING_PRIVATE_CODEX_HOME = ".codex-deepseek-self-training";
11
+ export function deepSeekCodexHome(homeDir, selfTraining = false) {
12
+ return resolve(homeDir, selfTraining ? SELF_TRAINING_PRIVATE_CODEX_HOME : PRIVATE_CODEX_HOME);
12
13
  }
13
- export function renderDeepSeekConfig(codexHome) {
14
+ function normalizedBaseUrl(value) {
15
+ const url = new URL(value);
16
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
17
+ throw new Error("DeepSeek provider base URL must use HTTP(S)");
18
+ }
19
+ if (!url.pathname.endsWith("/"))
20
+ url.pathname = `${url.pathname}/`;
21
+ return url.toString();
22
+ }
23
+ export function renderDeepSeekConfig(codexHome, baseUrl = DEEPSEEK_CODEX_BASE_URL) {
14
24
  const catalogPath = join(codexHome, "models.json");
15
25
  return [
16
26
  `model = ${JSON.stringify(DEEPSEEK_CODEX_MODEL)}`,
@@ -20,7 +30,7 @@ export function renderDeepSeekConfig(codexHome) {
20
30
  "",
21
31
  "[model_providers.deepseek]",
22
32
  'name = "deepseek"',
23
- `base_url = ${JSON.stringify(DEEPSEEK_CODEX_BASE_URL)}`,
33
+ `base_url = ${JSON.stringify(normalizedBaseUrl(baseUrl))}`,
24
34
  'wire_api = "responses"',
25
35
  `env_key = ${JSON.stringify(DEEPSEEK_CODEX_KEY_ENV)}`,
26
36
  "",
@@ -40,11 +50,11 @@ async function writePrivateAtomic(path, content) {
40
50
  await rm(temporary, { force: true });
41
51
  }
42
52
  }
43
- export async function materializeDeepSeekCodexHome(homeDir) {
44
- const codexHome = deepSeekCodexHome(homeDir);
53
+ export async function materializeDeepSeekCodexHome(homeDir, options = {}) {
54
+ const codexHome = deepSeekCodexHome(homeDir, options.selfTraining === true);
45
55
  await mkdir(codexHome, { recursive: true, mode: 0o700 });
46
56
  await chmod(codexHome, 0o700);
47
57
  await writePrivateAtomic(join(codexHome, "models.json"), DEEPSEEK_CODEX_CATALOG);
48
- await writePrivateAtomic(join(codexHome, "config.toml"), renderDeepSeekConfig(codexHome));
58
+ await writePrivateAtomic(join(codexHome, "config.toml"), renderDeepSeekConfig(codexHome, options.baseUrl));
49
59
  return codexHome;
50
60
  }
@@ -5,8 +5,6 @@
5
5
  import spawn from "cross-spawn";
6
6
  /** Minimum CLI whose real app-server contract supports NowCrew native project workspaces. */
7
7
  export const CODEX_NATIVE_WORKSPACE_MIN_VERSION = "0.148.0";
8
- /** Minimum CLI whose `multi_agent` collaboration tool contract was empirically verified (2026-08-28). */
9
- export const CODEX_COLLABORATION_MIN_VERSION = "0.149.1";
10
8
  // Codex CLI 原生 model_reasoning_effort 档位(codex 0.135.0 实测:非法值时 config 解析报错枚举这六档)。
11
9
  // 注意:codex 对非法值是硬失败(进程直接退出),所以必须白名单过滤;白名单外(含 "default"、
12
10
  // claude 专属的 "max")回落 CODEX_DEFAULT_EFFORT。
@@ -24,8 +22,6 @@ export function buildCodexArgs(input) {
24
22
  ? input.reasoning
25
23
  : CODEX_DEFAULT_EFFORT;
26
24
  args.push("-c", `model_reasoning_effort=${effort}`);
27
- // 纵深防御:exec 启动器没有 initialize 握手、无法断言版本,协作工具恒关(spec 2b)。
28
- args.push("-c", "features.multi_agent=false", "-c", "features.multi_agent_v2=false");
29
25
  if (input.projectRootMarkers) {
30
26
  args.push("-c", `project_root_markers=${JSON.stringify(input.projectRootMarkers)}`);
31
27
  }
@@ -36,6 +32,9 @@ export function buildCodexArgs(input) {
36
32
  else if (input.effectivePermission === "full_access" || (input.effectivePermission === undefined && input.dangerous)) {
37
33
  args.push("--dangerously-bypass-approvals-and-sandbox");
38
34
  }
35
+ if (input.effectivePermission === "workspace_write" && input.networkAccess === true) {
36
+ args.push("-c", "sandbox_workspace_write.network_access=true");
37
+ }
39
38
  for (const imagePath of input.imagePaths ?? [])
40
39
  args.push("--image", imagePath);
41
40
  // `-` instructs codex exec to read the prompt from stdin. Keeping the complete prompt out of argv
@@ -0,0 +1,15 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { isAbsolute, relative, sep } from "node:path";
3
+ /** Delete only a concrete child of this Agent's tasks root; never follow an ambiguous target. */
4
+ export async function cleanupSelfTrainingRunDir(tasksRoot, runDir) {
5
+ const child = relative(tasksRoot, runDir);
6
+ if (child === "" || isAbsolute(child) || child === ".." || child.startsWith(`..${sep}`))
7
+ return;
8
+ try {
9
+ await rm(runDir, { recursive: true, force: true });
10
+ }
11
+ catch (error) {
12
+ const detail = error instanceof Error ? error.message : String(error);
13
+ process.stderr.write(`[execution] failed to remove self-training run dir ${runDir}: ${detail}\n`);
14
+ }
15
+ }
package/dist/serve.js CHANGED
@@ -51,7 +51,6 @@ import { handleRuntimeProbeFrame, probeRuntimeHealth } from "./runtime-health.js
51
51
  import { buildControlPlaneUrl } from "./control-plane-url.js";
52
52
  import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
53
53
  import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
54
- import { attachStorageMigrationRuntime, createServeStorageStatusDelivery, handleStorageMigrationControl, replayStorageMigrationStatus, storageMigrationCapabilities, } from "./storage-serve-adapter.js";
55
54
  export { buildControlPlaneUrl } from "./control-plane-url.js";
56
55
  // normalize.ts 的活动种类 → activity 枚举
57
56
  const ACTIVITY_MAP = {
@@ -123,7 +122,6 @@ export function serve(config, opts = {}) {
123
122
  catch { /* reconnect/timeout reconciliation handles a lost status frame */ }
124
123
  },
125
124
  });
126
- let storageMigrationAdmissionClosed = () => false;
127
125
  const migrationController = createServeMigrationController({
128
126
  config,
129
127
  ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
@@ -144,7 +142,6 @@ export function serve(config, opts = {}) {
144
142
  const agentAbilityRuntime = createAgentAbilityRuntime(config.agentsRoot, {
145
143
  ...opts.agentAbility,
146
144
  coordinator: projectionCoordinator,
147
- isAdmissionClosed: () => storageMigrationAdmissionClosed(),
148
145
  });
149
146
  let projectSkillsController;
150
147
  const projectSkillsReconciler = opts.projectSkills?.reconciler ?? createProjectSkillsReconciler({
@@ -164,7 +161,6 @@ export function serve(config, opts = {}) {
164
161
  }
165
162
  catch { /* 下一次 ready 或项目操作会重新发送完整快照 */ }
166
163
  },
167
- isAdmissionClosed: () => storageMigrationAdmissionClosed(),
168
164
  reconcile: (handle, bindings) => projectSkillsReconciler.reconcile(handle, bindings),
169
165
  ensureSnapshot: (handle, snapshot, options) => projectSkillsReconciler.ensureSnapshot(handle, snapshot, options),
170
166
  });
@@ -179,37 +175,13 @@ export function serve(config, opts = {}) {
179
175
  });
180
176
  const machineHelloPublisher = createProjectSkillMachineHelloPublisher({
181
177
  status: projectSkillControllerReadiness.status,
182
- effective: (hello, status) => {
183
- const effective = withProjectSkillCapabilityStatus(hello, status);
184
- const configuredIdentity = opts.storageMigration?.identity;
185
- const currentIdentity = typeof configuredIdentity === "function"
186
- ? configuredIdentity()
187
- : configuredIdentity;
188
- return currentIdentity === undefined ? effective : { ...effective, ...currentIdentity };
189
- },
178
+ effective: withProjectSkillCapabilityStatus,
190
179
  onSent: (hello) => log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · installed=[${hello.runtimes.join(",")}] · executable=[${hello.executionRuntimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`),
191
180
  });
192
181
  const initializedProjectSkills = initializedProjectSkillsReconciler(projectSkillControllerReadiness.ensure, projectSkillsReconciler);
193
- const configuredCapabilities = opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform);
194
- const storageCapabilities = storageMigrationCapabilities(configuredCapabilities, opts.storageMigration);
195
- const storageStatusDelivery = createServeStorageStatusDelivery(opts.storageMigration, {
196
- onPersistenceError: (error) => dslog("daemon.storage_status_outbox_failed", "存储迁移状态 outbox 持久化失败", { level: "ERROR", error_message: error.message }),
197
- transmit: (frame) => { try {
198
- if (ws?.readyState === WebSocket.OPEN)
199
- ws.send(JSON.stringify(frame));
200
- }
201
- catch { /* retry */ } },
202
- });
203
- const storageMigrationRuntime = attachStorageMigrationRuntime(opts.storageMigration, {
204
- isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0,
205
- waitForExecutionFrames: () => executionFrameQueue,
206
- flushLocalState: flushSlog,
207
- sendStatus: storageStatusDelivery.send,
208
- });
209
- storageMigrationAdmissionClosed = storageMigrationRuntime.isAdmissionClosed;
210
182
  const prepareProjectSkillCapabilities = createProjectSkillConnectionCapabilityProvider({
211
183
  platform: process.platform,
212
- baseCapabilities: storageCapabilities,
184
+ baseCapabilities: opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform),
213
185
  additionalCapabilities: () => managedDaemonCapabilities(updateEligibility),
214
186
  probeRuntimeRootStorePrimitive: projectSkillRuntimeRootStartup.probeNativePublicationPrimitiveReadiness,
215
187
  initializeController: projectSkillControllerReadiness.ensure,
@@ -338,12 +310,6 @@ export function serve(config, opts = {}) {
338
310
  // 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
339
311
  void drainSpool();
340
312
  // 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
341
- storageStatusDelivery.replayPending();
342
- const storageReplay = replayStorageMigrationStatus(opts.storageMigration, openedSocket, () => ws === openedSocket, (error) => {
343
- dslog("daemon.storage_status_replay_failed", "存储迁移状态重放失败", {
344
- level: "ERROR", error_message: error.message,
345
- });
346
- }, storageStatusDelivery.send);
347
313
  const { detectInstalled, detectExecutable = detectExecutionRuntimesWithSignal } = opts.machineInfo ?? {};
348
314
  const helloPromise = (opts.machineInfo?.collect ?? collectMachineHello)(config.agentsRoot, config.executionLimits, process.platform, {
349
315
  ...(detectInstalled ? { detectInstalled } : {}),
@@ -359,7 +325,6 @@ export function serve(config, opts = {}) {
359
325
  if (hello.executionProtocol === undefined)
360
326
  return [];
361
327
  const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
362
- await storageReplay;
363
328
  if (ws === openedSocket && openedSocket.readyState === WebSocket.OPEN
364
329
  && detected.some((runtime) => !hello.executionRuntimes.includes(runtime))) {
365
330
  detectedExecutionRuntimes = detected;
@@ -374,8 +339,7 @@ export function serve(config, opts = {}) {
374
339
  return [];
375
340
  });
376
341
  void helloPromise
377
- .then(async (hello) => {
378
- await storageReplay;
342
+ .then((hello) => {
379
343
  if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
380
344
  return;
381
345
  detectedExecutionRuntimes = hello.executionRuntimes;
@@ -423,8 +387,6 @@ export function serve(config, opts = {}) {
423
387
  if (typeof decoded !== "object" || decoded === null)
424
388
  return;
425
389
  const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
426
- if (storageStatusDelivery.acknowledge(decoded))
427
- return;
428
390
  if (rawType === "daemon:update" || rawType === "daemon:restart") {
429
391
  void updateController.handle(decoded).catch((error) => {
430
392
  dslog("daemon.control_failed", "daemon 控制操作处理失败", {
@@ -441,11 +403,6 @@ export function serve(config, opts = {}) {
441
403
  void codexHomeModeController.handle(decoded).catch((error) => dslog("daemon.codex_home_mode_failed", "Codex Home 模式切换失败", { level: "ERROR", error_message: error.message }));
442
404
  return;
443
405
  }
444
- if (handleStorageMigrationControl(opts.storageMigration, rawType, decoded, storageStatusDelivery.send, (kind, error) => {
445
- dslog(kind === "migration" ? "daemon.storage_migration_failed" : "daemon.storage_migration_confirmation_failed", kind === "migration" ? "存储迁移控制操作失败" : "存储迁移 hello 确认处理失败", { level: "ERROR", error_message: error.message });
446
- })) {
447
- return;
448
- }
449
406
  if (rawType.startsWith("execution:")) {
450
407
  const parsedExecution = ServerToDaemonExecutionFrameSchema.safeParse(decoded);
451
408
  if (!parsedExecution.success) {
@@ -507,14 +464,6 @@ export function serve(config, opts = {}) {
507
464
  return;
508
465
  }
509
466
  const spec = frame;
510
- if (storageMigrationRuntime.isAdmissionClosed()) {
511
- safeExecutionSend(ExecutionRejectedSchema.parse({
512
- type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
513
- reason: "resource_limit", message: "Storage migration is quiescing this machine",
514
- at: new Date().toISOString(),
515
- }));
516
- return;
517
- }
518
467
  const workspaceRejection = projectWorkspaceCapabilityRejection(spec, capabilities.executionRunner, new Date().toISOString());
519
468
  if (workspaceRejection !== null)
520
469
  return void safeExecutionSend(workspaceRejection);
@@ -821,17 +770,6 @@ export function serve(config, opts = {}) {
821
770
  // ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
822
771
  const r = msg;
823
772
  setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
824
- if (opts.storageMigration?.ready === true && typeof r.workspaceId === "string") {
825
- try {
826
- opts.storageMigration.bindServerIdentity?.({ workspaceId: r.workspaceId });
827
- machineHelloPublisher.republish();
828
- }
829
- catch (error) {
830
- dslog("daemon.storage_identity_bind_failed", "存储迁移 Workspace 身份绑定失败", {
831
- level: "ERROR", error_message: error.message,
832
- });
833
- }
834
- }
835
773
  // 旧 server 无此字段 → 空集合(daemon 保持全部旧行为)。
836
774
  serverCapabilities = new Set(Array.isArray(r.serverCapabilities)
837
775
  ? r.serverCapabilities.filter((c) => typeof c === "string")
@@ -930,8 +868,6 @@ export function serve(config, opts = {}) {
930
868
  }
931
869
  if (msg.type !== "agent:start")
932
870
  return; // ready/error 等忽略
933
- if (storageMigrationRuntime.isAdmissionClosed())
934
- return;
935
871
  // 任务键:scheduled run 用 runId(每次运行独立 cwd/work-log;overlap 由 server 端 skip_if_running 管,
936
872
  // daemon 的 running 去重只兜底"同一 run 重复投递");普通唤醒仍是 线程锚点 ?? 频道。
937
873
  const scheduled = msg.reason === "scheduled_job" && msg.scheduledRun
@@ -1347,7 +1283,6 @@ export function serve(config, opts = {}) {
1347
1283
  concurrencyTelemetry.stop();
1348
1284
  runtimeProbe.stop();
1349
1285
  completionRetransmitter.stop();
1350
- storageStatusDelivery.stop();
1351
1286
  const deadline = createShutdownDeadline(shutdownTimeoutMs);
1352
1287
  if (reconnectTimer !== null) {
1353
1288
  clearTimeout(reconnectTimer);
@@ -46,6 +46,7 @@ export function supervisorLaunch(request) {
46
46
  effectivePermission: request.effectivePermission,
47
47
  ...(request.model === undefined ? {} : { model: request.model }),
48
48
  ...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
49
+ ...(request.networkAccess === undefined ? {} : { networkAccess: request.networkAccess }),
49
50
  ...(request.sessionId === undefined ? {} : { sessionId: request.sessionId }),
50
51
  ...(request.imagePaths === undefined ? {} : { imagePaths: request.imagePaths }),
51
52
  ...(request.agentRoot === undefined ? {} : {
@@ -58,9 +59,6 @@ export function supervisorLaunch(request) {
58
59
  ...(request.codexSkillRoots === undefined ? {} : {
59
60
  skillRoots: request.codexSkillRoots,
60
61
  }),
61
- ...(request.codexCollaborationTools === undefined ? {} : {
62
- codexCollaborationTools: request.codexCollaborationTools,
63
- }),
64
62
  resume: request.resume,
65
63
  }),
66
64
  };