@nowcrew/daemon 0.6.88 → 0.6.90

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 (122) hide show
  1. package/README.md +66 -4
  2. package/dist/agent-ability/controller.js +11 -1
  3. package/dist/agent-ability/git-errors.js +22 -0
  4. package/dist/agent-ability/materializer.js +21 -10
  5. package/dist/agent-ability/resolver.js +12 -4
  6. package/dist/agent-environment-contract.js +55 -0
  7. package/dist/agent-environment-control.js +24 -0
  8. package/dist/agent-environment-execution.js +98 -0
  9. package/dist/agent-environments.js +275 -0
  10. package/dist/automatic-daemon-installer.js +51 -2
  11. package/dist/automatic-install-target.js +8 -0
  12. package/dist/computer-main.js +18 -0
  13. package/dist/computer-profile.js +17 -4
  14. package/dist/computer-setup-client.js +318 -0
  15. package/dist/computer-setup-orchestration.js +203 -0
  16. package/dist/computer-setup.js +244 -0
  17. package/dist/config.js +30 -12
  18. package/dist/console-formatter.js +3 -3
  19. package/dist/console.js +15 -2
  20. package/dist/daemon-update-eligibility.js +1 -1
  21. package/dist/execution-journal.js +9 -6
  22. package/dist/execution-protocol.js +1 -0
  23. package/dist/execution-runner.js +13 -8
  24. package/dist/i18n.js +19 -0
  25. package/dist/list-models.js +12 -12
  26. package/dist/local-execution-lease.js +22 -0
  27. package/dist/local-executor.js +57 -84
  28. package/dist/local-memory/candidate.js +43 -0
  29. package/dist/local-memory/index-budget.js +8 -0
  30. package/dist/local-memory/inventory-cli.js +61 -0
  31. package/dist/local-memory/inventory-preview.js +159 -0
  32. package/dist/local-memory/inventory-source.js +159 -0
  33. package/dist/local-memory/pending-match.js +29 -0
  34. package/dist/local-memory/pending-query.js +100 -0
  35. package/dist/local-memory/pending-schema.js +29 -0
  36. package/dist/local-memory/pending-source.js +126 -0
  37. package/dist/local-memory/pending.js +34 -84
  38. package/dist/local-memory/prompt.js +19 -12
  39. package/dist/local-memory/read-cli.js +97 -12
  40. package/dist/local-memory/rollout.js +82 -0
  41. package/dist/local-memory/runtime.js +23 -17
  42. package/dist/local-memory/work-log-execution.js +31 -0
  43. package/dist/local-memory/work-log.js +58 -0
  44. package/dist/local-memory/write.js +27 -58
  45. package/dist/local-runtime-environment.js +50 -0
  46. package/dist/machine-environment-cache.js +147 -0
  47. package/dist/machine-environment-contract.js +10 -0
  48. package/dist/machine-environment-control.js +27 -0
  49. package/dist/machine-environment-policy.js +75 -0
  50. package/dist/machine-environment-process.js +110 -0
  51. package/dist/machine-info.js +70 -25
  52. package/dist/machine-runtime-environment.js +189 -0
  53. package/dist/machine-shell-environment-capture.js +135 -0
  54. package/dist/machine-windows-environment-capture.js +215 -0
  55. package/dist/machine-windows-environment-script.js +158 -0
  56. package/dist/main.js +4 -69
  57. package/dist/marketplace-command.js +21 -0
  58. package/dist/marketplace-controller.js +80 -0
  59. package/dist/marketplace-protocol.js +38 -0
  60. package/dist/marketplace-remote.js +48 -0
  61. package/dist/marketplace-runner.js +235 -0
  62. package/dist/marketplace-serve.js +17 -0
  63. package/dist/marketplace-source.js +103 -0
  64. package/dist/message-file-preview.js +246 -0
  65. package/dist/pre-admission-rejection.js +64 -0
  66. package/dist/project-skills/execution-adapter.js +3 -2
  67. package/dist/project-skills/runtime-launch.js +14 -0
  68. package/dist/provider-env.js +26 -15
  69. package/dist/runner.js +17 -4
  70. package/dist/runtime-environment-merge.js +16 -0
  71. package/dist/runtime-health-handler.js +10 -0
  72. package/dist/runtime-health.js +3 -1
  73. package/dist/runtime-path.js +22 -12
  74. package/dist/runtime-probe.js +35 -6
  75. package/dist/runtimes/acp-probe-transport.js +69 -0
  76. package/dist/runtimes/claude-models.js +20 -9
  77. package/dist/runtimes/deepseek-harness-launch.js +10 -0
  78. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  79. package/dist/runtimes/deepseek-harness-runner.js +226 -0
  80. package/dist/runtimes/deepseek-harness.js +76 -15
  81. package/dist/runtimes/harness-probe-retry.js +42 -0
  82. package/dist/runtimes/hermes-models.js +2 -1
  83. package/dist/runtimes/kimi-acp-runner.js +78 -13
  84. package/dist/runtimes/opencode.js +2 -2
  85. package/dist/serve.js +88 -176
  86. package/dist/server-url.js +25 -0
  87. package/dist/slog.js +2 -1
  88. package/dist/storage-identity.js +0 -3
  89. package/dist/storage-runtime.js +2 -2
  90. package/dist/storage-serve-adapter.js +6 -72
  91. package/dist/supervised-runtime.js +17 -2
  92. package/dist/token.js +0 -1
  93. package/dist/workspace-control-handler.js +27 -0
  94. package/dist/workspace-fs.js +66 -15
  95. package/dist/workspace.js +1 -1
  96. package/package.json +6 -4
  97. package/dist/codex-home-launch.js +0 -7
  98. package/dist/codex-home-mode-controller.js +0 -68
  99. package/dist/daemon-migration-controller.js +0 -211
  100. package/dist/daemon-migration-wiring.js +0 -22
  101. package/dist/runtimes/codex-home-migration-cli.js +0 -26
  102. package/dist/runtimes/codex-home-migration.js +0 -112
  103. package/dist/runtimes/codex-home.js +0 -236
  104. package/dist/storage-control.js +0 -212
  105. package/dist/storage-copy.js +0 -628
  106. package/dist/storage-credential-guard.js +0 -21
  107. package/dist/storage-doctor.js +0 -228
  108. package/dist/storage-manifest.js +0 -1027
  109. package/dist/storage-migration-controller.js +0 -440
  110. package/dist/storage-migration-manifest.js +0 -105
  111. package/dist/storage-migration-policy.js +0 -100
  112. package/dist/storage-runtime-controller.js +0 -381
  113. package/dist/storage-runtime-readiness.js +0 -240
  114. package/dist/storage-safe-fs.js +0 -302
  115. package/dist/storage-status-delivery.js +0 -224
  116. package/dist/storage-status-replay.js +0 -140
  117. package/dist/storage-target-confirmation.js +0 -129
  118. package/dist/storage-worker-command.js +0 -37
  119. package/dist/storage-worker-executor.js +0 -449
  120. package/dist/storage-worker-handoff.js +0 -151
  121. package/dist/storage-worker-operation.js +0 -294
  122. package/dist/storage-worker-status.js +0 -259
