@nowcrew/daemon 0.6.80-beta.1 → 0.6.80-beta.3

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 (40) hide show
  1. package/dist/agent-ability/resolver.js +10 -7
  2. package/dist/computer-cli.js +3 -9
  3. package/dist/computer-service.js +14 -0
  4. package/dist/daemon-local-home.js +23 -0
  5. package/dist/daemon-update-eligibility.js +7 -4
  6. package/dist/execution-protocol.js +1 -0
  7. package/dist/execution-runner.js +9 -3
  8. package/dist/local-execution-prompt.js +3 -2
  9. package/dist/local-executor.js +49 -28
  10. package/dist/machine-info.js +3 -0
  11. package/dist/main.js +5 -9
  12. package/dist/managed-service-lifecycle.js +6 -2
  13. package/dist/project-skills/capability.js +109 -0
  14. package/dist/project-skills/controller-convergence.js +57 -0
  15. package/dist/project-skills/projection-set-switch.js +419 -0
  16. package/dist/project-skills/projection-state-domain.js +170 -0
  17. package/dist/project-skills/projection-state-store.js +842 -0
  18. package/dist/project-skills/projection-state-transaction.js +322 -0
  19. package/dist/project-skills/projection-state.js +3 -0
  20. package/dist/project-skills/runtime-root-bootstrap.js +47 -0
  21. package/dist/project-skills/runtime-root-domain.js +268 -0
  22. package/dist/project-skills/runtime-root-gc.js +293 -0
  23. package/dist/project-skills/runtime-root-lease-artifact.js +46 -0
  24. package/dist/project-skills/runtime-root-leases.js +487 -0
  25. package/dist/project-skills/runtime-root-source-identity.js +60 -0
  26. package/dist/project-skills/runtime-root-startup.js +49 -0
  27. package/dist/project-skills/runtime-root-state-artifact-domain.js +143 -0
  28. package/dist/project-skills/runtime-root-state-index.js +356 -0
  29. package/dist/project-skills/runtime-root-store.js +722 -0
  30. package/dist/project-skills/serve-capability.js +28 -0
  31. package/dist/project-skills/serve-startup.js +37 -0
  32. package/dist/provider-env.js +21 -3
  33. package/dist/runtimes/codex-app-server-runner.js +12 -4
  34. package/dist/runtimes/codex-deepseek-config.js +17 -7
  35. package/dist/runtimes/codex.js +3 -0
  36. package/dist/self-training-workspace.js +15 -0
  37. package/dist/serve.js +1 -1
  38. package/dist/supervised-runtime.js +1 -0
  39. package/dist/workspace.js +13 -10
  40. package/package.json +9 -10
