@nowcrew/daemon 0.6.88-beta.2 → 0.6.88-beta.20

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 (74) hide show
  1. package/README.md +38 -10
  2. package/dist/agent-memory/policy.js +3 -1
  3. package/dist/daemon-update-eligibility.js +1 -1
  4. package/dist/execution-journal.js +9 -6
  5. package/dist/execution-protocol.js +2 -1
  6. package/dist/execution-runner.js +5 -3
  7. package/dist/list-models.js +2 -0
  8. package/dist/local-execution-lease.js +22 -0
  9. package/dist/local-executor.js +57 -37
  10. package/dist/local-memory/candidate.js +43 -0
  11. package/dist/local-memory/index-budget.js +8 -0
  12. package/dist/local-memory/inventory-cli.js +61 -0
  13. package/dist/local-memory/inventory-preview.js +154 -0
  14. package/dist/local-memory/inventory-source.js +159 -0
  15. package/dist/local-memory/pending-match.js +29 -0
  16. package/dist/local-memory/pending-query.js +100 -0
  17. package/dist/local-memory/pending-schema.js +29 -0
  18. package/dist/local-memory/pending-source.js +126 -0
  19. package/dist/local-memory/pending.js +34 -84
  20. package/dist/local-memory/prompt.js +14 -11
  21. package/dist/local-memory/read-cli.js +59 -8
  22. package/dist/local-memory/runtime.js +24 -17
  23. package/dist/local-memory/work-log-execution.js +31 -0
  24. package/dist/local-memory/work-log.js +58 -0
  25. package/dist/local-memory/write.js +27 -58
  26. package/dist/machine-info.js +54 -16
  27. package/dist/main.js +4 -69
  28. package/dist/native-thread-context.js +59 -0
  29. package/dist/pre-admission-rejection.js +64 -0
  30. package/dist/project-skills/execution-adapter.js +3 -2
  31. package/dist/project-skills/runtime-launch.js +14 -0
  32. package/dist/prompt.js +15 -15
  33. package/dist/provider-env.js +3 -4
  34. package/dist/runner.js +14 -15
  35. package/dist/runtime-probe.js +34 -5
  36. package/dist/runtimes/acp-probe-transport.js +69 -0
  37. package/dist/runtimes/claude-session-start-hook.js +24 -0
  38. package/dist/runtimes/claude-thread-runner.js +266 -0
  39. package/dist/runtimes/claude.js +23 -4
  40. package/dist/runtimes/codex-app-server-runner.js +43 -4
  41. package/dist/runtimes/deepseek-harness-profile.js +92 -0
  42. package/dist/runtimes/deepseek-harness-runner.js +224 -0
  43. package/dist/runtimes/deepseek-harness.js +64 -11
  44. package/dist/runtimes/harness-probe-retry.js +42 -0
  45. package/dist/runtimes/kimi-acp-runner.js +63 -10
  46. package/dist/runtimes/opencode-runner.js +7 -1
  47. package/dist/serve.js +58 -88
  48. package/dist/session.js +4 -0
  49. package/dist/storage-identity.js +0 -3
  50. package/dist/storage-serve-adapter.js +6 -72
  51. package/dist/supervised-runtime.js +28 -18
  52. package/dist/thread-input-delivery.js +217 -0
  53. package/package.json +2 -2
  54. package/dist/daemon-migration-controller.js +0 -211
  55. package/dist/daemon-migration-wiring.js +0 -22
  56. package/dist/storage-control.js +0 -212
  57. package/dist/storage-copy.js +0 -628
  58. package/dist/storage-credential-guard.js +0 -21
  59. package/dist/storage-doctor.js +0 -228
  60. package/dist/storage-manifest.js +0 -1027
  61. package/dist/storage-migration-controller.js +0 -440
  62. package/dist/storage-migration-manifest.js +0 -105
  63. package/dist/storage-migration-policy.js +0 -100
  64. package/dist/storage-runtime-controller.js +0 -381
  65. package/dist/storage-runtime-readiness.js +0 -240
  66. package/dist/storage-safe-fs.js +0 -302
  67. package/dist/storage-status-delivery.js +0 -224
  68. package/dist/storage-status-replay.js +0 -140
  69. package/dist/storage-target-confirmation.js +0 -129
  70. package/dist/storage-worker-command.js +0 -37
  71. package/dist/storage-worker-executor.js +0 -449
  72. package/dist/storage-worker-handoff.js +0 -151
  73. package/dist/storage-worker-operation.js +0 -294
  74. package/dist/storage-worker-status.js +0 -259