package/dist/serve.js CHANGED
@@ -1,21 +1,19 @@
1
- /**
2
- * 常驻模式 (M3c):连接 server 控制面 WS,注册机器,监听 agent:start 自动唤醒 agent。
3
- * 断线指数退避重连。一个 agent:start 进来就跑一次现有的 runAgent。
4
- */
1
+ /** 常驻控制面桥接;按请求拉起本地 runtime。 */
2
+ import { handleAgentEnvironmentFrame } from "./agent-environment-control.js";
3
+ import { handleMachineEnvironmentFrame } from "./machine-environment-control.js";
4
+ import { createMachineRuntimeEnvironment } from "./machine-runtime-environment.js";
5
5
  import { WebSocket } from "ws";
6
+ import { persistPreAdmissionRejection, rejectQueuedAdmission } from "./pre-admission-rejection.js";
6
7
  import { join } from "node:path";
7
8
  import { randomUUID } from "node:crypto";
8
9
  import { dslog, setSlogDefaults, drainSpool, flushSlog } from "./slog.js";
9
10
  import { mergeRunAgentResults, reportScheduledStartFailure, runAgent } from "./runner.js";
10
11
  import { buildOriginDecisionRetryPrompt, buildScheduledPrompt } from "./prompt.js";
11
- import { collectMachineHello, daemonCapabilities, daemonCapabilityBindings, detectExecutionRuntimesWithSignal, } from "./machine-info.js";
12
+ import { daemonCapabilities, daemonCapabilityBindings, } from "./machine-info.js";
12
13
  import { reconcileExecutionJournal } from "./execution-recovery.js";
13
14
  import { initReleaseSlog, reportDaemonConnectionClose, reportReleaseError } from "./release-channel-guard.js";
14
- import { conservativeExecutionRuntimes, createRuntimeProbeCoordinator, } from "./runtime-probe.js";
15
- import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
16
- import { listSkills } from "./skills.js";
15
+ import { handleWorkspaceControlRequest } from "./workspace-control-handler.js";
17
16
  import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
18
- import { listRuntimeModels } from "./list-models.js";
19
17
  import { normalizeScheduledContext } from "./scheduled-report.js";
20
18
  import { formatDaemonLogLine } from "./log-format.js";
21
19
  import { reportAgentRunComplete } from "./scheduled-run-report.js";
@@ -49,12 +47,10 @@ import { createProjectSkillsReconciler, } from "./project-skills/reconciler.js";
49
47
  import { initializedProjectSkillsReconciler } from "./project-skills/initialized-reconciler.js";
50
48
  import { parseMemoryPruneTraceId } from "./memory-prune-diagnostics.js";
51
49
  import { probeRuntimeHealth } from "./runtime-health.js";
52
- import { handleRuntimeHealthProbeFrame } from "./runtime-health-handler.js";
50
+ import { createServeMarketplace } from "./marketplace-serve.js";
53
51
  import { buildControlPlaneUrl } from "./control-plane-url.js";
54
- import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
55
- import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
56
52
  import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
57
- import { attachStorageMigrationRuntime, bindStorageMigrationServerIdentity, createServeStorageStatusDelivery, handleStorageMigrationControl, replayStorageMigrationStatus, storageMigrationCapabilities, } from "./storage-serve-adapter.js";
53
+ import { bindStorageServerIdentity, createStorageIdentityAdmission, } from "./storage-serve-adapter.js";
58
54
  export { buildControlPlaneUrl } from "./control-plane-url.js";
59
55
  // normalize.ts 的活动种类 → activity 枚举