@@ -0,0 +1,28 @@
1
+ import { WebSocket } from "ws";
2
+ import { daemonCapabilityBindings, } from "../machine-info.js";
3
+ import { prepareProjectSkillConnectionCapabilities, } from "./capability.js";
4
+ export function createProjectSkillConnectionCapabilityProvider(input) {
5
+ return async () => daemonCapabilityBindings(input.platform, await prepareProjectSkillConnectionCapabilities(input));
6
+ }
7
+ export function createProjectSkillMachineHelloPublisher(input) {
8
+ let latest = null;
9
+ let latestSocket = null;
10
+ const republish = () => {
11
+ if (latest === null || latestSocket?.readyState !== WebSocket.OPEN)
12
+ return;
13
+ const hello = input.effective(latest, input.status());
14
+ try {
15
+ latestSocket.send(JSON.stringify(hello));
16
+ input.onSent(hello);
17
+ }
18
+ catch { /* 非 OPEN,忽略 */ }
19
+ };
20
+ return Object.freeze({
21
+ publish: (hello, socket) => {
22
+ latest = hello;
23
+ latestSocket = socket;
24
+ republish();
25
+ },
26
+ republish,
27
+ });
28
+ }
@@ -0,0 +1,37 @@
1
+ import { createExecutionJournal } from "../execution-journal.js";
2
+ import { reconcileExecutionJournal } from "../execution-recovery.js";
3
+ import { dslog, flushSlog } from "../slog.js";
4
+ export async function initializeProjectSkillProtectedExecutionJournal(input) {
5
+ return input.startup.initializeAfterLeaseProtection(input.config.agentsRoot, async (snapshot) => {
6
+ input.protectedExecutionIds.clear();
7
+ for (const executionId of snapshot)
8
+ input.protectedExecutionIds.add(executionId);
9
+ const journal = input.injectedJournal ?? createExecutionJournal(input.config.agentsRoot, {
10
+ protectedExecutionIds: () => input.protectedExecutionIds,
11
+ onOperationTiming: (event) => {
12
+ if (event.operation !== "mark_runtime_ready"
13
+ && event.queueWaitMs < 250
14
+ && event.operationMs < 250)
15
+ return;
16
+ dslog("execution.journal_operation", "execution journal 操作完成", {
17
+ level: event.outcome === "succeeded" ? "INFO" : "ERROR",
18
+ execution_id: event.executionId,
19
+ operation: event.operation,
20
+ outcome: event.outcome,
21
+ queue_wait_ms: event.queueWaitMs,
22
+ operation_ms: event.operationMs,
23
+ total_ms: event.queueWaitMs + event.operationMs,
24
+ });
25
+ },
26
+ });
27
+ await reconcileExecutionJournal(journal, {
28
+ agentsRoot: input.config.agentsRoot,
29
+ serverUrl: input.config.serverUrl,
30
+ ...(input.profileName === undefined ? {} : { profileName: input.profileName }),
31
+ log: dslog,
32
+ flush: flushSlog,
33
+ writeStderr: (line) => process.stderr.write(line),
34
+ });
35
+ return journal;
36
+ });
37
+ }
@@ -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;
@@ -21,9 +21,14 @@ const RunnerInputSchema = z.object({
21
21
  cwd: z.string().min(1).optional(),
22
22
  workspaceRoots: z.array(z.string().min(1)).optional(),
23
23
  skillRoots: z.array(z.string().min(1)).optional(),
24
+ networkAccess: z.boolean().optional(),
24
25
  codexCollaborationTools: z.boolean().optional(),
25
26
  resume: z.boolean(),
26
- }).strict();
27
+ }).strict().superRefine((input, ctx) => {
28
+ if (input.networkAccess === true && input.effectivePermission !== "workspace_write") {
29
+ ctx.addIssue({ code: "custom", message: "network_access_requires_workspace_write" });
30
+ }
31
+ });
27
32
  const RPC_TIMEOUT_MS = 30_000;
28
33
  const INITIALIZE_RPC_TIMEOUT_MS = 60_000;
29
34
  const ERROR_MESSAGE_CAP = 2_000;
@@ -565,18 +570,18 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
565
570
  ...(input.model === undefined ? {} : { model: input.model }),
566
571
  };
567
572
  let thread;
568
- const nativeContractRequired = input.workspaceRoots !== undefined || input.skillRoots !== undefined;
573
+ const workspaceContractRequired = input.workspaceRoots !== undefined;
569
574
  try {
570
575
  thread = input.resume && input.sessionId !== undefined
571
576
  ? await rpc.request("thread/resume", { threadId: input.sessionId, ...threadParams })
572
577
  : await rpc.request("thread/start", threadParams);
573
578
  }
574
579
  catch (error) {
575
- if (nativeContractRequired)
580
+ if (workspaceContractRequired)
576
581
  throw codexProjectWorkspaceUnsupported();
577
582
  throw error;
578
583
  }
