@nowcrew/daemon 0.6.68 → 0.6.69
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/dist/agent-ability/materializer.js +30 -1
- package/dist/agent-ability/resolver.js +27 -16
- package/dist/agent-ability/runtime.js +3 -0
- package/dist/agent-ability/types.js +6 -11
- package/dist/automatic-install-target.js +15 -10
- package/dist/background-job.js +50 -0
- package/dist/computer-cli.js +10 -1
- package/dist/computer-service.js +41 -1
- package/dist/config.js +44 -0
- package/dist/console-formatter.js +76 -1
- package/dist/console.js +23 -4
- package/dist/daemon-migration-controller.js +16 -18
- package/dist/execution-protocol.js +0 -1
- package/dist/execution-runner.js +60 -11
- package/dist/i18n.js +2 -0
- package/dist/local-execution-prompt.js +32 -13
- package/dist/local-executor.js +107 -55
- package/dist/local-memory/index-budget.js +74 -0
- package/dist/local-memory/pending.js +111 -0
- package/dist/local-memory/runtime.js +87 -0
- package/dist/local-memory/types.js +1 -0
- package/dist/local-memory/write.js +448 -0
- package/dist/machine-info.js +3 -4
- package/dist/main.js +83 -0
- package/dist/managed-service-lifecycle.js +47 -2
- package/dist/managed-service-startup.js +2 -0
- package/dist/profile-layout.js +10 -4
- package/dist/project-dependencies.js +139 -124
- package/dist/project-dependency-lock.js +87 -0
- package/dist/project-dependency-manifest.js +2 -12
- package/dist/project-registry-wiring.js +1 -4
- package/dist/project-skills/controller.js +2 -0
- package/dist/project-skills/runtime-launch.js +10 -7
- package/dist/prompt.js +19 -1
- package/dist/provider-env.js +3 -21
- package/dist/repository-identity.js +45 -0
- package/dist/runner.js +4 -2
- package/dist/runtimes/codex-app-server-runner.js +55 -23
- package/dist/runtimes/codex-command-safety.js +75 -0
- package/dist/runtimes/codex-contract.js +20 -5
- package/dist/runtimes/codex-deepseek-config.js +7 -17
- package/dist/runtimes/codex-turn-retry.js +69 -0
- package/dist/runtimes/codex.js +4 -3
- package/dist/serve.js +68 -3
- package/dist/storage-config.js +175 -0
- package/dist/storage-control.js +193 -0
- package/dist/storage-copy.js +628 -0
- package/dist/storage-credential-guard.js +21 -0
- package/dist/storage-doctor.js +228 -0
- package/dist/storage-identity.js +29 -0
- package/dist/storage-layout.js +143 -0
- package/dist/storage-manifest.js +855 -0
- package/dist/storage-migration-controller.js +420 -0
- package/dist/storage-migration-manifest.js +105 -0
- package/dist/storage-migration-policy.js +100 -0
- package/dist/storage-runtime-controller.js +339 -0
- package/dist/storage-runtime-readiness.js +184 -0
- package/dist/storage-runtime.js +39 -0
- package/dist/storage-safe-fs.js +302 -0
- package/dist/storage-serve-adapter.js +65 -0
- package/dist/storage-status-delivery.js +209 -0
- package/dist/storage-status-replay.js +125 -0
- package/dist/storage-target-confirmation.js +129 -0
- package/dist/storage-worker-command.js +37 -0
- package/dist/storage-worker-executor.js +430 -0
- package/dist/storage-worker-handoff.js +151 -0
- package/dist/storage-worker-operation.js +284 -0
- package/dist/storage-worker-status.js +259 -0
- package/dist/supervised-runtime.js +3 -1
- package/dist/workspace.js +34 -14
- package/package.json +1 -1
- package/dist/self-training-workspace.js +0 -15
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { readStorageMigrationReceipt, storageMigrationReplayStatuses, } from "./storage-migration-controller.js";
|
|
3
|
+
import { readStorageWorkerFailureStatus, readStorageWorkerHeartbeat, } from "./storage-worker-status.js";
|
|
4
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
5
|
+
export async function latestSourceStorageMigrationReceipt(backupsRoot, expected) {
|
|
6
|
+
let entries;
|
|
7
|
+
try {
|
|
8
|
+
entries = await readdir(backupsRoot, { withFileTypes: true });
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code === "ENOENT")
|
|
12
|
+
return null;
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
if (entries.length > 10_000)
|
|
16
|
+
throw new Error("storage migration receipt directory limit exceeded");
|
|
17
|
+
let latest = null;
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
if (!entry.isDirectory() || !UUID.test(entry.name))
|
|
20
|
+
continue;
|
|
21
|
+
const receipt = await readStorageMigrationReceipt(backupsRoot, entry.name);
|
|
22
|
+
if (receipt === null || receipt.environment !== expected.environment || receipt.profile !== expected.profile)
|
|
23
|
+
continue;
|
|
24
|
+
const safeTerminal = receipt.status === "rolled_back"
|
|
25
|
+
|| (receipt.status === "failed" && receipt.sourceStopAttempted === false);
|
|
26
|
+
if (!safeTerminal)
|
|
27
|
+
continue;
|
|
28
|
+
const at = new Date(receipt.history.at(-1)?.at ?? 0).getTime();
|
|
29
|
+
if (latest === null || at > latest.at)
|
|
30
|
+
latest = { receipt, at };
|
|
31
|
+
}
|
|
32
|
+
return latest?.receipt ?? null;
|
|
33
|
+
}
|
|
34
|
+
export function storageMigrationStatusReplay(receipt, expected) {
|
|
35
|
+
if (expected !== undefined && (receipt.migrationId !== expected.migrationId
|
|
36
|
+
|| receipt.environment !== expected.environment
|
|
37
|
+
|| receipt.workspaceId !== expected.workspaceId
|
|
38
|
+
|| receipt.profile !== expected.profile)) {
|
|
39
|
+
throw new Error("storage migration receipt identity mismatch");
|
|
40
|
+
}
|
|
41
|
+
return Object.freeze(storageMigrationReplayStatuses(receipt).map((status) => Object.freeze({
|
|
42
|
+
type: "daemon:storage-migration-status",
|
|
43
|
+
migrationId: receipt.migrationId,
|
|
44
|
+
status,
|
|
45
|
+
environment: receipt.environment,
|
|
46
|
+
workspaceId: receipt.workspaceId,
|
|
47
|
+
profile: receipt.profile,
|
|
48
|
+
...(status === "verified" ? { finalManifestDigest: receipt.manifestDigest } : {}),
|
|
49
|
+
...(status === "failed"
|
|
50
|
+
? {
|
|
51
|
+
errorCode: receipt.errorCode ?? "storage_migration_failed",
|
|
52
|
+
...(receipt.errorMessage === undefined ? {} : { errorMessage: receipt.errorMessage }),
|
|
53
|
+
...(receipt.sourceStopAttempted === false ? { reopenAdmission: true } : {}),
|
|
54
|
+
}
|
|
55
|
+
: {}),
|
|
56
|
+
})));
|
|
57
|
+
}
|
|
58
|
+
const wait = (milliseconds) => new Promise((resolvePromise) => {
|
|
59
|
+
setTimeout(resolvePromise, milliseconds);
|
|
60
|
+
});
|
|
61
|
+
export async function monitorStorageMigrationStatus(input, send, injected = {}) {
|
|
62
|
+
const readReceipt = injected.readReceipt ?? readStorageMigrationReceipt;
|
|
63
|
+
const readWorkerFailure = injected.readWorkerFailure ?? readStorageWorkerFailureStatus;
|
|
64
|
+
const readWorkerHeartbeatStatus = injected.readWorkerHeartbeat ?? readStorageWorkerHeartbeat;
|
|
65
|
+
const now = injected.now ?? (() => new Date());
|
|
66
|
+
const delay = injected.delay ?? wait;
|
|
67
|
+
const pollIntervalMs = input.pollIntervalMs ?? 250;
|
|
68
|
+
const heartbeatTimeoutMs = input.workerHeartbeatTimeoutMs ?? 30_000;
|
|
69
|
+
const monitorStartedAt = now().getTime();
|
|
70
|
+
let sentCount = 0;
|
|
71
|
+
for (;;) {
|
|
72
|
+
const [receipt, failure] = await Promise.all([
|
|
73
|
+
readReceipt(input.backupsRoot, input.migrationId),
|
|
74
|
+
readWorkerFailure(input.stateRoot, input.migrationId),
|
|
75
|
+
]);
|
|
76
|
+
if (receipt !== null) {
|
|
77
|
+
const frames = storageMigrationStatusReplay(receipt, input);
|
|
78
|
+
for (const frame of frames.slice(sentCount))
|
|
79
|
+
send(frame);
|
|
80
|
+
sentCount = frames.length;
|
|
81
|
+
if (receipt.status === "service_switched" || receipt.status === "completed") {
|
|
82
|
+
return { status: receipt.status, reopenAdmission: false };
|
|
83
|
+
}
|
|
84
|
+
if (receipt.status === "rolled_back")
|
|
85
|
+
return { status: "rolled_back", reopenAdmission: true };
|
|
86
|
+
if (receipt.status === "rollback_failed")
|
|
87
|
+
return { status: "rollback_failed", reopenAdmission: false };
|
|
88
|
+
if (receipt.status === "failed" && receipt.sourceStopAttempted === false) {
|
|
89
|
+
return { status: "failed", reopenAdmission: true };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (failure !== null) {
|
|
93
|
+
const workerFailure = failure;
|
|
94
|
+
send({
|
|
95
|
+
type: "daemon:storage-migration-status",
|
|
96
|
+
migrationId: input.migrationId,
|
|
97
|
+
status: "failed",
|
|
98
|
+
environment: input.environment,
|
|
99
|
+
workspaceId: input.workspaceId,
|
|
100
|
+
profile: input.profile,
|
|
101
|
+
errorCode: workerFailure.errorCode,
|
|
102
|
+
errorMessage: workerFailure.errorMessage,
|
|
103
|
+
...(receipt?.sourceStopAttempted === false || receipt === null ? { reopenAdmission: true } : {}),
|
|
104
|
+
});
|
|
105
|
+
return { status: "failed", reopenAdmission: receipt?.sourceStopAttempted === false || receipt === null };
|
|
106
|
+
}
|
|
107
|
+
const heartbeat = await readWorkerHeartbeatStatus(input.stateRoot, input.migrationId);
|
|
108
|
+
const heartbeatAt = heartbeat === null ? monitorStartedAt : new Date(heartbeat.at).getTime();
|
|
109
|
+
if (now().getTime() - heartbeatAt > heartbeatTimeoutMs) {
|
|
110
|
+
send({
|
|
111
|
+
type: "daemon:storage-migration-status",
|
|
112
|
+
migrationId: input.migrationId,
|
|
113
|
+
status: "failed",
|
|
114
|
+
environment: input.environment,
|
|
115
|
+
workspaceId: input.workspaceId,
|
|
116
|
+
profile: input.profile,
|
|
117
|
+
errorCode: "worker_heartbeat_stale_recovery_required",
|
|
118
|
+
errorMessage: "storage migration worker heartbeat expired; explicit recovery is required",
|
|
119
|
+
...(receipt?.sourceStopAttempted === false || receipt === null ? { reopenAdmission: true } : {}),
|
|
120
|
+
});
|
|
121
|
+
return { status: "failed", reopenAdmission: receipt?.sourceStopAttempted === false || receipt === null };
|
|
122
|
+
}
|
|
123
|
+
await delay(pollIntervalMs);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { atomicPrivateWrite } from "./atomic-private-write.js";
|
|
5
|
+
import { readStorageMigrationReceipt } from "./storage-migration-controller.js";
|
|
6
|
+
const FingerprintSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/u);
|
|
7
|
+
const IdentitySchema = z.object({
|
|
8
|
+
type: z.literal("daemon:storage-migration-confirmed"),
|
|
9
|
+
migrationId: z.string().uuid(),
|
|
10
|
+
environment: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,31}$/u),
|
|
11
|
+
workspaceId: z.string().uuid(),
|
|
12
|
+
profile: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,47}$/u),
|
|
13
|
+
manifestDigest: z.string().regex(/^[0-9a-f]{64}$/u),
|
|
14
|
+
targetStorageFingerprint: FingerprintSchema,
|
|
15
|
+
}).strict();
|
|
16
|
+
const AcknowledgementSchema = IdentitySchema.omit({ type: true }).extend({
|
|
17
|
+
version: z.literal(1),
|
|
18
|
+
confirmedAt: z.string().datetime({ offset: true }),
|
|
19
|
+
}).strict();
|
|
20
|
+
const delay = (milliseconds) => new Promise((resolvePromise) => {
|
|
21
|
+
setTimeout(resolvePromise, milliseconds);
|
|
22
|
+
});
|
|
23
|
+
export function storageMigrationAcknowledgementPath(backupsRoot, migrationId) {
|
|
24
|
+
if (!isAbsolute(backupsRoot))
|
|
25
|
+
throw new Error("storage migration backups root must be absolute");
|
|
26
|
+
return join(backupsRoot, z.string().uuid().parse(migrationId), "hello-confirmed.json");
|
|
27
|
+
}
|
|
28
|
+
export async function waitForStorageMigrationAcknowledgement(input) {
|
|
29
|
+
const expected = IdentitySchema.omit({ type: true }).parse({
|
|
30
|
+
migrationId: input.migrationId,
|
|
31
|
+
environment: input.environment,
|
|
32
|
+
workspaceId: input.workspaceId,
|
|
33
|
+
profile: input.profile,
|
|
34
|
+
manifestDigest: input.manifestDigest,
|
|
35
|
+
targetStorageFingerprint: input.targetStorageFingerprint,
|
|
36
|
+
});
|
|
37
|
+
const timeoutMs = z.number().int().min(10).max(600_000).parse(input.timeoutMs ?? 120_000);
|
|
38
|
+
const pollIntervalMs = z.number().int().min(1).max(5_000).parse(input.pollIntervalMs ?? 250);
|
|
39
|
+
const path = storageMigrationAcknowledgementPath(input.backupsRoot, expected.migrationId);
|
|
40
|
+
const deadline = Date.now() + timeoutMs;
|
|
41
|
+
while (Date.now() <= deadline) {
|
|
42
|
+
try {
|
|
43
|
+
const acknowledgement = AcknowledgementSchema.parse(JSON.parse(await readFile(path, "utf8")));
|
|
44
|
+
const { version: _version, confirmedAt: _confirmedAt, ...identity } = acknowledgement;
|
|
45
|
+
if (JSON.stringify(identity) !== JSON.stringify(expected)) {
|
|
46
|
+
throw new Error("storage migration acknowledgement identity mismatch");
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error.code !== "ENOENT")
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
await delay(pollIntervalMs);
|
|
55
|
+
}
|
|
56
|
+
throw new Error("storage migration hello confirmation timeout");
|
|
57
|
+
}
|
|
58
|
+
export function createStorageTargetConfirmationHandler(input) {
|
|
59
|
+
const profile = z.string().regex(/^[a-z0-9][a-z0-9_-]{0,47}$/u).parse(input.profile);
|
|
60
|
+
const completionTimeoutMs = z.number().int().min(10).max(600_000).parse(input.completionTimeoutMs ?? 120_000);
|
|
61
|
+
const pollIntervalMs = z.number().int().min(1).max(5_000).parse(input.pollIntervalMs ?? 250);
|
|
62
|
+
if (input.identity.storageMigrationId === undefined) {
|
|
63
|
+
throw new Error("storage target confirmation requires a migration identity");
|
|
64
|
+
}
|
|
65
|
+
let active = null;
|
|
66
|
+
const handle = async (raw, send) => {
|
|
67
|
+
const rawType = typeof raw === "object" && raw !== null && "type" in raw ? raw.type : undefined;
|
|
68
|
+
if (rawType !== "daemon:storage-migration-confirmed")
|
|
69
|
+
return false;
|
|
70
|
+
const confirmation = IdentitySchema.parse(raw);
|
|
71
|
+
if (confirmation.migrationId !== input.identity.storageMigrationId
|
|
72
|
+
|| confirmation.environment !== input.identity.storageEnvironment
|
|
73
|
+
|| confirmation.workspaceId !== input.identity.storageWorkspaceId
|
|
74
|
+
|| confirmation.profile !== profile
|
|
75
|
+
|| confirmation.targetStorageFingerprint !== input.identity.storageIdFingerprint) {
|
|
76
|
+
throw new Error("storage migration confirmation identity mismatch");
|
|
77
|
+
}
|
|
78
|
+
const key = JSON.stringify(confirmation);
|
|
79
|
+
if (active !== null) {
|
|
80
|
+
if (active.key !== key)
|
|
81
|
+
throw new Error("storage migration confirmation changed while processing");
|
|
82
|
+
return active.promise;
|
|
83
|
+
}
|
|
84
|
+
const processConfirmation = async () => {
|
|
85
|
+
const receipt = await readStorageMigrationReceipt(input.backupsRoot, confirmation.migrationId);
|
|
86
|
+
if (receipt === null || receipt.status !== "service_switched"
|
|
87
|
+
|| receipt.environment !== confirmation.environment
|
|
88
|
+
|| receipt.workspaceId !== confirmation.workspaceId
|
|
89
|
+
|| receipt.profile !== confirmation.profile
|
|
90
|
+
|| receipt.manifestDigest !== confirmation.manifestDigest) {
|
|
91
|
+
throw new Error("storage migration confirmation receipt identity mismatch");
|
|
92
|
+
}
|
|
93
|
+
const { type: _type, ...confirmationIdentity } = confirmation;
|
|
94
|
+
await atomicPrivateWrite(storageMigrationAcknowledgementPath(input.backupsRoot, confirmation.migrationId), `${JSON.stringify(AcknowledgementSchema.parse({
|
|
95
|
+
version: 1,
|
|
96
|
+
...confirmationIdentity,
|
|
97
|
+
confirmedAt: new Date().toISOString(),
|
|
98
|
+
}), null, 2)}\n`);
|
|
99
|
+
const deadline = Date.now() + completionTimeoutMs;
|
|
100
|
+
while (Date.now() <= deadline) {
|
|
101
|
+
const current = await readStorageMigrationReceipt(input.backupsRoot, confirmation.migrationId);
|
|
102
|
+
if (current?.status === "completed") {
|
|
103
|
+
send({
|
|
104
|
+
type: "daemon:storage-migration-status",
|
|
105
|
+
migrationId: confirmation.migrationId,
|
|
106
|
+
status: "completed",
|
|
107
|
+
environment: confirmation.environment,
|
|
108
|
+
workspaceId: confirmation.workspaceId,
|
|
109
|
+
profile: confirmation.profile,
|
|
110
|
+
});
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (current !== null && ["failed", "rolled_back", "rollback_failed"].includes(current.status)) {
|
|
114
|
+
throw new Error(`storage migration ended before completion: ${current.status}`);
|
|
115
|
+
}
|
|
116
|
+
await delay(pollIntervalMs);
|
|
117
|
+
}
|
|
118
|
+
throw new Error("storage migration worker completion timeout");
|
|
119
|
+
};
|
|
120
|
+
const promise = processConfirmation();
|
|
121
|
+
active = { key, promise };
|
|
122
|
+
void promise.catch(() => {
|
|
123
|
+
if (active?.key === key)
|
|
124
|
+
active = null;
|
|
125
|
+
});
|
|
126
|
+
return promise;
|
|
127
|
+
};
|
|
128
|
+
return Object.freeze({ handle });
|
|
129
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { posix, win32 } from "node:path";
|
|
2
|
+
import { executeStorageMigrationWorker, recoverStorageMigrationWorker, } from "./storage-worker-executor.js";
|
|
3
|
+
import { completeStorageMigrationWorkerServiceForHandoff } from "./storage-worker-handoff.js";
|
|
4
|
+
import { startStorageWorkerHeartbeat, writeStorageWorkerFailureStatus, } from "./storage-worker-status.js";
|
|
5
|
+
export async function runStorageWorkerCommand(args, execute = executeStorageMigrationWorker, writeFailure = writeStorageWorkerFailureStatus, startHeartbeat = startStorageWorkerHeartbeat, completeService = completeStorageMigrationWorkerServiceForHandoff, recover = recoverStorageMigrationWorker) {
|
|
6
|
+
const command = args[0];
|
|
7
|
+
if (command !== "storage-migration-worker" && command !== "storage-migration-recover")
|
|
8
|
+
return null;
|
|
9
|
+
const path = args[1] === "--job" ? args[2] : undefined;
|
|
10
|
+
if (args.length !== 3 || path === undefined || path.length === 0
|
|
11
|
+
|| /[\0\r\n]/u.test(path) || (!posix.isAbsolute(path) && !win32.isAbsolute(path))) {
|
|
12
|
+
throw new Error("Invalid storage migration worker command");
|
|
13
|
+
}
|
|
14
|
+
const stopHeartbeat = await startHeartbeat(path);
|
|
15
|
+
const assertHeartbeatHealthy = stopHeartbeat.assertHealthy;
|
|
16
|
+
try {
|
|
17
|
+
if (command === "storage-migration-recover") {
|
|
18
|
+
await (assertHeartbeatHealthy === undefined
|
|
19
|
+
? recover(path)
|
|
20
|
+
: recover(path, { assertHeartbeatHealthy }));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
await (assertHeartbeatHealthy === undefined
|
|
24
|
+
? execute(path)
|
|
25
|
+
: execute(path, { assertHeartbeatHealthy }));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
await writeFailure(path, error).catch(() => undefined);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
await stopHeartbeat().catch(() => undefined);
|
|
34
|
+
await completeService(path);
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
}
|