60
56
  const ACTIVITY_MAP = {
@@ -80,9 +76,6 @@ export function serve(config, opts = {}) {
80
76
  ?? (config.agentMemory === null ? undefined : createAgentMemoryBridge(config.agentMemory));
81
77
  const executionTelemetry = createExecutionTelemetryJournal(config.agentsRoot);
82
78
  const executeProtocol = opts.execution?.runExecution ?? runExecution;
83
- let detectedExecutionRuntimes = [], executionRuntimesKnown = false;
84
- let runtimeFacts = null;
85
- const runtimeProbe = createRuntimeProbeCoordinator();
86
79
  const localHome = opts.localHome ?? resolveDaemonLocalStateRootFromEnv();
87
80
  const hostCoordinator = opts.execution?.hostCoordinator
88
81
  ?? createHostExecutionCoordinator({
@@ -127,28 +120,14 @@ export function serve(config, opts = {}) {
127
120
  catch { /* reconnect/timeout reconciliation handles a lost status frame */ }
128
121
  },
129
122
  });
130
- let storageMigrationAdmissionClosed = () => false;
131
- const migrationController = createServeMigrationController({
132
- config,
133
- ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
134
- isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0,
135
- sendStatus: (frame) => { try {
136
- if (ws?.readyState === WebSocket.OPEN)
137
- ws.send(JSON.stringify(frame));
138
- }
139
- catch { /* reconnect */ } },
140
- });
141
- const codexHomeModeController = createCodexHomeModeController({ ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }), scheduleRestart: opts.update?.scheduleRestart ?? scheduleServiceRestart, getServiceSpec: async () => { const eligibility = await updateEligibility(); return eligibility.eligible ? eligibility.serviceSpec : null; }, isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0, sendStatus: (frame) => { try {
142
- if (ws?.readyState === WebSocket.OPEN)
143
- ws.send(JSON.stringify(frame));
144
- }
145
- catch { /* reconnect */ } } });
123
+ const marketplace = createServeMarketplace(config, localSlots, hostCoordinator);
124
+ const storageIdentityAdmission = createStorageIdentityAdmission(opts.storageRuntime);
146
125
  const projectionCoordinator = createAgentProjectionCoordinator();
147
126
  const { projectRegistry, projectWorkspaceRegistry, projectDependencyProvisioner } = createProjectRegistryWiring(config.agentsRoot, opts.execution?.dependencies);
148
127
  const agentAbilityRuntime = createAgentAbilityRuntime(config.agentsRoot, {
149
128
  ...opts.agentAbility,
150
129
  coordinator: projectionCoordinator,
151
- isAdmissionClosed: () => storageMigrationAdmissionClosed(),
130
+ isAdmissionClosed: storageIdentityAdmission.isAdmissionClosed,
152
131
  });
153
132
  let projectSkillsController;
154
133
  const projectSkillsReconciler = opts.projectSkills?.reconciler ?? createProjectSkillsReconciler({
@@ -169,7 +148,7 @@ export function serve(config, opts = {}) {
169
148
  }
170
149
  catch { /* 下一次 ready 或项目操作会重新发送完整快照 */ }
171
150
  },
172
- isAdmissionClosed: () => storageMigrationAdmissionClosed(),
151
+ isAdmissionClosed: storageIdentityAdmission.isAdmissionClosed,
173
152
  reconcile: (handle, bindings) => projectSkillsReconciler.reconcile(handle, bindings),
174
153
  });
