@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
@@ -249,23 +249,6 @@ const verifyCache = async (agentsRoot, ability) => {
249
249
  };
250
250
  const layerRelativePath = (path, layer) => path.startsWith(`${layer}/`) ? path.slice(layer.length + 1) : path;
251
251
  const readManagedCatalog = async (trainingRoot) => JSON.parse(await readFile(join(trainingRoot, "ACTIVE_RELEASE.json"), "utf8"));
252
- const projectedSkillExists = async (trainingRoot, name) => {
253
- const root = join(trainingRoot, "skills", safeAbilityName(name));
254
- if (!await isRealDirectory(root))
255
- return false;
256
- const direct = await lstat(join(root, "SKILL.md")).catch(() => null);
257
- if (direct?.isFile() && !direct.isSymbolicLink())
258
- return true;
259
- for (const entry of await readdir(root, { withFileTypes: true })) {
260
- if (!entry.isDirectory())
261
- continue;
262
- const skill = await lstat(join(root, entry.name, "SKILL.md")).catch(() => null);
263
- if (skill?.isFile() && !skill.isSymbolicLink())
264
- return true;
265
- }
266
- return false;
267
- };
268
- const projectedSkillsComplete = async (trainingRoot, skills) => (await Promise.all(skills.map((skill) => projectedSkillExists(trainingRoot, skill.name)))).every(Boolean);
269
252
  const trainingSwitchJournalPath = (agentRoot) => join(agentRoot, ".training-switch.json");
270
253
  const trainingRollbackRoot = (agentRoot) => join(agentRoot, ".training-rollback");
271
254
  const trainingDiscardRoot = (agentRoot) => join(agentRoot, ".training-discard");
@@ -439,7 +422,6 @@ const activeTrainingRoot = async (agentsRoot, handle, ability) => {
439
422
  && catalog.releaseId === ability.releaseId
440
423
  && catalog.rootCommit === ability.rootCommit
441
424
  && catalog.artifactDigest === ability.artifactDigest
442
- && await projectedSkillsComplete(trainingRoot, ability.skills)
443
425
  ? trainingRoot
444
426
  : null;
445
427
  };
@@ -481,8 +463,7 @@ const materializeWorkspaceTraining = async (agentsRoot, handle, ability) => {
481
463
  }
482
464
  if (previousCatalog.releaseId === ability.releaseId
483
465
  && previousCatalog.artifactDigest === ability.artifactDigest
484
- && previousCatalog.projectionDigest === projectionDigest
485
- && await projectedSkillsComplete(trainingRoot, ability.skills))
466
+ && previousCatalog.projectionDigest === projectionDigest)
486
467
  return trainingRoot;
487
468
  }
488
469
  await mkdir(agentRoot, { recursive: true });
