@nowcrew/daemon 0.6.88-beta.3 → 0.6.88-beta.31

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 (129) hide show
  1. package/README.md +89 -14
  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/agent-memory/policy.js +3 -1
  11. package/dist/automatic-daemon-installer.js +51 -2
  12. package/dist/automatic-install-target.js +8 -0
  13. package/dist/computer-main.js +18 -0
  14. package/dist/computer-profile.js +17 -4
  15. package/dist/computer-setup-client.js +318 -0
  16. package/dist/computer-setup-orchestration.js +203 -0
  17. package/dist/computer-setup.js +244 -0
  18. package/dist/config.js +30 -12
  19. package/dist/console-formatter.js +3 -3
  20. package/dist/console.js +15 -2
  21. package/dist/daemon-update-eligibility.js +1 -1
  22. package/dist/execution-journal.js +9 -6
  23. package/dist/execution-protocol.js +2 -1
  24. package/dist/execution-runner.js +16 -10
  25. package/dist/i18n.js +19 -0
  26. package/dist/list-models.js +13 -11
  27. package/dist/local-execution-lease.js +22 -0
  28. package/dist/local-executor.js +88 -92
  29. package/dist/local-memory/candidate.js +43 -0
  30. package/dist/local-memory/index-budget.js +8 -0
  31. package/dist/local-memory/inventory-cli.js +61 -0
  32. package/dist/local-memory/inventory-preview.js +159 -0
  33. package/dist/local-memory/inventory-source.js +159 -0
  34. package/dist/local-memory/pending-match.js +29 -0
  35. package/dist/local-memory/pending-query.js +100 -0
  36. package/dist/local-memory/pending-schema.js +29 -0
  37. package/dist/local-memory/pending-source.js +126 -0
  38. package/dist/local-memory/pending.js +34 -84
  39. package/dist/local-memory/prompt.js +20 -12
  40. package/dist/local-memory/read-cli.js +97 -12
  41. package/dist/local-memory/rollout.js +82 -0
  42. package/dist/local-memory/runtime.js +24 -17
  43. package/dist/local-memory/work-log-execution.js +31 -0
  44. package/dist/local-memory/work-log.js +58 -0
  45. package/dist/local-memory/write.js +27 -58
  46. package/dist/local-runtime-environment.js +50 -0
  47. package/dist/machine-environment-cache.js +147 -0
  48. package/dist/machine-environment-contract.js +10 -0
  49. package/dist/machine-environment-control.js +27 -0
  50. package/dist/machine-environment-policy.js +75 -0
  51. package/dist/machine-environment-process.js +110 -0
  52. package/dist/machine-info.js +70 -25
  53. package/dist/machine-runtime-environment.js +189 -0
  54. package/dist/machine-shell-environment-capture.js +135 -0
  55. package/dist/machine-windows-environment-capture.js +215 -0
  56. package/dist/machine-windows-environment-script.js +158 -0
  57. package/dist/main.js +4 -69
  58. package/dist/marketplace-controller.js +75 -0
  59. package/dist/marketplace-protocol.js +38 -0
  60. package/dist/marketplace-runner.js +234 -0
  61. package/dist/marketplace-serve.js +17 -0
  62. package/dist/message-file-preview.js +246 -0
  63. package/dist/native-thread-context.js +59 -0
  64. package/dist/pre-admission-rejection.js +64 -0
  65. package/dist/project-skills/execution-adapter.js +3 -2
  66. package/dist/project-skills/runtime-launch.js +14 -0
  67. package/dist/prompt.js +15 -15
  68. package/dist/provider-env.js +26 -15
  69. package/dist/runner.js +31 -19
  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/claude-session-start-hook.js +24 -0
  78. package/dist/runtimes/claude-thread-runner.js +266 -0
  79. package/dist/runtimes/claude.js +23 -4
  80. package/dist/runtimes/codex-app-server-runner.js +43 -4
  81. package/dist/runtimes/deepseek-harness-launch.js +10 -0
  82. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  83. package/dist/runtimes/deepseek-harness-runner.js +226 -0
  84. package/dist/runtimes/deepseek-harness.js +76 -15
  85. package/dist/runtimes/harness-probe-retry.js +42 -0
  86. package/dist/runtimes/hermes-models.js +2 -1
  87. package/dist/runtimes/kimi-acp-runner.js +84 -15
  88. package/dist/runtimes/opencode-runner.js +7 -1
  89. package/dist/runtimes/opencode.js +2 -2
  90. package/dist/serve.js +95 -184
  91. package/dist/server-url.js +25 -0
  92. package/dist/session.js +4 -0
  93. package/dist/slog.js +2 -1
  94. package/dist/storage-identity.js +0 -3
  95. package/dist/storage-runtime.js +2 -2
  96. package/dist/storage-serve-adapter.js +6 -72
  97. package/dist/supervised-runtime.js +28 -18
  98. package/dist/thread-input-delivery.js +217 -0
  99. package/dist/token.js +0 -1
  100. package/dist/workspace-control-handler.js +27 -0
  101. package/dist/workspace-fs.js +66 -15
  102. package/dist/workspace.js +1 -1
  103. package/package.json +5 -4
  104. package/dist/codex-home-launch.js +0 -7
  105. package/dist/codex-home-mode-controller.js +0 -68
  106. package/dist/daemon-migration-controller.js +0 -211
  107. package/dist/daemon-migration-wiring.js +0 -22
  108. package/dist/runtimes/codex-home-migration-cli.js +0 -26
  109. package/dist/runtimes/codex-home-migration.js +0 -112
  110. package/dist/runtimes/codex-home.js +0 -236
  111. package/dist/storage-control.js +0 -212
  112. package/dist/storage-copy.js +0 -628
  113. package/dist/storage-credential-guard.js +0 -21
  114. package/dist/storage-doctor.js +0 -228
  115. package/dist/storage-manifest.js +0 -1027
  116. package/dist/storage-migration-controller.js +0 -440
  117. package/dist/storage-migration-manifest.js +0 -105
  118. package/dist/storage-migration-policy.js +0 -100
  119. package/dist/storage-runtime-controller.js +0 -381
  120. package/dist/storage-runtime-readiness.js +0 -240
  121. package/dist/storage-safe-fs.js +0 -302
  122. package/dist/storage-status-delivery.js +0 -224
  123. package/dist/storage-status-replay.js +0 -140
  124. package/dist/storage-target-confirmation.js +0 -129
  125. package/dist/storage-worker-command.js +0 -37
  126. package/dist/storage-worker-executor.js +0 -449
  127. package/dist/storage-worker-handoff.js +0 -151
  128. package/dist/storage-worker-operation.js +0 -294
  129. 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 重复投递");普通唤醒仍是 线程锚点 ?? 频道。
