@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
@@ -10,8 +10,8 @@ import { PROJECT_MANIFEST_SOURCE_PATH, ProjectDependencyManifestSchema, } from "
10
10
  import { normalizeRepositoryIdentity } from "../repository-identity.js";
11
11
  const runFile = promisify(execFile);
12
12
  const MAX_FILES = 5_000;
13
- const MAX_TOTAL_BYTES = 25 * 1024 * 1024;
14
- const MAX_FILE_BYTES = 2 * 1024 * 1024;
13
+ export const AGENT_ABILITY_MAX_TOTAL_BYTES = 200 * 1024 * 1024;
14
+ export const AGENT_ABILITY_MAX_FILE_BYTES = 100 * 1024 * 1024;
15
15
  const MAX_INSTRUCTIONS_BYTES = 96 * 1024;
16
16
  const MAX_MEMORY_BYTES = 64 * 1024;
17
17
  const AUTHORING_ONLY_ROOTS = new Set([".claude"]);
@@ -125,10 +125,11 @@ export const collectAbilityFiles = async (root) => {
125
125
  if (info.isDirectory())
126
126
  await visit(absolute);
127
127
  else if (info.isFile()) {
128
- if (info.size > MAX_FILE_BYTES)
128
+ if (info.size > AGENT_ABILITY_MAX_FILE_BYTES) {
129
129
  throw new Error(`ability_file_size_exceeded:${relative(root, absolute)}`);
130
+ }
130
131
  totalBytes += info.size;
131
- if (totalBytes > MAX_TOTAL_BYTES)
132
+ if (totalBytes > AGENT_ABILITY_MAX_TOTAL_BYTES)
132
133
  throw new Error("ability_total_size_exceeded");
133
134
  if (files.length >= MAX_FILES)
134
135
  throw new Error("ability_file_count_exceeded");
@@ -262,7 +263,7 @@ export const validateAbilityDependencyDeclarations = (manifest, lock) => {
262
263
  throw new Error("ability_lock_has_undeclared_dependency");
263
264
  }
264
265
  };
265
- const scan = (files, manifest) => {
266
+ export const scanAbilityFiles = (files, manifest) => {
266
267
  const findings = [];
267
268
  const secrets = [
268
269
  /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/u,
@@ -270,7 +271,9 @@ const scan = (files, manifest) => {
270
271
  /\bAK(?:ID|IA)[A-Z0-9]{12,}\b/u,
271
272
  ];
272
273
  const executable = new Set([".exe", ".dll", ".dylib", ".so", ".bin", ".class", ".jar"]);
273
- const allowedBinaryDocuments = new Set([".pdf", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".xlsx", ".xls", ".docx", ".pptx"]);
274
+ const allowedBinaryDocuments = new Set([
275
+ ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".xlsx", ".xls", ".docx", ".pptx", ".ttf", ".otf",
276
+ ]);
274
277
  for (const file of files) {
275
278
  let text;
276
279
  try {
@@ -396,7 +399,7 @@ export async function resolveAbilityRelease(agentsRoot, command) {
396
399
  }
397
400
  const externalPaths = await verifyExternalDependencies(cacheRoot, resolved.root, lock);
398
401
  files = await collectAbilityFiles(resolved.root);
399
- const security = scan(files, manifest);
402
+ const security = scanAbilityFiles(files, manifest);
400
403
  const kinds = new Set(lock.assets.map((asset) => asset.kind));
401
404
  const memory = [];
402
405
  for (const [layer, mounts] of [["release-policy", manifest.spec.memory.policy], ["release-seed", manifest.spec.memory.seeds]]) {
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { posix, resolve, win32 } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { assertProfileAgentsRootUnique, daemonHome, inspectProfileAgentsRoot, listProfiles, loadProfile, PROFILE_AGENTS_ROOT_CONFLICT_MESSAGE, ProfileAgentsRootConflictError, profileIsPrivate, profilePath, publicProfile, removeProfile, resolveAgentsRoot, saveProfile, windowsDpapiProtector, } from "./computer-profile.js";
6
- import { buildServiceSpec, doctorService, hardenWindowsProfile, installService, readServiceDescriptor, serviceAction, serviceStatus, systemCommandRunner, uninstallService, upgradeDaemon, windowsProfileIsPrivate, } from "./computer-service.js";
6
+ import { buildServiceSpec, doctorService, hardenWindowsProfile, installService, readServiceDescriptor, serviceAction, serviceStatus, systemCommandRunner, uninstallService, upgradeDaemon, windowsProfileIsPrivate, storageServiceEnvironment, } from "./computer-service.js";
7
7
  import { releaseTagForServerUrl, assertReleaseTagForServerUrl } from "./automatic-install-target.js";
8
8
  import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
9
9
  import { npmInvocation } from "./npm-command.js";
@@ -89,14 +89,6 @@ function translatedError(error, td) {
89
89
  }
90
90
  return td(error.message);
91
91
  }
92
- function storageServiceEnvironment(env) {
93
- return {
94
- ...(env.NOWWORK_HOME === undefined ? {} : { NOWWORK_HOME: env.NOWWORK_HOME }),
95
- ...(env.NOWWORK_ENV_TAG === undefined ? {} : { NOWWORK_ENV_TAG: env.NOWWORK_ENV_TAG }),
96
- ...(env.NOWWORK_STORAGE_POLICY_PATH === undefined
97
- ? {} : { NOWWORK_STORAGE_POLICY_PATH: env.NOWWORK_STORAGE_POLICY_PATH }),
98
- };
99
- }
100
92
  export async function runComputerCommand(argv, overrides = {}) {
101
93
  const command = argv[0];
102
94
  if (!command || !COMPUTER_COMMANDS.has(command))
@@ -192,6 +184,8 @@ export async function runComputerCommand(argv, overrides = {}) {
192
184
  platform: deps.platform,
193
185
  explicit: deps.env.NOWWORK_LOCAL_HOME,
194
186
  mode: configuredLocalHomeMode,
187
+ ...(resolvedProfile === undefined ? {} : { profile: resolvedProfile }),
188
+ ...(home === undefined ? {} : { daemonHome: home }),
195
189
  });
196
190
  const profileStorage = {
197
191
  platform: deps.platform,
@@ -32,6 +32,18 @@ export const systemCommandRunner = async (command, args) => {
32
32
  };
33
33
  }
34
34
  };
35
+ /** Build the non-secret storage environment persisted in managed services. */
36
+ export function storageServiceEnvironment(env) {
37
+ return {
38
+ ...(env.NOWWORK_HOME === undefined ? {} : { NOWWORK_HOME: env.NOWWORK_HOME }),
39
+ ...(env.NOWWORK_ENV_TAG === undefined ? {} : { NOWWORK_ENV_TAG: env.NOWWORK_ENV_TAG }),
40
+ ...(env.NOWWORK_STORAGE_POLICY_PATH === undefined
41
+ ? {} : { NOWWORK_STORAGE_POLICY_PATH: env.NOWWORK_STORAGE_POLICY_PATH }),
42
+ ...(env.NOWWORK_LOCAL_HOME === undefined ? {} : { NOWWORK_LOCAL_HOME: env.NOWWORK_LOCAL_HOME }),
43
+ ...(env.NOWWORK_LOCAL_HOME_MODE === undefined && env.NOWWORK_STORAGE_MODE === undefined
44
+ ? {} : { NOWWORK_LOCAL_HOME_MODE: env.NOWWORK_LOCAL_HOME_MODE ?? env.NOWWORK_STORAGE_MODE }),
45
+ };
46
+ }
35
47
  function serviceIdentitySuffix(profileHome, userHome) {
36
48
  if (profileHome === undefined || resolve(profileHome) === resolve(userHome, ".crew", "daemon"))
37
49
  return "";
@@ -80,6 +92,8 @@ export function buildServiceSpec(input) {
80
92
  ["NOWWORK_HOME", environment.NOWWORK_HOME],
81
93
  ["NOWWORK_ENV_TAG", environment.NOWWORK_ENV_TAG],
82
94
  ["NOWWORK_STORAGE_POLICY_PATH", environment.NOWWORK_STORAGE_POLICY_PATH],
95
+ ["NOWWORK_LOCAL_HOME", environment.NOWWORK_LOCAL_HOME],
96
+ ["NOWWORK_LOCAL_HOME_MODE", environment.NOWWORK_LOCAL_HOME_MODE],
83
97
  ].flatMap(([key, value]) => value === undefined
84
98
  ? []
85
99
  : [` <key>${key}</key><string>${xml(assertSingleLine(value, key))}</string>`]);
@@ -4,6 +4,13 @@ import { posix, win32 } from "node:path";
4
4
  const PROFILE = /^[a-z0-9][a-z0-9_-]{0,47}$/u;
5
5
  const CONTROL = /[\0\r\n]/u;
6
6
  const pathsFor = (platform) => platform === "win32" ? win32 : posix;
7
+ function samePath(left, right, platform) {
8
+ const normalizedLeft = pathsFor(platform).resolve(left);
9
+ const normalizedRight = pathsFor(platform).resolve(right);
10
+ return platform === "win32"
11
+ ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
12
+ : normalizedLeft === normalizedRight;
13
+ }
7
14
  export function parseDaemonLocalHomeMode(value) {
8
15
  if (value === undefined || value.trim() === "")
9
16
  return "auto";
@@ -86,6 +93,22 @@ export function resolveDaemonLocalStateRoot(input) {
86
93
  return legacyRoot;
87
94
  if (input.explicit !== undefined || mode === "nowwork")
88
95
  return nowworkRoot;
96
+ // Managed service descriptors created before local-home was persisted carry
97
+ // only --daemon-home. Do not treat every --daemon-home as legacy: migrated
98
+ // services also use that argument, but point it at ~/.nowwork/config.
99
+ // Prefer the descriptor's exact home when it identifies either layout.
100
+ if (input.daemonHome !== undefined && input.profile !== undefined) {
101
+ const nowworkDaemonHome = resolution("nowwork", nowworkRoot, input.profile, input.platform, "auto_nowwork_existing").daemonHome;
102
+ const legacyDaemonHome = resolution("legacy", legacyRoot, input.profile, input.platform, "auto_legacy_existing").daemonHome;
103
+ if (samePath(input.daemonHome, nowworkDaemonHome, input.platform))
104
+ return nowworkRoot;
105
+ if (samePath(input.daemonHome, legacyDaemonHome, input.platform))
106
+ return legacyRoot;
107
+ // An arbitrary explicit daemon home has historically implied the legacy
108
+ // local state root. Preserve that behavior unless the descriptor declares
109
+ // a mode explicitly.
110
+ return legacyRoot;
111
+ }
89
112
  const exists = input.exists ?? existsSync;
90
113
  // Once the new root exists, shared state must follow it even while the
91
114
  // legacy root is retained as a rollback source.
@@ -4,7 +4,7 @@ import { realpathSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import { daemonHome, loadProfile, resolveAgentsRoot } from "./computer-profile.js";
6
6
  import { builtDaemonEntry } from "./computer-cli.js";
7
- import { buildServiceSpec, readServiceDescriptor, serviceStatus, } from "./computer-service.js";
7
+ import { buildServiceSpec, readServiceDescriptor, storageServiceEnvironment, serviceStatus, } from "./computer-service.js";
8
8
  import { daemonGlobalInstallation } from "./daemon-installation.js";
9
9
  import { daemonInstallationLeaseHeld } from "./daemon-installation-lease.js";
10
10
  import { inspectLegacyServiceInstallation } from "./legacy-service-installation.js";
@@ -21,9 +21,9 @@ export async function managedDaemonCapabilities(eligibility) {
21
21
  return [];
22
22
  }
23
23
  }
24
- function defaults(profileName) {
24
+ function defaults(profileName, selectedLocalHome) {
25
25
  const userHome = homedir();
26
- const localHome = resolveDaemonLocalStateRoot({
26
+ const localHome = selectedLocalHome ?? resolveDaemonLocalStateRoot({
27
27
  userHome,
28
28
  platform: process.platform,
29
29
  explicit: process.env.NOWWORK_LOCAL_HOME,
@@ -31,6 +31,7 @@ function defaults(profileName) {
31
31
  });
32
32
  return {
33
33
  platform: process.platform,
34
+ localHome,
34
35
  profileHome: daemonHome(process.env, profileName),
35
36
  userHome,
36
37
  uid: process.getuid?.(),
@@ -65,7 +66,7 @@ async function registryCoversUpdateScope(services, descriptorPaths, targetDescri
65
66
  return true;
66
67
  }
67
68
  export async function detectDaemonUpdateEligibility(profileName, overrides = {}) {
68
- const deps = { ...defaults(profileName), ...overrides };
69
+ const deps = { ...defaults(profileName, overrides.localHome), ...overrides };
69
70
  if (deps.platform !== "darwin" && deps.platform !== "linux" && deps.platform !== "win32") {
70
71
  return { eligible: false, reason: "unsupported_platform" };
71
72
  }
@@ -92,6 +93,7 @@ export async function detectDaemonUpdateEligibility(profileName, overrides = {})
92
93
  nodePath: deps.nodePath,
93
94
  entryPath: deps.entryPath,
94
95
  profileHome: deps.profileHome,
96
+ environment: storageServiceEnvironment(process.env),
95
97
  });
96
98
  if (deps.platform === "win32") {
97
99
  const scope = await deps.inspectWindowsServiceUpdateScope(spec, installation.npmPrefix)
@@ -169,6 +171,7 @@ export async function detectDaemonUpdateEligibility(profileName, overrides = {})
169
171
  entryPath: deps.entryPath,
170
172
  profileHome: deps.profileHome,
171
173
  legacyServiceId: true,
174
+ environment: storageServiceEnvironment(process.env),
172
175
  });
173
176
  const legacyDescriptor = await deps.readServiceDescriptor(legacySpec).catch(() => null);
174
177
  if (legacyDescriptor !== legacySpec.descriptor) {
@@ -200,6 +200,7 @@ export const ExecutionStartSchema = z.object({
200
200
  language: z.enum(["zh", "en"]),
201
201
  systemPrompt: z.string(),
202
202
  wakePrompt: z.string(),
203
+ maintenancePurpose: z.literal("self_training").optional(),
203
204
  }).strict(),
204
205
  runtime: z.object({
205
206
  name: RuntimeSchema,
@@ -567,8 +567,9 @@ export async function runExecution(config, input, dependencies) {
567
567
  projectContext = await cancellable(resolveProjectContext(projectSnapshot, projectWorkspaceRegistry), dependencies.cancellation);
568
568
  sessionContextFingerprint = projectSessionContextFingerprint(spec.runtime.name, projectSnapshot);
569
569
  }
570
+ const selfTraining = spec.instructions.maintenancePurpose === "self_training";
570
571
  let recalledMemory = "";
571
- if (spec.agent.memoryEnabled === true && dependencies.agentMemory !== undefined) {
572
+ if (!selfTraining && spec.agent.memoryEnabled === true && dependencies.agentMemory !== undefined) {
572
573
  try {
573
574
  recalledMemory = await cancellable(dependencies.agentMemory.recall(spec.agent.handle, spec.instructions.wakePrompt, spec.executionId, spec.agent.memoryAgentId ?? null), dependencies.cancellation);
574
575
  }
@@ -827,7 +828,10 @@ export async function runExecution(config, input, dependencies) {
827
828
  const baseSystemPromptBudget = spec.agent.abilityRelease === undefined
828
829
  ? systemPromptBudget
829
830
  : Math.max(0, Math.min(systemPromptBudget, Math.max(32 * 1024, systemPromptBudget - 168 * 1024)));
830
- const systemPromptWithLocalFacts = withLocalExecutionFacts(spec.instructions.systemPrompt, baseSystemPromptBudget, { collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex" });
831
+ const systemPromptWithLocalFacts = withLocalExecutionFacts(spec.instructions.systemPrompt, baseSystemPromptBudget, {
832
+ ...(selfTraining ? { isolateLocalMemory: true } : {}),
833
+ collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
834
+ });
831
835
  const boundedSystemPrompt = (context) => appendAgentMemoryContext(typeof systemPromptWithLocalFacts === "string"
832
836
  ? systemPromptWithLocalFacts
833
837
  : systemPromptWithLocalFacts(context), recalledMemory, baseSystemPromptBudget);
@@ -876,12 +880,13 @@ export async function runExecution(config, input, dependencies) {
876
880
  } : {}),
877
881
  },
878
882
  session: {
879
- enabled: config.resume,
883
+ enabled: selfTraining ? false : config.resume,
880
884
  warmMs: config.resumeWarmMs,
881
885
  budgetTokens: config.sessionBudgetTokens,
882
886
  softTokens: config.sessionSoftTokens,
883
887
  maxTurns: config.sessionMaxTurns,
884
888
  },
889
+ ...(selfTraining ? { maintenancePurpose: "self_training" } : {}),
885
890
  localMemoryV2Enabled: config.localMemoryV2Handles?.has(spec.agent.handle) === true,
886
891
  };
887
892
  const result = await Promise.race([
@@ -1048,6 +1053,7 @@ export async function runExecution(config, input, dependencies) {
1048
1053
  }
1049
1054
  await dependencies.journal.complete(spec.executionId, completion);
1050
1055
  if (completion.outcome === "succeeded"
1056
+ && spec.instructions.maintenancePurpose !== "self_training"
1051
1057
  && spec.agent.memoryEnabled === true
1052
1058
  && memoryCaptureFinalText !== null
1053
1059
  && dependencies.agentMemory !== undefined) {
@@ -47,12 +47,13 @@ function takeCompletePromptSections(sections, maxBytes) {
47
47
  /** v1 server instructions opt in to daemon-owned paths and bounded local workspace context. */
48
48
  export function withLocalExecutionFacts(serverPrompt, maxBytes, options = {}) {
49
49
  return ({ workspace, resuming, projectContext, trainingProjectContext }) => {
50
- const memory = capMemoryForInject(workspace.memory);
50
+ const memory = options.isolateLocalMemory ? "" : capMemoryForInject(workspace.memory);
51
51
  const workLog = resuming ? "" : capWorkLogForInject(workspace.workLog);
52
52
  const collaborationPrompt = options.collaborationTools === true ? buildCollaborationToolsPrompt() : "";
53
53
  const mandatoryWorkspacePrompt = `${buildRuntimeWorkspacePrompt(projectContext)}${buildTrainingGitProjectPrompt(trainingProjectContext)}`;
54
+ const crewHome = options.isolateLocalMemory ? workspace.runDir : workspace.dir;
54
55
  const localFactSections = [`\n\n## Local execution facts
55
- - $CREW_HOME: ${workspace.dir}
56
+ - $CREW_HOME: ${crewHome}
56
57
  - $CREW_TASK_LOG: ${workspace.workLogPath}`,
57
58
  ...(memory ? [`\n\n## Injected MEMORY.md (bounded local context)\n${memory}`] : []),
58
59
  ...(workLog ? [`\n\n## Injected work-log (bounded local context)\n${workLog}`] : []),
@@ -1,6 +1,7 @@
1
1
  import { createInterface } from "node:readline";
2
2
  import { readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { delimiter, join } from "node:path";
4
+ import { cleanupSelfTrainingRunDir } from "./self-training-workspace.js";
4
5
  import { prepareWorkspace, rotateAgentSession, safeKey, } from "./workspace.js";
5
6
  import { CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV, isClaudeAdditionalDirectoryInstructionsSupported, probeClaudeVersion, spawnClaude, } from "./runtimes/claude.js";
6
7
  import { spawnCodex } from "./runtimes/codex.js";
@@ -111,6 +112,7 @@ async function launchLegacyRuntime(request) {
111
112
  effectivePermission: request.effectivePermission,
112
113
  ...(request.model === undefined ? {} : { model: request.model }),
113
114
  ...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
115
+ ...(request.networkAccess === undefined ? {} : { networkAccess: request.networkAccess }),
114
116
  };
115
117
  if (request.runtime === "claude") {
116
118
  const additionalDirectories = request.claudeAdditionalDirectories
@@ -200,6 +202,7 @@ export async function executeLocal(input, callbacks = {}, dependencies = {}) {
200
202
  async function executeLocalUnlocked(input, callbacks, dependencies) {
201
203
  const providerConfig = input.launch.providerConfig ?? {};
202
204
  const { runtime } = input;
205
+ const isolatedMaintenance = input.maintenancePurpose === "self_training";
203
206
  const isDeepSeekCodex = runtime.name === "codex" && providerConfig.provider === "deepseek";
204
207
  const currentModel = isDeepSeekCodex ? DEEPSEEK_CODEX_MODEL : runtime.model ?? null;
205
208
  const launchModel = isDeepSeekCodex ? DEEPSEEK_CODEX_MODEL : runtime.model;
@@ -224,6 +227,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
224
227
  ? {}
225
228
  : { sessionContextFingerprint: input.sessionContextFingerprint }),
226
229
  ...(input.launch.description ? { description: input.launch.description } : {}),
230
+ ...(isolatedMaintenance ? { skipLocalMemory: true } : {}),
227
231
  }), dependencies.cancellation);
228
232
  }
229
233
  catch (error) {
@@ -254,12 +258,23 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
254
258
  }
255
259
  const sourceCodexHome = inheritedEnv.CODEX_HOME
256
260
  ?? (inheritedEnv.HOME === undefined ? undefined : join(inheritedEnv.HOME, ".codex"));
261
+ let materializedDeepSeekHome;
257
262
  try {
258
263
  if (isDeepSeekCodex && !providerConfig.providerApiKey) {
259
264
  throw new Error("DeepSeek API key is not configured for this Agent");
260
265
  }
261
266
  if (isDeepSeekCodex) {
262
- await awaitWithCancellation((dependencies.materializeDeepSeekCodexHome ?? materializeDeepSeekCodexHome)(workspace.homeDir), dependencies.cancellation);
267
+ const providerBaseUrl = providerConfig.providerBaseUrl
268
+ ? new URL(providerConfig.providerBaseUrl, input.launch.serverUrl).toString()
269
+ : undefined;
270
+ const materialize = dependencies.materializeDeepSeekCodexHome ?? materializeDeepSeekCodexHome;
271
+ const materializeOptions = {
272
+ ...(providerBaseUrl === undefined ? {} : { baseUrl: providerBaseUrl }),
273
+ ...(isolatedMaintenance ? { selfTraining: true } : {}),
274
+ };
275
+ materializedDeepSeekHome = await awaitWithCancellation(providerBaseUrl === undefined && !isolatedMaintenance
276
+ ? materialize(workspace.homeDir)
277
+ : materialize(workspace.homeDir, materializeOptions), dependencies.cancellation);
263
278
  }
264
279
  const supportsNativeResume = runtime.name === "claude"
265
280
  || (dependencies.launchRuntime !== undefined && runtimeCapability(runtime.name).nativeResume);
@@ -387,29 +402,30 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
387
402
  const systemPrompt = localMemoryResultPath === null
388
403
  ? baseSystemPrompt
389
404
  : `${baseSystemPrompt}${structuredMemoryInstructions ?? ""}`;
390
- try {
391
- localMemoryTelemetry = createLocalMemoryTelemetry({
392
- executionId: input.executionId,
393
- agentHandle: input.handle,
394
- ...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
395
- runtime: runtime.name,
396
- stableProtocolRuntime: dependencies.launchRuntime !== undefined,
397
- agentDir: executionWorkspace.dir,
398
- runDir: executionWorkspace.runDir,
399
- isMemoryPrune: memoryPruneTraceId !== null,
400
- diagnosticsTimeoutMs: dependencies.localMemoryDiagnosticsTimeoutMs
401
- ?? LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS,
402
- });
403
- await localMemoryTelemetry.captureBefore();
404
- }
405
- catch (error) {
406
- localMemoryTelemetry = null;
407
- logLocalMemoryDiagnosticsFailure({
408
- executionId: input.executionId,
409
- agentHandle: input.handle,
410
- ...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
411
- }, "initialize", error);
412
- }
405
+ if (!isolatedMaintenance)
406
+ try {
407
+ localMemoryTelemetry = createLocalMemoryTelemetry({
408
+ executionId: input.executionId,
409
+ agentHandle: input.handle,
410
+ ...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
411
+ runtime: runtime.name,
412
+ stableProtocolRuntime: dependencies.launchRuntime !== undefined,
413
+ agentDir: executionWorkspace.dir,
414
+ runDir: executionWorkspace.runDir,
415
+ isMemoryPrune: memoryPruneTraceId !== null,
416
+ diagnosticsTimeoutMs: dependencies.localMemoryDiagnosticsTimeoutMs
417
+ ?? LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS,
418
+ });
419
+ await localMemoryTelemetry.captureBefore();
420
+ }
421
+ catch (error) {
422
+ localMemoryTelemetry = null;
423
+ logLocalMemoryDiagnosticsFailure({
424
+ executionId: input.executionId,
425
+ agentHandle: input.handle,
426
+ ...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
427
+ }, "initialize", error);
428
+ }
413
429
  if (memoryPruneTraceId !== null) {
414
430
  memoryPruneSharedWriteKey = JSON.stringify([input.launch.agentsRoot, input.handle]);
415
431
  const activePruneCount = (activeMemoryPrunes.get(memoryPruneSharedWriteKey) ?? 0) + 1;
@@ -443,6 +459,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
443
459
  }
444
460
  }
445
461
  memoryPruneFailurePhase = "runtime_prepare";
462
+ const crewHome = isolatedMaintenance ? executionWorkspace.runDir : workspace.dir;
446
463
  const baseEnv = {
447
464
  ...inheritedEnv,
448
465
  ...sanitizeEnvVars(providerConfig.envVars),
@@ -454,7 +471,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
454
471
  CREW_SERVER_URL: input.launch.serverUrl,
455
472
  CREW_TOKEN: input.launch.token,
456
473
  CREW_CHANNEL: input.channelId,
457
- CREW_HOME: workspace.dir,
474
+ CREW_HOME: crewHome,
458
475
  // Keep the daemon-selected storage root authoritative for runtimes.
459
476
  // In profile-backed NowWork sessions the daemon config may differ from
460
477
  // the daemon process environment, so inheriting CREW_AGENTS_ROOT is not
@@ -464,7 +481,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
464
481
  ...(input.launch.qaReportRoot === undefined ? {} : { QA_REPORT_HUB: input.launch.qaReportRoot }),
465
482
  // Expose only the training root; ability-specific workspace layout stays
466
483
  // owned by the training package rather than the NowWork daemon.
467
- QA_RUNTIME_ROOT: join(workspace.dir, "training"),
484
+ QA_RUNTIME_ROOT: join(crewHome, "training"),
468
485
  CREW_TASK_DIR: executionWorkspace.runDir,
469
486
  CREW_TASK_LOG: executionWorkspace.workLogPath,
470
487
  ...(input.wakeMessageId ? { CREW_WAKE_MESSAGE_ID: input.wakeMessageId } : {}),
@@ -480,7 +497,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
480
497
  };
481
498
  const providerEnv = applyProviderEnv(baseEnv, runtime.name, input.launch.codexHomeMode === undefined
482
499
  ? providerConfig
483
- : { ...providerConfig, codexHomeMode: input.launch.codexHomeMode }, workspace.homeDir);
500
+ : { ...providerConfig, codexHomeMode: input.launch.codexHomeMode }, workspace.homeDir, materializedDeepSeekHome);
484
501
  const childEnv = runtime.name === "deepseek-harness"
485
502
  ? applyDeepSeekHarnessMachineEnv(providerEnv, inheritedEnv)
486
503
  : providerEnv;
@@ -492,7 +509,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
492
509
  qa_runtime_root_present: childEnv.QA_RUNTIME_ROOT !== undefined,
493
510
  inherited_qa_runtime_root_present: inheritedEnv.QA_RUNTIME_ROOT !== undefined,
494
511
  ...packageManagerEnvFacts(childEnv),
495
- training_root: join(workspace.dir, "training"),
512
+ training_root: join(crewHome, "training"),
496
513
  project_context_present: input.projectContext !== undefined,
497
514
  project_ids: JSON.stringify(input.projectContext === undefined
498
515
  ? []
@@ -624,6 +641,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
624
641
  ...(input.projectContext === undefined ? {} : {
625
642
  projectContext: input.projectContext,
626
643
  }),
644
+ ...(isolatedMaintenance && runtime.name === "codex" ? { networkAccess: true } : {}),
627
645
  ...(input.projectContext === undefined && workspaceRoots.length === 0
628
646
  ? {}
629
647
  : { workspaceRoots: Object.freeze(workspaceRoots) }),
@@ -1107,5 +1125,8 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
1107
1125
  const detail = error instanceof Error ? error.message : String(error);
1108
1126
  process.stderr.write(`[execution] failed to remove prompt ${workspace.systemPromptPath}: ${detail}\n`);
1109
1127
  }
1128
+ if (isolatedMaintenance) {
1129
+ await cleanupSelfTrainingRunDir(join(workspace.dir, "tasks"), executionWorkspace.runDir);
1130
+ }
1110
1131
  }
1111
1132
  }
@@ -25,7 +25,9 @@ export { executableRuntimes } from "./runtime-capabilities.js";
25
25
  const execFileP = promisify(execFile);
26
26
  export const PROJECT_WORKSPACES_CAPABILITY = "project_workspaces_v1";
27
27
  export const TRAINING_PROJECT_DEPENDENCIES_CAPABILITY = "training_project_dependencies_v1";
28
+ export const SELF_TRAINING_EXECUTION_CAPABILITY = "self_training_execution_v1";
28
29
  export const EXECUTION_LARGE_ATTACHMENTS_CAPABILITY = "execution_attachments_300m_v1";
30
+ export const PROJECT_SKILL_PROJECTION_CAPABILITY = "project_skill_projection_v2";
29
31
  export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
30
32
  resolverReady: true,
31
33
  nativeRuntimeAdaptersReady: true,
@@ -42,6 +44,7 @@ export const DAEMON_CAPABILITIES = Object.freeze([
42
44
  "execution_machine_queue_v1",
43
45
  "execution_agent_memory_policy_v1",
44
46
  "execution_agent_memory_mapping_v1",
47
+ SELF_TRAINING_EXECUTION_CAPABILITY,
45
48
  "project_skills_v1",
46
49
  "project_skill_projection_v2",
47
50
  PROJECT_WORKSPACES_CAPABILITY,
package/dist/main.js CHANGED
@@ -31,6 +31,7 @@ import { runStorageWorkerCommand } from "./storage-worker-command.js";
31
31
  import { probeStorageMigrationRuntime } from "./storage-runtime-readiness.js";
32
32
  import { createStorageTargetConfirmationHandler } from "./storage-target-confirmation.js";
33
33
  import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
34
+ import { storageServiceEnvironment } from "./computer-service.js";
34
35
  async function main() {
35
36
  const workerResult = await runStorageWorkerCommand(process.argv.slice(2));
36
37
  if (workerResult !== null) {
@@ -78,9 +79,9 @@ async function main() {
78
79
  userHome: homedir(),
79
80
  platform: process.platform,
80
81
  explicit: process.env.NOWWORK_LOCAL_HOME,
81
- mode: process.env.NOWWORK_LOCAL_HOME_MODE
82
- ?? process.env.NOWWORK_STORAGE_MODE
83
- ?? (values["daemon-home"] === undefined ? undefined : "legacy"),
82
+ mode: process.env.NOWWORK_LOCAL_HOME_MODE ?? process.env.NOWWORK_STORAGE_MODE,
83
+ ...(values.profile === undefined ? {} : { profile: values.profile }),
84
+ ...(values["daemon-home"] === undefined ? {} : { daemonHome: values["daemon-home"] }),
84
85
  });
85
86
  if (values.profile) {
86
87
  const home = daemonHome(process.env, values.profile);
@@ -98,12 +99,7 @@ async function main() {
98
99
  agentsRoot: resolveAgentsRoot(profile.agentsRoot, homedir(), process.platform),
99
100
  nodePath: process.execPath,
100
101
  entryPath: builtDaemonEntry(),
101
- environment: {
102
- ...(process.env.NOWWORK_HOME === undefined ? {} : { NOWWORK_HOME: process.env.NOWWORK_HOME }),
103
- ...(process.env.NOWWORK_ENV_TAG === undefined ? {} : { NOWWORK_ENV_TAG: process.env.NOWWORK_ENV_TAG }),
104
- ...(process.env.NOWWORK_STORAGE_POLICY_PATH === undefined
105
- ? {} : { NOWWORK_STORAGE_POLICY_PATH: process.env.NOWWORK_STORAGE_POLICY_PATH }),
106
- },
102
+ environment: storageServiceEnvironment(process.env),
107
103
  });
108
104
  }
109
105
  applyProfileToEnv(profile, process.env);
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { acquireDaemonInstallationLease } from "./daemon-installation-lease.js";
2
+ import { acquireDaemonInstallationLease, } from "./daemon-installation-lease.js";
3
3
  import { installService, ensureServiceEnabled, readServiceDescriptor, serviceAction, replaceInstalledServiceDescriptor, uninstallService, } from "./computer-service.js";
4
4
  import { ManagedServiceConflictError, ManagedServiceRegistryError, assertRegistryCoversDescriptors, listManagedServiceDescriptorPaths, managedServiceIdentityMatches, managedServiceConflict, normalizeManagedServiceRecord, serviceDescriptorSha256, withManagedServiceRegistryLock, } from "./managed-service-registry.js";
5
5
  function requirePortableManagedSpec(spec) {
@@ -43,7 +43,7 @@ function sameManagedResource(left, right) {
43
43
  function descriptorDiffIsStorageEnvironmentOnly(previous, next) {
44
44
  if (!next.includes("\n <key>EnvironmentVariables</key>\n"))
45
45
  return false;
46
- const strip = (descriptor) => descriptor.replace(/\n <key>EnvironmentVariables<\/key>\n <dict>\n(?: <key>(?:NOWWORK_HOME|NOWWORK_ENV_TAG|NOWWORK_STORAGE_POLICY_PATH)<\/key><string>[^<]*<\/string>\n)+ <\/dict>\n/u, "\n");
46
+ const strip = (descriptor) => descriptor.replace(/\n <key>EnvironmentVariables<\/key>\n <dict>\n(?: <key>(?:NOWWORK_HOME|NOWWORK_ENV_TAG|NOWWORK_STORAGE_POLICY_PATH|NOWWORK_LOCAL_HOME|NOWWORK_LOCAL_HOME_MODE)<\/key><string>[^<]*<\/string>\n)+ <\/dict>\n/u, "\n");
47
47
  return strip(previous) === strip(next);
48
48
  }
49
49
  function assertRecordDescribesSpec(input) {
@@ -90,6 +90,7 @@ export async function installManagedService(input) {
90
90
  }
91
91
  const lease = await acquire(input.record.npmPrefix, {
92
92
  ...(input.registry?.userHome === undefined ? {} : { userHome: input.registry.userHome }),
93
+ ...(input.registry?.localHome === undefined ? {} : { localHome: input.registry.localHome }),
93
94
  });
94
95
  try {
95
96
  await installService(input.spec, input.runner);
@@ -136,6 +137,7 @@ export async function installManagedService(input) {
136
137
  }
137
138
  const lease = await acquire(input.record.npmPrefix, {
138
139
  ...(input.registry?.userHome === undefined ? {} : { userHome: input.registry.userHome }),
140
+ ...(input.registry?.localHome === undefined ? {} : { localHome: input.registry.localHome }),
139
141
  });
140
142
  try {
141
143
  const descriptor = await readServiceDescriptor(input.spec);
@@ -170,6 +172,7 @@ export async function uninstallUnregisteredManagedService(input) {
170
172
  const acquire = input.acquireInstallationLease ?? acquireDaemonInstallationLease;
171
173
  const lease = await acquire(input.npmPrefix, {
172
174
  ...(input.registry?.userHome === undefined ? {} : { userHome: input.registry.userHome }),
175
+ ...(input.registry?.localHome === undefined ? {} : { localHome: input.registry.localHome }),
173
176
  });
174
177
  try {
175
178
  await uninstallService(input.spec, input.runner);
@@ -199,6 +202,7 @@ export async function uninstallManagedService(input) {
199
202
  }
200
203
  const lease = await acquire(input.record.npmPrefix, {
201
204
  ...(input.registry?.userHome === undefined ? {} : { userHome: input.registry.userHome }),
205
+ ...(input.registry?.localHome === undefined ? {} : { localHome: input.registry.localHome }),
202
206
  });
203
207
  try {
204
208
  if (descriptor !== null)