@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/dist/main.js
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
10
|
import { loadConfig, ConfigError } from "./config.js";
|
|
12
11
|
import { detectDaemonLang, formatDaemonText, translateDaemon } from "./i18n.js";
|
|
13
12
|
import { cliVersion, daemonVersion } from "./machine-info.js";
|
|
@@ -22,18 +21,7 @@ import { formatDaemonStartupError } from "./daemon-startup-error.js";
|
|
|
22
21
|
import { assertManagedServiceStartup } from "./managed-service-startup.js";
|
|
23
22
|
import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
24
23
|
import { acquireDaemonInstallationLease, runWithDaemonInstallationLease, } from "./daemon-installation-lease.js";
|
|
25
|
-
import { loadStorageRuntime } from "./storage-runtime.js";
|
|
26
|
-
import { readStorageMigrationReceipt } from "./storage-migration-controller.js";
|
|
27
|
-
import { storageMigrationStatusReplay } from "./storage-status-replay.js";
|
|
28
|
-
import { runStorageWorkerCommand } from "./storage-worker-command.js";
|
|
29
|
-
import { probeStorageMigrationRuntime } from "./storage-runtime-readiness.js";
|
|
30
|
-
import { createStorageTargetConfirmationHandler } from "./storage-target-confirmation.js";
|
|
31
24
|
async function main() {
|
|
32
|
-
const workerResult = await runStorageWorkerCommand(process.argv.slice(2));
|
|
33
|
-
if (workerResult !== null) {
|
|
34
|
-
process.exitCode = workerResult;
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
25
|
const computerResult = await runComputerCommand(process.argv.slice(2));
|
|
38
26
|
if (computerResult !== null) {
|
|
39
27
|
process.exitCode = computerResult;
|
|
@@ -69,10 +57,8 @@ async function main() {
|
|
|
69
57
|
// 不把 machine token 放进 argv / plist / systemd unit / scheduled task。
|
|
70
58
|
if (values["daemon-home"])
|
|
71
59
|
process.env.CREW_DAEMON_HOME = values["daemon-home"];
|
|
72
|
-
let activeDaemonHome;
|
|
73
60
|
if (values.profile) {
|
|
74
61
|
const home = daemonHome();
|
|
75
|
-
activeDaemonHome = home;
|
|
76
62
|
const profile = await loadProfile(values.profile, home);
|
|
77
63
|
if (cmd === "serve") {
|
|
78
64
|
await assertProfileAgentsRootUnique(profile, home, homedir());
|
|
@@ -85,12 +71,6 @@ async function main() {
|
|
|
85
71
|
agentsRoot: resolveAgentsRoot(profile.agentsRoot, homedir(), process.platform),
|
|
86
72
|
nodePath: process.execPath,
|
|
87
73
|
entryPath: builtDaemonEntry(),
|
|
88
|
-
environment: {
|
|
89
|
-
...(process.env.NOWWORK_HOME === undefined ? {} : { NOWWORK_HOME: process.env.NOWWORK_HOME }),
|
|
90
|
-
...(process.env.NOWWORK_ENV_TAG === undefined ? {} : { NOWWORK_ENV_TAG: process.env.NOWWORK_ENV_TAG }),
|
|
91
|
-
...(process.env.NOWWORK_STORAGE_POLICY_PATH === undefined
|
|
92
|
-
? {} : { NOWWORK_STORAGE_POLICY_PATH: process.env.NOWWORK_STORAGE_POLICY_PATH }),
|
|
93
|
-
},
|
|
94
74
|
});
|
|
95
75
|
}
|
|
96
76
|
applyProfileToEnv(profile, process.env);
|
|
@@ -120,72 +100,9 @@ async function main() {
|
|
|
120
100
|
const installationLease = installation === null
|
|
121
101
|
? null
|
|
122
102
|
: await acquireDaemonInstallationLease(installation.npmPrefix);
|
|
123
|
-
const storageRuntime = values.profile === undefined
|
|
124
|
-
? null
|
|
125
|
-
: await loadStorageRuntime({
|
|
126
|
-
profile: values.profile,
|
|
127
|
-
serverUrl: config.serverUrl,
|
|
128
|
-
userHome: homedir(),
|
|
129
|
-
platform: process.platform,
|
|
130
|
-
environment: process.env,
|
|
131
|
-
});
|
|
132
|
-
const migrationRuntime = values.profile === undefined || activeDaemonHome === undefined || storageRuntime !== null
|
|
133
|
-
? { ready: false, reason: "not_legacy_source" }
|
|
134
|
-
: await probeStorageMigrationRuntime({
|
|
135
|
-
profile: values.profile,
|
|
136
|
-
serverUrl: config.serverUrl,
|
|
137
|
-
currentDaemonHome: activeDaemonHome,
|
|
138
|
-
currentAgentsRoot: config.agentsRoot,
|
|
139
|
-
userHome: homedir(),
|
|
140
|
-
platform: process.platform,
|
|
141
|
-
...(process.getuid === undefined ? {} : { uid: process.getuid() }),
|
|
142
|
-
environment: process.env,
|
|
143
|
-
});
|
|
144
|
-
const targetConfirmation = storageRuntime?.identity.storageMigrationId === undefined
|
|
145
|
-
? null
|
|
146
|
-
: createStorageTargetConfirmationHandler({
|
|
147
|
-
backupsRoot: storageRuntime.local.backupsRoot,
|
|
148
|
-
identity: storageRuntime.identity,
|
|
149
|
-
profile: storageRuntime.installation.profile,
|
|
150
|
-
completionTimeoutMs: 180_000,
|
|
151
|
-
});
|
|
152
|
-
const storageMigration = storageRuntime !== null
|
|
153
|
-
? {
|
|
154
|
-
ready: false,
|
|
155
|
-
statusOutboxPath: join(storageRuntime.local.stateRoot, "storage-status-outbox.json"),
|
|
156
|
-
identity: storageRuntime.identity,
|
|
157
|
-
...(targetConfirmation === null ? {} : { confirm: targetConfirmation.handle }),
|
|
158
|
-
...(storageRuntime.identity.storageMigrationId === undefined ? {} : {
|
|
159
|
-
replayStatus: async (send) => {
|
|
160
|
-
const migrationId = storageRuntime.identity.storageMigrationId;
|
|
161
|
-
const receipt = await readStorageMigrationReceipt(storageRuntime.local.backupsRoot, migrationId);
|
|
162
|
-
if (receipt === null)
|
|
163
|
-
throw new Error(`storage migration receipt not found: ${migrationId}`);
|
|
164
|
-
for (const frame of storageMigrationStatusReplay(receipt, {
|
|
165
|
-
migrationId,
|
|
166
|
-
environment: storageRuntime.identity.storageEnvironment,
|
|
167
|
-
workspaceId: storageRuntime.identity.storageWorkspaceId,
|
|
168
|
-
profile: storageRuntime.installation.profile,
|
|
169
|
-
}))
|
|
170
|
-
send(frame);
|
|
171
|
-
},
|
|
172
|
-
}),
|
|
173
|
-
}
|
|
174
|
-
: migrationRuntime.ready
|
|
175
|
-
? {
|
|
176
|
-
ready: true,
|
|
177
|
-
statusOutboxPath: migrationRuntime.runtime.statusOutboxPath,
|
|
178
|
-
identity: migrationRuntime.runtime.identity,
|
|
179
|
-
controller: migrationRuntime.runtime.controller,
|
|
180
|
-
attach: migrationRuntime.runtime.attach,
|
|
181
|
-
bindServerIdentity: migrationRuntime.runtime.bindServerIdentity,
|
|
182
|
-
replayStatus: migrationRuntime.runtime.replayStatus,
|
|
183
|
-
}
|
|
184
|
-
: undefined;
|
|
185
103
|
await runWithDaemonInstallationLease(installationLease, async () => {
|
|
186
104
|
const service = serve(config, {
|
|
187
105
|
...(values.profile === undefined ? {} : { profileName: values.profile }),
|
|
188
|
-
...(storageMigration === undefined ? {} : { storageMigration }),
|
|
189
106
|
});
|
|
190
107
|
await runServeLifecycle(service);
|
|
191
108
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { acquireDaemonInstallationLease } from "./daemon-installation-lease.js";
|
|
3
|
-
import { installService, ensureServiceEnabled, readServiceDescriptor, serviceAction,
|
|
3
|
+
import { installService, ensureServiceEnabled, readServiceDescriptor, serviceAction, 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) {
|
|
6
6
|
if ((spec.platform !== "darwin" && spec.platform !== "linux")
|
|
@@ -35,17 +35,6 @@ function sameManagedIdentity(left, right) {
|
|
|
35
35
|
const { generation: _generation, createdAt: _createdAt, ...expected } = right;
|
|
36
36
|
return managedServiceIdentityMatches(left, expected);
|
|
37
37
|
}
|
|
38
|
-
function sameManagedResource(left, right) {
|
|
39
|
-
const { generation: _leftGeneration, createdAt: _leftCreatedAt, descriptorSha256: _leftHash, ...leftResource } = left;
|
|
40
|
-
const { generation: _rightGeneration, createdAt: _rightCreatedAt, descriptorSha256: _rightHash, ...rightResource } = right;
|
|
41
|
-
return JSON.stringify(leftResource) === JSON.stringify(rightResource);
|
|
42
|
-
}
|
|
43
|
-
function descriptorDiffIsStorageEnvironmentOnly(previous, next) {
|
|
44
|
-
if (!next.includes("\n <key>EnvironmentVariables</key>\n"))
|
|
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");
|
|
47
|
-
return strip(previous) === strip(next);
|
|
48
|
-
}
|
|
49
38
|
function assertRecordDescribesSpec(input) {
|
|
50
39
|
requirePortableManagedSpec(input.spec);
|
|
51
40
|
const { spec, record } = input;
|
|
@@ -62,20 +51,8 @@ function assertRecordDescribesSpec(input) {
|
|
|
62
51
|
}
|
|
63
52
|
}
|
|
64
53
|
export async function installManagedService(input) {
|
|
54
|
+
assertRecordDescribesSpec(input);
|
|
65
55
|
requirePortableManagedSpec(input.spec);
|
|
66
|
-
const requestedDescriptor = input.spec.descriptor;
|
|
67
|
-
const expectedDescriptorHash = serviceDescriptorSha256(input.spec.descriptor);
|
|
68
|
-
const expectedPath = normalizeManagedServiceRecord({
|
|
69
|
-
...input.record,
|
|
70
|
-
descriptorPath: input.spec.descriptorPath,
|
|
71
|
-
descriptorSha256: expectedDescriptorHash,
|
|
72
|
-
}).descriptorPath;
|
|
73
|
-
if (input.record.platform !== input.spec.platform
|
|
74
|
-
|| input.record.serviceId !== input.spec.id
|
|
75
|
-
|| input.record.profile !== input.spec.profile
|
|
76
|
-
|| input.record.descriptorPath !== expectedPath) {
|
|
77
|
-
throw new ManagedServiceRegistryError(`Managed service record does not describe '${input.spec.id}'`);
|
|
78
|
-
}
|
|
79
56
|
const acquire = input.acquireInstallationLease ?? acquireDaemonInstallationLease;
|
|
80
57
|
await withManagedServiceRegistryLock(input.registry ?? {}, async ({ services, replace }) => {
|
|
81
58
|
const exact = services.find((record) => sameManagedIdentity(record, input.record));
|
|
@@ -99,28 +76,6 @@ export async function installManagedService(input) {
|
|
|
99
76
|
}
|
|
100
77
|
return;
|
|
101
78
|
}
|
|
102
|
-
const registered = services.find((record) => record.serviceId === input.record.serviceId
|
|
103
|
-
&& record.profile === input.record.profile
|
|
104
|
-
&& record.daemonHome === input.record.daemonHome);
|
|
105
|
-
if (registered !== undefined && sameManagedResource(registered, input.record)) {
|
|
106
|
-
const current = await readServiceDescriptor(input.spec);
|
|
107
|
-
if (current === null || serviceDescriptorSha256(current) !== registered.descriptorSha256) {
|
|
108
|
-
throw new ManagedServiceRegistryError(`Installed descriptor for '${input.spec.id}' does not match its registry record`);
|
|
109
|
-
}
|
|
110
|
-
if (!descriptorDiffIsStorageEnvironmentOnly(current, requestedDescriptor)) {
|
|
111
|
-
throw new ManagedServiceRegistryError(`Installed descriptor for '${input.spec.id}' conflicts with the requested identity`);
|
|
112
|
-
}
|
|
113
|
-
await replaceInstalledServiceDescriptor(input.spec, current, input.runner);
|
|
114
|
-
try {
|
|
115
|
-
await replace(services.map((record) => record === registered ? input.record : record));
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
const rollbackSpec = { ...input.spec, descriptor: current };
|
|
119
|
-
await replaceInstalledServiceDescriptor(rollbackSpec, requestedDescriptor, input.runner).catch(() => undefined);
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
79
|
const conflict = managedServiceConflict(services, input.record);
|
|
125
80
|
if (conflict !== null) {
|
|
126
81
|
throw new ManagedServiceConflictError(conflict.resource, input.record, conflict.owner);
|
|
@@ -14,7 +14,6 @@ export async function assertManagedServiceStartup(input) {
|
|
|
14
14
|
nodePath: input.nodePath,
|
|
15
15
|
entryPath: input.entryPath,
|
|
16
16
|
profileHome: input.daemonHome,
|
|
17
|
-
...(input.environment === undefined ? {} : { environment: input.environment }),
|
|
18
17
|
});
|
|
19
18
|
const readRegistry = input.readManagedServices
|
|
20
19
|
?? (() => readManagedServiceRegistry({ userHome: input.userHome }));
|
|
@@ -43,7 +42,6 @@ export async function assertManagedServiceStartup(input) {
|
|
|
43
42
|
nodePath: input.nodePath,
|
|
44
43
|
entryPath: input.entryPath,
|
|
45
44
|
profileHome: input.daemonHome,
|
|
46
|
-
...(input.environment === undefined ? {} : { environment: input.environment }),
|
|
47
45
|
legacyServiceId: true,
|
|
48
46
|
});
|
|
49
47
|
const legacyDescriptor = await readDescriptor(legacySpec);
|
package/dist/profile-layout.js
CHANGED
|
@@ -2,10 +2,6 @@ import { posix, win32 } from "node:path";
|
|
|
2
2
|
export const PROFILE_LAYOUT_VERSION = 1;
|
|
3
3
|
const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,47}$/u;
|
|
4
4
|
const LOCAL_INSTANCE_NAME = /^[a-z0-9][a-z0-9_-]{0,39}$/u;
|
|
5
|
-
/** Accept the legacy `in` tag at the boundary, but never persist it in a new layout. */
|
|
6
|
-
export function normalizeProfileEnvironment(value) {
|
|
7
|
-
return value === "in" ? "production" : value;
|
|
8
|
-
}
|
|
9
5
|
export function validateLocalInstanceName(value) {
|
|
10
6
|
const normalized = value.trim().toLowerCase();
|
|
11
7
|
if (!LOCAL_INSTANCE_NAME.test(normalized) || normalized.includes("..")) {
|
|
@@ -14,21 +10,19 @@ export function validateLocalInstanceName(value) {
|
|
|
14
10
|
return normalized;
|
|
15
11
|
}
|
|
16
12
|
export function profileForLocalInstance(environment, localInstanceName) {
|
|
17
|
-
const
|
|
18
|
-
const profile = `nw-${canonicalEnvironment}-${validateLocalInstanceName(localInstanceName)}`;
|
|
13
|
+
const profile = `nw-${environment}-${validateLocalInstanceName(localInstanceName)}`;
|
|
19
14
|
if (!PROFILE_NAME.test(profile))
|
|
20
15
|
throw new Error("derived daemon profile exceeds 48 characters");
|
|
21
16
|
return profile;
|
|
22
17
|
}
|
|
23
18
|
export function profileLayout(input) {
|
|
24
|
-
const environment = normalizeProfileEnvironment(input.environment);
|
|
25
19
|
const localInstanceName = validateLocalInstanceName(input.localInstanceName);
|
|
26
|
-
const profile = profileForLocalInstance(environment, localInstanceName);
|
|
20
|
+
const profile = profileForLocalInstance(input.environment, localInstanceName);
|
|
27
21
|
const paths = input.platform === "win32" ? win32 : posix;
|
|
28
|
-
const base = `${environment}-${localInstanceName}`;
|
|
22
|
+
const base = `${input.environment}-${localInstanceName}`;
|
|
29
23
|
return Object.freeze({
|
|
30
24
|
layoutVersion: PROFILE_LAYOUT_VERSION,
|
|
31
|
-
environment,
|
|
25
|
+
environment: input.environment,
|
|
32
26
|
localInstanceName,
|
|
33
27
|
profile,
|
|
34
28
|
daemonHome: paths.join(input.userHome, ".crew", `daemon-${base}`),
|
package/dist/runner.js
CHANGED
|
@@ -105,7 +105,6 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
|
|
|
105
105
|
softTokens: config.sessionSoftTokens,
|
|
106
106
|
maxTurns: config.sessionMaxTurns,
|
|
107
107
|
},
|
|
108
|
-
localMemoryV2Enabled: config.localMemoryV2Handles?.has(input.handle) === true,
|
|
109
108
|
}, {
|
|
110
109
|
onActivity,
|
|
111
110
|
onConsole,
|
package/dist/serve.js
CHANGED
|
@@ -51,7 +51,6 @@ import { handleRuntimeProbeFrame, probeRuntimeHealth } from "./runtime-health.js
|
|
|
51
51
|
import { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
52
52
|
import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
|
|
53
53
|
import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
|
|
54
|
-
import { attachStorageMigrationRuntime, createServeStorageStatusDelivery, handleStorageMigrationControl, replayStorageMigrationStatus, storageMigrationCapabilities, } from "./storage-serve-adapter.js";
|
|
55
54
|
export { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
56
55
|
// normalize.ts 的活动种类 → activity 枚举
|
|
57
56
|
const ACTIVITY_MAP = {
|
|
@@ -176,36 +175,13 @@ export function serve(config, opts = {}) {
|
|
|
176
175
|
});
|
|
177
176
|
const machineHelloPublisher = createProjectSkillMachineHelloPublisher({
|
|
178
177
|
status: projectSkillControllerReadiness.status,
|
|
179
|
-
effective:
|
|
180
|
-
const effective = withProjectSkillCapabilityStatus(hello, status);
|
|
181
|
-
const configuredIdentity = opts.storageMigration?.identity;
|
|
182
|
-
const currentIdentity = typeof configuredIdentity === "function"
|
|
183
|
-
? configuredIdentity()
|
|
184
|
-
: configuredIdentity;
|
|
185
|
-
return currentIdentity === undefined ? effective : { ...effective, ...currentIdentity };
|
|
186
|
-
},
|
|
178
|
+
effective: withProjectSkillCapabilityStatus,
|
|
187
179
|
onSent: (hello) => log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · installed=[${hello.runtimes.join(",")}] · executable=[${hello.executionRuntimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`),
|
|
188
180
|
});
|
|
189
181
|
const initializedProjectSkills = initializedProjectSkillsReconciler(projectSkillControllerReadiness.ensure, projectSkillsReconciler);
|
|
190
|
-
const configuredCapabilities = opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform);
|
|
191
|
-
const storageCapabilities = storageMigrationCapabilities(configuredCapabilities, opts.storageMigration);
|
|
192
|
-
const storageStatusDelivery = createServeStorageStatusDelivery(opts.storageMigration, {
|
|
193
|
-
onPersistenceError: (error) => dslog("daemon.storage_status_outbox_failed", "存储迁移状态 outbox 持久化失败", { level: "ERROR", error_message: error.message }),
|
|
194
|
-
transmit: (frame) => { try {
|
|
195
|
-
if (ws?.readyState === WebSocket.OPEN)
|
|
196
|
-
ws.send(JSON.stringify(frame));
|
|
197
|
-
}
|
|
198
|
-
catch { /* retry */ } },
|
|
199
|
-
});
|
|
200
|
-
const storageMigrationRuntime = attachStorageMigrationRuntime(opts.storageMigration, {
|
|
201
|
-
isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0,
|
|
202
|
-
waitForExecutionFrames: () => executionFrameQueue,
|
|
203
|
-
flushLocalState: flushSlog,
|
|
204
|
-
sendStatus: storageStatusDelivery.send,
|
|
205
|
-
});
|
|
206
182
|
const prepareProjectSkillCapabilities = createProjectSkillConnectionCapabilityProvider({
|
|
207
183
|
platform: process.platform,
|
|
208
|
-
baseCapabilities:
|
|
184
|
+
baseCapabilities: opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform),
|
|
209
185
|
additionalCapabilities: () => managedDaemonCapabilities(updateEligibility),
|
|
210
186
|
probeRuntimeRootStorePrimitive: projectSkillRuntimeRootStartup.probeNativePublicationPrimitiveReadiness,
|
|
211
187
|
initializeController: projectSkillControllerReadiness.ensure,
|
|
@@ -334,12 +310,6 @@ export function serve(config, opts = {}) {
|
|
|
334
310
|
// 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
|
|
335
311
|
void drainSpool();
|
|
336
312
|
// 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
|
|
337
|
-
storageStatusDelivery.replayPending();
|
|
338
|
-
const storageReplay = replayStorageMigrationStatus(opts.storageMigration, openedSocket, () => ws === openedSocket, (error) => {
|
|
339
|
-
dslog("daemon.storage_status_replay_failed", "存储迁移状态重放失败", {
|
|
340
|
-
level: "ERROR", error_message: error.message,
|
|
341
|
-
});
|
|
342
|
-
}, storageStatusDelivery.send);
|
|
343
313
|
const { detectInstalled, detectExecutable = detectExecutionRuntimesWithSignal } = opts.machineInfo ?? {};
|
|
344
314
|
const helloPromise = (opts.machineInfo?.collect ?? collectMachineHello)(config.agentsRoot, config.executionLimits, process.platform, {
|
|
345
315
|
...(detectInstalled ? { detectInstalled } : {}),
|
|
@@ -355,7 +325,6 @@ export function serve(config, opts = {}) {
|
|
|
355
325
|
if (hello.executionProtocol === undefined)
|
|
356
326
|
return [];
|
|
357
327
|
const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
|
|
358
|
-
await storageReplay;
|
|
359
328
|
if (ws === openedSocket && openedSocket.readyState === WebSocket.OPEN
|
|
360
329
|
&& detected.some((runtime) => !hello.executionRuntimes.includes(runtime))) {
|
|
361
330
|
detectedExecutionRuntimes = detected;
|
|
@@ -370,8 +339,7 @@ export function serve(config, opts = {}) {
|
|
|
370
339
|
return [];
|
|
371
340
|
});
|
|
372
341
|
void helloPromise
|
|
373
|
-
.then(
|
|
374
|
-
await storageReplay;
|
|
342
|
+
.then((hello) => {
|
|
375
343
|
if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
|
|
376
344
|
return;
|
|
377
345
|
detectedExecutionRuntimes = hello.executionRuntimes;
|
|
@@ -419,8 +387,6 @@ export function serve(config, opts = {}) {
|
|
|
419
387
|
if (typeof decoded !== "object" || decoded === null)
|
|
420
388
|
return;
|
|
421
389
|
const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
|
|
422
|
-
if (storageStatusDelivery.acknowledge(decoded))
|
|
423
|
-
return;
|
|
424
390
|
if (rawType === "daemon:update" || rawType === "daemon:restart") {
|
|
425
391
|
void updateController.handle(decoded).catch((error) => {
|
|
426
392
|
dslog("daemon.control_failed", "daemon 控制操作处理失败", {
|
|
@@ -437,11 +403,6 @@ export function serve(config, opts = {}) {
|
|
|
437
403
|
void codexHomeModeController.handle(decoded).catch((error) => dslog("daemon.codex_home_mode_failed", "Codex Home 模式切换失败", { level: "ERROR", error_message: error.message }));
|
|
438
404
|
return;
|
|
439
405
|
}
|
|
440
|
-
if (handleStorageMigrationControl(opts.storageMigration, rawType, decoded, storageStatusDelivery.send, (kind, error) => {
|
|
441
|
-
dslog(kind === "migration" ? "daemon.storage_migration_failed" : "daemon.storage_migration_confirmation_failed", kind === "migration" ? "存储迁移控制操作失败" : "存储迁移 hello 确认处理失败", { level: "ERROR", error_message: error.message });
|
|
442
|
-
})) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
406
|
if (rawType.startsWith("execution:")) {
|
|
446
407
|
const parsedExecution = ServerToDaemonExecutionFrameSchema.safeParse(decoded);
|
|
447
408
|
if (!parsedExecution.success) {
|
|
@@ -503,14 +464,6 @@ export function serve(config, opts = {}) {
|
|
|
503
464
|
return;
|
|
504
465
|
}
|
|
505
466
|
const spec = frame;
|
|
506
|
-
if (storageMigrationRuntime.isAdmissionClosed()) {
|
|
507
|
-
safeExecutionSend(ExecutionRejectedSchema.parse({
|
|
508
|
-
type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
|
|
509
|
-
reason: "resource_limit", message: "Storage migration is quiescing this machine",
|
|
510
|
-
at: new Date().toISOString(),
|
|
511
|
-
}));
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
467
|
const workspaceRejection = projectWorkspaceCapabilityRejection(spec, capabilities.executionRunner, new Date().toISOString());
|
|
515
468
|
if (workspaceRejection !== null)
|
|
516
469
|
return void safeExecutionSend(workspaceRejection);
|
|
@@ -817,17 +770,6 @@ export function serve(config, opts = {}) {
|
|
|
817
770
|
// ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
|
|
818
771
|
const r = msg;
|
|
819
772
|
setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
|
|
820
|
-
if (opts.storageMigration?.ready === true && typeof r.workspaceId === "string") {
|
|
821
|
-
try {
|
|
822
|
-
opts.storageMigration.bindServerIdentity?.({ workspaceId: r.workspaceId });
|
|
823
|
-
machineHelloPublisher.republish();
|
|
824
|
-
}
|
|
825
|
-
catch (error) {
|
|
826
|
-
dslog("daemon.storage_identity_bind_failed", "存储迁移 Workspace 身份绑定失败", {
|
|
827
|
-
level: "ERROR", error_message: error.message,
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
773
|
// 旧 server 无此字段 → 空集合(daemon 保持全部旧行为)。
|
|
832
774
|
serverCapabilities = new Set(Array.isArray(r.serverCapabilities)
|
|
833
775
|
? r.serverCapabilities.filter((c) => typeof c === "string")
|
|
@@ -926,8 +868,6 @@ export function serve(config, opts = {}) {
|
|
|
926
868
|
}
|
|
927
869
|
if (msg.type !== "agent:start")
|
|
928
870
|
return; // ready/error 等忽略
|
|
929
|
-
if (storageMigrationRuntime.isAdmissionClosed())
|
|
930
|
-
return;
|
|
931
871
|
// 任务键:scheduled run 用 runId(每次运行独立 cwd/work-log;overlap 由 server 端 skip_if_running 管,
|
|
932
872
|
// daemon 的 running 去重只兜底"同一 run 重复投递");普通唤醒仍是 线程锚点 ?? 频道。
|
|
933
873
|
const scheduled = msg.reason === "scheduled_job" && msg.scheduledRun
|
|
@@ -1343,7 +1283,6 @@ export function serve(config, opts = {}) {
|
|
|
1343
1283
|
concurrencyTelemetry.stop();
|
|
1344
1284
|
runtimeProbe.stop();
|
|
1345
1285
|
completionRetransmitter.stop();
|
|
1346
|
-
storageStatusDelivery.stop();
|
|
1347
1286
|
const deadline = createShutdownDeadline(shutdownTimeoutMs);
|
|
1348
1287
|
if (reconnectTimer !== null) {
|
|
1349
1288
|
clearTimeout(reconnectTimer);
|
package/dist/workspace.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* spawn runtime 时把 <ws>/.crew 置于 PATH 最前,于是 agent 的 `crew ...` 命中 wrapper。
|
|
10
10
|
*/
|
|
11
|
-
import { mkdir, writeFile, readFile, chmod, access
|
|
11
|
+
import { mkdir, writeFile, readFile, chmod, access } from "node:fs/promises";
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { dslog } from "./slog.js";
|
|
@@ -34,28 +34,8 @@ async function exists(p) {
|
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
async function hasLocalMemoryV2Marker(agentDir) {
|
|
38
|
-
const crewDir = join(agentDir, ".crew");
|
|
39
|
-
const memoryDir = join(crewDir, "memory");
|
|
40
|
-
const markerPath = join(agentDir, ".crew", "memory", "v2-enabled");
|
|
41
|
-
try {
|
|
42
|
-
const crewStat = await lstat(crewDir);
|
|
43
|
-
const memoryStat = await lstat(memoryDir);
|
|
44
|
-
if (!crewStat.isDirectory() || crewStat.isSymbolicLink()
|
|
45
|
-
|| !memoryStat.isDirectory() || memoryStat.isSymbolicLink())
|
|
46
|
-
return false;
|
|
47
|
-
const stat = await lstat(markerPath);
|
|
48
|
-
if (!stat.isFile() || stat.isSymbolicLink())
|
|
49
|
-
return false;
|
|
50
|
-
return (await readFile(markerPath, "utf8")) === "1\n";
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
37
|
export async function prepareWorkspace(input) {
|
|
57
38
|
const dir = join(input.agentsRoot, input.handle);
|
|
58
|
-
const agentHomePreexisted = await exists(dir);
|
|
59
39
|
const crewDir = join(dir, ".crew");
|
|
60
40
|
await mkdir(crewDir, { recursive: true });
|
|
61
41
|
// 预先创建明细目录,让收尾流程可以按功能创建 notes/<topic>.md。
|
|
@@ -63,7 +43,6 @@ export async function prepareWorkspace(input) {
|
|
|
63
43
|
// MEMORY.md:首次创建"持久身份 + 知识索引"骨架,之后由 agent 自己维护
|
|
64
44
|
const memoryPath = join(dir, "MEMORY.md");
|
|
65
45
|
const memorySeedCreated = !(await exists(memoryPath));
|
|
66
|
-
const localMemoryV2Initialized = await hasLocalMemoryV2Marker(dir);
|
|
67
46
|
if (memorySeedCreated) {
|
|
68
47
|
try {
|
|
69
48
|
await writeFile(memoryPath, memorySeed(input.handle, input.description), "utf8");
|
|
@@ -145,9 +124,7 @@ export async function prepareWorkspace(input) {
|
|
|
145
124
|
crewDir,
|
|
146
125
|
systemPromptPath,
|
|
147
126
|
memory,
|
|
148
|
-
agentHomePreexisted,
|
|
149
127
|
memorySeedCreated,
|
|
150
|
-
localMemoryV2Initialized,
|
|
151
128
|
homeDir,
|
|
152
129
|
runDir,
|
|
153
130
|
workLogPath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.64",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
|
|
23
|
+
"prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
19
27
|
"dependencies": {
|
|
20
28
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
21
29
|
"@nowcrew/cli": "^0.4.13",
|
|
@@ -34,12 +42,5 @@
|
|
|
34
42
|
"tsx": "^4.19.0",
|
|
35
43
|
"typescript": "^5.6.0",
|
|
36
44
|
"vitest": "^2.1.0"
|
|
37
|
-
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
40
|
-
"build": "tsc -p tsconfig.json",
|
|
41
|
-
"codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
|
|
42
|
-
"test": "vitest run",
|
|
43
|
-
"typecheck": "tsc --noEmit"
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
export const LOCAL_MEMORY_TOTAL_CODE_POINT_CAP = 6_000;
|
|
2
|
-
export const LOCAL_MEMORY_INDEX_CODE_POINT_CAP = 3_000;
|
|
3
|
-
export const LOCAL_MEMORY_INDEX_ENTRY_CAP = 20;
|
|
4
|
-
const PROJECTABLE_STATUSES = new Set(["pinned", "active"]);
|
|
5
|
-
function codePointLength(value) {
|
|
6
|
-
return Array.from(value).length;
|
|
7
|
-
}
|
|
8
|
-
export function renderLocalMemoryIndexCard(entry) {
|
|
9
|
-
return `### ${entry.title}\n`
|
|
10
|
-
+ `- id: ${entry.id}\n`
|
|
11
|
-
+ `- file: ${entry.file}\n`
|
|
12
|
-
+ `- type: ${entry.type}\n`
|
|
13
|
-
+ `- status: ${entry.status}\n`
|
|
14
|
-
+ `- summary: ${entry.summary}\n`
|
|
15
|
-
+ `- triggers: ${entry.triggers.join(", ")}\n`
|
|
16
|
-
+ `- priority: ${entry.priority}\n`
|
|
17
|
-
+ `- revision: ${entry.revision}\n`
|
|
18
|
-
+ `- anchor: ${entry.anchor ?? ""}\n`
|
|
19
|
-
+ `- updated: ${entry.updated}\n`
|
|
20
|
-
+ (entry.reviewAfter === null ? "" : `- review_after: ${entry.reviewAfter}\n`);
|
|
21
|
-
}
|
|
22
|
-
function compareEntries(left, right) {
|
|
23
|
-
const statusRank = (status) => status === "pinned" ? 0 : 1;
|
|
24
|
-
const priorityRank = (priority) => (priority === "high" ? 0 : priority === "medium" ? 1 : 2);
|
|
25
|
-
return statusRank(left.status) - statusRank(right.status)
|
|
26
|
-
|| priorityRank(left.priority) - priorityRank(right.priority)
|
|
27
|
-
|| right.updated.localeCompare(left.updated)
|
|
28
|
-
|| left.id.localeCompare(right.id);
|
|
29
|
-
}
|
|
30
|
-
function cardLength(entry, hasPrevious) {
|
|
31
|
-
return codePointLength(renderLocalMemoryIndexCard(entry)) + (hasPrevious ? 1 : 0);
|
|
32
|
-
}
|
|
33
|
-
function overBudgetReason(entries) {
|
|
34
|
-
return entries.some((entry) => entry.status === "pinned")
|
|
35
|
-
? "index_over_budget_pinned"
|
|
36
|
-
: "index_over_budget_active";
|
|
37
|
-
}
|
|
38
|
-
export function selectLocalMemoryIndexEntries(entries, budget, options = {}) {
|
|
39
|
-
const projectable = entries.filter((entry) => PROJECTABLE_STATUSES.has(entry.status));
|
|
40
|
-
const omittedIds = entries
|
|
41
|
-
.filter((entry) => !PROJECTABLE_STATUSES.has(entry.status))
|
|
42
|
-
.map((entry) => entry.id);
|
|
43
|
-
const protectedIds = options.protectedIds ?? new Set();
|
|
44
|
-
const protectedEntries = projectable.filter((entry) => protectedIds.has(entry.id)).sort(compareEntries);
|
|
45
|
-
const candidates = projectable.filter((entry) => !protectedIds.has(entry.id)).sort(compareEntries);
|
|
46
|
-
const selected = [];
|
|
47
|
-
let renderedLength = 0;
|
|
48
|
-
for (const entry of protectedEntries) {
|
|
49
|
-
selected.push(entry);
|
|
50
|
-
renderedLength += cardLength(entry, selected.length > 1);
|
|
51
|
-
}
|
|
52
|
-
if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap) {
|
|
53
|
-
return {
|
|
54
|
-
entries: selected,
|
|
55
|
-
omittedIds: [...omittedIds, ...candidates.map((entry) => entry.id)],
|
|
56
|
-
reason: overBudgetReason(selected),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
for (const entry of candidates) {
|
|
60
|
-
if (selected.length >= budget.entryCap || renderedLength + cardLength(entry, selected.length > 0) > budget.indexCodePointCap) {
|
|
61
|
-
omittedIds.push(entry.id);
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
selected.push(entry);
|
|
65
|
-
renderedLength += cardLength(entry, selected.length > 1);
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
entries: selected,
|
|
69
|
-
omittedIds,
|
|
70
|
-
reason: omittedIds.length === entries.filter((item) => !PROJECTABLE_STATUSES.has(item.status)).length
|
|
71
|
-
? null
|
|
72
|
-
: "index_over_budget",
|
|
73
|
-
};
|
|
74
|
-
}
|