package/dist/serve.js CHANGED
@@ -3,6 +3,7 @@
3
3
  * 断线指数退避重连。一个 agent:start 进来就跑一次现有的 runAgent。
4
4
  */
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";
@@ -51,10 +52,9 @@ import { parseMemoryPruneTraceId } from "./memory-prune-diagnostics.js";
51
52
  import { probeRuntimeHealth } from "./runtime-health.js";
52
53
  import { handleRuntimeHealthProbeFrame } from "./runtime-health-handler.js";
53
54
  import { buildControlPlaneUrl } from "./control-plane-url.js";
54
- import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
55
55
  import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
56
56
  import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
57
- import { attachStorageMigrationRuntime, bindStorageMigrationServerIdentity, createServeStorageStatusDelivery, handleStorageMigrationControl, replayStorageMigrationStatus, storageMigrationCapabilities, } from "./storage-serve-adapter.js";
57
+ import { bindStorageServerIdentity, createStorageIdentityAdmission, } from "./storage-serve-adapter.js";
58
58
  export { buildControlPlaneUrl } from "./control-plane-url.js";
59
59
  // normalize.ts 的活动种类 → activity 枚举
60
60
  const ACTIVITY_MAP = {
@@ -127,17 +127,7 @@ export function serve(config, opts = {}) {
127
127
  catch { /* reconnect/timeout reconciliation handles a lost status frame */ }
128
128
  },
129
129
  });
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
- });
130
+ const storageIdentityAdmission = createStorageIdentityAdmission(opts.storageRuntime);
141
131
  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
132
  if (ws?.readyState === WebSocket.OPEN)
143
133
  ws.send(JSON.stringify(frame));
@@ -148,7 +138,7 @@ export function serve(config, opts = {}) {
148
138
  const agentAbilityRuntime = createAgentAbilityRuntime(config.agentsRoot, {
149
139
  ...opts.agentAbility,
150
140
  coordinator: projectionCoordinator,
151
- isAdmissionClosed: () => storageMigrationAdmissionClosed(),
141
+ isAdmissionClosed: storageIdentityAdmission.isAdmissionClosed,
152
142
  });
153
143
  let projectSkillsController;
154
144
  const projectSkillsReconciler = opts.projectSkills?.reconciler ?? createProjectSkillsReconciler({
@@ -169,7 +159,7 @@ export function serve(config, opts = {}) {
169
159
  }
170
160
  catch { /* 下一次 ready 或项目操作会重新发送完整快照 */ }
171
161
  },
172
- isAdmissionClosed: () => storageMigrationAdmissionClosed(),
162
+ isAdmissionClosed: storageIdentityAdmission.isAdmissionClosed,
173
163
  reconcile: (handle, bindings) => projectSkillsReconciler.reconcile(handle, bindings),
174
164
  });
175
165
  const logRegistryFailure = (error) => dslog("project_skills.registry_failed", "本机项目注册表读取失败,本次不上报项目清单(能力不受影响)", {
@@ -177,7 +167,7 @@ export function serve(config, opts = {}) {
177
167
  });
178
168
  const machineHelloPublisher = createMachineHelloPublisher({
179
169
  effective: (hello) => {
180
- const configuredIdentity = opts.storageMigration?.identity;
170
+ const configuredIdentity = opts.storageRuntime?.identity;
181
171
  const currentIdentity = typeof configuredIdentity === "function"
182
172
  ? configuredIdentity()
183
173
  : configuredIdentity;
@@ -187,23 +177,7 @@ export function serve(config, opts = {}) {
187
177
  });
188
178
  const initializedProjectSkills = initializedProjectSkillsReconciler(() => projectSkillsController.ensureScanned(), projectSkillsReconciler);
189
179
  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)]);