@@ -9,8 +9,8 @@ import { abilityWorkspaceProjectionPath, DaemonAbilityLockSchema, DaemonAbilityM
9
9
  import { PROJECT_MANIFEST_SOURCE_PATH, ProjectDependencyManifestSchema, } from "../project-dependency-manifest.js";
10
10
  const runFile = promisify(execFile);
11
11
  const MAX_FILES = 5_000;
12
- const MAX_TOTAL_BYTES = 25 * 1024 * 1024;
13
- const MAX_FILE_BYTES = 2 * 1024 * 1024;
12
+ export const AGENT_ABILITY_MAX_TOTAL_BYTES = 200 * 1024 * 1024;
13
+ export const AGENT_ABILITY_MAX_FILE_BYTES = 100 * 1024 * 1024;
14
14
  const MAX_INSTRUCTIONS_BYTES = 96 * 1024;
15
15
  const MAX_MEMORY_BYTES = 64 * 1024;
16
16
  const AUTHORING_ONLY_ROOTS = new Set([".claude"]);
@@ -114,10 +114,11 @@ export const collectAbilityFiles = async (root) => {
114
114
  if (info.isDirectory())
115
115
  await visit(absolute);
116
116
  else if (info.isFile()) {
117
- if (info.size > MAX_FILE_BYTES)
117
+ if (info.size > AGENT_ABILITY_MAX_FILE_BYTES) {
118
118
  throw new Error(`ability_file_size_exceeded:${relative(root, absolute)}`);
119
+ }
119
120
  totalBytes += info.size;
120
- if (totalBytes > MAX_TOTAL_BYTES)
121
+ if (totalBytes > AGENT_ABILITY_MAX_TOTAL_BYTES)
121
122
  throw new Error("ability_total_size_exceeded");
122
123
  if (files.length >= MAX_FILES)
123
124
  throw new Error("ability_file_count_exceeded");
@@ -251,7 +252,7 @@ export const validateAbilityDependencyDeclarations = (manifest, lock) => {
251
252
  throw new Error("ability_lock_has_undeclared_dependency");
252
253
  }
253
254
  };
254
- const scan = (files, manifest) => {
255
+ export const scanAbilityFiles = (files, manifest) => {
255
256
  const findings = [];
256
257
  const secrets = [
257
258
  /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/u,
@@ -259,7 +260,9 @@ const scan = (files, manifest) => {
259
260
  /\bAK(?:ID|IA)[A-Z0-9]{12,}\b/u,
260
261
  ];
261
262
  const executable = new Set([".exe", ".dll", ".dylib", ".so", ".bin", ".class", ".jar"]);
262
- const allowedBinaryDocuments = new Set([".pdf", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".xlsx", ".xls", ".docx", ".pptx"]);
263
+ const allowedBinaryDocuments = new Set([
264
+ ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".xlsx", ".xls", ".docx", ".pptx", ".ttf", ".otf",
265
+ ]);
263
266
  for (const file of files) {
264
267
  let text;
265
268
  try {
@@ -382,7 +385,7 @@ export async function resolveAbilityRelease(agentsRoot, command) {
382
385
  }
383
386
  const externalPaths = await verifyExternalDependencies(cacheRoot, resolved.root, lock);
384
387
  files = await collectAbilityFiles(resolved.root);
385
- const security = scan(files, manifest);
388
+ const security = scanAbilityFiles(files, manifest);
386
389
  const kinds = new Set(lock.assets.map((asset) => asset.kind));
387
390
  const memory = [];
388
391
  for (const [layer, mounts] of [["release-policy", manifest.spec.memory.policy], ["release-seed", manifest.spec.memory.seeds]]) {
@@ -32,9 +32,6 @@ export function createAgentAbilityRuntime(agentsRoot, options = {}) {
32
32
  else if (type === "agent:ability:resolve") {
33
33
  result = await controller.handle(input);
34
34
  }
35
- else if (options.isAdmissionClosed?.() === true) {
36
- result = { ok: false, error: "storage_migration_quiescing" };
37
- }
38
35
  else {
39
36
  const parsed = AbilityApplyCommandSchema.safeParse(input);
40
37
  if (!parsed.success)
@@ -3,12 +3,12 @@ import { profileLayout, validateLocalInstanceName } from "./profile-layout.js";
3
3
  const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,47}$/;
4
4
  const KNOWN_TARGETS = {
5
5
  "nowwork-in.nowcoder.com": {
6
- environment: "production",
6
+ profile: "in",
7
7
  daemonDirectory: "daemon",
8
8
  agentsDirectory: "agents",
9
9
  },
10
10
  "nowwork-dev.nowcoder.com": {
11
- environment: "dev",
11
+ profile: "dev",
12
12
  daemonDirectory: "daemon-dev",
13
13
  agentsDirectory: "agents-dev",
14
14
  },
@@ -42,37 +42,32 @@ function requireCustomProfile(hostname, value) {
42
42
  if (!PROFILE_NAME.test(value)) {
43
43
  throw new Error("Invalid custom profile; expected a lowercase name of 1-48 letters, digits, '_' or '-'");
44
44
  }
45
- if (value === "in" || value === "dev" || value === "production") {
45
+ if (value === "in" || value === "dev") {
46
46
  throw new Error(`Profile '${value}' is reserved for a known NowWork hostname`);
47
47
  }
48
48
  return value;
49
49
  }
50
50
  function knownTargetForProfile(environment, requestedProfile, input, parsed) {
51
51
  const paths = input.platform === "win32" ? win32 : posix;
52
- // Keep the no-flag target readable by old daemons; any workspace-scoped profile below
53
- // uses the canonical environment token (production, never in).
54
- const legacyProfile = environment === "production" ? "in" : environment;
52
+ const legacyProfile = environment;
55
53
  if (requestedProfile === undefined || requestedProfile === legacyProfile) {
56
54
  return Object.freeze({
57
55
  kind: "known",
58
56
  profile: legacyProfile,
59
57
  serverUrl: normalizedServerUrl(parsed),
60
- daemonHome: paths.join(input.userHome, ".crew", environment === "production" ? "daemon" : "daemon-dev"),
61
- agentsRoot: paths.join(input.userHome, ".crew", environment === "production" ? "agents" : "agents-dev"),
58
+ daemonHome: paths.join(input.userHome, ".crew", environment === "in" ? "daemon" : "daemon-dev"),
59
+ agentsRoot: paths.join(input.userHome, ".crew", environment === "in" ? "agents" : "agents-dev"),
62
60
  npmPrefix: paths.join(input.userHome, ".crew", "daemon-runtimes", legacyProfile),
63
61
  });
64
62
  }
65
63
  if (requestedProfile === "in" || requestedProfile === "dev") {
66
- throw new Error(`Server hostname '${new URL(input.serverUrl).hostname}' requires profile '${environment}'`);
64
+ throw new Error(`Server hostname '${new URL(input.serverUrl).hostname}' requires profile '${legacyProfile}'`);
67
65
  }
68
66
  const prefix = `nw-${environment}-`;
69
- const legacyPrefix = environment === "production" ? "nw-in-" : prefix;
70
- if (!requestedProfile.startsWith(prefix) && !requestedProfile.startsWith(legacyPrefix)) {
67
+ if (!requestedProfile.startsWith(prefix)) {
71
68
  throw new Error(`Server hostname '${new URL(input.serverUrl).hostname}' requires profile '${prefix}<local-name>'`);
72
69
  }
73
- const localInstanceName = validateLocalInstanceName(requestedProfile.startsWith(prefix)
74
- ? requestedProfile.slice(prefix.length)
75
- : requestedProfile.slice(legacyPrefix.length));
70
+ const localInstanceName = validateLocalInstanceName(requestedProfile.slice(prefix.length));
76
71
  const layout = profileLayout({
77
72
  environment,
78
73
  localInstanceName,
@@ -96,7 +91,7 @@ export function resolveAutomaticInstallTarget(input) {
96
91
  const paths = input.platform === "win32" ? win32 : posix;
97
92
  const known = KNOWN_TARGETS[hostname];
98
93
  if (known !== undefined) {
99
- return knownTargetForProfile(known.environment, input.requestedProfile, input, parsed);
94
+ return knownTargetForProfile(known.profile, input.requestedProfile, input, parsed);
100
95
  }
101
96
  const profile = requireCustomProfile(hostname, input.requestedProfile);
102
97
  return Object.freeze({
@@ -82,14 +82,6 @@ function translatedError(error, td) {
82
82
  }
83
83
  return td(error.message);
84
84
  }
85
- function storageServiceEnvironment(env) {
86
- return {
87
- ...(env.NOWWORK_HOME === undefined ? {} : { NOWWORK_HOME: env.NOWWORK_HOME }),
88
- ...(env.NOWWORK_ENV_TAG === undefined ? {} : { NOWWORK_ENV_TAG: env.NOWWORK_ENV_TAG }),
89
- ...(env.NOWWORK_STORAGE_POLICY_PATH === undefined
90
- ? {} : { NOWWORK_STORAGE_POLICY_PATH: env.NOWWORK_STORAGE_POLICY_PATH }),
91
- };
92
- }
93
85
  export async function runComputerCommand(argv, overrides = {}) {
94
86
  const command = argv[0];
95
87
  if (!command || !COMPUTER_COMMANDS.has(command))
@@ -210,7 +202,6 @@ export async function runComputerCommand(argv, overrides = {}) {
210
202
  ...deps,
211
203
  profile: restartProfile.name,
212
204
  profileHome: home,
213
- environment: storageServiceEnvironment(deps.env),
214
205
  })
215
206
  : null;
216
207
  if (spec && restartProfile) {
@@ -281,7 +272,7 @@ export async function runComputerCommand(argv, overrides = {}) {
281
272
  throw new Error(td("Service lifecycle requires the built daemon entry (.js), not a TypeScript development entry"));
282
273
  }
283
274
  const spec = buildServiceSpec({
284
- ...deps, profile: name, profileHome: home, environment: storageServiceEnvironment(deps.env),
275
+ ...deps, profile: name, profileHome: home,
285
276
  });
286
277
  const installation = daemonGlobalInstallation(deps.entryPath, deps.platform);
287
278
  const managedRecord = installation !== null && spec.platform !== "win32"
@@ -53,7 +53,6 @@ export function windowsCommandArg(value) {
53
53
  }
54
54
  export function buildServiceSpec(input) {
55
55
  const { platform, profile, userHome } = input;
56
- const environment = input.environment ?? process.env;
57
56
  if (platform !== "darwin" && platform !== "linux" && platform !== "win32") {
58
57
  throw new Error(`Computer services are not supported on platform '${platform}'`);
59
58
  }
@@ -73,16 +72,6 @@ export function buildServiceSpec(input) {
73
72
  throw new Error("Cannot install launchd service without a numeric uid");
74
73
  const id = `com.nowcrew.daemon.${profile}${identitySuffix}`;
75
74
  const args = daemonCommand.map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
76
- const environmentEntries = [
77
- ["NOWWORK_HOME", environment.NOWWORK_HOME],
78
- ["NOWWORK_ENV_TAG", environment.NOWWORK_ENV_TAG],
79
- ["NOWWORK_STORAGE_POLICY_PATH", environment.NOWWORK_STORAGE_POLICY_PATH],
80
- ].flatMap(([key, value]) => value === undefined
81
- ? []
82
- : [` <key>${key}</key><string>${xml(assertSingleLine(value, key))}</string>`]);
83
- const environmentBlock = environmentEntries.length === 0
84
- ? ""
85
- : ` <key>EnvironmentVariables</key>\n <dict>\n${environmentEntries.join("\n")}\n </dict>\n`;
86
75
  return {
87
76
  platform,
88
77
  profile,
@@ -90,7 +79,7 @@ export function buildServiceSpec(input) {
90
79
  descriptorPath: resolve(userHome, "Library", "LaunchAgents", `${id}.plist`),
91
80
  daemonCommand,
92
81
  managerDomain: `gui/${input.uid}`,
93
- descriptor: `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n <key>Label</key><string>${xml(id)}</string>\n <key>ProgramArguments</key>\n <array>\n${args}\n </array>\n${environmentBlock} <key>KeepAlive</key><true/>\n <key>ProcessType</key><string>Background</string>\n</dict>\n</plist>\n`,
82
+ descriptor: `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n <key>Label</key><string>${xml(id)}</string>\n <key>ProgramArguments</key>\n <array>\n${args}\n </array>\n <key>KeepAlive</key><true/>\n <key>ProcessType</key><string>Background</string>\n</dict>\n</plist>\n`,
94
83
  };
95
84
  }
96
85
  if (platform === "linux") {
@@ -277,35 +266,6 @@ export async function installService(spec, runner = systemCommandRunner) {
277
266
  throw error;
278
267
  }
279
268
  }
280
- export async function replaceInstalledServiceDescriptor(spec, previousDescriptor, runner = systemCommandRunner) {
281
- if (spec.platform === "win32" || spec.descriptorPath === null || spec.descriptor === null) {
282
- throw new Error("Installed service descriptor replacement requires launchd or systemd");
283
- }
284
- const current = await readServiceDescriptor(spec);
285
- if (current !== previousDescriptor) {
286
- throw new Error(`Installed descriptor for '${spec.id}' changed before replacement`);
287
- }
288
- const status = await serviceStatus(spec, runner);
289
- if (!status.installed)
290
- throw new Error(`Service '${spec.id}' is not installed`);
291
- if (status.running || status.loaded)
292
- await serviceAction(spec, "stop", runner);
293
- try {
294
- await writePrivateAtomic(spec.descriptorPath, spec.descriptor);
295
- if (spec.platform === "linux") {
296
- await checked(runner, "systemctl", ["--user", "daemon-reload"], "systemd reload");
297
- await checked(runner, "systemctl", ["--user", "enable", spec.id], "systemd enable");
298
- }
299
- if (status.running)
300
- await serviceAction(spec, "start", runner);
301
- }
302
- catch (error) {
303
- await writePrivateAtomic(spec.descriptorPath, previousDescriptor).catch(() => undefined);
304
- if (status.running)
305
- await serviceAction(spec, "start", runner).catch(() => undefined);
306
- throw error;
307
- }
308
- }
309
269
  export async function ensureServiceEnabled(spec, runner = systemCommandRunner) {
310
270
  if (spec.platform === "linux") {
311
271
  if (spec.descriptorPath === null || !await pathExists(spec.descriptorPath)) {
package/dist/config.js CHANGED
@@ -59,40 +59,6 @@ function defaultCliPath() {
59
59
  return resolve(here, "../../cli/dist/main.js");
60
60
  }
61
61
  }
62
- // 临时 dev 验收名单。测试完成后删除此常量,恢复未配置时默认关闭的行为。
63
- const TEMP_LOCAL_MEMORY_V2_TEST_HANDLES = [
64
- "memory-v2-test-a",
65
- "memory-v2-test-b",
66
- "memory-v2-test-c",
67
- ];
68
- function isTemporaryDevServer(serverUrl) {
69
- try {
70
- const hostname = new URL(serverUrl).hostname.toLowerCase();
71
- return hostname === "localhost"
72
- || hostname === "127.0.0.1"
73
- || hostname === "::1"
74
- || hostname === "nowwork-dev.nowcoder.com";
75
- }
76
- catch {
77
- return false;
78
- }
79
- }
80
- function localMemoryV2Handles(env, serverUrl) {
81
- const configured = env.CREW_LOCAL_MEMORY_V2_HANDLES;
82
- if (configured === undefined) {
83
- return isTemporaryDevServer(serverUrl)
84
- ? new Set(TEMP_LOCAL_MEMORY_V2_TEST_HANDLES)
85
- : new Set();
86
- }
87
- const raw = configured.trim();
88
- if (raw === "")
89
- return new Set();
90
- const handles = raw.split(",").map((value) => value.trim()).filter(Boolean);
91
- if (handles.some((handle) => !/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(handle))) {
92
- throw new ConfigError("CREW_LOCAL_MEMORY_V2_HANDLES contains an invalid agent handle");
93
- }
94
- return new Set(handles);
95
- }
96
62
  export const DEFAULT_SERVER_URL = "http://127.0.0.1:3001";
97
63
  function codexHomeModeEnv(env) {
98
64
  const value = env.CREW_CODEX_HOME_MODE;
@@ -102,14 +68,6 @@ function codexHomeModeEnv(env) {
102
68
  return "private";
103
69
  throw new ConfigError("CREW_CODEX_HOME_MODE must be shared or private");
104
70
  }
105
- function codexCollaborationToolsEnv(env) {
106
- const value = (env.CREW_CODEX_COLLABORATION_TOOLS ?? "").trim().toLowerCase();
107
- if (value === "" || value === "0" || value === "false" || value === "off")
108
- return false;
109
- if (value === "1" || value === "true" || value === "on")
110
- return true;
111
- throw new ConfigError("CREW_CODEX_COLLABORATION_TOOLS must be one of 1/true/on/0/false/off");
112
- }
113
71
  export function loadConfig(env = process.env) {
114
72
  const serverUrl = (env.CREW_SERVER_URL ?? DEFAULT_SERVER_URL).replace(/\/+$/, "");
115
73
  const machineToken = env.CREW_MACHINE_TOKEN ?? "";
@@ -141,7 +99,6 @@ export function loadConfig(env = process.env) {
141
99
  cliPath: env.CREW_CLI_PATH ?? defaultCliPath(),
142
100
  runtimeBin: env.CREW_RUNTIME ?? "claude",
143
101
  codexHomeMode: codexHomeModeEnv(env),
144
- codexCollaborationTools: codexCollaborationToolsEnv(env),
145
102
  dangerous: env.CREW_RUNTIME_SAFE !== "1", // 默认开启 (headless agent 在自有 workspace 内运行)
146
103
  resume: env.CREW_RESUME !== "off" && env.CREW_RESUME !== "0", // 默认开启;一键回退现状用 CREW_RESUME=off
147
104
  resumeWarmMs: env.CREW_RESUME_WARM_MS != null ? Number(env.CREW_RESUME_WARM_MS) : 3_600_000, // 默认 1h
@@ -151,7 +108,6 @@ export function loadConfig(env = process.env) {
151
108
  sessionMaxTurns: env.CREW_SESSION_MAX_TURNS != null ? Number(env.CREW_SESSION_MAX_TURNS) : 30,
152
109
  productName: env.CREW_PRODUCT_NAME ?? "nowwork",
153
110
  agentMemory: loadAgentMemoryConfig(env),
154
- localMemoryV2Handles: localMemoryV2Handles(env, serverUrl),
155
111
  executionLimits,
156
112
  };
157
113
  }
@@ -10,7 +10,6 @@ import { buildServiceSpec, captureExactService, serviceStatus, systemCommandRunn
10
10
  import { readManagedServiceRegistry } from "./managed-service-registry.js";
11
11
  import { uninstallManagedService } from "./managed-service-lifecycle.js";
12
12
  import { resolveAutomaticInstallTarget } from "./automatic-install-target.js";
13
- import { buildStorageMigrationMappings, createSafeServiceManifest, CredentialMaterialError, } from "./storage-migration-manifest.js";
14
13
  const MigrationMessageSchema = z.object({
15
14
  type: z.literal("daemon:migrate"),
16
15
  migrationId: z.string().uuid(),
@@ -48,30 +47,33 @@ async function hashedFiles(root, directory = root) {
48
47
  return files;
49
48
  }
50
49
  async function backupAndCopy(input, id, targetHome, targetAgents, service) {
51
- // Validate and project service identity before creating any backup bytes. A descriptor containing
52
- // a token must stop the migration before it can be copied into the local backup root.
53
- const safeService = createSafeServiceManifest({
54
- spec: service.spec,
55
- snapshot: service.snapshot,
56
- managedRecord: service.managedRecord,
57
- });
58
50
  const backup = migrationBackupRoot(input, id);
59
51
  await mkdir(backup, { recursive: true, mode: 0o700 });
60
52
  await cp(input.currentDaemonHome, resolve(backup, "daemon-home"), { recursive: true, force: false });
61
53
  await cp(input.currentAgentsRoot, resolve(backup, "agents-root"), { recursive: true, force: false });
62
54
  await cp(input.currentDaemonHome, targetHome, { recursive: true, force: false });
63
55
  await cp(input.currentAgentsRoot, targetAgents, { recursive: true, force: false });
64
- const daemonFiles = await hashedFiles(resolve(backup, "daemon-home"));
65
- const agentFiles = await hashedFiles(resolve(backup, "agents-root"));
66
- const mappings = buildStorageMigrationMappings({ daemonFiles, agentFiles });
56
+ const files = [
57
+ ...(await hashedFiles(resolve(backup, "daemon-home"))).map((file) => ({ ...file, path: `daemon-home/${file.path}` })),
58
+ ...(await hashedFiles(resolve(backup, "agents-root"))).map((file) => ({ ...file, path: `agents-root/${file.path}` })),
59
+ ];
67
60
  await writeFile(resolve(backup, "manifest.json"), `${JSON.stringify({
68
- version: 2,
61
+ version: 1,
69
62
  migrationId: id,
70
63
  profile: input.profileName,
71
64
  daemonHome: input.currentDaemonHome,
72
65
  agentsRoot: input.currentAgentsRoot,
73
- service: safeService,
74
- mappings,
66
+ service: {
67
+ id: service.spec.id,
68
+ platform: service.spec.platform,
69
+ descriptorPath: service.spec.descriptorPath,
70
+ descriptor: service.spec.descriptor,
71
+ daemonCommand: service.spec.daemonCommand,
72
+ installed: service.snapshot.installed,
73
+ running: service.snapshot.running,
74
+ managedRecord: service.managedRecord,
75
+ },
76
+ files,
75
77
  }, null, 2)}\n`, { mode: 0o600 });
76
78
  return backup;
77
79
  }
@@ -145,7 +147,7 @@ export function createDaemonMigrationController(input) {
145
147
  }
146
148
  }
147
149
  catch (error) {
148
- sendFailure(message.migrationId, error instanceof CredentialMaterialError ? "credential_material" : "target_conflict", error);
150
+ sendFailure(message.migrationId, "target_conflict", error);
149
151
  return;
150
152
  }
151
153
  input.sendStatus({ type: "daemon:migration-status", migrationId: message.migrationId, status: "backing_up" });
@@ -156,7 +158,7 @@ export function createDaemonMigrationController(input) {
156
158
  }
157
159
  catch (error) {
158
160
  await Promise.allSettled([rm(targetPath, { recursive: true, force: true }), rm(targetAgents, { recursive: true, force: true })]);
159
- sendFailure(message.migrationId, error instanceof CredentialMaterialError ? "credential_material" : "backup_failed", error);
161
+ sendFailure(message.migrationId, "backup_failed", error);
160
162
  return;
161
163
  }
162
164
  const install = input.install ?? installDaemonAutomatically;
@@ -197,6 +197,7 @@ export const ExecutionStartSchema = z.object({
197
197
  language: z.enum(["zh", "en"]),
198
198
  systemPrompt: z.string(),
199
199
  wakePrompt: z.string(),
200
+ maintenancePurpose: z.literal("self_training").optional(),
200
201
  }).strict(),
201
202
  runtime: z.object({
202
203
  name: RuntimeSchema,
@@ -515,8 +515,9 @@ export async function runExecution(config, input, dependencies) {
515
515
  projectContext = await cancellable(resolveProjectContext(projectSnapshot, dependencies.projectWorkspaceRegistry ?? createProjectRegistry(config.agentsRoot)), dependencies.cancellation);
516
516
  sessionContextFingerprint = projectSessionContextFingerprint(spec.runtime.name, projectSnapshot);
517
517
  }
518
+ const selfTraining = spec.instructions.maintenancePurpose === "self_training";
518
519
  let recalledMemory = "";
519
- if (spec.agent.memoryEnabled === true && dependencies.agentMemory !== undefined) {
520
+ if (!selfTraining && spec.agent.memoryEnabled === true && dependencies.agentMemory !== undefined) {
520
521
  try {
521
522
  recalledMemory = await cancellable(dependencies.agentMemory.recall(spec.agent.handle, spec.instructions.wakePrompt, spec.executionId, spec.agent.memoryAgentId ?? null), dependencies.cancellation);
522
523
  }
@@ -775,7 +776,9 @@ export async function runExecution(config, input, dependencies) {
775
776
  const baseSystemPromptBudget = spec.agent.abilityRelease === undefined
776
777
  ? systemPromptBudget
777
778
  : Math.max(0, Math.min(systemPromptBudget, Math.max(32 * 1024, systemPromptBudget - 168 * 1024)));
778
- const systemPromptWithLocalFacts = withLocalExecutionFacts(spec.instructions.systemPrompt, baseSystemPromptBudget, { collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex" });
779
+ const systemPromptWithLocalFacts = withLocalExecutionFacts(spec.instructions.systemPrompt, baseSystemPromptBudget, {
780
+ ...(selfTraining ? { isolateLocalMemory: true } : {}),
781
+ });
779
782
  const boundedSystemPrompt = (context) => appendAgentMemoryContext(typeof systemPromptWithLocalFacts === "string"
780
783
  ? systemPromptWithLocalFacts
781
784
  : systemPromptWithLocalFacts(context), recalledMemory, baseSystemPromptBudget);
@@ -801,7 +804,6 @@ export async function runExecution(config, input, dependencies) {
801
804
  name: spec.runtime.name,
802
805
  ...(spec.runtime.model === undefined ? {} : { model: spec.runtime.model }),
803
806
  ...(spec.runtime.reasoning === undefined ? {} : { reasoning: spec.runtime.reasoning }),
804
- codexCollaborationTools: config.codexCollaborationTools,
805
807
  },
806
808
  effectivePermission: permission,
807
809
  captureFinal: spec.reporting.captureFinal,
@@ -821,13 +823,13 @@ export async function runExecution(config, input, dependencies) {
821
823
  } : {}),
822
824
  },
823
825
  session: {
824
- enabled: config.resume,
826
+ enabled: selfTraining ? false : config.resume,
825
827
  warmMs: config.resumeWarmMs,
826
828
  budgetTokens: config.sessionBudgetTokens,
827
829
  softTokens: config.sessionSoftTokens,
828
830
  maxTurns: config.sessionMaxTurns,
829
831
  },
830
- localMemoryV2Enabled: config.localMemoryV2Handles?.has(spec.agent.handle) === true,
832
+ ...(selfTraining ? { maintenancePurpose: "self_training" } : {}),
831
833
  };
832
834
  const result = await Promise.race([
833
835
  execute(localInput, callbacks, localDependencies),
@@ -993,6 +995,7 @@ export async function runExecution(config, input, dependencies) {
993
995
  }
994
996
  await dependencies.journal.complete(spec.executionId, completion);
995
997
  if (completion.outcome === "succeeded"
998
+ && spec.instructions.maintenancePurpose !== "self_training"
996
999
  && spec.agent.memoryEnabled === true
997
1000
  && memoryCaptureFinalText !== null
998
1001
  && dependencies.agentMemory !== undefined) {
@@ -4,7 +4,7 @@
4
4
  * server 提示词是权威部分,绝不裁剪;项目工作区提示词是绑定执行的强制部分,放不下就直接失败;
5
5
  * 只有本机事实(CREW_HOME / work-log / MEMORY 注入)是可选增强,按整段丢弃或按字节截断。
6
6
  */
7
- import { buildCollaborationToolsPrompt, buildRuntimeWorkspacePrompt, capMemoryForInject, capWorkLogForInject, } from "./prompt.js";
7
+ import { buildRuntimeWorkspacePrompt, capMemoryForInject, capWorkLogForInject, } from "./prompt.js";
8
8
  export function truncateUtf8(value, maxBytes) {
9
9
  if (maxBytes <= 0)
10
10
  return "";
@@ -32,31 +32,27 @@ function takeCompletePromptSections(sections, maxBytes) {
32
32
  /** v1 server instructions opt in to daemon-owned paths and bounded local workspace context. */
33
33
  export function withLocalExecutionFacts(serverPrompt, maxBytes, options = {}) {
34
34
  return ({ workspace, resuming, projectContext }) => {
35
- const memory = capMemoryForInject(workspace.memory);
35
+ const memory = options.isolateLocalMemory ? "" : capMemoryForInject(workspace.memory);
36
36
  const workLog = resuming ? "" : capWorkLogForInject(workspace.workLog);
37
- const collaborationPrompt = options.collaborationTools === true ? buildCollaborationToolsPrompt() : "";
38
37
  const mandatoryWorkspacePrompt = buildRuntimeWorkspacePrompt(projectContext);
38
+ const crewHome = options.isolateLocalMemory ? workspace.runDir : workspace.dir;
39
39
  const localFactSections = [`\n\n## Local execution facts
40
- - $CREW_HOME: ${workspace.dir}
40
+ - $CREW_HOME: ${crewHome}
41
41
  - $CREW_TASK_LOG: ${workspace.workLogPath}`,
42
42
  ...(memory ? [`\n\n## Injected MEMORY.md (bounded local context)\n${memory}`] : []),
43
43
  ...(workLog ? [`\n\n## Injected work-log (bounded local context)\n${workLog}`] : []),
44
44
  ];
45
- const serverPromptBytes = Buffer.byteLength(serverPrompt, "utf8");
46
- const collaborationBytes = Buffer.byteLength(collaborationPrompt, "utf8");
47
- const mandatoryBytes = Buffer.byteLength(mandatoryWorkspacePrompt, "utf8");
48
- const requiredBytes = serverPromptBytes + collaborationBytes + mandatoryBytes;
49
- // 必需预算与 projectContext 无关:协作段落永远参与;无协作时与旧实现逐字节等价。
50
- if (collaborationPrompt !== "" && requiredBytes > maxBytes) {
51
- throw new Error("collaboration_tools_prompt_budget_exceeded");
52
- }
45
+ const localFacts = localFactSections.join("");
53
46
  if (mandatoryWorkspacePrompt !== "") {
47
+ const serverPromptBytes = Buffer.byteLength(serverPrompt, "utf8");
48
+ const mandatoryBytes = Buffer.byteLength(mandatoryWorkspacePrompt, "utf8");
49
+ const requiredBytes = serverPromptBytes + mandatoryBytes;
54
50
  if (requiredBytes > maxBytes)
55
51
  throw new Error("project_workspace_prompt_budget_exceeded");
56
52
  const optionalFacts = takeCompletePromptSections(localFactSections, maxBytes - requiredBytes);
57
- return `${serverPrompt}${collaborationPrompt}${optionalFacts}${mandatoryWorkspacePrompt}`;
53
+ return `${serverPrompt}${optionalFacts}${mandatoryWorkspacePrompt}`;
58
54
  }
59
- const remaining = maxBytes - requiredBytes;
60
- return `${serverPrompt}${collaborationPrompt}${truncateUtf8(localFactSections.join(""), remaining)}`;
55
+ const remaining = maxBytes - Buffer.byteLength(serverPrompt, "utf8");
56
+ return `${serverPrompt}${truncateUtf8(localFacts, remaining)}`;
61
57
  };
62
58
  }