175
154
  const logRegistryFailure = (error) => dslog("project_skills.registry_failed", "本机项目注册表读取失败,本次不上报项目清单(能力不受影响)", {
@@ -177,7 +156,7 @@ export function serve(config, opts = {}) {
177
156
  });
178
157
  const machineHelloPublisher = createMachineHelloPublisher({
179
158
  effective: (hello) => {
180
- const configuredIdentity = opts.storageMigration?.identity;
159
+ const configuredIdentity = opts.storageRuntime?.identity;
181
160
  const currentIdentity = typeof configuredIdentity === "function"
182
161
  ? configuredIdentity()
183
162
  : configuredIdentity;
@@ -185,25 +164,19 @@ export function serve(config, opts = {}) {
185
164
  },
186
165
  onSent: (hello) => log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · installed=[${hello.runtimes.join(",")}] · executable=[${hello.executionRuntimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`),
187
166
  });
167
+ const machineRuntime = createMachineRuntimeEnvironment({
168
+ config,
169
+ ...(opts.machineEnvironment === undefined ? {} : { provider: opts.machineEnvironment }),
170
+ ...(opts.machineInfo === undefined ? {} : { machineInfo: opts.machineInfo }),
171
+ ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
172
+ additionalCapabilities: async () => managedDaemonCapabilities(updateEligibility),
173
+ publish: machineHelloPublisher.publish,
174
+ onError: (code, context) => dslog("execution.runtime_detection_failed", "机器环境或 runtime 探测失败", { level: "ERROR", error_code: code, ...context }),
175
+ });
176
+ machineRuntime.prewarm();
188
177
  const initializedProjectSkills = initializedProjectSkillsReconciler(() => projectSkillsController.ensureScanned(), projectSkillsReconciler);
189
178
  const configuredCapabilities = opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform);
190
- const storageCapabilities = storageMigrationCapabilities(configuredCapabilities, opts.storageMigration);
191
- const storageStatusDelivery = createServeStorageStatusDelivery(opts.storageMigration, {
192
- onPersistenceError: (error) => dslog("daemon.storage_status_outbox_failed", "存储迁移状态 outbox 持久化失败", { level: "ERROR", error_message: error.message }),
193
- transmit: (frame) => { try {
194
- if (ws?.readyState === WebSocket.OPEN)
195
- ws.send(JSON.stringify(frame));
196
- }
197
- catch { /* retry */ } },
198
- });
199
- const storageMigrationRuntime = attachStorageMigrationRuntime(opts.storageMigration, {
200
- isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0,
201
- waitForExecutionFrames: () => executionFrameQueue,
202
- flushLocalState: flushSlog,
203
- sendStatus: storageStatusDelivery.send,
204
- });
205
- storageMigrationAdmissionClosed = storageMigrationRuntime.isAdmissionClosed;
206
- const prepareCapabilities = async () => daemonCapabilityBindings(process.platform, [...storageCapabilities, ...await managedDaemonCapabilities(updateEligibility)]);
179
+ const prepareCapabilities = async () => daemonCapabilityBindings(process.platform, [...configuredCapabilities, ...await managedDaemonCapabilities(updateEligibility)]);
207
180
  const safeExecutionSend = (frame) => {
208
181
  try {
209
182
  if (ws?.readyState !== WebSocket.OPEN)
@@ -237,6 +210,11 @@ export function serve(config, opts = {}) {
237
210
  return;
238
211
  }
239
212
  safeExecutionSend(frame);
213
+ if (frame.type === "execution:rejected" && frame.preAdmissionProof) {
214
+ const entry = await executionJournal.get(frame.executionId);
215
+ if (entry?.completion)
216
+ completionRetransmitter.track(entry.completion);
217
+ }
240
218
  };
241
219
  const replayExecutionTelemetry = async () => {
242
220
  const frames = await executionTelemetry.replay();
@@ -312,67 +290,29 @@ export function serve(config, opts = {}) {
312
290
  if (stopped)
313
291
  return;
314
292
  const wsUrl = (opts.buildControlPlaneUrl ?? buildControlPlaneUrl)(config.serverUrl, config.machineToken, process.platform, undefined, capabilities.controlPlaneUrl);
315
- ws = createWebSocket(wsUrl);
293
+ const connectionSocket = createWebSocket(wsUrl);
294
+ ws = connectionSocket;
295
+ const sendToConnection = (frame) => {
296
+ try {
297
+ if (!stopped && ws === connectionSocket && connectionSocket.readyState === WebSocket.OPEN)
298
+ connectionSocket.send(JSON.stringify(frame));
299
+ }
300
+ catch { /* 旧连接请求由服务端超时处理。 */ }
301
+ };
316
302
  let connectionEpochToken;
317
- ws.on("open", () => {
303
+ connectionSocket.on("open", () => {
318
304
  connectionEpochToken = projectSkillsController.beginConnectionEpoch();
319
- storageMigrationRuntime.beginIdentityBinding();
320
- const openedSocket = ws;
305
+ storageIdentityAdmission.beginIdentityBinding();
306
+ const openedSocket = connectionSocket;
307
+ const connectionId = randomUUID();
321
308
  backoff = 1000;
322
309
  connectedAt = Date.now();
323
310
  log(`🔌 已连接控制面 ${config.serverUrl}`);
324
- dslog("daemon.ws_open", "已连接控制面", { server_url: config.serverUrl });
311
+ dslog("daemon.ws_open", "已连接控制面", { server_url: config.serverUrl, connection_id: connectionId });
325
312
  // 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
326
313
  void drainSpool();
327
314
  // 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
328
- storageStatusDelivery.replayPending();
329
- const storageReplay = replayStorageMigrationStatus(opts.storageMigration, openedSocket, () => ws === openedSocket, (error) => {
330
- dslog("daemon.storage_status_replay_failed", "存储迁移状态重放失败", {
331
- level: "ERROR", error_message: error.message,
332
- });
333
- }, storageStatusDelivery.send);
334
- const { detectInstalled, detectExecutable = detectExecutionRuntimesWithSignal } = opts.machineInfo ?? {};
335
- const helloPromise = (opts.machineInfo?.collect ?? collectMachineHello)(config.agentsRoot, config.executionLimits, process.platform, {
336
- ...(detectInstalled ? { detectInstalled } : {}),
337
- ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
338
- // First hello must not wait for third-party handshakes; optional transports arrive in the refresh.
339
- detectExecutable: async (installed) => conservativeExecutionRuntimes(installed),
340
- additionalCapabilities: async () => [...(await managedDaemonCapabilities(updateEligibility)), ...(opts.profileName === undefined ? [] : ["codex_home_mode_v1"])],
341
- capabilities: capabilities.machineHello,
342
- codexHomeMode: config.codexHomeMode,
343
- });
344
- runtimeFacts = helloPromise
345
- .then(async (hello) => {
346
- if (hello.executionProtocol === undefined) {
347
- executionRuntimesKnown = true;
348
- return [];
349
- }
350
- const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
351
- executionRuntimesKnown = true;
352
- await storageReplay;
353
- if (ws === openedSocket && openedSocket.readyState === WebSocket.OPEN
354
- && detected.some((runtime) => !hello.executionRuntimes.includes(runtime))) {
355
- detectedExecutionRuntimes = detected;
356
- machineHelloPublisher.publish({ ...hello, executionRuntimes: [...detected] }, openedSocket);
357
- }
358
- return detected;
359
- })
360
- .catch((error) => {
361
- executionRuntimesKnown = true;
362
- dslog("execution.runtime_detection_failed", "runtime 探测失败", {
363
- level: "ERROR", error_message: error.message,
364
- });
365
- return [];
366
- });
367
- void helloPromise
368
- .then(async (hello) => {
369
- await storageReplay;
370
- if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
371
- return;
372
- detectedExecutionRuntimes = hello.executionRuntimes;
373
- machineHelloPublisher.publish(hello, openedSocket);
374
- })
375
- .catch(() => { });
315
+ machineRuntime.open(openedSocket, { capabilities: capabilities.machineHello, beforePublish: Promise.resolve(), connectionId });
376
316
  opts.onOpen?.(ws);
377
317
  void sendSnapshot(`reconnect-${randomUUID()}`);
378
318
  void executionJournal.replay()
@@ -401,8 +341,8 @@ export function serve(config, opts = {}) {
401
341
  });
402
342
  });
403
343
  });
404
- ws.on("message", async (data) => {
405
- if (stopped)
344
+ connectionSocket.on("message", async (data) => {
345
+ if (stopped || ws !== connectionSocket)
406
346
  return;
407
347
  let decoded;
408
348
  try {
@@ -414,8 +354,10 @@ export function serve(config, opts = {}) {
414
354
  if (typeof decoded !== "object" || decoded === null)
415
355
  return;
416
356
  const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
417
- if (storageStatusDelivery.acknowledge(decoded))
357
+ if (rawType === "machine:environment:get" || rawType === "machine:environment:refresh") {
358
+ await handleMachineEnvironmentFrame(decoded, machineRuntime.provider, sendToConnection);
418
359
  return;
360
+ }
419
361
  if (rawType === "daemon:update" || rawType === "daemon:restart") {
420
362
  void updateController.handle(decoded).catch((error) => {
421
363
  dslog("daemon.control_failed", "daemon 控制操作处理失败", {
@@ -424,19 +366,6 @@ export function serve(config, opts = {}) {
424
366
  });
425
367
  return;
426
368
  }
427
- if (rawType === "daemon:migrate") {
428
- void handleMigrationControlMessage(migrationController, decoded).catch((error) => dslog("daemon.migration_failed", "daemon 布局迁移失败", { level: "ERROR", error_message: error.message }));
429
- return;
430
- }
431
- if (rawType === "daemon:codex-home-mode") {
432
- void codexHomeModeController.handle(decoded).catch((error) => dslog("daemon.codex_home_mode_failed", "Codex Home 模式切换失败", { level: "ERROR", error_message: error.message }));
433
- return;
434
- }
435
- if (handleStorageMigrationControl(opts.storageMigration, rawType, decoded, storageStatusDelivery.send, (kind, error) => {
436
- dslog(kind === "migration" ? "daemon.storage_migration_failed" : "daemon.storage_migration_confirmation_failed", kind === "migration" ? "存储迁移控制操作失败" : "存储迁移 hello 确认处理失败", { level: "ERROR", error_message: error.message });
437
- })) {
438
- return;
439
- }
440
369
  if (rawType.startsWith("execution:")) {
441
370
  const parsedExecution = ServerToDaemonExecutionFrameSchema.safeParse(decoded);
442
371
  if (!parsedExecution.success) {
@@ -498,10 +427,10 @@ export function serve(config, opts = {}) {
498
427
  return;
499
428
  }
500
429
  const spec = frame;
501
- if (storageMigrationRuntime.isAdmissionClosed()) {
430
+ if (storageIdentityAdmission.isAdmissionClosed()) {
502
431
  safeExecutionSend(ExecutionRejectedSchema.parse({
503
432
  type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
504
- reason: "resource_limit", message: "Storage migration is quiescing this machine",
433
+ reason: "resource_limit", message: "Storage identity binding is not complete",
505
434
  at: new Date().toISOString(),
506
435
  }));
507
436
  return;
@@ -560,18 +489,8 @@ export function serve(config, opts = {}) {
560
489
  : spec.context.scheduledRunId === undefined ? "normal" : "scheduled";
561
490
  let reservation = sharedSlots.reserve(spec.agent.handle, "execution", executionClass);
562
491
  if (!reservation.accepted) {
563
- dslog("execution.machine_queue_rejected", "机器执行队列已满", {
564
- level: "WARN",
565
- execution_id: spec.executionId,
566
- agent_handle: spec.agent.handle,
567
- execution_class: executionClass,
568
- ...reservation.facts,
569
- });
570
- safeExecutionSend(ExecutionRejectedSchema.parse({
571
- type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
572
- reason: "resource_limit", message: "Local machine execution queue is full",
573
- at: new Date().toISOString(),
574
- }));
492
+ await rejectQueuedAdmission({ journal: executionJournal, knownExecutionHashes, executionRuns,
493
+ report: reportExecutionFrame, replay: sendJournalStatus }, spec, executionClass, reservation.facts);
575
494
  return;
576
495
  }
577
496
  knownExecutionHashes.set(spec.executionId, hash);
@@ -710,9 +629,7 @@ export function serve(config, opts = {}) {
710
629
  let availableRuntimes;
711
630
  try {
712
631
  availableRuntimes = opts.execution?.availableRuntimes?.()
713
- ?? (detectedExecutionRuntimes.includes(spec.runtime.name)
714
- ? detectedExecutionRuntimes
715
- : await (runtimeFacts ?? Promise.resolve(detectedExecutionRuntimes)));
632
+ ?? await machineRuntime.availableRuntimes(spec.runtime.name);
716
633
  }
717
634
  catch (error) {
718
635
  cleanupExecutionReservation("runtime_detection_failed");
@@ -729,6 +646,7 @@ export function serve(config, opts = {}) {
729
646
  }
730
647
  const execution = executeProtocol(config, spec, {
731
648
  ...opts.execution?.dependencies,
649
+ machineEnvironment: machineRuntime.provider,
732
650
  capabilities: capabilities.executionRunner,
733
651
  projectSkills: opts.execution?.dependencies?.projectSkills ?? initializedProjectSkills,
734
652
  abilityRelease: opts.execution?.dependencies?.abilityRelease ?? agentAbilityRuntime.materializer,
@@ -741,6 +659,13 @@ export function serve(config, opts = {}) {
741
659
  ...reservation.facts,
742
660
  },
743
661
  report: reportExecutionFrame,
662
+ confirmPreAdmission: async (rejection) => {
663
+ const prior = await executionJournal.get(spec.executionId);
664
+ if (prior !== null || stopped || knownExecutionHashes.get(spec.executionId) !== hash
665
+ || executionRuns.get(spec.executionId) !== execution)
666
+ return false;
667
+ return await persistPreAdmissionRejection(executionJournal, spec, rejection) !== null;
668
+ },
744
669
  startupGate: runtimeStartupGate,
745
670
  startupTimeoutMs: config.executionLimits.startupTimeoutMs,
746
671
  onRuntimePhase: (phase, runtime) => {
@@ -812,17 +737,17 @@ export function serve(config, opts = {}) {
812
737
  // ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
813
738
  const r = msg;
814
739
  setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
815
- if (opts.storageMigration !== undefined && typeof r.workspaceId === "string" && typeof r.machineId === "string") {
740
+ if (opts.storageRuntime !== undefined && typeof r.workspaceId === "string" && typeof r.machineId === "string") {
816
741
  const bindingSocket = ws;
817
- bindStorageMigrationServerIdentity(opts.storageMigration, { workspaceId: r.workspaceId, machineId: r.machineId }, {
742
+ bindStorageServerIdentity(opts.storageRuntime, { workspaceId: r.workspaceId, machineId: r.machineId }, {
818
743
  onBound: () => {
819
744
  if (bindingSocket !== ws || bindingSocket?.readyState !== WebSocket.OPEN)
820
745
  return;
821
- storageMigrationRuntime.completeIdentityBinding();
746
+ storageIdentityAdmission.completeIdentityBinding();
822
747
  machineHelloPublisher.republish();
823
748
  },
824
749
  onError: (error) => {
825
- dslog("daemon.storage_identity_bind_failed", "存储迁移 Workspace 身份绑定失败", { level: "ERROR", error_message: error.message });
750
+ dslog("daemon.storage_identity_bind_failed", "存储 Workspace 身份绑定失败", { level: "ERROR", error_message: error.message });
826
751
  if (bindingSocket === ws && bindingSocket?.readyState === WebSocket.OPEN)
827
752
  bindingSocket.close(1011, "storage identity binding failed");
828
753
  },
@@ -867,15 +792,17 @@ export function serve(config, opts = {}) {
867
792
  return;
868
793
  }
869
794
  if (msg.type === "runtime-health:probe") {
870
- const result = await handleRuntimeHealthProbeFrame(msg, opts.runtimeHealth?.probe, executionRuntimesKnown, detectedExecutionRuntimes);
795
+ const result = await machineRuntime.health(msg, opts.runtimeHealth?.probe);
871
796
  if (result !== null) {
872
- try {
873
- ws?.send(JSON.stringify({ type: "fs:result", ...result }));
874
- }
875
- catch { /* server 会超时 */ }
797
+ sendToConnection({ type: "fs:result", ...result });
876
798
  }
877
799
  return;
878
800
  }
801
+ if (marketplace.handle(msg, frame => { try {
802
+ ws?.send(JSON.stringify(frame));
803
+ }
804
+ catch { /* server 会按请求超时处理 */ } }))
805
+ return;
879
806
  if (await agentAbilityRuntime.tryHandleControlMessage(msg, serverCapabilities, (frame) => ws?.send(JSON.stringify(frame))))
880
807
  return;
881
808
  // 导入 raft agent 工作区:inspect 反填 name/description;import 复制用户内容
@@ -901,31 +828,16 @@ export function serve(config, opts = {}) {
901
828
  }
902
829
  return;
903
830
  }
904
- // workspace 文件浏览 / skills 枚举请求 (只读、沙箱;见 workspace-fs.ts / skills.ts)
905
- if (msg.type === "fs:list" || msg.type === "fs:read" || msg.type === "skills:list" || msg.type === "probe-models") {
906
- const req = msg;
907
- const root = join(config.agentsRoot, req.handle);
908
- const reply = (r) => {
909
- try {
910
- ws?.send(JSON.stringify({ type: "fs:result", reqId: req.reqId, ...r }));
911
- }
912
- catch { /* 忽略 */ }
913
- };
914
- try {
915
- const data2 = req.type === "fs:list" ? await listWorkspace(root, req.path)
916
- : req.type === "fs:read" ? await readWorkspaceFile(root, req.path)
917
- : req.type === "skills:list" ? await listSkills(req.runtime)
918
- : { models: await listRuntimeModels(req.type === "probe-models" ? (req.runtime ?? "") : "") };
919
- reply({ ok: true, data: data2 });
920
- }
921
- catch (e) {
922
- reply({ ok: false, error: e.message });
923
- }
831
+ if (await handleAgentEnvironmentFrame(msg, config.agentsRoot, sendToConnection))
832
+ return;
833
+ const fileResult = await handleWorkspaceControlRequest(msg, config.agentsRoot, projectWorkspaceRegistry, machineRuntime.models);
834
+ if (fileResult) {
835
+ sendToConnection(fileResult);
924
836
  return;
925
837
  }
926
838
  if (msg.type !== "agent:start")
927
839
  return; // ready/error 等忽略
928
- if (storageMigrationRuntime.isAdmissionClosed())
840
+ if (storageIdentityAdmission.isAdmissionClosed())
929
841
  return;
930
842
  // 任务键:scheduled run 用 runId(每次运行独立 cwd/work-log;overlap 由 server 端 skip_if_running 管,
931
843
  // daemon 的 running 去重只兜底"同一 run 重复投递");普通唤醒仍是 线程锚点 ?? 频道。
@@ -1128,6 +1040,7 @@ export function serve(config, opts = {}) {
1128
1040
  ...(!scheduled && msg.wake?.seq !== undefined ? { wakeContextUpToSeq: msg.wake.seq } : {}),
1129
1041
  ...(attemptWake ? { wake: attemptWake } : {}),
1130
1042
  }, reportActivity, reportConsole, {
1043
+ machineEnvironment: machineRuntime.provider,
1131
1044
  cancellation: controller.cancellation,
1132
1045
  startupGate: runtimeStartupGate,
1133
1046
  startupTimeoutMs: config.executionLimits.startupTimeoutMs,
@@ -1290,8 +1203,9 @@ export function serve(config, opts = {}) {
1290
1203
  void flushSlog(); // 每轮收尾冲一次,保证 run.end 尽快可查
1291
1204
  }
1292
1205
  });
1293
- ws.on("close", (code) => {
1294
- if (stopped)
1206
+ connectionSocket.on("close", (code) => {
1207
+ machineRuntime.close(connectionSocket);
1208
+ if (stopped || ws !== connectionSocket)
1295
1209
  return;
1296
1210
  // server 能力随连接失效;下个连接的 ready 帧重新声明(防降级重连后沿用过期能力)。
1297
1211
  serverCapabilities = new Set();
@@ -1315,9 +1229,9 @@ export function serve(config, opts = {}) {
1315
1229
  }, backoff);
1316
1230
  backoff = Math.min(backoff * 2, maxBackoff);
1317
1231
  });
1318
- ws.on("error", () => {
1232
+ connectionSocket.on("error", () => {
1319
1233
  if (!stopped)
1320
- ws?.close();
1234
+ connectionSocket.close();
1321
1235
  });
1322
1236
  }
1323
1237
  const ready = (async () => {
@@ -1355,9 +1269,7 @@ export function serve(config, opts = {}) {
1355
1269
  stopPromise = (async () => {
1356
1270
  stopped = true;
1357
1271
  concurrencyTelemetry.stop();
1358
- runtimeProbe.stop();
1359
1272
  completionRetransmitter.stop();
1360
- storageStatusDelivery.stop();
1361
1273
  const deadline = createShutdownDeadline(shutdownTimeoutMs);
1362
1274
  if (reconnectTimer !== null) {
1363
1275
  clearTimeout(reconnectTimer);
@@ -1365,10 +1277,10 @@ export function serve(config, opts = {}) {
1365
1277
  }
1366
1278
  const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
1367
1279
  const pending = [
1280
+ machineRuntime.stop(),
1368
1281
  webSocketClosed,
1369
1282
  updateController.drain(),
1370
- ...(migrationController === null ? [] : [migrationController.drain()]),
1371
- codexHomeModeController.drain(),
1283
+ marketplace.stop(),
1372
1284
  executionFrameQueue,
1373
1285
  ...executionRuns.values(),
1374
1286
  ...[...legacyRuns.values()].map((run) => run.done),
@@ -0,0 +1,25 @@
1
+ const CANONICAL_PRODUCTION_HOST = "nowwork.nowcoder.com";
2
+ const PRODUCTION_HOST_ALIASES = new Set([
3
+ CANONICAL_PRODUCTION_HOST,
4
+ "nowwork-in.nowcoder.com",
5
+ ]);
6
+ function canonicalHostname(hostname) {
7
+ const normalized = hostname.toLowerCase();
8
+ return isProductionServerHostname(normalized) ? CANONICAL_PRODUCTION_HOST : normalized;
9
+ }
10
+ export function isProductionServerHostname(hostname) {
11
+ return PRODUCTION_HOST_ALIASES.has(hostname.toLowerCase());
12
+ }
13
+ /** Host identity used by profile/service guards; the public and internal production names are aliases. */
14
+ export function serverHostIdentity(serverUrl) {
15
+ return canonicalHostname(new URL(serverUrl).hostname);
16
+ }
17
+ /** URL identity used by storage records; preserve path/port/query while collapsing production host aliases. */
18
+ export function serverUrlIdentity(serverUrl) {
19
+ const parsed = new URL(serverUrl);
20
+ parsed.hostname = canonicalHostname(parsed.hostname);
21
+ return parsed.toString().replace(/\/+$/u, "");
22
+ }
23
+ export function serverUrlsMatch(left, right) {
24
+ return serverUrlIdentity(left) === serverUrlIdentity(right);
25
+ }
package/dist/slog.js CHANGED
@@ -16,6 +16,7 @@ import { join, resolve } from "node:path";
16
16
  import { createHash } from "node:crypto";
17
17
  import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
18
18
  import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
19
+ import { isProductionServerHostname } from "./server-url.js";
19
20
  const FLUSH_INTERVAL_MS = 1500;
20
21
  const BATCH_SIZE = 50;
21
22
  const SPOOL_MAX_FILES = 50; // spool 目录文件数上限,超出丢最旧(日志非关键数据,防无限膨胀)
@@ -30,7 +31,7 @@ let lastErrorAt = 0;
30
31
  let exitHookInstalled = false;
31
32
  let localStateRoot;
32
33
  function deploymentEnvironment(serverHost) {
33
- if (serverHost === "nowwork-in.nowcoder.com")
34
+ if (serverHost !== undefined && isProductionServerHostname(serverHost))
34
35
  return "prod";
35
36
  if (serverHost === "nowwork-dev.nowcoder.com")
36
37
  return "dev";
@@ -39,8 +39,5 @@ export function storageHelloIdentity(input) {
39
39
  daemonRootFingerprint: fingerprint("daemon-root", layout.daemonRoot),
40
40
  agentRootFingerprint: fingerprint("agent-root", layout.agentItemsRoot),
41
41
  ...(ownership === undefined ? {} : { storageOwnership: ownership }),
42
- ...(installation.storageMigrationId === undefined
43
- ? {}
44
- : { storageMigrationId: installation.storageMigrationId }),
45
42
  });
46
43
  }
@@ -2,12 +2,12 @@ import { findInstallationForProfile, readInstallations, resolveLocalStorageRoots
2
2
  import { storageHelloIdentity } from "./storage-identity.js";
3
3
  import { storageLayout } from "./storage-layout.js";
4
4
  import { readStorageOwnershipClaim, resolveWorkspaceStorageOwnershipClaim, } from "./storage-ownership.js";
5
- const canonicalServerUrl = (value) => value.replace(/\/+$/u, "");
5
+ import { serverUrlsMatch } from "./server-url.js";
6
6
  export function resolveStorageRuntime(input) {
7
7
  const installation = findInstallationForProfile(input.records, input.profile);
8
8
  if (installation === null)
9
9
  return null;
10
- if (canonicalServerUrl(installation.serverUrl) !== canonicalServerUrl(input.serverUrl)) {
10
+ if (!serverUrlsMatch(installation.serverUrl, input.serverUrl)) {
11
11
  throw new Error("installation server identity does not match the active profile");
12
12
  }
13
13
  const local = resolveLocalStorageRoots({
@@ -1,55 +1,12 @@
1
- import { STORAGE_MIGRATION_CAPABILITY, STORAGE_MIGRATION_STATUS_CAPABILITY, } from "./storage-control.js";
2
- import { createStorageStatusDelivery } from "./storage-status-delivery.js";
3
- export function storageMigrationCapabilities(configured, options) {
4
- if (options === undefined)
5
- return configured;
6
- return Object.freeze([...new Set([
7
- ...configured,
8
- STORAGE_MIGRATION_STATUS_CAPABILITY,
9
- ...(options.ready ? [STORAGE_MIGRATION_CAPABILITY] : []),
10
- ])]);
1
+ export function bindStorageServerIdentity(options, identity, callbacks) {
2
+ void Promise.resolve().then(() => options.bindServerIdentity?.(identity))
3
+ .then(callbacks.onBound).catch(callbacks.onError);
11
4
  }
12
- export function bindStorageMigrationServerIdentity(options, identity, callbacks) {
13
- const bind = options?.bindServerIdentity;
14
- if (bind === undefined) {
15
- if (options?.ready === true)
16
- callbacks.onBound();
17
- return;
18
- }
19
- void Promise.resolve().then(() => bind(identity)).then(callbacks.onBound).catch(callbacks.onError);
20
- }
21
- export function createServeStorageStatusDelivery(options, dependencies) {
22
- return createStorageStatusDelivery({
23
- ...(options?.statusOutboxPath === undefined ? {} : { outboxPath: options.statusOutboxPath }),
24
- ...(options?.statusRetryMs === undefined ? {} : { retryMs: options.statusRetryMs }),
25
- ...dependencies,
26
- });
27
- }
28
- export function attachStorageMigrationRuntime(options, dependencies) {
29
- let migrationAdmissionClosed = false;
5
+ /** 新连接必须重新验证 Server 身份,不能沿用上一次连接的执行准入。 */
6
+ export function createStorageIdentityAdmission(options) {
30
7
  let identityAdmissionClosed = options?.bindServerIdentity !== undefined;
31
- if (options?.ready === true) {
32
- options.attach?.({
33
- gate: {
34
- isBusy: dependencies.isBusy,
35
- closeAndFlush: async () => {
36
- migrationAdmissionClosed = true;
37
- await dependencies.waitForExecutionFrames();
38
- if (dependencies.isBusy()) {
39
- migrationAdmissionClosed = false;
40
- throw Object.assign(new Error("active execution entered before storage migration quiesce"), {
41
- code: "runtime_busy",
42
- });
43
- }
44
- await dependencies.flushLocalState();
45
- },
46
- reopen: () => { migrationAdmissionClosed = false; },
47
- },
48
- sendStatus: dependencies.sendStatus,
49
- });
50
- }
51
8
  return Object.freeze({
52
- isAdmissionClosed: () => migrationAdmissionClosed || identityAdmissionClosed,
9
+ isAdmissionClosed: () => identityAdmissionClosed,
53
10
  beginIdentityBinding: () => {
54
11
  if (options?.bindServerIdentity !== undefined)
55
12
  identityAdmissionClosed = true;
@@ -57,26 +14,3 @@ export function attachStorageMigrationRuntime(options, dependencies) {
57
14
  completeIdentityBinding: () => { identityAdmissionClosed = false; },
58
15
  });
59
16
  }
60
- export function replayStorageMigrationStatus(options, socket, isCurrent, onError, deliver) {
61
- return options?.replayStatus?.((frame) => {
62
- if (!isCurrent())
63
- return;
64
- if (deliver !== undefined)
65
- deliver(frame);
66
- else if (socket.readyState === socket.OPEN)
67
- socket.send(JSON.stringify(frame));
68
- }).catch(onError) ?? Promise.resolve();
69
- }
70
- export function handleStorageMigrationControl(options, rawType, raw, sendStatus, onError) {
71
- if (rawType === "daemon:storage-doctor" || rawType === "daemon:storage-migrate") {
72
- if (options?.ready === true)
73
- void options.controller.handle(raw).catch((error) => onError("migration", error));
74
- return true;
75
- }
76
- if (rawType !== "daemon:storage-migration-confirmed")
77
- return false;
78
- const confirm = options?.ready === false ? options.confirm : undefined;
79
- if (confirm !== undefined)
80
- void confirm(raw, sendStatus).catch((error) => onError("confirmation", error));
81
- return true;
82
- }