180
+ const prepareCapabilities = async () => daemonCapabilityBindings(process.platform, [...configuredCapabilities, ...await managedDaemonCapabilities(updateEligibility)]);
207
181
  const safeExecutionSend = (frame) => {
208
182
  try {
209
183
  if (ws?.readyState !== WebSocket.OPEN)
@@ -237,6 +211,11 @@ export function serve(config, opts = {}) {
237
211
  return;
238
212
  }
239
213
  safeExecutionSend(frame);
214
+ if (frame.type === "execution:rejected" && frame.preAdmissionProof) {
215
+ const entry = await executionJournal.get(frame.executionId);
216
+ if (entry?.completion)
217
+ completionRetransmitter.track(entry.completion);
218
+ }
240
219
  };
241
220
  const replayExecutionTelemetry = async () => {
242
221
  const frames = await executionTelemetry.replay();
@@ -316,21 +295,16 @@ export function serve(config, opts = {}) {
316
295
  let connectionEpochToken;
317
296
  ws.on("open", () => {
318
297
  connectionEpochToken = projectSkillsController.beginConnectionEpoch();
319
- storageMigrationRuntime.beginIdentityBinding();
298
+ storageIdentityAdmission.beginIdentityBinding();
320
299
  const openedSocket = ws;
300
+ const connectionId = randomUUID();
321
301
  backoff = 1000;
322
302
  connectedAt = Date.now();
323
303
  log(`🔌 已连接控制面 ${config.serverUrl}`);
324
- dslog("daemon.ws_open", "已连接控制面", { server_url: config.serverUrl });
304
+ dslog("daemon.ws_open", "已连接控制面", { server_url: config.serverUrl, connection_id: connectionId });
325
305
  // 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
326
306
  void drainSpool();
327
307
  // 上报本机信息 (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
308
  const { detectInstalled, detectExecutable = detectExecutionRuntimesWithSignal } = opts.machineInfo ?? {};
335
309
  const helloPromise = (opts.machineInfo?.collect ?? collectMachineHello)(config.agentsRoot, config.executionLimits, process.platform, {
336
310
  ...(detectInstalled ? { detectInstalled } : {}),
@@ -345,28 +319,41 @@ export function serve(config, opts = {}) {
345
319
  .then(async (hello) => {
346
320
  if (hello.executionProtocol === undefined) {
347
321
  executionRuntimesKnown = true;
322
+ dslog("runtime_probe.hello_refresh", "runtime hello 刷新决策", {
323
+ connection_id: connectionId, reason: "protocol_unavailable",
324
+ current_socket: ws === openedSocket, socket_open: openedSocket.readyState === WebSocket.OPEN,
325
+ socket_ready_state: openedSocket.readyState, installed_runtimes: JSON.stringify(hello.runtimes),
326
+ execution_runtimes: JSON.stringify(hello.executionRuntimes),
327
+ });
348
328
  return [];
349
329
  }
350
- const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
330
+ const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable, connectionId);
351
331
  executionRuntimesKnown = true;
352
- await storageReplay;
353
- if (ws === openedSocket && openedSocket.readyState === WebSocket.OPEN
354
- && detected.some((runtime) => !hello.executionRuntimes.includes(runtime))) {
332
+ const currentSocket = ws === openedSocket;
333
+ const socketOpen = openedSocket.readyState === WebSocket.OPEN;
334
+ const hasNewRuntime = detected.some((runtime) => !hello.executionRuntimes.includes(runtime));
335
+ dslog("runtime_probe.hello_refresh", "runtime hello 刷新决策", {
336
+ connection_id: connectionId, current_socket: currentSocket, socket_open: socketOpen,
337
+ socket_ready_state: openedSocket.readyState, installed_runtimes: JSON.stringify(hello.runtimes),
338
+ initial_execution_runtimes: JSON.stringify(hello.executionRuntimes), execution_runtimes: JSON.stringify(detected),
339
+ reason: !currentSocket ? "stale_connection" : !socketOpen ? "socket_not_open" : !hasNewRuntime ? "unchanged" : "publish",
340
+ });
341
+ if (currentSocket && socketOpen && hasNewRuntime) {
355
342
  detectedExecutionRuntimes = detected;
356
343
  machineHelloPublisher.publish({ ...hello, executionRuntimes: [...detected] }, openedSocket);
357
344
  }
358
345
  return detected;
359
346
  })
360
- .catch((error) => {
347
+ .catch(() => {
361
348
  executionRuntimesKnown = true;
362
349
  dslog("execution.runtime_detection_failed", "runtime 探测失败", {
363
- level: "ERROR", error_message: error.message,
350
+ level: "ERROR", reason: "detection_threw", connection_id: connectionId,
351
+ current_socket: ws === openedSocket, socket_open: openedSocket.readyState === WebSocket.OPEN,
364
352
  });
365
353
  return [];
366
354
  });
367
355
  void helloPromise
368
356
  .then(async (hello) => {
369
- await storageReplay;
370
357
  if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
371
358
  return;
372
359
  detectedExecutionRuntimes = hello.executionRuntimes;
@@ -414,8 +401,6 @@ export function serve(config, opts = {}) {
414
401
  if (typeof decoded !== "object" || decoded === null)
415
402
  return;
416
403
  const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
417
- if (storageStatusDelivery.acknowledge(decoded))
418
- return;
419
404
  if (rawType === "daemon:update" || rawType === "daemon:restart") {
420
405
  void updateController.handle(decoded).catch((error) => {
421
406
  dslog("daemon.control_failed", "daemon 控制操作处理失败", {
@@ -424,19 +409,10 @@ export function serve(config, opts = {}) {
424
409
  });
425
410
  return;
426
411
  }
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
412
  if (rawType === "daemon:codex-home-mode") {
432
413
  void codexHomeModeController.handle(decoded).catch((error) => dslog("daemon.codex_home_mode_failed", "Codex Home 模式切换失败", { level: "ERROR", error_message: error.message }));
433
414
  return;
434
415
  }
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
416
  if (rawType.startsWith("execution:")) {
441
417
  const parsedExecution = ServerToDaemonExecutionFrameSchema.safeParse(decoded);
442
418
  if (!parsedExecution.success) {
@@ -498,10 +474,10 @@ export function serve(config, opts = {}) {
498
474
  return;
499
475
  }
500
476
  const spec = frame;
501
- if (storageMigrationRuntime.isAdmissionClosed()) {
477
+ if (storageIdentityAdmission.isAdmissionClosed()) {
502
478
  safeExecutionSend(ExecutionRejectedSchema.parse({
503
479
  type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
504
- reason: "resource_limit", message: "Storage migration is quiescing this machine",
480
+ reason: "resource_limit", message: "Storage identity binding is not complete",
505
481
  at: new Date().toISOString(),
506
482
  }));
507
483
  return;
@@ -560,18 +536,8 @@ export function serve(config, opts = {}) {
560
536
  : spec.context.scheduledRunId === undefined ? "normal" : "scheduled";
561
537
  let reservation = sharedSlots.reserve(spec.agent.handle, "execution", executionClass);
562
538
  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
- }));
539
+ await rejectQueuedAdmission({ journal: executionJournal, knownExecutionHashes, executionRuns,
540
+ report: reportExecutionFrame, replay: sendJournalStatus }, spec, executionClass, reservation.facts);
575
541
  return;
576
542
  }
577
543
  knownExecutionHashes.set(spec.executionId, hash);
@@ -741,6 +707,13 @@ export function serve(config, opts = {}) {
741
707
  ...reservation.facts,
742
708
  },
743
709
  report: reportExecutionFrame,
710
+ confirmPreAdmission: async (rejection) => {
711
+ const prior = await executionJournal.get(spec.executionId);
712
+ if (prior !== null || stopped || knownExecutionHashes.get(spec.executionId) !== hash
713
+ || executionRuns.get(spec.executionId) !== execution)
714
+ return false;
715
+ return await persistPreAdmissionRejection(executionJournal, spec, rejection) !== null;
716
+ },
744
717
  startupGate: runtimeStartupGate,
745
718
  startupTimeoutMs: config.executionLimits.startupTimeoutMs,
746
719
  onRuntimePhase: (phase, runtime) => {
@@ -812,17 +785,17 @@ export function serve(config, opts = {}) {
812
785
  // ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
813
786
  const r = msg;
814
787
  setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
815
- if (opts.storageMigration !== undefined && typeof r.workspaceId === "string" && typeof r.machineId === "string") {
788
+ if (opts.storageRuntime !== undefined && typeof r.workspaceId === "string" && typeof r.machineId === "string") {
816
789
  const bindingSocket = ws;
817
- bindStorageMigrationServerIdentity(opts.storageMigration, { workspaceId: r.workspaceId, machineId: r.machineId }, {
790
+ bindStorageServerIdentity(opts.storageRuntime, { workspaceId: r.workspaceId, machineId: r.machineId }, {
818
791
  onBound: () => {
819
792
  if (bindingSocket !== ws || bindingSocket?.readyState !== WebSocket.OPEN)
820
793
  return;
821
- storageMigrationRuntime.completeIdentityBinding();
794
+ storageIdentityAdmission.completeIdentityBinding();
822
795
  machineHelloPublisher.republish();
823
796
  },
824
797
  onError: (error) => {
825
- dslog("daemon.storage_identity_bind_failed", "存储迁移 Workspace 身份绑定失败", { level: "ERROR", error_message: error.message });
798
+ dslog("daemon.storage_identity_bind_failed", "存储 Workspace 身份绑定失败", { level: "ERROR", error_message: error.message });
826
799
  if (bindingSocket === ws && bindingSocket?.readyState === WebSocket.OPEN)
827
800
  bindingSocket.close(1011, "storage identity binding failed");
828
801
  },
@@ -925,7 +898,7 @@ export function serve(config, opts = {}) {
925
898
  }
926
899
  if (msg.type !== "agent:start")
927
900
  return; // ready/error 等忽略
928
- if (storageMigrationRuntime.isAdmissionClosed())
901
+ if (storageIdentityAdmission.isAdmissionClosed())
929
902
  return;
930
903
  // 任务键:scheduled run 用 runId(每次运行独立 cwd/work-log;overlap 由 server 端 skip_if_running 管,
931
904
  // daemon 的 running 去重只兜底"同一 run 重复投递");普通唤醒仍是 线程锚点 ?? 频道。
@@ -1090,15 +1063,14 @@ export function serve(config, opts = {}) {
1090
1063
  const ackHint = `\n**协作礼仪:决定接手后,先用 \`${sendCmd}\` 在该任务线程说一声你来跟进**——用日常语言带上你对问题的理解或打算先查什么,不要写"我接 task #N"这类内部编号;写不出实质内容就不单发,并进第一条实质进展,但不要长时间闷头干活把线程空着。`;
1091
1064
  // 图片/文件附件:crew message read 会在消息下列出附件及其 id;图片需下载后用 Read 工具查看,才能真正"看到"内容。
1092
1065
  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}`;
1066
+ // 启动上下文由执行层交付;legacy 提示词也不能要求模型补读或退回频道历史。
1067
+ const contextHint = threadId
1068
+ ? "直接依据程序交付的本线程上下文与新消息处理,无需启动补读历史。"
1069
+ : "没有线程锚点,只处理明确来信或请求补充当前任务线程,不读取频道历史猜测。";
1098
1070
  const wakeText = scheduled
1099
1071
  ? buildScheduledPrompt(msg.channelId, msg.wake?.content ?? "", scheduled.outputPolicy, scheduled.externalNotificationPolicy)
1100
1072
  : (msg.wake?.content
1101
- ? `你被唤醒(${msg.reason}): ${msg.wake.content}\n用 ${readCmd} 读${threadId ? "本线程" : "频道"}后按需处理。${reasonHint}${ackHint}${threadHint}${attHint}`
1073
+ ? `你被唤醒(${msg.reason}): ${msg.wake.content}\n${contextHint}${reasonHint}${ackHint}${threadHint}${attHint}`
1102
1074
  : undefined);
1103
1075
  const wakeOrigin = scheduled ? undefined : msg.wake?.origin;
1104
1076
  const guarded = await runWithOriginDecisionGuard(wakeOrigin, async (attempt) => {
@@ -1124,8 +1096,8 @@ export function serve(config, opts = {}) {
1124
1096
  },
1125
1097
  } : {}),
1126
1098
  ...(wakeOrigin ? { wakeOrigin, originDecisionAttempt: attempt } : {}),
1127
- // 唤醒锚点是具体消息(非纯频道唤醒)时,把它透传下去,供 `crew task create` 锚定到该消息。
1128
- ...(!scheduled && threadId ? { wakeMessageId: threadId } : {}),
1099
+ // legacy wake.threadId 已由 Server 归一到线程根;与普通触发消息 id 分开透传。
1100
+ ...(!scheduled && threadId ? { threadId, wakeMessageId: threadId } : {}),
1129
1101
  ...(!scheduled && msg.wake?.seq !== undefined ? { wakeContextUpToSeq: msg.wake.seq } : {}),
1130
1102
  ...(attemptWake ? { wake: attemptWake } : {}),
1131
1103
  }, reportActivity, reportConsole, {
@@ -1358,7 +1330,6 @@ export function serve(config, opts = {}) {
1358
1330
  concurrencyTelemetry.stop();
1359
1331
  runtimeProbe.stop();
1360
1332
  completionRetransmitter.stop();
1361
- storageStatusDelivery.stop();
1362
1333
  const deadline = createShutdownDeadline(shutdownTimeoutMs);
1363
1334
  if (reconnectTimer !== null) {
1364
1335
  clearTimeout(reconnectTimer);
@@ -1368,7 +1339,6 @@ export function serve(config, opts = {}) {
1368
1339
  const pending = [
1369
1340
  webSocketClosed,
1370
1341
  updateController.drain(),
1371
- ...(migrationController === null ? [] : [migrationController.drain()]),
1372
1342
  codexHomeModeController.drain(),
1373
1343
  executionFrameQueue,
1374
1344
  ...executionRuns.values(),
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,
@@ -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
  }
@@ -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
- }
@@ -1,7 +1,7 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import { startDormantSupervisor, } from "./execution-supervisor.js";
3
- import { buildClaudeArgs } from "./runtimes/claude.js";
4
- import { DEEPSEEK_HARNESS_CONFIG_ENV, deepSeekHarnessConfigPath, } from "./runtimes/deepseek-harness.js";
3
+ import { buildClaudeLaunch } from "./runtimes/claude.js";
4
+ import { DEEPSEEK_HARNESS_CONFIG_ENV, deepSeekHarnessConfigPath, deepSeekHarnessLauncher, } from "./runtimes/deepseek-harness.js";
5
5
  import { RuntimeCancelledError } from "./runtime-cancellation.js";
6
6
  export function supervisorLaunch(request) {
7
7
  const common = {
@@ -13,23 +13,18 @@ export function supervisorLaunch(request) {
13
13
  };
14
14
  if (request.runtime === "claude") {
15
15
  const additionalDirectories = request.claudeAdditionalDirectories;
16
- return {
17
- command: request.bin,
18
- args: buildClaudeArgs({
19
- ...common,
20
- bin: request.bin,
21
- cwd: request.cwd,
22
- env: request.env,
23
- systemPromptPath: request.systemPromptPath,
24
- ...(additionalDirectories === undefined ? {} : { additionalDirectories }),
25
- ...(request.sessionId === undefined ? {} : {
26
- sessionId: request.sessionId,
27
- resume: request.resume,
28
- }),
29
- }),
16
+ return buildClaudeLaunch({
17
+ ...common,
18
+ bin: request.bin,
30
19
  cwd: request.cwd,
31
20
  env: request.env,
32
- };
21
+ systemPromptPath: request.systemPromptPath,
22
+ ...(additionalDirectories === undefined ? {} : { additionalDirectories }),
23
+ ...(request.sessionId === undefined ? {} : {
24
+ sessionId: request.sessionId,
25
+ resume: request.resume,
26
+ }),
27
+ });
33
28
  }
34
29
  if (request.runtime === "codex") {
35
30
  return {
@@ -87,7 +82,22 @@ export function supervisorLaunch(request) {
87
82
  if (request.runtime === "deepseek-harness") {
88
83
  if (request.resume)
89
84
  throw new Error("DeepSeek Harness ACP does not support session resume");
90
- const configPath = deepSeekHarnessConfigPath();
85
+ const launcher = deepSeekHarnessLauncher(request.env);
86
+ if (launcher.kind === "profile") {
87
+ return {
88
+ command: process.execPath,
89
+ args: [fileURLToPath(new URL("./runtimes/deepseek-harness-runner.js", import.meta.url)),
90
+ "--bin", request.bin, ...(launcher.patchPath ? ["--patch", launcher.patchPath] : [])],
91
+ cwd: request.cwd, env: request.env,
92
+ stdinText: JSON.stringify({ systemPrompt: request.systemPrompt, wakePrompt: request.wakePrompt,
93
+ stateRoot: request.harnessStateRoot ?? request.cwd, skillRoots: request.harnessSkillRoots ?? [],
94
+ effectivePermission: request.effectivePermission }),
95
+ };
96
+ }
97
+ if ((request.harnessSkillRoots?.length ?? 0) > 0) {
98
+ throw new Error("DeepSeek Harness bound Skills require the modern dsh ACP profile");
99
+ }
100
+ const configPath = deepSeekHarnessConfigPath(request.env);
91
101
  if (configPath === null) {
92
102
  throw new Error(`${DEEPSEEK_HARNESS_CONFIG_ENV} must be an absolute path`);
93
103
  }