@@ -1090,15 +1002,14 @@ export function serve(config, opts = {}) {
1090
1002
  const ackHint = `\n**协作礼仪:决定接手后,先用 \`${sendCmd}\` 在该任务线程说一声你来跟进**——用日常语言带上你对问题的理解或打算先查什么,不要写"我接 task #N"这类内部编号;写不出实质内容就不单发,并进第一条实质进展,但不要长时间闷头干活把线程空着。`;
1091
1003
  // 图片/文件附件:crew message read 会在消息下列出附件及其 id;图片需下载后用 Read 工具查看,才能真正"看到"内容。
1092
1004
  const attHint = `\n若消息带图片/文件附件(read 会列出 id),用 \`crew attachment get <id>\` 下载到本地,图片再用 Read 工具打开查看后再处理。`;
1093
- // 线程隔离:有 threadId 时用 `crew thread read` 只读本线程(避免被其他线程消息干扰);
1094
- // 顶层消息(无 threadId)用 `crew message read` 读整个频道。
1095
- const readCmd = threadId
1096
- ? `crew thread read`
1097
- : `crew message read --channel ${msg.channelId}`;
1005
+ // 启动上下文由执行层交付;legacy 提示词也不能要求模型补读或退回频道历史。
1006
+ const contextHint = threadId
1007
+ ? "直接依据程序交付的本线程上下文与新消息处理,无需启动补读历史。"
1008
+ : "没有线程锚点,只处理明确来信或请求补充当前任务线程,不读取频道历史猜测。";
1098
1009
  const wakeText = scheduled
1099
1010
  ? buildScheduledPrompt(msg.channelId, msg.wake?.content ?? "", scheduled.outputPolicy, scheduled.externalNotificationPolicy)
1100
1011
  : (msg.wake?.content
1101
- ? `你被唤醒(${msg.reason}): ${msg.wake.content}\n用 ${readCmd} 读${threadId ? "本线程" : "频道"}后按需处理。${reasonHint}${ackHint}${threadHint}${attHint}`
1012
+ ? `你被唤醒(${msg.reason}): ${msg.wake.content}\n${contextHint}${reasonHint}${ackHint}${threadHint}${attHint}`
1102
1013
  : undefined);
1103
1014
  const wakeOrigin = scheduled ? undefined : msg.wake?.origin;
1104
1015
  const guarded = await runWithOriginDecisionGuard(wakeOrigin, async (attempt) => {
@@ -1124,11 +1035,12 @@ export function serve(config, opts = {}) {
1124
1035
  },
1125
1036
  } : {}),
1126
1037
  ...(wakeOrigin ? { wakeOrigin, originDecisionAttempt: attempt } : {}),
1127
- // 唤醒锚点是具体消息(非纯频道唤醒)时,把它透传下去,供 `crew task create` 锚定到该消息。
1128
- ...(!scheduled && threadId ? { wakeMessageId: threadId } : {}),
1038
+ // legacy wake.threadId 已由 Server 归一到线程根;与普通触发消息 id 分开透传。
1039
+ ...(!scheduled && threadId ? { threadId, wakeMessageId: threadId } : {}),
1129
1040
  ...(!scheduled && msg.wake?.seq !== undefined ? { wakeContextUpToSeq: msg.wake.seq } : {}),
1130
1041
  ...(attemptWake ? { wake: attemptWake } : {}),
1131
1042
  }, reportActivity, reportConsole, {
1043
+ machineEnvironment: machineRuntime.provider,
1132
1044
  cancellation: controller.cancellation,
1133
1045
  startupGate: runtimeStartupGate,
1134
1046
  startupTimeoutMs: config.executionLimits.startupTimeoutMs,
@@ -1291,8 +1203,9 @@ export function serve(config, opts = {}) {
1291
1203
  void flushSlog(); // 每轮收尾冲一次,保证 run.end 尽快可查
1292
1204
  }
1293
1205
  });
