@nowcrew/daemon 0.6.60 → 0.6.64
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.
- package/README.md +9 -8
- package/dist/agent-ability/materializer.js +1 -20
- package/dist/automatic-install-target.js +10 -15
- package/dist/computer-cli.js +1 -10
- package/dist/computer-service.js +1 -41
- package/dist/config.js +0 -35
- package/dist/daemon-migration-controller.js +18 -16
- package/dist/execution-runner.js +13 -35
- package/dist/local-executor.js +1 -44
- package/dist/machine-info.js +2 -3
- package/dist/main.js +0 -83
- package/dist/managed-service-lifecycle.js +2 -47
- package/dist/managed-service-startup.js +0 -2
- package/dist/profile-layout.js +4 -10
- package/dist/runner.js +0 -1
- package/dist/serve.js +3 -64
- package/dist/workspace.js +1 -24
- package/package.json +10 -9
- package/dist/local-memory/index-budget.js +0 -74
- package/dist/local-memory/pending.js +0 -111
- package/dist/local-memory/runtime.js +0 -87
- package/dist/local-memory/types.js +0 -1
- package/dist/local-memory/write.js +0 -448
- package/dist/storage-config.js +0 -175
- package/dist/storage-control.js +0 -131
- package/dist/storage-copy.js +0 -444
- package/dist/storage-credential-guard.js +0 -21
- package/dist/storage-doctor.js +0 -175
- package/dist/storage-identity.js +0 -29
- package/dist/storage-layout.js +0 -143
- package/dist/storage-manifest.js +0 -440
- package/dist/storage-migration-controller.js +0 -417
- package/dist/storage-migration-manifest.js +0 -105
- package/dist/storage-migration-policy.js +0 -100
- package/dist/storage-runtime-controller.js +0 -313
- package/dist/storage-runtime-readiness.js +0 -184
- package/dist/storage-runtime.js +0 -39
- package/dist/storage-safe-fs.js +0 -293
- package/dist/storage-serve-adapter.js +0 -65
- package/dist/storage-status-delivery.js +0 -201
- package/dist/storage-status-replay.js +0 -125
- package/dist/storage-target-confirmation.js +0 -129
- package/dist/storage-worker-command.js +0 -27
- package/dist/storage-worker-executor.js +0 -383
- package/dist/storage-worker-handoff.js +0 -151
- package/dist/storage-worker-operation.js +0 -283
- package/dist/storage-worker-status.js +0 -105
package/README.md
CHANGED
|
@@ -380,6 +380,7 @@ CREW_EXECUTION_MAX_STARTING_TOTAL=10
|
|
|
380
380
|
CREW_EXECUTION_MAX_STARTING_PER_RUNTIME=10
|
|
381
381
|
CREW_EXECUTION_START_GAP_MS=500
|
|
382
382
|
CREW_EXECUTION_STARTUP_TIMEOUT_MS=120000
|
|
383
|
+
# Retained for compatibility; accepted executions wait indefinitely for a slot.
|
|
383
384
|
CREW_EXECUTION_MAX_QUEUE_WAIT_MS=300000
|
|
384
385
|
CREW_EXECUTION_MAX_FIRST_OUTPUT_WAIT_MS=120000
|
|
385
386
|
CREW_EXECUTION_FIRST_OUTPUT_GRACE_MS=180000
|
|
@@ -391,14 +392,14 @@ protocol-v1 and legacy work.
|
|
|
391
392
|
Runtime startup is a separate FIFO gate: by default up to ten Claude, Codex, or Kimi processes may be
|
|
392
393
|
initializing at a time, and launches are spaced by 500ms. A process leaves the startup gate
|
|
393
394
|
only after its runtime-specific ready event; startup timeout cancels the owned process tree.
|
|
394
|
-
An accepted execution
|
|
395
|
-
the daemon waits up to two minutes for the first model event, with a
|
|
396
|
-
slow providers. When
|
|
397
|
-
terminal result is written to the execution journal before it is
|
|
398
|
-
replayable instead of being silently dropped.
|
|
399
|
-
most twice (three total attempts). Each retry releases its current
|
|
400
|
-
new one, which puts it behind work already waiting in the queue. A
|
|
401
|
-
not requeued; other failures are not automatically retried.
|
|
395
|
+
An accepted execution waits indefinitely for a local and host slot unless it is explicitly cancelled.
|
|
396
|
+
After runtime readiness, the daemon waits up to two minutes for the first model event, with a
|
|
397
|
+
three-minute grace window for slow providers. When the first-output liveness limit expires, the
|
|
398
|
+
supervisor is stopped and the terminal result is written to the execution journal before it is
|
|
399
|
+
reported, so the execution remains replayable instead of being silently dropped. First-output
|
|
400
|
+
timeouts are retried locally at most twice (three total attempts). Each retry releases its current
|
|
401
|
+
machine/host slot and reserves a new one, which puts it behind work already waiting in the queue. A
|
|
402
|
+
third timeout is terminal and is not requeued; other failures are not automatically retried.
|
|
402
403
|
|
|
403
404
|
Local policy can reduce server-requested access and limits; it cannot grant more access than requested.
|
|
404
405
|
Provider credentials and configured environment are prepared locally and never carried in execution
|
|
@@ -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 });
|
|
@@ -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
|
-
|
|
6
|
+
profile: "in",
|
|
7
7
|
daemonDirectory: "daemon",
|
|
8
8
|
agentsDirectory: "agents",
|
|
9
9
|
},
|
|
10
10
|
"nowwork-dev.nowcoder.com": {
|
|
11
|
-
|
|
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"
|
|
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
|
-
|
|
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 === "
|
|
61
|
-
agentsRoot: paths.join(input.userHome, ".crew", environment === "
|
|
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 '${
|
|
64
|
+
throw new Error(`Server hostname '${new URL(input.serverUrl).hostname}' requires profile '${legacyProfile}'`);
|
|
67
65
|
}
|
|
68
66
|
const prefix = `nw-${environment}-`;
|
|
69
|
-
|
|
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.
|
|
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.
|
|
94
|
+
return knownTargetForProfile(known.profile, input.requestedProfile, input, parsed);
|
|
100
95
|
}
|
|
101
96
|
const profile = requireCustomProfile(hostname, input.requestedProfile);
|
|
102
97
|
return Object.freeze({
|
package/dist/computer-cli.js
CHANGED
|
@@ -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,
|
|
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"
|
package/dist/computer-service.js
CHANGED
|
@@ -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
|
|
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;
|
|
@@ -142,7 +108,6 @@ export function loadConfig(env = process.env) {
|
|
|
142
108
|
sessionMaxTurns: env.CREW_SESSION_MAX_TURNS != null ? Number(env.CREW_SESSION_MAX_TURNS) : 30,
|
|
143
109
|
productName: env.CREW_PRODUCT_NAME ?? "nowwork",
|
|
144
110
|
agentMemory: loadAgentMemoryConfig(env),
|
|
145
|
-
localMemoryV2Handles: localMemoryV2Handles(env, serverUrl),
|
|
146
111
|
executionLimits,
|
|
147
112
|
};
|
|
148
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
|
|
65
|
-
|
|
66
|
-
|
|
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:
|
|
61
|
+
version: 1,
|
|
69
62
|
migrationId: id,
|
|
70
63
|
profile: input.profileName,
|
|
71
64
|
daemonHome: input.currentDaemonHome,
|
|
72
65
|
agentsRoot: input.currentAgentsRoot,
|
|
73
|
-
service:
|
|
74
|
-
|
|
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,
|
|
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,
|
|
161
|
+
sendFailure(message.migrationId, "backup_failed", error);
|
|
160
162
|
return;
|
|
161
163
|
}
|
|
162
164
|
const install = input.install ?? installDaemonAutomatically;
|
package/dist/execution-runner.js
CHANGED
|
@@ -299,12 +299,6 @@ class ExecutionCancelledError extends Error {
|
|
|
299
299
|
this.name = "ExecutionCancelledError";
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
|
-
class ExecutionQueueTimeoutError extends Error {
|
|
303
|
-
constructor(timeoutMs) {
|
|
304
|
-
super(`Execution queue wait exceeded ${timeoutMs}ms`);
|
|
305
|
-
this.name = "ExecutionQueueTimeoutError";
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
302
|
async function cancellable(promise, cancellation) {
|
|
309
303
|
if (cancellation === undefined)
|
|
310
304
|
return promise;
|
|
@@ -315,24 +309,9 @@ async function cancellable(promise, cancellation) {
|
|
|
315
309
|
cancellation.requested.then(() => { throw new ExecutionCancelledError(); }),
|
|
316
310
|
]);
|
|
317
311
|
}
|
|
318
|
-
async function cancellableWithTimeout(promise, timeoutMs, cancellation, timeoutError) {
|
|
319
|
-
let timer;
|
|
320
|
-
try {
|
|
321
|
-
return await Promise.race([
|
|
322
|
-
cancellable(promise, cancellation),
|
|
323
|
-
new Promise((_resolve, reject) => {
|
|
324
|
-
timer = setTimeout(() => reject(timeoutError), timeoutMs);
|
|
325
|
-
}),
|
|
326
|
-
]);
|
|
327
|
-
}
|
|
328
|
-
finally {
|
|
329
|
-
if (timer !== undefined)
|
|
330
|
-
clearTimeout(timer);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
312
|
function failedCompletion(spec, error, startedAt, finishedAt) {
|
|
334
313
|
const message = error instanceof Error ? error.message : String(error);
|
|
335
|
-
const errorCode = error instanceof
|
|
314
|
+
const errorCode = error instanceof HostReservationCancelledError
|
|
336
315
|
? "queue_timeout"
|
|
337
316
|
: error instanceof ProjectContextUnavailableError
|
|
338
317
|
? error.code
|
|
@@ -519,7 +498,9 @@ export async function runExecution(config, input, dependencies) {
|
|
|
519
498
|
});
|
|
520
499
|
try {
|
|
521
500
|
if (dependencies.slot !== undefined) {
|
|
522
|
-
|
|
501
|
+
// Queueing is not a liveness failure. Keep the execution durable until its
|
|
502
|
+
// machine/host slot is granted or the server explicitly cancels it.
|
|
503
|
+
await cancellable(dependencies.slot.ready, dependencies.cancellation);
|
|
523
504
|
}
|
|
524
505
|
let projectContext;
|
|
525
506
|
let sessionContextFingerprint;
|
|
@@ -562,17 +543,15 @@ export async function runExecution(config, input, dependencies) {
|
|
|
562
543
|
if (cancel !== null && timeout === undefined && !dependencies.cancellation?.isRequested()) {
|
|
563
544
|
const firstOutputDeadline = config.executionLimits.maxFirstOutputWaitMs
|
|
564
545
|
+ config.executionLimits.firstOutputGraceMs;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}, firstOutputDeadline);
|
|
575
|
-
}
|
|
546
|
+
firstOutputTimer = setTimeout(() => {
|
|
547
|
+
firstOutputTimedOut = true;
|
|
548
|
+
try {
|
|
549
|
+
void cancel().catch(rejectCancellationFailure);
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
rejectCancellationFailure(error);
|
|
553
|
+
}
|
|
554
|
+
}, firstOutputDeadline);
|
|
576
555
|
timeout = setTimeout(() => {
|
|
577
556
|
timedOut = true;
|
|
578
557
|
try {
|
|
@@ -847,7 +826,6 @@ export async function runExecution(config, input, dependencies) {
|
|
|
847
826
|
softTokens: config.sessionSoftTokens,
|
|
848
827
|
maxTurns: config.sessionMaxTurns,
|
|
849
828
|
},
|
|
850
|
-
localMemoryV2Enabled: config.localMemoryV2Handles?.has(spec.agent.handle) === true,
|
|
851
829
|
};
|
|
852
830
|
const result = await Promise.race([
|
|
853
831
|
execute(localInput, callbacks, localDependencies),
|
package/dist/local-executor.js
CHANGED
|
@@ -31,7 +31,6 @@ import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPr
|
|
|
31
31
|
import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
|
|
32
32
|
import { CodexStartupStageParser } from "./codex-startup-stage.js";
|
|
33
33
|
import { formatRuntimeStartupError } from "./runtime-startup-error.js";
|
|
34
|
-
import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, renderLocalMemoryResultInstructions, } from "./local-memory/runtime.js";
|
|
35
34
|
function memoryPruneFileFactFields(fileFacts) {
|
|
36
35
|
const fields = {};
|
|
37
36
|
for (const [label, fact] of Object.entries(fileFacts)) {
|
|
@@ -278,7 +277,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
278
277
|
}, "workspace_prepare", error);
|
|
279
278
|
throw error;
|
|
280
279
|
}
|
|
281
|
-
const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, input.localMemoryV2Enabled === true);
|
|
282
280
|
let materialized = null;
|
|
283
281
|
let knownAttachmentDirectory = null;
|
|
284
282
|
const startupReservationState = { current: null };
|
|
@@ -288,7 +286,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
288
286
|
let memoryPruneFailurePhase = "diagnostics_before";
|
|
289
287
|
let memoryPruneBeforeSnapshot = null;
|
|
290
288
|
let memoryPruneSharedWriteKey = null;
|
|
291
|
-
let localMemoryResultPath = null;
|
|
292
289
|
let executionWorkspace = workspace;
|
|
293
290
|
let localMemoryTelemetry = null;
|
|
294
291
|
const inheritedEnv = { ...process.env };
|
|
@@ -374,6 +371,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
374
371
|
nearBudget,
|
|
375
372
|
...(input.projectContext === undefined ? {} : { projectContext: input.projectContext }),
|
|
376
373
|
};
|
|
374
|
+
const systemPrompt = resolvePrompt(input.systemPrompt, promptContext);
|
|
377
375
|
if (input.attachments && input.attachments.length > 0) {
|
|
378
376
|
if (dependencies.cancellation?.isRequested())
|
|
379
377
|
throw new RuntimeCancelledError();
|
|
@@ -407,29 +405,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
407
405
|
const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
|
|
408
406
|
const wakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
|
|
409
407
|
memoryPruneTraceId = parseMemoryPruneTraceId(wakePrompt);
|
|
410
|
-
if (input.localMemoryV2Enabled === true && memoryPruneTraceId !== null
|
|
411
|
-
&& isLocalMemoryV2Eligible({
|
|
412
|
-
enabledForHandle: true,
|
|
413
|
-
agentHomePreexisted: workspace.agentHomePreexisted === true,
|
|
414
|
-
memorySeedCreated: executionWorkspace.memorySeedCreated === true && (localMemoryV2Initialized || workspace.localMemoryV2Initialized !== false),
|
|
415
|
-
localMemoryV2Initialized,
|
|
416
|
-
memoryPrune: true,
|
|
417
|
-
})) {
|
|
418
|
-
localMemoryResultPath = makeLocalMemoryResultPath(executionWorkspace.runDir, input.executionId);
|
|
419
|
-
}
|
|
420
|
-
const baseSystemPrompt = resolvePrompt(input.systemPrompt, promptContext);
|
|
421
|
-
const structuredMemoryInstructions = localMemoryResultPath === null
|
|
422
|
-
? null
|
|
423
|
-
: renderLocalMemoryResultInstructions(localMemoryResultPath);
|
|
424
|
-
const structuredPromptFits = structuredMemoryInstructions === null
|
|
425
|
-
|| input.maxSystemPromptBytes === undefined
|
|
426
|
-
|| Buffer.byteLength(baseSystemPrompt, "utf8") + Buffer.byteLength(structuredMemoryInstructions, "utf8")
|
|
427
|
-
<= input.maxSystemPromptBytes;
|
|
428
|
-
if (!structuredPromptFits)
|
|
429
|
-
localMemoryResultPath = null;
|
|
430
|
-
const systemPrompt = localMemoryResultPath === null
|
|
431
|
-
? baseSystemPrompt
|
|
432
|
-
: `${baseSystemPrompt}${structuredMemoryInstructions ?? ""}`;
|
|
433
408
|
try {
|
|
434
409
|
localMemoryTelemetry = createLocalMemoryTelemetry({
|
|
435
410
|
executionId: input.executionId,
|
|
@@ -917,24 +892,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
917
892
|
const { exitCode, spawnError, terminationSignal } = runtimeExit;
|
|
918
893
|
localMemoryTelemetry?.markRuntimeExited(exitCode);
|
|
919
894
|
memoryPruneRuntimeExitCode = exitCode;
|
|
920
|
-
if (localMemoryResultPath !== null) {
|
|
921
|
-
const committed = await commitLocalMemoryResultAfterExit({
|
|
922
|
-
resultPath: localMemoryResultPath,
|
|
923
|
-
agentDir: executionWorkspace.dir,
|
|
924
|
-
executionId: input.executionId,
|
|
925
|
-
pruneTraceId: memoryPruneTraceId,
|
|
926
|
-
exitCode,
|
|
927
|
-
});
|
|
928
|
-
if (committed.status === "failed") {
|
|
929
|
-
dslog("local_memory.commit_failed", "结构化本地记忆候选未落地,保留结果文件", {
|
|
930
|
-
level: "WARN",
|
|
931
|
-
execution_id: input.executionId,
|
|
932
|
-
agent_handle: input.handle,
|
|
933
|
-
error_code: committed.error,
|
|
934
|
-
result_path: localMemoryResultPath,
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
895
|
const errorTail = [
|
|
939
896
|
stderrTail.trim(),
|
|
940
897
|
spawnError === undefined
|
package/dist/machine-info.js
CHANGED
|
@@ -191,11 +191,10 @@ export async function collectMachineHello(agentsRoot, executionLimits, runtimePl
|
|
|
191
191
|
profileName: dependencies.profileName,
|
|
192
192
|
daemonHome: dependencies.daemonHome ?? daemonHome(),
|
|
193
193
|
agentsRoot,
|
|
194
|
-
...(dependencies.profileName.match(/^nw-(?:
|
|
194
|
+
...(dependencies.profileName.match(/^nw-(?:in|dev)-([a-z0-9][a-z0-9_-]{0,39})$/u)?.[1] === undefined
|
|
195
195
|
? {}
|
|
196
|
-
: { localInstanceName: dependencies.profileName.match(/^nw-(?:
|
|
196
|
+
: { localInstanceName: dependencies.profileName.match(/^nw-(?:in|dev)-([a-z0-9][a-z0-9_-]{0,39})$/u)[1] }),
|
|
197
197
|
}),
|
|
198
|
-
...(dependencies.storageIdentity ?? {}),
|
|
199
198
|
...(backend.supported ? {
|
|
200
199
|
executionProtocol: EXECUTION_PROTOCOL,
|
|
201
200
|
executionLimits: Object.freeze({
|