579
- if (nativeContractRequired) {
584
+ if (workspaceContractRequired) {
580
585
  thread = assertCodexProjectWorkspaceResponse(thread, {
581
586
  cwd: threadCwd,
582
587
  workspaceRoots: input.workspaceRoots ?? [],
@@ -613,6 +618,9 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
613
618
  ...(input.reasoning === undefined || input.reasoning === "default"
614
619
  ? {}
615
620
  : { effort: input.reasoning }),
621
+ ...(input.networkAccess === true ? {
622
+ sandboxPolicy: { type: "workspaceWrite", networkAccess: true },
623
+ } : {}),
616
624
  });
617
625
  logStage(activeStage, "ok", attempt, stageStartedAt);
618
626
  turnId = started.turn.id;
@@ -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
  }
@@ -36,6 +36,9 @@ export function buildCodexArgs(input) {
36
36
  else if (input.effectivePermission === "full_access" || (input.effectivePermission === undefined && input.dangerous)) {
37
37
  args.push("--dangerously-bypass-approvals-and-sandbox");
38
38
  }
39
+ if (input.effectivePermission === "workspace_write" && input.networkAccess === true) {
40
+ args.push("-c", "sandbox_workspace_write.network_access=true");
41
+ }
39
42
  for (const imagePath of input.imagePaths ?? [])
40
43
  args.push("--image", imagePath);
41
44
  // `-` 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
@@ -106,7 +106,7 @@ export function serve(config, opts = {}) {
106
106
  const cancellations = new Map();
107
107
  const legacyRuns = new Map();
108
108
  const updateEligibility = opts.update?.eligibility
109
- ?? (() => detectDaemonUpdateEligibility(opts.profileName));
109
+ ?? (() => detectDaemonUpdateEligibility(opts.profileName, { localHome }));
110
110
  const updateController = createDaemonUpdateController({
111
111
  eligibility: updateEligibility,
112
112
  install: opts.update?.install ?? ((input) => installExactDaemonUpdate({
@@ -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 ? {} : {
package/dist/workspace.js CHANGED
@@ -59,12 +59,13 @@ export async function prepareWorkspace(input) {
59
59
  const crewDir = join(dir, ".crew");
60
60
  await mkdir(crewDir, { recursive: true });
61
61
  // 预先创建明细目录,让收尾流程可以按功能创建 notes/<topic>.md。
62
- await mkdir(join(dir, "notes"), { recursive: true });
62
+ if (!input.skipLocalMemory)
63
+ await mkdir(join(dir, "notes"), { recursive: true });
63
64
  // MEMORY.md:首次创建"持久身份 + 知识索引"骨架,之后由 agent 自己维护
64
65
  const memoryPath = join(dir, "MEMORY.md");
65
- const memorySeedCreated = !(await exists(memoryPath));
66
+ const memorySeedCreated = !input.skipLocalMemory && !(await exists(memoryPath));
66
67
  const localMemoryV2Initialized = await hasLocalMemoryV2Marker(dir);
67
- if (memorySeedCreated) {
68
+ if (!input.skipLocalMemory && memorySeedCreated) {
68
69
  try {
69
70
  await writeFile(memoryPath, memorySeed(input.handle, input.description), "utf8");
70
71
  }
@@ -73,13 +74,15 @@ export async function prepareWorkspace(input) {
73
74
  throw error;
74
75
  }
75
76
  }
76
- let memory;
77
- try {
78
- memory = await readFile(memoryPath, "utf8");
79
- }
80
- catch (error) {
81
- logMemoryPrepareFailure(input, "memory_read", error);
82
- throw error;
77
+ let memory = "";
78
+ if (!input.skipLocalMemory) {
79
+ try {
80
+ memory = await readFile(memoryPath, "utf8");
81
+ }
82
+ catch (error) {
83
+ logMemoryPrepareFailure(input, "memory_read", error);
84
+ throw error;
85
+ }
83
86
  }
84
87
  // 系统提示词按 execution 隔离;省略 identity 的兼容调用每次使用随机路径。
85
88
  const promptDir = join(crewDir, "prompts");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.80-beta.1",
3
+ "version": "0.6.80-beta.3",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",
@@ -16,14 +16,6 @@
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
- },
27
19
  "dependencies": {
28
20
  "@agentclientprotocol/sdk": "1.2.1",
29
21
  "@nowcrew/cli": "0.4.13",
@@ -44,5 +36,12 @@
44
36
  "tsx": "^4.19.0",
45
37
  "typescript": "^5.6.0",
46
38
  "vitest": "^2.1.0"
39
+ },
40
+ "scripts": {
41
+ "daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
42
+ "build": "tsc -p tsconfig.json",
43
+ "codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
44
+ "test": "vitest run",
45
+ "typecheck": "tsc --noEmit"
47
46
  }
48
- }
47
+ }