1294
- ws.on("close", (code) => {
1295
- if (stopped)
1206
+ connectionSocket.on("close", (code) => {
1207
+ machineRuntime.close(connectionSocket);
1208
+ if (stopped || ws !== connectionSocket)
1296
1209
  return;
1297
1210
  // server 能力随连接失效;下个连接的 ready 帧重新声明(防降级重连后沿用过期能力)。
1298
1211
  serverCapabilities = new Set();
@@ -1316,9 +1229,9 @@ export function serve(config, opts = {}) {
1316
1229
  }, backoff);
1317
1230
  backoff = Math.min(backoff * 2, maxBackoff);
1318
1231
  });
1319
- ws.on("error", () => {
1232
+ connectionSocket.on("error", () => {
1320
1233
  if (!stopped)
1321
- ws?.close();
1234
+ connectionSocket.close();
1322
1235
  });
1323
1236
  }
1324
1237
  const ready = (async () => {
@@ -1356,9 +1269,7 @@ export function serve(config, opts = {}) {
1356
1269
  stopPromise = (async () => {
1357
1270
  stopped = true;
1358
1271
  concurrencyTelemetry.stop();
1359
- runtimeProbe.stop();
1360
1272
  completionRetransmitter.stop();
1361
- storageStatusDelivery.stop();
1362
1273
  const deadline = createShutdownDeadline(shutdownTimeoutMs);
1363
1274
  if (reconnectTimer !== null) {
1364
1275
  clearTimeout(reconnectTimer);
@@ -1366,10 +1277,10 @@ export function serve(config, opts = {}) {
1366
1277
  }
1367
1278
  const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
1368
1279
  const pending = [
1280
+ machineRuntime.stop(),
1369
1281
  webSocketClosed,
1370
1282
  updateController.drain(),
1371
- ...(migrationController === null ? [] : [migrationController.drain()]),
1372
- codexHomeModeController.drain(),
1283
+ marketplace.stop(),
1373
1284
  executionFrameQueue,
1374
1285
  ...executionRuns.values(),
1375
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/session.js CHANGED
@@ -24,6 +24,10 @@ export async function readSession(runDir) {
24
24
  if (typeof o.sessionId !== "string" || !o.sessionId)
25
25
  return null;
26
26
  return {
27
+ ...(typeof o.runtime === "string" ? { runtime: o.runtime } : {}),
28
+ ...(typeof o.threadRoot === "string" ? { threadRoot: o.threadRoot } : {}),
29
+ ...(Number.isSafeInteger(o.threadContextSeq) && o.threadContextSeq >= 0 ? { threadContextSeq: o.threadContextSeq } : {}),
30
+ ...(typeof o.threadHistoryComplete === "boolean" ? { threadHistoryComplete: o.threadHistoryComplete } : {}),
27
31
  sessionId: o.sessionId,
28
32
  lastRunAt: typeof o.lastRunAt === "number" ? o.lastRunAt : 0,
29
33
  turns: typeof o.turns === "number" ? o.turns : 0,
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({