oasis_test_v2 2.2.48 → 2.2.49

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 (2) hide show
  1. package/dist/index.js +874 -596
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -440,6 +440,9 @@ function isUnread(eventAt, marker) {
440
440
  if (Number.isNaN(e) || Number.isNaN(m2)) return true;
441
441
  return e > m2;
442
442
  }
443
+ function isChatSessionUnread(session, marker) {
444
+ return session.lastTurnQuality !== void 0 && isUnread(session.touchedAt, marker);
445
+ }
443
446
  var chatScope, letterScope, INBOX_BASELINE_SCOPE;
444
447
  var init_inbox_read = __esm({
445
448
  "../contract/src/inbox-read.ts"() {
@@ -4185,7 +4188,7 @@ var init_claude_code = __esm({
4185
4188
  if (composed) errorParts.push(composed);
4186
4189
  }
4187
4190
  if (stderrTail2.length) errorParts.push(stderrTail2.join("\n").trim());
4188
- const errorMessage = isError ? errorParts.filter(Boolean).join("\n---\n").slice(-4e3).trim() || void 0 : void 0;
4191
+ const errorMessage2 = isError ? errorParts.filter(Boolean).join("\n---\n").slice(-4e3).trim() || void 0 : void 0;
4189
4192
  exited = {
4190
4193
  code: killReason ? null : code2,
4191
4194
  ...reason ? { reason } : {},
@@ -4193,7 +4196,7 @@ var init_claude_code = __esm({
4193
4196
  workdirRef,
4194
4197
  ...usage ? { usage } : {},
4195
4198
  ...sessionModel ? { model: sessionModel } : {},
4196
- ...errorMessage ? { errorMessage } : {},
4199
+ ...errorMessage2 ? { errorMessage: errorMessage2 } : {},
4197
4200
  // ADR-0147:容器化会话带上镜像 digest(可复现凭据,经 dispatcher 落 agent_runs.metadata)。
4198
4201
  ...containerized?.imageDigest ? { imageDigest: containerized.imageDigest } : {}
4199
4202
  };
@@ -6906,7 +6909,7 @@ ${task}` : task;
6906
6909
  const runtimeSessionId = telemetry.sessionId ?? resumeSessionId;
6907
6910
  const sawStreamError = streamErrorMessages.length > 0;
6908
6911
  const isError = !killReason && (code2 !== 0 || sawStreamError);
6909
- const errorMessage = isError ? [...streamErrorMessages, ...stdoutErrorLines, ...stderrTail2].join("\n").trim().slice(-4e3) || void 0 : void 0;
6912
+ const errorMessage2 = isError ? [...streamErrorMessages, ...stdoutErrorLines, ...stderrTail2].join("\n").trim().slice(-4e3) || void 0 : void 0;
6910
6913
  const sessionNotFound = !killReason && isResume && isSessionNotFoundError([...streamErrorMessages, ...stdoutErrorLines, ...stderrTail2].join("\n"));
6911
6914
  const reason = killReason ? killReason : sessionNotFound ? "session-not-found" : sawStreamError ? "error" : code2 === 0 ? "clean" : "error";
6912
6915
  finish({
@@ -6919,7 +6922,7 @@ ${task}` : task;
6919
6922
  ...telemetry.usage ? { usage: telemetry.usage } : {},
6920
6923
  ...model ? { model } : {},
6921
6924
  ...runtimeSessionId ? { runtimeSessionId } : {},
6922
- ...errorMessage ? { errorMessage } : {},
6925
+ ...errorMessage2 ? { errorMessage: errorMessage2 } : {},
6923
6926
  // 跑在哪个镜像上要落账:同一题换了镜像,分数就不可比(ADR-0147 M5)
6924
6927
  ...containerized?.imageDigest ? { imageDigest: containerized.imageDigest } : {}
6925
6928
  });
@@ -8239,8 +8242,8 @@ function classifyHermesPromptFailure(turn) {
8239
8242
  if (errors.length === 0 && !failedSentence && !silent) return void 0;
8240
8243
  return failure(errors.length > 0 ? clip(errors.slice(-MAX_ERROR_RECORDS).join("\n")) : failedSentence ?? `hermes \u672C\u8F6E\u6CA1\u6709\u6B63\u5E38\u6536\u5C3E\uFF08\u54CD\u5E94\u4E0D\u5E26\u7528\u91CF\uFF09\uFF0C\u6CA1\u6709\u8F93\u51FA\uFF0Cstderr \u4E5F\u6CA1\u6709 ERROR \u65E5\u5FD7\u3002${stderrTail(turn.stderrLines)}`);
8241
8244
  }
8242
- function failure(errorMessage) {
8243
- return { reason: QUOTA_RE.test(errorMessage) ? "rate-limit" : "error", errorMessage };
8245
+ function failure(errorMessage2) {
8246
+ return { reason: QUOTA_RE.test(errorMessage2) ? "rate-limit" : "error", errorMessage: errorMessage2 };
8244
8247
  }
8245
8248
  function promptResultHasUsage(result) {
8246
8249
  if (typeof result !== "object" || result === null) return false;
@@ -9003,12 +9006,12 @@ async function runStreamJson(job, cfg) {
9003
9006
  child.on("exit", (code2) => {
9004
9007
  clearTimeout(timer);
9005
9008
  const isError = !killReason && typeof code2 === "number" && code2 !== 0;
9006
- const errorMessage = isError ? stderrTail2.join("\n").trim().slice(-4e3) || void 0 : void 0;
9009
+ const errorMessage2 = isError ? stderrTail2.join("\n").trim().slice(-4e3) || void 0 : void 0;
9007
9010
  finish({
9008
9011
  code: killReason ? null : code2,
9009
9012
  reason: killReason ?? (code2 === 0 ? "clean" : "error"),
9010
9013
  transcriptRef: transcriptRefPtr.value,
9011
- ...errorMessage ? { errorMessage } : {},
9014
+ ...errorMessage2 ? { errorMessage: errorMessage2 } : {},
9012
9015
  // ADR-0147:容器化会话带上镜像 digest(可复现凭据)。
9013
9016
  ...containerized?.imageDigest ? { imageDigest: containerized.imageDigest } : {}
9014
9017
  });
@@ -9106,12 +9109,12 @@ async function runOneShotText(job, cfg) {
9106
9109
  child.on("exit", (code2) => {
9107
9110
  clearTimeout(timer);
9108
9111
  const isError = !killReason && typeof code2 === "number" && code2 !== 0;
9109
- const errorMessage = isError ? stderrTail2.join("\n").trim().slice(-4e3) || void 0 : void 0;
9112
+ const errorMessage2 = isError ? stderrTail2.join("\n").trim().slice(-4e3) || void 0 : void 0;
9110
9113
  finish({
9111
9114
  code: killReason ? null : code2,
9112
9115
  reason: killReason ?? (code2 === 0 ? "clean" : "error"),
9113
9116
  transcriptRef: transcriptRefPtr.value,
9114
- ...errorMessage ? { errorMessage } : {},
9117
+ ...errorMessage2 ? { errorMessage: errorMessage2 } : {},
9115
9118
  // ADR-0147:容器化会话带上镜像 digest(可复现凭据)。
9116
9119
  ...containerized?.imageDigest ? { imageDigest: containerized.imageDigest } : {}
9117
9120
  });
@@ -10496,6 +10499,9 @@ var init_chat_item_ledger = __esm({
10496
10499
  finished = false;
10497
10500
  /** 上一次见到的归一层 turnId——变了就清映射(provider 开了新 turn)。 */
10498
10501
  lastTurnId = null;
10502
+ /** 本账本已经落库的 user 行(按落库顺序)。{@link onUserItemPersisted} 的晚到订阅者从这里补。 */
10503
+ persistedUserItems = [];
10504
+ userItemListeners = /* @__PURE__ */ new Set();
10499
10505
  /** 会话逻辑时钟走一格。**唯一发号点**(ADR-0510 附录 A.4)——别在别处自己数。 */
10500
10506
  tick() {
10501
10507
  return ++this.clock;
@@ -10513,6 +10519,37 @@ var init_chat_item_ledger = __esm({
10513
10519
  const row = this.rows.get(itemId);
10514
10520
  return row ? { createdAt: row.createdAt, completedAt: row.completedAt } : void 0;
10515
10521
  }
10522
+ /**
10523
+ * 订阅「本账本的 **user 行**真的落库了」(拿到服务端派的 `ord`、写号与这一行自己的 payload)。
10524
+ * 订阅那一刻**已经**落库的先按落库顺序同步补一遍,之后的逐条到达。返回退订。
10525
+ *
10526
+ * ## 为什么是订阅而不是构造参数
10527
+ *
10528
+ * 唯一的消费者是 `LiveChatRegistry.start`:用户那条消息(首条提交 / 中途插话)落库即推上 v3 流。
10529
+ * 此前这件事挂在 `server.ts` 构造账本时给的 {@link ChatItemLedgerDeps.onPersisted} 上,
10530
+ * 于是**只有 `/api/chat` 那一条开轮入口**接了;后台自起轮(工单播报 / 委派回流 / 授权卡续跑)、
10531
+ * serve 重启重挂、委派子会话三条入口建账本时都没给——插话已被 runtime 消费、库里也记了送达,
10532
+ * 页面却收不到那一行,排队区一直转圈,直到本轮结束重拉快照才出现
10533
+ * (2026-09-17 生产会话 `3b89b474`,子任务回传唤起的那一轮,插话晚显示约 4 分钟)。
10534
+ * 挂到「有 live 轮就有」的那一处,新长出来的开轮入口不需要记得再接一次。
10535
+ *
10536
+ * ## 为什么要补发
10537
+ *
10538
+ * `/api/chat` 的首条提交在 `liveChat.start` **之前**就排进落库链,谁先完成纯看 INSERT 往返
10539
+ * (2026-09-10「位置有时候变来变去」那场竞速)。补发让「落库早于开轮」与「开轮早于落库」同一个结果。
10540
+ */
10541
+ onUserItemPersisted(cb) {
10542
+ for (const info of this.persistedUserItems.slice()) {
10543
+ try {
10544
+ cb(info);
10545
+ } catch {
10546
+ }
10547
+ }
10548
+ this.userItemListeners.add(cb);
10549
+ return () => {
10550
+ this.userItemListeners.delete(cb);
10551
+ };
10552
+ }
10516
10553
  get failures() {
10517
10554
  return this.failureCount;
10518
10555
  }
@@ -10777,6 +10814,9 @@ var init_chat_item_ledger = __esm({
10777
10814
  /**
10778
10815
  * 切段(人中途插入):此刻起建的 item 属于**下一条** assistant 行。
10779
10816
  * 已有 provider item 保留身份与位置;新 item 才归下一段,迟到快照不能复制旧块。
10817
+ *
10818
+ * 返回新段的段号。那条 assistant 行落库后回填归属时**必须带上它**({@link bindMessage}):
10819
+ * 行是异步落库的,回填到达时后面可能已经又切过段。
10780
10820
  */
10781
10821
  beginSegment() {
10782
10822
  if (this.segmentEndEmitted) {
@@ -10788,6 +10828,7 @@ var init_chat_item_ledger = __esm({
10788
10828
  this.messageId = null;
10789
10829
  this.segment += 1;
10790
10830
  this.segmentItemCount = 0;
10831
+ return this.segment;
10791
10832
  }
10792
10833
  /**
10793
10834
  * 人插话时,先把「这一段到此为止」落定;**必须在那条 user 行之前调用**。
@@ -10824,15 +10865,31 @@ var init_chat_item_ledger = __esm({
10824
10865
  payload: { code: "segment_end", note: SEGMENT_END_NOTE }
10825
10866
  });
10826
10867
  }
10827
- /** 行落库了,把本段还挂着 NULL 的 assistant item 认领回去,并让后续 item 直接带上它。 */
10828
- bindMessage(messageId) {
10829
- this.messageId = messageId;
10868
+ /**
10869
+ * 第 `segment` 段那条 assistant 行落库了:把**那一段**还挂着 NULL 的 assistant item 认领回去;
10870
+ * 那一段仍是当前段时,后续 item 直接带上它。缺省段号 = 当前段。
10871
+ *
10872
+ * ## 为什么必须按段收窄(2026-09-17)
10873
+ *
10874
+ * 切段后新行的落库挂在调用方的 checkpoint 链上(先收口前段、再落 user 行、再落新 assistant 行),
10875
+ * 回填要等这几次写都完成;而 runtime 的输出和第二次插话不等它。库慢一点时事件序可以是:
10876
+ * 插话 1 切到第 1 段 → 第 1 段输出 → 插话 2 切到第 2 段 → 第 2 段输出 → 第 1 段的行才落库、回填到达。
10877
+ * 此前回填把**全部** NULL 的 assistant item 认给这一行、并无条件把当前归属改成它:第 2 段的 item
10878
+ * 被归到第 1 段那条消息上(库里与帧上都是),之后新建的 item 也跟着倒退到第 1 段,
10879
+ * 直到第 2 段的回填到达也认领不回来(它们已经不是 NULL)。所以回填只认这一段、只认此刻在账本里的
10880
+ * 那几条 item(store 侧按精确 id 更新),当前归属只在段号仍是当前段时才前进。
10881
+ */
10882
+ bindMessage(messageId, segment = this.segment) {
10883
+ if (segment === this.segment) this.messageId = messageId;
10884
+ const itemIds = [];
10830
10885
  for (const row of this.rows.values()) {
10831
- if (row.role === "assistant" && row.messageId === null) row.messageId = messageId;
10886
+ if (row.role !== "assistant" || row.messageId !== null || row.segment !== segment) continue;
10887
+ row.messageId = messageId;
10888
+ if (row.persist) itemIds.push(row.id);
10832
10889
  }
10833
- if (!this.persistEnabled) return;
10890
+ if (!this.persistEnabled || itemIds.length === 0) return;
10834
10891
  this.enqueue(async () => {
10835
- await this.deps.items.bindMessage(this.deps.sessionId, this.deps.turnId, messageId);
10892
+ await this.deps.items.bindMessage(this.deps.sessionId, this.deps.turnId, messageId, itemIds);
10836
10893
  }, "bindMessage");
10837
10894
  }
10838
10895
  /**
@@ -11018,17 +11075,27 @@ var init_chat_item_ledger = __esm({
11018
11075
  version: writeVersion
11019
11076
  });
11020
11077
  row.ord = created.ord;
11078
+ const info = {
11079
+ itemId: id,
11080
+ ord: created.ord,
11081
+ version: writeVersion,
11082
+ role: input.role,
11083
+ text: input.text,
11084
+ payload: payload2
11085
+ };
11021
11086
  try {
11022
- this.deps.onPersisted?.({
11023
- itemId: id,
11024
- ord: created.ord,
11025
- version: writeVersion,
11026
- role: input.role,
11027
- text: input.text,
11028
- payload: payload2
11029
- });
11087
+ this.deps.onPersisted?.(info);
11030
11088
  } catch {
11031
11089
  }
11090
+ if (input.role === "user") {
11091
+ this.persistedUserItems.push(info);
11092
+ for (const cb of this.userItemListeners) {
11093
+ try {
11094
+ cb(info);
11095
+ } catch {
11096
+ }
11097
+ }
11098
+ }
11032
11099
  }, `create:${input.kind}`);
11033
11100
  return id;
11034
11101
  }
@@ -11216,6 +11283,46 @@ async function finalizeAssistantRow(deps) {
11216
11283
  } catch {
11217
11284
  }
11218
11285
  }
11286
+ function beginInsertedAssistantSegment(deps) {
11287
+ const segText = deps.ledger.projectContent();
11288
+ const nextId = (deps.newId ?? import_node_crypto14.randomUUID)();
11289
+ const segment = deps.ledger.beginSegment();
11290
+ deps.live.setAssistantMessageId(nextId);
11291
+ const at = (deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))();
11292
+ const { chatStore, closingId, segParts } = deps;
11293
+ const persist = async () => {
11294
+ if (closingId) {
11295
+ await chatStore.updateMessage(closingId, {
11296
+ content: segText,
11297
+ status: "done",
11298
+ completedAt: at,
11299
+ ...segParts.length ? { parts: segParts } : {}
11300
+ }).catch(() => void 0);
11301
+ }
11302
+ await chatStore.appendMessage({
11303
+ id: (0, import_node_crypto14.randomUUID)(),
11304
+ sessionId: deps.chatSessionId,
11305
+ role: "user",
11306
+ content: deps.text,
11307
+ createdAt: at,
11308
+ // ADR-0166 D9.1:插入带的附件也落在**这条 user 行**上(同 `/api/chat` 那侧只存引用)。
11309
+ ...deps.attachments?.length ? { attachments: deps.attachments } : {},
11310
+ ...deps.turnId ? { turnId: deps.turnId } : {}
11311
+ // 插话切段仍属同一轮(同 turnId)
11312
+ }).catch(() => void 0);
11313
+ await chatStore.appendMessage({
11314
+ id: nextId,
11315
+ sessionId: deps.chatSessionId,
11316
+ role: "assistant",
11317
+ content: "",
11318
+ createdAt: at,
11319
+ status: "running",
11320
+ ...deps.runId ? { runId: deps.runId } : {},
11321
+ ...deps.turnId ? { turnId: deps.turnId } : {}
11322
+ }).then(() => deps.ledger.bindMessage(nextId, segment)).catch(() => void 0);
11323
+ };
11324
+ return { nextId, segText, persist };
11325
+ }
11219
11326
  function outputsPartProvider(scan2, chatSessionId, companyId) {
11220
11327
  if (!scan2) return void 0;
11221
11328
  return async () => {
@@ -11307,6 +11414,8 @@ function runBackgroundAssistantChatTurn(deps) {
11307
11414
  chatStore: deps.chatStore,
11308
11415
  chatSessionId: deps.chatSessionId,
11309
11416
  ...runId ? { runId } : {},
11417
+ // 与 `/api/chat` 同口径(ADR 0300 I1):消息行带这一轮的 `chat_session_turns.id`。此前后台轮不带。
11418
+ ...deps.turnId ? { turnId: deps.turnId } : {},
11310
11419
  now
11311
11420
  });
11312
11421
  const itemLedger = new ChatItemLedger({
@@ -11339,9 +11448,34 @@ function runBackgroundAssistantChatTurn(deps) {
11339
11448
  );
11340
11449
  const localParts = [];
11341
11450
  let localSeq = 0;
11342
- const collectedParts = () => localParts.filter((p2) => p2.type !== "done" && p2.type !== "user");
11451
+ let segmentBaseSeq = 0;
11452
+ const collectedParts = () => localParts.filter((p2) => p2.type !== "done" && p2.type !== "user" && p2.seq > segmentBaseSeq);
11453
+ let currentMsgId = assistantMsgId;
11454
+ const closedSegmentsText = [];
11343
11455
  let checkpointChain = Promise.resolve();
11344
11456
  let ckptDirty = false;
11457
+ if (liveTurn && typeof deps.chatStore.appendMessage === "function" && typeof deps.chatStore.updateMessage === "function") {
11458
+ const live = liveTurn;
11459
+ live.onUserInput((text6, attachments) => {
11460
+ const split = beginInsertedAssistantSegment({
11461
+ chatStore: deps.chatStore,
11462
+ chatSessionId: deps.chatSessionId,
11463
+ ledger: itemLedger,
11464
+ live,
11465
+ closingId: currentMsgId,
11466
+ segParts: collectedParts(),
11467
+ text: text6,
11468
+ attachments,
11469
+ ...runId ? { runId } : {},
11470
+ turnId: deps.turnId,
11471
+ now
11472
+ });
11473
+ closedSegmentsText.push(split.segText);
11474
+ currentMsgId = split.nextId;
11475
+ segmentBaseSeq = localSeq;
11476
+ checkpointChain = checkpointChain.then(split.persist).catch(() => void 0);
11477
+ });
11478
+ }
11345
11479
  normalizedSource.onNormalizedProviderEvent((event) => {
11346
11480
  try {
11347
11481
  if (liveTurn) liveTurn.applyNormalizedEvent(event);
@@ -11369,11 +11503,12 @@ function runBackgroundAssistantChatTurn(deps) {
11369
11503
  let ckptTimer = null;
11370
11504
  if (checkpointMs > 0 && assistantMsgId) {
11371
11505
  ckptTimer = setInterval(() => {
11372
- if (!ckptDirty) return;
11506
+ if (!ckptDirty || !currentMsgId) return;
11373
11507
  ckptDirty = false;
11374
11508
  const parts = collectedParts();
11375
11509
  const content3 = itemLedger.projectContent();
11376
- checkpointChain = checkpointChain.then(() => deps.chatStore.updateMessage(assistantMsgId, {
11510
+ const target = currentMsgId;
11511
+ checkpointChain = checkpointChain.then(() => deps.chatStore.updateMessage(target, {
11377
11512
  content: content3,
11378
11513
  status: "running",
11379
11514
  ...parts.length ? { parts } : {}
@@ -11393,11 +11528,13 @@ function runBackgroundAssistantChatTurn(deps) {
11393
11528
  await finalizeAssistantRow({
11394
11529
  chatStore: deps.chatStore,
11395
11530
  chatSessionId: deps.chatSessionId,
11396
- assistantMsgId,
11531
+ // 插话切过段就收最后一段那一行;前面各段在切段时已转 done。
11532
+ assistantMsgId: currentMsgId,
11397
11533
  content: projectedContent,
11398
11534
  parts,
11399
11535
  status,
11400
11536
  ...runId ? { runId } : {},
11537
+ ...deps.turnId ? { turnId: deps.turnId } : {},
11401
11538
  ...deps.outputsPart ? { outputsPart: deps.outputsPart } : {},
11402
11539
  now
11403
11540
  });
@@ -11413,7 +11550,8 @@ function runBackgroundAssistantChatTurn(deps) {
11413
11550
  }
11414
11551
  }
11415
11552
  if (liveTurn) {
11416
- if (projectedContent) liveTurn.emit({ type: "text", text: projectedContent });
11553
+ const turnText = [...closedSegmentsText, projectedContent].join("");
11554
+ if (turnText) liveTurn.emit({ type: "text", text: turnText });
11417
11555
  liveTurn.emitLive({
11418
11556
  protocolVersion: 2,
11419
11557
  streamId: `background:${deps.chatSessionId}`,
@@ -11421,7 +11559,7 @@ function runBackgroundAssistantChatTurn(deps) {
11421
11559
  itemId: `background:${assistantMsgId ?? runId}`,
11422
11560
  itemType: "message",
11423
11561
  operation: "completed",
11424
- payload: { role: "assistant", text: projectedContent }
11562
+ payload: { role: "assistant", text: turnText }
11425
11563
  });
11426
11564
  }
11427
11565
  try {
@@ -11895,11 +12033,11 @@ function decryptFeishu(encrypt, encryptKey) {
11895
12033
  function decodeBody(body2, encryptKey) {
11896
12034
  if (typeof body2 !== "object" || body2 === null) return null;
11897
12035
  const rec = body2;
11898
- const enc4 = rec["encrypt"];
11899
- if (typeof enc4 === "string") {
12036
+ const enc3 = rec["encrypt"];
12037
+ if (typeof enc3 === "string") {
11900
12038
  if (!encryptKey) return null;
11901
12039
  try {
11902
- const parsed = JSON.parse(decryptFeishu(enc4, encryptKey));
12040
+ const parsed = JSON.parse(decryptFeishu(enc3, encryptKey));
11903
12041
  return typeof parsed === "object" && parsed !== null ? parsed : null;
11904
12042
  } catch {
11905
12043
  return null;
@@ -125218,6 +125356,90 @@ var init_state = __esm({
125218
125356
  }
125219
125357
  });
125220
125358
 
125359
+ // ../engine/src/upstream-stability.ts
125360
+ function inputsMatch(state, node2, latestWork) {
125361
+ const inbound = state.edgesInto(node2.id);
125362
+ const currInputs = new Set(
125363
+ inbound.filter((e) => e.kind === "data" && e.pinnedWorkId).map((e) => e.pinnedWorkId)
125364
+ );
125365
+ const latestInputs = new Set(state.workInputsOf(latestWork.id).map((i) => i.upstreamWorkId));
125366
+ return currInputs.size === latestInputs.size && [...currInputs].every((x2) => latestInputs.has(x2));
125367
+ }
125368
+ function isEdgeInputUnobtainable(state, edge) {
125369
+ const upstream = state.node(edge.fromNodeId);
125370
+ return !!upstream?.cancelledAt && !upstream.latestAcceptId;
125371
+ }
125372
+ function concernsCurrentDelivery(state, node2, issue2) {
125373
+ const targetNode = issue2.aboutNodeId ? state.node(issue2.aboutNodeId) : node2;
125374
+ if (!issue2.aboutWorkId || issue2.aboutWorkId === targetNode?.latestAcceptId) return true;
125375
+ const target = state.work(issue2.aboutWorkId);
125376
+ return !target?.acceptedAt;
125377
+ }
125378
+ function hasDeliveryIssue(state, node2) {
125379
+ return state.allIssues().some((issue2) => {
125380
+ if (issue2.resolvedAt !== null || issue2.intent === "advisory" || !concernsCurrentDelivery(state, node2, issue2)) return false;
125381
+ if (issue2.kind === "gap") return issue2.raisedByNodeId === node2.id;
125382
+ if (issue2.kind === "escalation") return issue2.aboutNodeId === node2.id;
125383
+ if (issue2.kind === "change_request") return issue2.aboutNodeId === node2.id || issue2.raisedByNodeId === node2.id;
125384
+ return issue2.kind === "comment" && issue2.raisedByNodeId !== null && (issue2.aboutNodeId === node2.id || issue2.raisedByNodeId === node2.id);
125385
+ });
125386
+ }
125387
+ function unsettledResolvedGap(state, nodeId) {
125388
+ return findUndeliveredResolvedGap(nodeId, state.mainWorksOf(nodeId).filter((work) => workState(work, hasOutput(work, state.artifactsOf(work.id).length)) === "success" && (!!work.acceptedAt || !hasOutput(work, state.artifactsOf(work.id).length))), state.allIssues());
125389
+ }
125390
+ function hasOrdinaryNotificationBatch(state, nodeId, issueIds) {
125391
+ return issueIds.some((id) => state.issue(id)?.kind === "comment") && issueIds.every((id) => {
125392
+ const issue2 = state.issue(id);
125393
+ return issue2?.kind === "comment" && issue2.aboutNodeId === nodeId && issue2.raisedByNodeId === null || issue2?.kind === "gap" && issue2.resolvedAt !== null && issue2.raisedByNodeId === nodeId;
125394
+ });
125395
+ }
125396
+ function isNotificationDispatch(state, node2, issueIds) {
125397
+ const accepted = node2.latestAcceptId ? state.work(node2.latestAcceptId) : void 0;
125398
+ return !!accepted && accepted.nodeVersion === node2.version && inputsMatch(state, node2, accepted) && (node2.latestProposedWorkId === accepted.id || node2.latestWorkId === accepted.id) && !unsettledResolvedGap(state, node2.id) && hasOrdinaryNotificationBatch(state, node2.id, issueIds);
125399
+ }
125400
+ function isNotificationWork(state, node2, work) {
125401
+ return isMainWork(work) && work.id !== node2.latestAcceptId && !hasOutput(work, state.artifactsOf(work.id).length) && work.nodeVersion === node2.version && inputsMatch(state, node2, work) && isNotificationDispatch(state, node2, work.frozenIssueIds ?? []);
125402
+ }
125403
+ function findUnstableRequiredUpstream(state, nodeId, pendingEvents = []) {
125404
+ const memo = /* @__PURE__ */ new Map();
125405
+ const visiting = /* @__PURE__ */ new Set();
125406
+ const mayStillChange2 = (id) => {
125407
+ const cached3 = memo.get(id);
125408
+ if (cached3 !== void 0) return cached3;
125409
+ if (visiting.has(id)) return true;
125410
+ visiting.add(id);
125411
+ const node2 = state.node(id);
125412
+ let unstable = true;
125413
+ if (node2?.cancelledAt && node2.latestAcceptId) {
125414
+ unstable = false;
125415
+ } else if (node2?.latestAcceptId) {
125416
+ const accepted = state.work(node2.latestAcceptId);
125417
+ const latest = node2.latestWorkId ? state.work(node2.latestWorkId) : void 0;
125418
+ const acceptedIsCurrent = node2.latestAcceptId === node2.latestWorkId || node2.latestAcceptId === node2.latestProposedWorkId;
125419
+ const latestHasOutput = latest ? hasOutput(latest, state.artifactsOf(latest.id).length) : false;
125420
+ const latestState = latest ? workState(latest, latestHasOutput) : null;
125421
+ const producing = latest && latest.id !== accepted?.id && isMainWork(latest) && !isNotificationWork(state, node2, latest) && (latestState === "running" || latestState === "retry" || latestHasOutput && latestState !== "dead");
125422
+ const queued = [...state.emitted, ...pendingEvents].some((event) => event.kind === "work.create" && event.nodeId === id && !isNotificationDispatch(state, node2, event.reasons.filter((r) => r.kind === "issue").map((r) => r.issueId)));
125423
+ unstable = !accepted || accepted.nodeVersion !== node2.version || !inputsMatch(state, node2, accepted) || !acceptedIsCurrent || !!producing || hasDeliveryIssue(state, node2) || !!unsettledResolvedGap(state, id) || queued;
125424
+ if (!unstable) unstable = state.edgesInto(id).some((edge) => edge.kind === "data" && edge.required && !isEdgeInputUnobtainable(state, edge) && mayStillChange2(edge.fromNodeId));
125425
+ }
125426
+ visiting.delete(id);
125427
+ memo.set(id, unstable);
125428
+ return unstable;
125429
+ };
125430
+ for (const edge of state.edgesInto(nodeId)) {
125431
+ if (edge.kind === "data" && edge.required && !isEdgeInputUnobtainable(state, edge) && mayStillChange2(edge.fromNodeId)) return edge.fromNodeId;
125432
+ }
125433
+ return null;
125434
+ }
125435
+ var init_upstream_stability = __esm({
125436
+ "../engine/src/upstream-stability.ts"() {
125437
+ "use strict";
125438
+ init_model();
125439
+ init_views2();
125440
+ }
125441
+ });
125442
+
125221
125443
  // ../engine/src/letters.ts
125222
125444
  function repliesSorted(state, issueId) {
125223
125445
  return [...state.repliesOf(issueId)].sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
@@ -125592,8 +125814,17 @@ function scan(state, ctx) {
125592
125814
  continue;
125593
125815
  }
125594
125816
  }
125595
- const terminated = events.length === 0 && checkTerminated(state);
125596
- return { events, terminated };
125817
+ const admitted = events.filter((event) => {
125818
+ if (event.kind !== "work.create") return true;
125819
+ const node2 = state.node(event.nodeId);
125820
+ return !!node2 && !dispatchBlock(state, node2, events, isNotificationDispatch(
125821
+ state,
125822
+ node2,
125823
+ event.reasons.filter((r) => r.kind === "issue").map((r) => r.issueId)
125824
+ ));
125825
+ });
125826
+ const terminated = admitted.length === 0 && checkTerminated(state);
125827
+ return { events: admitted, terminated };
125597
125828
  }
125598
125829
  function checkRunningWorkTimeout(state, node2, work, ctx, events) {
125599
125830
  const start = work.lastActivityAt ?? work.startedAt ?? work.createdAt;
@@ -125684,6 +125915,18 @@ function scanBlockersForNode(state, node2, latest) {
125684
125915
  }
125685
125916
  return blockers;
125686
125917
  }
125918
+ function dispatchBlock(state, node2, pendingEvents = [], notification = false) {
125919
+ if (!node2.assigneeActorId) return "no_assignee";
125920
+ const latest = node2.latestWorkId ? state.work(node2.latestWorkId) ?? null : null;
125921
+ if (unexemptedVetoBlockers(scanBlockersForNode(state, node2, latest), "dispatch").length > 0) return "issue";
125922
+ if (notification) return null;
125923
+ for (const edge of state.edgesInto(node2.id)) {
125924
+ if (edge.kind !== "data" || !edge.required || isEdgeInputUnobtainable(state, edge)) continue;
125925
+ const upstream = state.node(edge.fromNodeId);
125926
+ if (!upstream?.latestAcceptId || isBusinessDependencyEdge(state, edge) && !businessHandoffBarrierSatisfied(state, upstream.id, upstream.latestAcceptId)) return "upstream_not_ready";
125927
+ }
125928
+ return findUnstableRequiredUpstream(state, node2.id, pendingEvents) ? "upstream_unstable" : null;
125929
+ }
125687
125930
  function tryToRun(state, nodeId, ctx, events) {
125688
125931
  const node2 = state.node(nodeId);
125689
125932
  if (!node2) return;
@@ -125709,15 +125952,6 @@ function tryToRun(state, nodeId, ctx, events) {
125709
125952
  }
125710
125953
  return;
125711
125954
  }
125712
- const inbound = state.edgesInto(nodeId);
125713
- for (const e of inbound) {
125714
- if (e.kind !== "data" || !e.required) continue;
125715
- if (isEdgeInputUnobtainable(state, e)) continue;
125716
- const upstream = state.node(e.fromNodeId);
125717
- if (!upstream?.latestAcceptId) return;
125718
- if (isBusinessDependencyEdge(state, e) && !businessHandoffBarrierSatisfied(state, upstream.id, upstream.latestAcceptId)) return;
125719
- }
125720
- if (findUnstableRequiredUpstream(state, nodeId, events)) return;
125721
125955
  if (state.hasQueuedWorkFor(nodeId)) return;
125722
125956
  const workId = deriveWorkId(nodeId, state.allWorksIncludingReplies(nodeId).length + 1);
125723
125957
  const letters = pendingLetters(state, node2);
@@ -125733,6 +125967,8 @@ function tryToRun(state, nodeId, ctx, events) {
125733
125967
  if (deliveredNow.has(g2.id)) continue;
125734
125968
  reasons.push({ kind: "issue", issueId: g2.id, issueKind: g2.kind });
125735
125969
  }
125970
+ const notification = isNotificationDispatch(state, node2, reasons.filter((r) => r.kind === "issue").map((r) => r.issueId));
125971
+ if (dispatchBlock(state, node2, events, notification)) return;
125736
125972
  events.push({
125737
125973
  kind: "work.create",
125738
125974
  workorderId: state.workorder.id,
@@ -125882,59 +126118,6 @@ function collectStallTrips(state, node2, ctx) {
125882
126118
  }
125883
126119
  return out;
125884
126120
  }
125885
- function inputsMatch(state, node2, latestWork) {
125886
- const inbound = state.edgesInto(node2.id);
125887
- const currInputs = new Set(
125888
- inbound.filter((e) => e.kind === "data" && e.pinnedWorkId).map((e) => e.pinnedWorkId)
125889
- );
125890
- const latestInputs = new Set(state.workInputsOf(latestWork.id).map((i) => i.upstreamWorkId));
125891
- return currInputs.size === latestInputs.size && [...currInputs].every((x2) => latestInputs.has(x2));
125892
- }
125893
- function findUnstableRequiredUpstream(state, nodeId, pendingEvents = []) {
125894
- const memo = /* @__PURE__ */ new Map();
125895
- const visiting = /* @__PURE__ */ new Set();
125896
- const mayStillChange2 = (candidateId) => {
125897
- const cached3 = memo.get(candidateId);
125898
- if (cached3 !== void 0) return cached3;
125899
- if (visiting.has(candidateId)) return true;
125900
- visiting.add(candidateId);
125901
- const candidate = state.node(candidateId);
125902
- let unstable = false;
125903
- if (!candidate) {
125904
- unstable = true;
125905
- } else if (candidate.cancelledAt && candidate.latestAcceptId) {
125906
- unstable = false;
125907
- } else if (!candidate.latestAcceptId) {
125908
- unstable = true;
125909
- } else {
125910
- const accepted = state.work(candidate.latestAcceptId);
125911
- if (!accepted || accepted.nodeVersion !== candidate.version || !inputsMatch(state, candidate, accepted)) {
125912
- unstable = true;
125913
- } else {
125914
- const acceptedIsCurrent = candidate.latestAcceptId === candidate.latestWorkId || candidate.latestAcceptId === candidate.latestProposedWorkId;
125915
- const queuedThisScan = pendingEvents.some((event) => event.kind === "work.create" && event.nodeId === candidateId);
125916
- unstable = !acceptedIsCurrent || state.hasQueuedWorkFor(candidateId) || queuedThisScan;
125917
- }
125918
- if (!unstable) {
125919
- for (const edge of state.edgesInto(candidateId)) {
125920
- if (edge.kind !== "data" || !edge.required || isEdgeInputUnobtainable(state, edge)) continue;
125921
- if (mayStillChange2(edge.fromNodeId)) {
125922
- unstable = true;
125923
- break;
125924
- }
125925
- }
125926
- }
125927
- }
125928
- visiting.delete(candidateId);
125929
- memo.set(candidateId, unstable);
125930
- return unstable;
125931
- };
125932
- for (const edge of state.edgesInto(nodeId)) {
125933
- if (edge.kind !== "data" || !edge.required || isEdgeInputUnobtainable(state, edge)) continue;
125934
- if (mayStillChange2(edge.fromNodeId)) return edge.fromNodeId;
125935
- }
125936
- return null;
125937
- }
125938
126121
  function reviewMaxRetriesOf(policy) {
125939
126122
  const act = policy.resolve({ executor: "agent", scenario: "review" }).timeoutAction;
125940
126123
  return act.kind === "kill-rebuild" ? act.maxRetries : AGENT_REVIEW_MAX_RETRIES;
@@ -126096,10 +126279,6 @@ function mergedClosureRequirements(state, nodeId, closureActors) {
126096
126279
  const kept = state.requirementsOf(nodeId).filter((r) => r.source !== "closure").map((r) => ({ reviewerActorId: r.reviewerActorId, reviewGroup: r.reviewGroup, source: r.source }));
126097
126280
  return [...kept, ...closureReviewerSpecs(wantedClosureActors(state, nodeId, closureActors))];
126098
126281
  }
126099
- function isEdgeInputUnobtainable(state, edge) {
126100
- const upstream = state.node(edge.fromNodeId);
126101
- return !!upstream?.cancelledAt && !upstream.latestAcceptId;
126102
- }
126103
126282
  function isBusinessDependencyEdge(state, edge) {
126104
126283
  const downstream = state.node(edge.toNodeId);
126105
126284
  return edge.kind === "data" && edge.required && downstream?.fields?.[BUSINESS_HANDOFF_POLICY_FIELD] !== BUSINESS_HANDOFF_POLICY_EXEMPT;
@@ -126206,6 +126385,8 @@ function assessConvergence(state, ctx) {
126206
126385
  var init_activate = __esm({
126207
126386
  "../engine/src/activate.ts"() {
126208
126387
  "use strict";
126388
+ init_upstream_stability();
126389
+ init_upstream_stability();
126209
126390
  init_continuation();
126210
126391
  init_src4();
126211
126392
  init_src5();
@@ -130097,7 +130278,7 @@ var require_split2 = __commonJS({
130097
130278
  var { StringDecoder } = require("string_decoder");
130098
130279
  var kLast = Symbol("last");
130099
130280
  var kDecoder = Symbol("decoder");
130100
- function transform2(chunk, enc4, cb) {
130281
+ function transform2(chunk, enc3, cb) {
130101
130282
  let list2;
130102
130283
  if (this.overflow) {
130103
130284
  const buf = this[kDecoder].write(chunk);
@@ -132713,7 +132894,9 @@ var init_store_postgres = __esm({
132713
132894
  WHERE (
132714
132895
  (e.status = 'pending' AND (e.available_at IS NULL OR e.available_at <= $1))
132715
132896
  -- \u5D29\u6E83\u81EA\u6108\uFF1A\u5361\u5728 applied \u592A\u4E45\u7684\uFF08\u8FDB\u7A0B\u6B7B\u5728\u300C\u843D\u72B6\u6001\u300D\u4E0E\u300C\u6536\u5C3E\u300D\u4E4B\u95F4\uFF09\u4E5F\u56DE\u6536\u91CD\u8DD1
132716
- OR ($2::timestamptz IS NOT NULL AND e.status = 'applied' AND e.applied_at < $2::timestamptz)
132897
+ OR (e.status = 'applied' AND (
132898
+ (e.available_at IS NOT NULL AND e.available_at <= $1)
132899
+ OR (e.available_at IS NULL AND $2::timestamptz IS NOT NULL AND e.applied_at < $2::timestamptz)))
132717
132900
  )
132718
132901
  AND NOT EXISTS (
132719
132902
  SELECT 1 FROM ${this.s}.events p
@@ -132730,13 +132913,23 @@ var init_store_postgres = __esm({
132730
132913
  async markEvent(seq, status, failedReason, at) {
132731
132914
  await this.c.query(
132732
132915
  `UPDATE ${this.s}.events
132733
- SET status = $2, failed_reason = $3,
132916
+ SET status = $2, failed_reason = $3, available_at = NULL,
132734
132917
  applied_at = CASE WHEN $2 = 'applied' THEN $4::timestamptz ELSE applied_at END,
132735
132918
  done_at = CASE WHEN $2 = 'done' THEN $4::timestamptz ELSE done_at END
132736
132919
  WHERE seq = $1`,
132737
132920
  [seq, status, failedReason, at]
132738
132921
  );
132739
132922
  }
132923
+ async getEvent(seq) {
132924
+ const res = await this.c.query(`SELECT * FROM ${this.s}.events WHERE seq = $1`, [seq]);
132925
+ return res.rows[0] ? toRecord(res.rows[0]) : null;
132926
+ }
132927
+ async deferEvent(seq, status, reason, availableAt) {
132928
+ await this.c.query(
132929
+ `UPDATE ${this.s}.events SET status = $2, failed_reason = $3, available_at = $4::timestamptz WHERE seq = $1`,
132930
+ [seq, status, reason, availableAt]
132931
+ );
132932
+ }
132740
132933
  async isHandlerDone(seq, handlerName) {
132741
132934
  const res = await this.c.query(
132742
132935
  `SELECT 1 FROM ${this.s}.event_handler_log WHERE event_seq = $1 AND handler_name = $2`,
@@ -132770,7 +132963,9 @@ var init_store_postgres = __esm({
132770
132963
  WHERE (
132771
132964
  (e.status = 'pending' AND (e.available_at IS NULL OR e.available_at <= $1))
132772
132965
  -- \u5D29\u6E83\u81EA\u6108\uFF1A\u89C1 claimNextEvent \u7684\u6CE8\u91CA
132773
- OR ($2::timestamptz IS NOT NULL AND e.status = 'applied' AND e.applied_at < $2::timestamptz)
132966
+ OR (e.status = 'applied' AND (
132967
+ (e.available_at IS NOT NULL AND e.available_at <= $1)
132968
+ OR (e.available_at IS NULL AND $2::timestamptz IS NOT NULL AND e.applied_at < $2::timestamptz)))
132774
132969
  )
132775
132970
  AND NOT EXISTS (SELECT 1 FROM ${this.s}.events p WHERE p.workorder_id = e.workorder_id
132776
132971
  AND p.status IN ('pending','applied') AND p.seq < e.seq)
@@ -133153,12 +133348,29 @@ var init_plan = __esm({
133153
133348
  });
133154
133349
 
133155
133350
  // ../engine/src/handlers/workorder.ts
133156
- var workorderCreate, workorderMeta, workorderPause, workorderResume, workorderPing;
133351
+ var workorderInitialize, workorderCreate, workorderMeta, workorderPause, workorderResume, workorderPing;
133157
133352
  var init_workorder = __esm({
133158
133353
  "../engine/src/handlers/workorder.ts"() {
133159
133354
  "use strict";
133160
133355
  init_kill();
133161
133356
  init_plan();
133357
+ workorderInitialize = {
133358
+ name: "workorder/initialize",
133359
+ kind: "workorder.initialized",
133360
+ retryFailedEffect: true,
133361
+ apply(e, state, ctx) {
133362
+ state.updateWorkorder({ lifecycle: "active", updatedAt: ctx.at });
133363
+ if (!e.dispatch && !state.workorder.dispatchPaused) workorderPause.apply({
133364
+ kind: "workorder.paused",
133365
+ workorderId: e.workorderId,
133366
+ by: ctx.actorId,
133367
+ reason: e.reason ?? ""
133368
+ }, state, ctx);
133369
+ },
133370
+ async effect(e, ctx) {
133371
+ if (!e.dispatch) await ctx.cancelKilledSessions(e.workorderId);
133372
+ }
133373
+ };
133162
133374
  workorderCreate = {
133163
133375
  name: "workorder/create",
133164
133376
  kind: "workorder.created",
@@ -133195,6 +133407,7 @@ var init_workorder = __esm({
133195
133407
  workorderPause = {
133196
133408
  name: "workorder/pause",
133197
133409
  kind: "workorder.paused",
133410
+ retryFailedEffect: true,
133198
133411
  apply(e, state, ctx) {
133199
133412
  state.updateWorkorder({ dispatchPaused: true, pausedBy: e.by, pausedReason: e.reason, updatedAt: ctx.at });
133200
133413
  killAllWorks(state, ctx.at);
@@ -133240,6 +133453,7 @@ var workCreate, workRedispatch, workSnapshotRecorded, workKill, workResponse, wo
133240
133453
  var init_work = __esm({
133241
133454
  "../engine/src/handlers/work.ts"() {
133242
133455
  "use strict";
133456
+ init_upstream_stability();
133243
133457
  init_continuation();
133244
133458
  init_views2();
133245
133459
  init_kill();
@@ -133552,6 +133766,10 @@ var init_work = __esm({
133552
133766
  apply(e, state, ctx) {
133553
133767
  const w2 = state.work(e.workId);
133554
133768
  if (!w2 || w2.deadAt) return;
133769
+ const node2 = state.node(w2.nodeId);
133770
+ if (e.upstreamStabilityVersion === 1 && node2 && isMainWork(w2) && w2.id !== node2.latestAcceptId && !hasOutput(w2, state.artifactsOf(w2.id).length) && (e.artifacts.length > 0 || e.conclusion != null) && hasOrdinaryNotificationBatch(state, node2.id, w2.frozenIssueIds ?? []) && findUnstableRequiredUpstream(state, node2.id)) {
133771
+ throw new Error("\u4E0A\u6E38\u4EA4\u4ED8\u5C1A\u672A\u7A33\u5B9A\uFF1A\u672C\u8F6E\u53EF\u7EE7\u7EED\u7B54\u590D\u901A\u77E5\uFF0C\u6682\u4E0D\u80FD\u63D0\u4EA4\u65B0\u4EA4\u4ED8\uFF1B\u8BF7\u5728\u4E0A\u6E38\u7A33\u5B9A\u540E\u91CD\u8BD5");
133772
+ }
133555
133773
  if (e.artifacts.length === 0 && w2.acceptanceState === "accepted" && state.artifactsOf(e.workId).length > 0) {
133556
133774
  return;
133557
133775
  }
@@ -134164,8 +134382,9 @@ var init_handlers = __esm({
134164
134382
  init_continuation2();
134165
134383
  init_types();
134166
134384
  HANDLERS = [
134167
- /* ── 工作区级(5)── */
134385
+ /* ── 工作区级(6)── */
134168
134386
  erase(workorderCreate),
134387
+ erase(workorderInitialize),
134169
134388
  erase(workorderMeta),
134170
134389
  erase(workorderPause),
134171
134390
  erase(workorderResume),
@@ -134218,6 +134437,7 @@ var init_handlers = __esm({
134218
134437
 
134219
134438
  // ../engine/src/bus.ts
134220
134439
  function versionCommandEvent(event) {
134440
+ if (event.kind === "work.submit_output") return { ...event, upstreamStabilityVersion: 1 };
134221
134441
  if (event.kind === "work.create" || event.kind === "review.create") {
134222
134442
  return { ...event, pauseGateVersion: 1, ...event.kind === "work.create" ? { continuationGateVersion: 1 } : {} };
134223
134443
  }
@@ -134242,6 +134462,9 @@ function versionCommandEvent(event) {
134242
134462
  return event;
134243
134463
  }
134244
134464
  }
134465
+ function errorMessage(err) {
134466
+ return err instanceof Error ? err.message : String(err);
134467
+ }
134245
134468
  function emptySnapshotPlaceholder(record8) {
134246
134469
  return {
134247
134470
  workorder: {
@@ -134272,7 +134495,7 @@ function emptySnapshotPlaceholder(record8) {
134272
134495
  reviews: []
134273
134496
  };
134274
134497
  }
134275
- var SYSTEM_ACTOR, NOOP_IO, SYSTEM_CLOCK, HandlerRegistry, DEFAULT_STUCK_APPLIED_RECOVERY_MS, SETTLE_FOREIGN_PROGRESS_FALLBACK_MS, EngineBus, ApplyFailure, sleep3;
134498
+ var SYSTEM_ACTOR, NOOP_IO, SYSTEM_CLOCK, HandlerRegistry, DEFAULT_STUCK_APPLIED_RECOVERY_MS, SETTLE_FOREIGN_PROGRESS_FALLBACK_MS, EVENT_RETRY_DELAY_MS, EngineBus, ApplyFailure, sleep3;
134276
134499
  var init_bus = __esm({
134277
134500
  "../engine/src/bus.ts"() {
134278
134501
  "use strict";
@@ -134282,6 +134505,7 @@ var init_bus = __esm({
134282
134505
  init_model();
134283
134506
  init_handlers();
134284
134507
  init_state();
134508
+ init_upstream_stability();
134285
134509
  init_continuation();
134286
134510
  SYSTEM_ACTOR = "actor:system:engine";
134287
134511
  NOOP_IO = {
@@ -134313,6 +134537,7 @@ var init_bus = __esm({
134313
134537
  };
134314
134538
  DEFAULT_STUCK_APPLIED_RECOVERY_MS = 5 * 6e4;
134315
134539
  SETTLE_FOREIGN_PROGRESS_FALLBACK_MS = 200;
134540
+ EVENT_RETRY_DELAY_MS = 1e3;
134316
134541
  EngineBus = class {
134317
134542
  store;
134318
134543
  io;
@@ -134472,6 +134697,7 @@ var init_bus = __esm({
134472
134697
  if (!await this.drainOne()) break;
134473
134698
  }
134474
134699
  }
134700
+ await this.assertProcessed([seq]);
134475
134701
  }
134476
134702
  /** 同一命令的一组最终修改一起入队。scan-on-empty 在末项之前始终看到 pending,不能派发中间状态。 */
134477
134703
  async submitBatchAndProcess(inputs) {
@@ -134498,6 +134724,17 @@ var init_bus = __esm({
134498
134724
  } else {
134499
134725
  for (let i = 0; i < 2e3; i++) if (!await this.drainOne()) break;
134500
134726
  }
134727
+ await this.assertProcessed(seqs);
134728
+ }
134729
+ async assertProcessed(seqs) {
134730
+ await this.store.transaction(async (tx) => {
134731
+ for (const seq of seqs) {
134732
+ const record8 = await tx.getEvent(seq);
134733
+ if (record8?.status !== "done" || record8.failedReason) {
134734
+ throw new Error(`engine event ${seq} ${record8?.status ?? "missing"}: ${record8?.failedReason ?? "processing has not completed; retry later"}`);
134735
+ }
134736
+ }
134737
+ });
134501
134738
  }
134502
134739
  /* ═══════════════════════ 调度器主循环 ═══════════════════════ */
134503
134740
  /**
@@ -134547,8 +134784,7 @@ var init_bus = __esm({
134547
134784
  }
134548
134785
  touched.add(record8.workorderId);
134549
134786
  try {
134550
- await this.applyRecord(record8);
134551
- await this.effectRecord(record8);
134787
+ if (await this.applyRecord(record8)) await this.effectRecord(record8);
134552
134788
  } catch (err) {
134553
134789
  console.error(`[bus] event ${record8.seq} ${record8.event.kind}:`, err);
134554
134790
  }
@@ -134679,9 +134915,10 @@ var init_bus = __esm({
134679
134915
  /* ═══════════════════════ 旧 drain(测试兼容) ═══════════════════════ */
134680
134916
  /** 取一条事件 apply+effect,然后 scan 其工单。仅测试用——生产代码走 run() 循环。 */
134681
134917
  async drainOne() {
134682
- const record8 = await this.applyPhase();
134683
- if (!record8) return false;
134684
- await this.effectPhase(record8);
134918
+ const phase = await this.applyPhase();
134919
+ if (!phase) return false;
134920
+ const { record: record8, applied } = phase;
134921
+ if (applied) await this.effectPhase(record8);
134685
134922
  this.emitEvents([record8]);
134686
134923
  try {
134687
134924
  const result = await this.scan(record8.workorderId);
@@ -134698,10 +134935,11 @@ var init_bus = __esm({
134698
134935
  async drain(limit = 1e3) {
134699
134936
  let processed = 0, failed = 0;
134700
134937
  for (let i = 0; i < limit; i++) {
134701
- const record8 = await this.applyPhase();
134702
- if (!record8) break;
134938
+ const phase = await this.applyPhase();
134939
+ if (!phase) break;
134940
+ const { record: record8, applied } = phase;
134703
134941
  processed++;
134704
- const ok3 = await this.effectPhase(record8);
134942
+ const ok3 = applied && await this.effectPhase(record8);
134705
134943
  if (!ok3) failed++;
134706
134944
  this.emitEvents([record8]);
134707
134945
  }
@@ -134713,7 +134951,7 @@ var init_bus = __esm({
134713
134951
  * 公开方法,供 kernel-bridge 和 run() 共用。
134714
134952
  */
134715
134953
  async applyRecord(record8) {
134716
- await this.store.transaction(async (tx) => {
134954
+ const applied = await this.store.transaction(async (tx) => {
134717
134955
  const snapshot = await tx.loadWorkorder(record8.workorderId);
134718
134956
  const state = new WorkorderState(snapshot ?? emptySnapshotPlaceholder(record8));
134719
134957
  const ctx = {
@@ -134722,10 +134960,15 @@ var init_bus = __esm({
134722
134960
  actorId: record8.actorId,
134723
134961
  config: this.config
134724
134962
  };
134963
+ const completedHandlers = [];
134725
134964
  for (const h of this.registry.for(record8.event.kind)) {
134726
134965
  if (await tx.isHandlerDone(record8.seq, h.name)) continue;
134727
- h.apply(record8.event, state, ctx);
134728
- await tx.markHandlerDone(record8.seq, h.name);
134966
+ try {
134967
+ h.apply(record8.event, state, ctx);
134968
+ } catch (err) {
134969
+ throw new ApplyFailure(record8, errorMessage(err));
134970
+ }
134971
+ completedHandlers.push(h.name);
134729
134972
  }
134730
134973
  await tx.applyMutations(record8.workorderId, state.mutations);
134731
134974
  if (state.emitted.length) {
@@ -134742,20 +134985,28 @@ var init_bus = __esm({
134742
134985
  ...eventAnchors(event)
134743
134986
  })));
134744
134987
  }
134988
+ for (const name of completedHandlers) await tx.markHandlerDone(record8.seq, name);
134745
134989
  await tx.markEvent(record8.seq, "applied", null, this.clock.now());
134990
+ return true;
134746
134991
  }).catch(async (err) => {
134747
- if (err instanceof ApplyFailure) {
134748
- await this.store.transaction(
134749
- (tx) => tx.markEvent(err.record.seq, "failed", err.reason, this.clock.now())
134750
- );
134751
- return;
134752
- }
134753
- throw err;
134992
+ const permanent = !record8.appliedAt && (err instanceof ApplyFailure || /^(22|23)/.test(String(err?.code ?? "")));
134993
+ const reason = errorMessage(err);
134994
+ const retryStatus = record8.appliedAt ? "applied" : "pending";
134995
+ await this.store.transaction((tx) => permanent ? tx.markEvent(record8.seq, "failed", reason, this.clock.now()) : tx.deferEvent(record8.seq, retryStatus, reason, this.retryAt()));
134996
+ record8.status = permanent ? "failed" : retryStatus;
134997
+ console.error(`[bus] apply ${record8.seq} ${record8.status}: ${reason}`);
134998
+ return false;
134754
134999
  });
134755
135000
  this.emitApplied(record8.workorderId);
135001
+ if (applied) record8.status = "applied";
135002
+ return applied;
135003
+ }
135004
+ retryAt() {
135005
+ return new Date(Date.parse(this.clock.now()) + EVENT_RETRY_DELAY_MS).toISOString();
134756
135006
  }
134757
135007
  /** 事务二:跑 IO + 收尾。 */
134758
135008
  async effectRecord(record8) {
135009
+ if (record8.status !== "applied") return false;
134759
135010
  const handlers = this.registry.for(record8.event.kind).filter((h) => h.effect);
134760
135011
  const buffered = [];
134761
135012
  const ctx = {
@@ -134767,32 +135018,53 @@ var init_bus = __esm({
134767
135018
  dispatchReview: (id, nodeId, reviewerActorId) => this.io.dispatchReview(id, nodeId, reviewerActorId),
134768
135019
  canDispatchWork: async (workorderId, workId, opts) => {
134769
135020
  const snap = await this.store.transaction((tx) => tx.loadWorkorder(workorderId));
134770
- if (!snap || snap.workorder.dispatchPaused) return false;
135021
+ if (!snap || snap.workorder.lifecycle !== "active" || snap.workorder.dispatchPaused) return false;
134771
135022
  const work = snap.works.find((row) => row.id === workId);
134772
135023
  if (work && work.lane === "main" && !work.startedAt && !work.sessionRef && !work.lastActivityAt) {
134773
- const node2 = snap.nodes.find((row) => row.id === work.nodeId);
134774
- if (node2 && !node2.continuationBudget && this.io.continuationPolicy) {
135024
+ const node3 = snap.nodes.find((row) => row.id === work.nodeId);
135025
+ if (node3 && !node3.continuationBudget && this.io.continuationPolicy) {
134775
135026
  const policy = await this.io.continuationPolicy(work.assigneeActorId);
134776
135027
  if (policy) {
134777
135028
  if (!Number.isSafeInteger(policy.maxRounds) || policy.maxRounds < 1 || policy.maxRounds > 100) {
134778
135029
  throw new Error(`invalid continuation round limit for ${work.assigneeActorId}`);
134779
135030
  }
134780
- buffered.push({ event: { kind: "continuation.initialized", workorderId, nodeId: node2.id, rounds: policy.maxRounds } });
135031
+ buffered.push({ event: { kind: "continuation.initialized", workorderId, nodeId: node3.id, rounds: policy.maxRounds } });
134781
135032
  buffered.push({ event: { kind: "work.redispatch", workorderId, workId } });
134782
135033
  return false;
134783
135034
  }
134784
135035
  }
134785
- if (node2 && continuationExhausted(new WorkorderState(snap), node2)) return false;
135036
+ if (node3 && continuationExhausted(new WorkorderState(snap), node3)) return false;
134786
135037
  }
134787
135038
  if (opts?.neverStarted && work) {
134788
- const node2 = snap.nodes.find((row) => row.id === work.nodeId);
134789
- if (work.startedAt || work.sessionRef || !node2 || node2.cancelledAt || node2.latestWorkId !== workId) return false;
135039
+ const node3 = snap.nodes.find((row) => row.id === work.nodeId);
135040
+ if (work.startedAt || work.sessionRef || !node3 || node3.cancelledAt || node3.latestWorkId !== workId) return false;
135041
+ }
135042
+ if (!work || work.status !== "running" || work.deadAt || work.cancelledAt || work.endedAt) return false;
135043
+ const node2 = snap.nodes.find((row) => row.id === work.nodeId);
135044
+ if (!node2 || node2.cancelledAt) return false;
135045
+ const forced = record8.event.kind === "work.create" && record8.event.reasons.some((r) => r.kind === "forced");
135046
+ if (work.lane === "main" && !forced && !work.startedAt && !work.sessionRef) {
135047
+ if (node2.latestWorkId !== work.id) return false;
135048
+ const state = new WorkorderState(snap);
135049
+ const blocked = work.nodeVersion !== node2.version || !inputsMatch(state, node2, work) || dispatchBlock(state, node2, [], isNotificationWork(state, node2, work));
135050
+ if (blocked) {
135051
+ if (record8.event.kind === "work.create") await ctx.cancelPreviousWork(node2.id, work.id);
135052
+ buffered.push({ event: {
135053
+ kind: "work.kill",
135054
+ workorderId,
135055
+ nodeId: node2.id,
135056
+ workId: work.id,
135057
+ reason: "\u6D3E\u53D1\u524D\u6761\u4EF6\u5DF2\u53D8\u5316\uFF0C\u7B49\u5F85\u4F9D\u8D56\u6216\u4E8B\u9879\u89E3\u9664\u540E\u91CD\u8BD5"
135058
+ } });
135059
+ buffered.push({ event: { kind: "plan.node_retry", workorderId, nodeId: node2.id, by: SYSTEM_ACTOR } });
135060
+ return false;
135061
+ }
134790
135062
  }
134791
- return work?.status === "running" && !work.deadAt && !work.cancelledAt && !work.endedAt;
135063
+ return true;
134792
135064
  },
134793
135065
  canDispatchReview: async (workorderId, reviewId) => {
134794
135066
  const snap = await this.store.transaction((tx) => tx.loadWorkorder(workorderId));
134795
- if (!snap || snap.workorder.dispatchPaused) return false;
135067
+ if (!snap || snap.workorder.lifecycle !== "active" || snap.workorder.dispatchPaused) return false;
134796
135068
  const review = snap.reviews.find((row) => row.id === reviewId);
134797
135069
  return review?.status === "running" && !review.cancelledAt && !review.endedAt;
134798
135070
  },
@@ -134809,19 +135081,14 @@ var init_bus = __esm({
134809
135081
  // apply 已把被杀 work/review 置 deadAt/cancelledAt,这里按 store 找它们仍挂着的会话 cancel
134810
135082
  //(deadAt 且 endedAt 未置 = 刚被杀未退出的在途会话)。没会话 / 已退 → no-op。
134811
135083
  cancelKilledSessions: async (_workorderId, reviewScope) => {
134812
- let snap;
134813
- try {
134814
- snap = await this.store.transaction((tx) => tx.loadWorkorder(record8.workorderId));
134815
- } catch (err) {
134816
- console.warn(`[bus] cancelKilledSessions ${record8.workorderId} \u8BFB\u5FEB\u7167\u5931\u8D25: ${String(err)}`);
134817
- return;
134818
- }
135084
+ const snap = await this.store.transaction((tx) => tx.loadWorkorder(record8.workorderId));
134819
135085
  if (!snap) return;
135086
+ const failures = [];
134820
135087
  const cancel = async (id, what) => {
134821
135088
  try {
134822
135089
  await this.io.cancelSession(id);
134823
135090
  } catch (err) {
134824
- console.warn(`[bus] cancelKilledSessions ${what} ${id}: ${String(err)}`);
135091
+ failures.push(`${what} ${id}: ${errorMessage(err)}`);
134825
135092
  }
134826
135093
  };
134827
135094
  for (const w2 of reviewScope === void 0 ? snap.works : []) {
@@ -134833,10 +135100,12 @@ var init_bus = __esm({
134833
135100
  ))) continue;
134834
135101
  if (r.cancelledAt && r.endedAt === null) await cancel(r.id, "review");
134835
135102
  }
135103
+ if (failures.length) throw new Error(`session cancellation incomplete: ${failures.join("; ")}`);
134836
135104
  },
134837
135105
  notify: (ids2, payload2) => this.io.notify(ids2, payload2)
134838
135106
  };
134839
135107
  let ok3 = true;
135108
+ let failureReason = null;
134840
135109
  for (const h of handlers) {
134841
135110
  const effectKey = `${h.name}#effect`;
134842
135111
  if (record8.appliedAt && await this.store.transaction((tx) => tx.isHandlerDone(record8.seq, effectKey))) {
@@ -134844,6 +135113,7 @@ var init_bus = __esm({
134844
135113
  }
134845
135114
  let lastErr = "";
134846
135115
  for (let attempt = 1; attempt <= this.maxEffectAttempts; attempt++) {
135116
+ buffered.length = 0;
134847
135117
  try {
134848
135118
  await h.effect(record8.event, ctx);
134849
135119
  lastErr = "";
@@ -134854,27 +135124,30 @@ var init_bus = __esm({
134854
135124
  }
134855
135125
  if (lastErr) {
134856
135126
  ok3 = false;
135127
+ if (h.retryFailedEffect) {
135128
+ await this.store.transaction((tx) => tx.deferEvent(record8.seq, "applied", lastErr, this.retryAt()));
135129
+ return false;
135130
+ }
135131
+ failureReason = lastErr;
134857
135132
  } else {
134858
- await this.store.transaction((tx) => tx.markHandlerDone(record8.seq, effectKey));
135133
+ await this.store.transaction(async (tx) => {
135134
+ if (buffered.length) await tx.appendEvents(buffered.map((b2) => ({
135135
+ companyId: record8.companyId,
135136
+ workorderId: record8.workorderId,
135137
+ actorId: b2.actorId ?? SYSTEM_ACTOR,
135138
+ handActorId: null,
135139
+ event: versionCommandEvent(b2.event),
135140
+ causedBySeq: record8.seq,
135141
+ origin: "effect",
135142
+ dedupKey: b2.dedupKey ?? null,
135143
+ createdAt: this.clock.now(),
135144
+ ...eventAnchors(b2.event)
135145
+ })));
135146
+ await tx.markHandlerDone(record8.seq, effectKey);
135147
+ });
134859
135148
  }
134860
135149
  }
134861
- await this.store.transaction(async (tx) => {
134862
- if (buffered.length) {
134863
- await tx.appendEvents(buffered.map((b2) => ({
134864
- companyId: record8.companyId,
134865
- workorderId: record8.workorderId,
134866
- actorId: b2.actorId ?? SYSTEM_ACTOR,
134867
- handActorId: null,
134868
- event: versionCommandEvent(b2.event),
134869
- causedBySeq: record8.seq,
134870
- origin: "effect",
134871
- dedupKey: b2.dedupKey ?? null,
134872
- createdAt: this.clock.now(),
134873
- ...eventAnchors(b2.event)
134874
- })));
134875
- }
134876
- await tx.markEvent(record8.seq, "done", null, this.clock.now());
134877
- });
135150
+ await this.store.transaction((tx) => tx.markEvent(record8.seq, "done", failureReason, this.clock.now()));
134878
135151
  return ok3;
134879
135152
  }
134880
135153
  /* ═══════════════════════ scan:唯一的调度入口 ═══════════════════════ */
@@ -134940,57 +135213,10 @@ var init_bus = __esm({
134940
135213
  }
134941
135214
  /* ═══════════════════════ 内部:旧 applyPhase(claim + apply 一体) ═══════════════════════ */
134942
135215
  async applyPhase() {
134943
- return this.store.transaction(async (tx) => {
134944
- const record8 = await tx.claimNextEvent(this.clock.now(), this.recoverAppliedBefore());
134945
- if (!record8) return null;
134946
- const snapshot = await tx.loadWorkorder(record8.workorderId);
134947
- if (snapshot?.workorder.doneAt && record8.event.kind !== "work.content_edited") {
134948
- await tx.applyMutations(record8.workorderId, [{
134949
- t: "workorder.update",
134950
- patch: { doneAt: null }
134951
- }]);
134952
- }
134953
- const state = new WorkorderState(snapshot ?? emptySnapshotPlaceholder(record8));
134954
- const ctx = {
134955
- seq: record8.seq,
134956
- at: record8.createdAt,
134957
- actorId: record8.actorId,
134958
- config: this.config
134959
- };
134960
- for (const h of this.registry.for(record8.event.kind)) {
134961
- if (await tx.isHandlerDone(record8.seq, h.name)) continue;
134962
- h.apply(record8.event, state, ctx);
134963
- await tx.markHandlerDone(record8.seq, h.name);
134964
- }
134965
- await tx.applyMutations(record8.workorderId, state.mutations);
134966
- if (state.emitted.length) {
134967
- await tx.appendEvents(state.emitted.map((event) => ({
134968
- companyId: record8.companyId,
134969
- workorderId: record8.workorderId,
134970
- actorId: SYSTEM_ACTOR,
134971
- handActorId: null,
134972
- event: versionCommandEvent(event),
134973
- causedBySeq: record8.seq,
134974
- origin: "apply",
134975
- dedupKey: null,
134976
- createdAt: record8.createdAt,
134977
- ...eventAnchors(event)
134978
- })));
134979
- }
134980
- await tx.markEvent(record8.seq, "applied", null, this.clock.now());
134981
- return record8;
134982
- }).catch(async (err) => {
134983
- if (err instanceof ApplyFailure) {
134984
- await this.store.transaction(
134985
- (tx) => tx.markEvent(err.record.seq, "failed", err.reason, this.clock.now())
134986
- );
134987
- return err.record;
134988
- }
134989
- throw err;
134990
- }).then((record8) => {
134991
- if (record8) this.emitApplied(record8.workorderId);
134992
- return record8;
134993
- });
135216
+ const record8 = await this.store.transaction((tx) => tx.claimNextEvent(this.clock.now(), this.recoverAppliedBefore()));
135217
+ if (!record8) return null;
135218
+ if (record8.event.kind !== "work.content_edited") await this.unmarkDone(record8.workorderId);
135219
+ return { record: record8, applied: await this.applyRecord(record8) };
134994
135220
  }
134995
135221
  async effectPhase(record8) {
134996
135222
  return this.effectRecord(record8);
@@ -135012,7 +135238,7 @@ var init_bus = __esm({
135012
135238
  const events = await this.store.transaction((tx) => tx.listEvents(workorderId));
135013
135239
  let state = null;
135014
135240
  for (const record8 of events) {
135015
- if (record8.origin === "effect") continue;
135241
+ if (record8.status === "failed" || record8.status === "pending" || record8.origin === "effect") continue;
135016
135242
  if (!state) state = new WorkorderState(emptySnapshotPlaceholder(record8));
135017
135243
  for (const h of this.registry.for(record8.event.kind)) {
135018
135244
  h.apply(record8.event, state, {
@@ -139006,16 +139232,16 @@ function classifyExitReason(info) {
139006
139232
  if (AUTH_RE.test(msg)) return "auth-error";
139007
139233
  return info.reason;
139008
139234
  }
139009
- function quotaResetHorizon(errorMessage) {
139010
- if (!errorMessage) return "unknown";
139011
- if (/\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+\d{1,2}/i.test(errorMessage)) return "long";
139012
- if (/\d{4}-\d{2}-\d{2}/.test(errorMessage)) return "long";
139013
- if (/\b\d{1,2}(?::\d{2})?\s*(?:am|pm)\b/i.test(errorMessage)) return "short";
139235
+ function quotaResetHorizon(errorMessage2) {
139236
+ if (!errorMessage2) return "unknown";
139237
+ if (/\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+\d{1,2}/i.test(errorMessage2)) return "long";
139238
+ if (/\d{4}-\d{2}-\d{2}/.test(errorMessage2)) return "long";
139239
+ if (/\b\d{1,2}(?::\d{2})?\s*(?:am|pm)\b/i.test(errorMessage2)) return "short";
139014
139240
  return "unknown";
139015
139241
  }
139016
- function extractResetHint(errorMessage) {
139017
- if (!errorMessage) return void 0;
139018
- const m2 = /((?:resets?|try again at)\s+[^·。\n]{1,48})/i.exec(errorMessage);
139242
+ function extractResetHint(errorMessage2) {
139243
+ if (!errorMessage2) return void 0;
139244
+ const m2 = /((?:resets?|try again at)\s+[^·。\n]{1,48})/i.exec(errorMessage2);
139019
139245
  return m2?.[1]?.trim().replace(/[.,;]$/, "");
139020
139246
  }
139021
139247
  var QUOTA_RE2, UNREACHABLE_RE, AUTH_RE;
@@ -140326,7 +140552,7 @@ var init_dispatcher = __esm({
140326
140552
  assertSpawnAttemptCurrent(jobKey, attempt) {
140327
140553
  if (!this.isSpawnAttemptCurrent(jobKey, attempt)) throw new SpawnAttemptStale(jobKey);
140328
140554
  }
140329
- async recordSpawnFailure(jobKey, spec, reason, errorMessage, bypassScheduling = false) {
140555
+ async recordSpawnFailure(jobKey, spec, reason, errorMessage2, bypassScheduling = false) {
140330
140556
  if (bypassScheduling) return;
140331
140557
  if (!this.strikeLedgerLive) return;
140332
140558
  const seqAtVerdict = this.opts.kernel.model.lastSeq.get(spec.artifactId) ?? 0;
@@ -140355,7 +140581,7 @@ var init_dispatcher = __esm({
140355
140581
  firstFailedAt: prev?.firstFailedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
140356
140582
  lastReason: reason,
140357
140583
  totalFailures: (prev?.totalFailures ?? prev?.count ?? 0) + 1,
140358
- ...errorMessage ? { lastErrorMessage: errorMessage.slice(0, 400) } : {},
140584
+ ...errorMessage2 ? { lastErrorMessage: errorMessage2.slice(0, 400) } : {},
140359
140585
  // 半开一旦开启就**粘着**,直到真的成功(自己落了 op)才随整条记录清掉。
140360
140586
  // 不粘的话:试探失败却因自愈类/宽限期没能立刻回熔断时,标记会丢,等于白送回满血 3 次。
140361
140587
  ...prev?.halfOpen ? { halfOpen: true } : {},
@@ -142814,6 +143040,10 @@ var init_kernel_bridge = __esm({
142814
143040
  * 用来兜住"本进程没收到事件"的写入——多实例部署下别的实例改的工单就属于这种。
142815
143041
  */
142816
143042
  async refreshModel(opts) {
143043
+ if (opts?.workorderId) {
143044
+ this.trackedWorkorders.add(opts.workorderId);
143045
+ this.markWorkorderDirty(opts.workorderId);
143046
+ }
142817
143047
  if (opts?.resync) this.resyncRequested = true;
142818
143048
  await this.refreshTracked();
142819
143049
  }
@@ -144303,6 +144533,24 @@ var init_kernel_bridge = __esm({
144303
144533
  }
144304
144534
  });
144305
144535
  }
144536
+ /** Durable creation receipt, separate from later pause/resume and work outcomes. */
144537
+ async isWorkorderCreationComplete(workorderId) {
144538
+ return this.store.transaction((tx) => tx.isEventDone(`workorder-initialized:${workorderId}`));
144539
+ }
144540
+ async completeWorkorderCreation(workorderId, actor, dispatch, reason = null) {
144541
+ if (await this.isWorkorderCreationComplete(workorderId)) return;
144542
+ if (!dispatch) {
144543
+ const snap = await this.store.transaction((tx) => tx.loadWorkorder(workorderId));
144544
+ if (snap && !snap.workorder.dispatchPaused) await this.pauseWorkorder(workorderId, actor, reason ?? "");
144545
+ }
144546
+ await this.commit({
144547
+ companyId: "",
144548
+ workorderId,
144549
+ actorId: actor,
144550
+ dedupKey: `workorder-initialized:${workorderId}`,
144551
+ event: { kind: "workorder.initialized", workorderId, dispatch, reason }
144552
+ });
144553
+ }
144306
144554
  /** 工单级恢复:workorder.resumed handler 解冻全部节点 + scan 重建 work。 */
144307
144555
  async resumeWorkorder(workorderId, actor) {
144308
144556
  this.trackedWorkorders.add(workorderId);
@@ -144600,7 +144848,7 @@ var init_kernel_bridge = __esm({
144600
144848
  const scopedModel = ws ? filterReadModelByWorkspace(this.model, ws) : this.model;
144601
144849
  return previewIntervention(scopedModel, plan, { schema: this.schema, roleIndex: this.roleIndex });
144602
144850
  }
144603
- async applyIntervention(plan, _baseSeqs, _actor) {
144851
+ async applyIntervention(plan, _baseSeqs, _actor, options = {}) {
144604
144852
  const ws = this.effectiveWorkspace(plan);
144605
144853
  const scopedModel = ws ? filterReadModelByWorkspace(this.model, ws) : this.model;
144606
144854
  plan = resolveInterventionRefs(scopedModel, plan, this.schema);
@@ -144849,7 +145097,7 @@ var init_kernel_bridge = __esm({
144849
145097
  break;
144850
145098
  }
144851
145099
  }
144852
- await queue({
145100
+ if (!options.deferActivation) await queue({
144853
145101
  companyId: "",
144854
145102
  workorderId: wid,
144855
145103
  actorId: _actor,
@@ -145676,15 +145924,15 @@ var init_parseUtil = __esm({
145676
145924
  message: issueData.message
145677
145925
  };
145678
145926
  }
145679
- let errorMessage = "";
145927
+ let errorMessage2 = "";
145680
145928
  const maps = errorMaps.filter((m2) => !!m2).slice().reverse();
145681
145929
  for (const map of maps) {
145682
- errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message;
145930
+ errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
145683
145931
  }
145684
145932
  return {
145685
145933
  ...issueData,
145686
145934
  path: fullPath,
145687
- message: errorMessage
145935
+ message: errorMessage2
145688
145936
  };
145689
145937
  };
145690
145938
  EMPTY_PATH = [];
@@ -162251,7 +162499,52 @@ var init_keyed_mutex = __esm({
162251
162499
  }
162252
162500
  });
162253
162501
 
162502
+ // ../server/src/domains/collab/initialize-workorder.ts
162503
+ async function seedWorkorderBrief(kernel, blobs, workspace, briefId, actor, text6) {
162504
+ await kernel.refreshModel({ workorderId: workspace });
162505
+ const snap = await kernel.getStore().transaction((tx) => tx.loadWorkorder(workspace));
162506
+ const node2 = snap?.nodes.find((row) => row.id === briefId);
162507
+ if (!node2) throw new Error(`\u5EFA\u5355\u6536\u5C3E\u627E\u4E0D\u5230\u603B\u7EB2\u8282\u70B9\uFF1A${briefId}`);
162508
+ const work = snap.works.find((row) => row.id === node2.latestWorkId);
162509
+ const hasBody = !!work && snap.artifacts.some((row) => row.workId === work.id);
162510
+ if (hasBody && work?.status === "success" && work.endedAt && work.conclusion && !work.deadAt && !work.cancelledAt) return;
162511
+ if (snap.workorder.dispatchPaused || node2.cancelledAt || work?.deadAt || work?.cancelledAt) {
162512
+ throw new Error("\u5DE5\u5355\u5DF2\u88AB\u6682\u505C\u6216\u53D6\u6D88\uFF0C\u4E0D\u80FD\u901A\u8FC7\u91CD\u8BD5\u5EFA\u5355\u6062\u590D\u6267\u884C");
162513
+ }
162514
+ if (!text6.trim() && !hasBody) return;
162515
+ if (!hasBody) {
162516
+ const contentRef = await blobs.put(new TextEncoder().encode(text6));
162517
+ await kernel.proposeRevision({ artifactId: briefId, actor, contentRef, reason: "\u5EFA\u5355\uFF1A\u843D\u5B9A brief \u9996\u7248" });
162518
+ }
162519
+ await kernel.advanceQueue(briefId, actor);
162520
+ await kernel.conclude({ artifactId: briefId, actor, note: "\u5EFA\u5355\u65F6\u7531\u53D1\u8D77\u4EBA\u62CD\u677F\u5B9A\u7A3F" });
162521
+ }
162522
+ function legacyCreationComplete(snap, briefId) {
162523
+ if (snap?.workorder.lifecycle !== "active") return false;
162524
+ return snap.works.some((work) => work.nodeId === briefId && work.status === "success" && !!work.endedAt && !!work.conclusion && !work.deadAt && !work.cancelledAt && snap.artifacts.some((row) => row.workId === work.id));
162525
+ }
162526
+ var init_initialize_workorder = __esm({
162527
+ "../server/src/domains/collab/initialize-workorder.ts"() {
162528
+ "use strict";
162529
+ }
162530
+ });
162531
+
162254
162532
  // ../server/src/live-chat.ts
162533
+ function publishInputFromPersisted(info) {
162534
+ const payload2 = info.payload && typeof info.payload === "object" && !Array.isArray(info.payload) ? info.payload : void 0;
162535
+ const csid = typeof payload2?.["clientSubmitId"] === "string" ? payload2["clientSubmitId"] : void 0;
162536
+ const csids = Array.isArray(payload2?.["clientSubmitIds"]) ? payload2["clientSubmitIds"].filter((v2) => typeof v2 === "string") : void 0;
162537
+ const attachments = Array.isArray(payload2?.["attachments"]) ? payload2["attachments"] : void 0;
162538
+ return {
162539
+ itemId: info.itemId,
162540
+ ord: info.ord,
162541
+ version: info.version,
162542
+ text: info.text,
162543
+ ...csid ? { clientSubmitId: csid } : {},
162544
+ ...csids?.length ? { clientSubmitIds: csids } : {},
162545
+ ...attachments?.length ? { attachments } : {}
162546
+ };
162547
+ }
162255
162548
  function normalizedKindToItemType(kind) {
162256
162549
  if (kind === "text") return "message";
162257
162550
  if (kind === "thinking") return "thinking";
@@ -162343,6 +162636,8 @@ var init_live_chat = __esm({
162343
162636
  };
162344
162637
  this.turns.set(chatSessionId, turn);
162345
162638
  this.flushPendingUserItems(chatSessionId);
162639
+ const ledger = opts.items;
162640
+ const stopUserItems = ledger && typeof ledger.onUserItemPersisted === "function" ? ledger.onUserItemPersisted((info) => this.publishPersistedUserItem(turn, info)) : void 0;
162346
162641
  const waiters = this.turnStartWaiters.get(chatSessionId);
162347
162642
  this.turnStartWaiters.delete(chatSessionId);
162348
162643
  for (const wake of waiters ?? []) {
@@ -162393,6 +162688,7 @@ var init_live_chat = __esm({
162393
162688
  };
162394
162689
  },
162395
162690
  finish: (status) => {
162691
+ stopUserItems?.();
162396
162692
  for (const receipt of turn.inputReceipts.values()) receipt.abandon();
162397
162693
  turn.inputReceipts.clear();
162398
162694
  try {
@@ -162682,11 +162978,11 @@ var init_live_chat = __esm({
162682
162978
  * 空对话首次发送时那个最大号是 0,随后到达的历史(号更大)会全部排到乐观条**后面**——
162683
162979
  * 正是 ADR D9 点名必须验的「空桶先发送」那一档。号只能由服务端给。
162684
162980
  *
162685
- * ## 拿不到轮就静默跳过
162981
+ * ## 谁来调
162686
162982
  *
162687
- * 本方法由账本的落库链回调触发,而那一刻这条会话的 live 轮可能还没 `start`
162688
- * (`server.ts` 里 `recordUserSubmission` 在 `liveChat.start` 之前)。拿不到就不发——
162689
- * 前端退回「等下一次快照」,与修复前一致,不会更坏。
162983
+ * 生产上只有 {@link start} 订阅账本(`ChatItemLedger.onUserItemPersisted`)之后经
162984
+ * {@link publishPersistedUserItem} 调,四条开轮入口一处接好;「落库早于开轮」由账本订阅时补发兜住。
162985
+ * 下面按会话号暂存的那条路只服务直接调本方法、手上又没有轮的调用方。
162690
162986
  */
162691
162987
  /**
162692
162988
  * 「user item 已落库、但这条会话的 live 轮还没 start」时的暂存位(见 {@link publishUserItem})。
@@ -162736,6 +163032,18 @@ var init_live_chat = __esm({
162736
163032
  this.publishV3(turn, this.assignV3Seq(turn, frame));
162737
163033
  return true;
162738
163034
  }
163035
+ /**
163036
+ * 账本订阅回来的 user 行 → 推给**持有这本账本的那一轮**({@link start} 里订阅)。
163037
+ *
163038
+ * 只认这条轮本身,不按会话号重新查:这条会话若已经换了一轮(上一轮在 grace、新一轮已 start),
163039
+ * 旧账本迟到的回执不许推进新一轮的流。收了尾的轮也不推——观众已散,页面收尾后会重读快照;
163040
+ * 各入口都在 `finish` 之前抽干账本,已消费的插话在那之前就落了库、发了帧。
163041
+ */
163042
+ publishPersistedUserItem(turn, info) {
163043
+ if (info.role !== "user") return;
163044
+ if (this.turns.get(turn.chatSessionId) !== turn || turn.status !== "running") return;
163045
+ this.publishUserItem(turn.chatSessionId, publishInputFromPersisted(info));
163046
+ }
162739
163047
  /** 恢复路在 DB 收口旧 item 后,将同一身份/版本的终态发给已挂上的观众。 */
162740
163048
  publishItemStatus(chatSessionId, item) {
162741
163049
  const turn = this.turns.get(chatSessionId);
@@ -205373,15 +205681,6 @@ async function loadWorkorderSnapshot(kernel, workorderId) {
205373
205681
  if (!store) return null;
205374
205682
  return store.transaction((tx) => tx.loadWorkorder(workorderId));
205375
205683
  }
205376
- function workorderCreationState(snap, briefArtifactId) {
205377
- const briefWorkIds = new Set(
205378
- snap.works.filter((w2) => w2.nodeId === briefArtifactId).map((w2) => w2.id)
205379
- );
205380
- return {
205381
- hasNodes: snap.nodes.length > 0,
205382
- briefHasBody: snap.artifacts.some((a) => briefWorkIds.has(a.workId))
205383
- };
205384
- }
205385
205684
  function wantsChatParts(req) {
205386
205685
  const accept = req.headers.accept;
205387
205686
  return typeof accept === "string" && accept.includes("application/x-ndjson");
@@ -206030,18 +206329,12 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
206030
206329
  }
206031
206330
  };
206032
206331
  }
206033
- const applied = await kernel.applyIntervention(planned.plan, preview2.baseSeqs, actor);
206332
+ const applied = await kernel.applyIntervention(planned.plan, preview2.baseSeqs, actor, { deferActivation: true });
206034
206333
  const briefArtifactId = planned.rootArtifactId;
206035
- if (brief && brief.trim()) {
206036
- const briefRef = await blobs.put(enc(brief));
206037
- await kernel.proposeRevision({ artifactId: briefArtifactId, actor, contentRef: briefRef, reason: "\u5EFA\u5355\uFF1A\u843D\u5B9A\u53D1\u8D77\u4EBA\u6572\u5B9A\u7684 brief \u9996\u7248" });
206038
- await kernel.advanceQueue(briefArtifactId, actor);
206039
- await kernel.conclude({ artifactId: briefArtifactId, actor, note: "\u5EFA\u5355\u65F6\u7531\u53D1\u8D77\u4EBA\u62CD\u677F\u5B9A\u7A3F" });
206040
- }
206041
- if (args["dispatch"] === false) {
206042
- await ctx.artifactState?.setWorkspaceDispatchHold(workspace, true);
206043
- await kernel.pauseWorkorder?.(workspace, actor, WORKORDER_HOLD_AT_CREATE_REASON);
206044
- }
206334
+ await seedWorkorderBrief(kernel, blobs, workspace, briefArtifactId, actor, brief ?? "");
206335
+ const dispatch = args["dispatch"] !== false;
206336
+ if (!dispatch) await ctx.artifactState?.setWorkspaceDispatchHold?.(workspace, true);
206337
+ await kernel.completeWorkorderCreation(workspace, actor, dispatch, WORKORDER_HOLD_AT_CREATE_REASON);
206045
206338
  return {
206046
206339
  message: planned.issues.length > 0 ? `planned ${workspace}: created ${planned.spawned.length} nodes\uFF0C${planned.issues.length} \u4E2A staffing \u63D0\u793A\u5DF2\u5199\u5165\u5DE5\u5355` : `planned ${workspace}: created ${planned.spawned.length} nodes`,
206047
206340
  data: {
@@ -207291,10 +207584,10 @@ async function startOasisServer(opts) {
207291
207584
  ...fp(p2)
207292
207585
  }))
207293
207586
  ] : [];
207294
- const { randomUUID: randomUUID47 } = await import("node:crypto");
207587
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
207295
207588
  try {
207296
207589
  await store.enqueuePendingMessage({
207297
- id: randomUUID47(),
207590
+ id: randomUUID46(),
207298
207591
  chatSessionId: input.chatSessionId,
207299
207592
  text: input.text,
207300
207593
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -207327,11 +207620,11 @@ async function startOasisServer(opts) {
207327
207620
  ...f2?.mtime ? { srcMtime: f2.mtime } : {}
207328
207621
  };
207329
207622
  });
207330
- const { randomUUID: randomUUID47 } = await import("node:crypto");
207623
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
207331
207624
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
207332
207625
  try {
207333
207626
  await store.enqueuePendingMessage({
207334
- id: randomUUID47(),
207627
+ id: randomUUID46(),
207335
207628
  chatSessionId: input.chatSessionId,
207336
207629
  text: "",
207337
207630
  createdAt: nowIso,
@@ -208259,14 +208552,13 @@ async function startOasisServer(opts) {
208259
208552
  }
208260
208553
  }
208261
208554
  const existingSnap = targetWs ? await loadWorkorderSnapshot(engine.kernel, targetWs) : null;
208262
- const resumeState = existingSnap ? workorderCreationState(existingSnap, body2.briefArtifactId) : { hasNodes: false, briefHasBody: false };
208263
- if (resumeState.hasNodes && resumeState.briefHasBody) {
208555
+ if (targetWs && (await engine.kernel.isWorkorderCreationComplete(targetWs) || legacyCreationComplete(existingSnap, body2.briefArtifactId))) {
208264
208556
  res.writeHead(409, { "content-type": "application/json" }).end(
208265
208557
  JSON.stringify({ error: "WORKORDER_ALREADY_CREATED", message: "\u8BE5\u5DE5\u5355\u5DF2\u521B\u5EFA\uFF0C\u4E0D\u80FD\u91CD\u590D\u521B\u5EFA\uFF08\u53EF\u76F4\u63A5\u6253\u5F00\u5DF2\u521B\u5EFA\u7684\u5DE5\u5355\uFF09", workspace: targetWs })
208266
208558
  );
208267
208559
  return;
208268
208560
  }
208269
- const resuming = resumeState.hasNodes;
208561
+ const resuming = (existingSnap?.nodes.length ?? 0) > 0;
208270
208562
  let sessionProjectId;
208271
208563
  let sessionBindingState;
208272
208564
  if (body2.chatSessionId && opts.chatSession) {
@@ -208330,10 +208622,7 @@ async function startOasisServer(opts) {
208330
208622
  if (targetWs && sessionProjectId) {
208331
208623
  await sessionBindingState.upsertWorkspaceBinding({ workOrderId: targetWs, projectId: sessionProjectId });
208332
208624
  }
208333
- const result = resuming ? { interventionId: "", applied: 0, resumed: true } : await engine.kernel.applyIntervention(body2.plan, body2.baseSeqs, actor);
208334
- if (resuming) {
208335
- await engine.kernel.activateWorkorder?.(targetWs, actor);
208336
- }
208625
+ const result = resuming ? { interventionId: "", applied: 0, resumed: true } : await engine.kernel.applyIntervention(body2.plan, body2.baseSeqs, actor, { deferActivation: true });
208337
208626
  const briefSpawn = body2.plan.ops.find(
208338
208627
  (op) => op.action === "spawn" && op.id === body2.briefArtifactId
208339
208628
  );
@@ -208344,26 +208633,15 @@ async function startOasisServer(opts) {
208344
208633
  throw new Error(`\u5EFA\u5355\u6536\u5C3E\u62FF\u4E0D\u5230 workspace\uFF08brief=${body2.briefArtifactId}\uFF09\u2014\u2014\u62D2\u7EDD\u62A5\u6210\u529F\uFF0C\u8BF7\u91CD\u8BD5`);
208345
208634
  }
208346
208635
  const warnings = [];
208347
- if (briefText) {
208348
- const briefRef = await engine.blobs.put(new TextEncoder().encode(briefText));
208349
- await engine.kernel.proposeRevision({ artifactId: body2.briefArtifactId, actor, contentRef: briefRef, reason: "\u5EFA\u5355\uFF1A\u843D\u5B9A\u53D1\u8D77\u4EBA\u6572\u5B9A\u7684 brief \u9996\u7248" });
208350
- await engine.kernel.advanceQueue(body2.briefArtifactId, actor);
208351
- await engine.kernel.conclude({ artifactId: body2.briefArtifactId, actor, note: "\u5EFA\u5355\u65F6\u7531\u53D1\u8D77\u4EBA\u62CD\u677F\u5B9A\u7A3F" });
208352
- } else {
208353
- warnings.push("\u5DE5\u5355\u603B\u7EB2\u6CA1\u6709\u6B63\u6587\uFF0C\u672A\u843D\u9996\u7248");
208354
- }
208636
+ await seedWorkorderBrief(engine.kernel, engine.blobs, workspace, body2.briefArtifactId, actor, briefText);
208637
+ if (!briefText) warnings.push("\u5DE5\u5355\u603B\u7EB2\u6CA1\u6709\u6B63\u6587\uFF0C\u672A\u843D\u9996\u7248");
208355
208638
  if (body2.dispatch === false) {
208356
- await opts.artifactState?.setWorkspaceDispatchHold(workspace, true);
208357
- await engine.kernel.pauseWorkorder?.(workspace, actor, WORKORDER_HOLD_AT_CREATE_REASON);
208639
+ await (await artifactStateNow())?.setWorkspaceDispatchHold?.(workspace, true);
208358
208640
  }
208359
208641
  await wodraftPlannerIssues.remove(workspace);
208360
208642
  if (opts.chatSession && body2.chatSessionId && workspace !== "ws:default") {
208361
208643
  const applyLinkStore = await chatStoreNow();
208362
- await applyLinkStore.linkWorkOrder(body2.chatSessionId, workspace, "origin").catch((err) => {
208363
- const detail = err instanceof Error ? err.message : String(err);
208364
- console.error(`[chat-session] Failed to link work order ${workspace} to session ${body2.chatSessionId}:`, err);
208365
- warnings.push(`\u672A\u80FD\u628A\u5DE5\u5355\u5173\u8054\u56DE\u672C\u6B21\u5BF9\u8BDD\uFF1A${detail}`);
208366
- });
208644
+ await applyLinkStore.linkWorkOrder(body2.chatSessionId, workspace, "origin");
208367
208645
  const handoffArtifacts = await artifactStateNow();
208368
208646
  if (handoffArtifacts?.addWorkOrderFile) {
208369
208647
  const handoff = await linkChatAttachmentsToWorkOrder({
@@ -208393,6 +208671,7 @@ async function startOasisServer(opts) {
208393
208671
  const missing = plannedSpawnIds.filter((id) => !present.has(id));
208394
208672
  if (missing.length > 0) warnings.push(`\u534F\u4F5C\u56FE\u7F3A ${missing.length} \u4E2A\u8282\u70B9\u672A\u5EFA\u51FA\uFF1A${missing.join("\u3001")}`);
208395
208673
  }
208674
+ await engine.kernel.completeWorkorderCreation(workspace, actor, body2.dispatch !== false, WORKORDER_HOLD_AT_CREATE_REASON);
208396
208675
  res.writeHead(200, { "content-type": "application/json" }).end(
208397
208676
  JSON.stringify({
208398
208677
  ...result,
@@ -208674,7 +208953,7 @@ async function startOasisServer(opts) {
208674
208953
  const openRoom = async () => {
208675
208954
  let chatSessionId2;
208676
208955
  if (chatStore) {
208677
- const { randomUUID: randomUUID47 } = await import("node:crypto");
208956
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
208678
208957
  const known = discussSessions.get(itemKey);
208679
208958
  const knownSession = known ? await chatStore.getSession(known) : null;
208680
208959
  if (knownSession && knownSession.workspace === ctx.workspace && knownSession.humanActorId === ctx.ownerId && (!body2.escalationId || knownSession.escalationId === body2.escalationId)) chatSessionId2 = known;
@@ -208683,7 +208962,7 @@ async function startOasisServer(opts) {
208683
208962
  chatSessionId2 = existing[0]?.id;
208684
208963
  }
208685
208964
  if (!chatSessionId2) {
208686
- chatSessionId2 = randomUUID47();
208965
+ chatSessionId2 = randomUUID46();
208687
208966
  const now = (/* @__PURE__ */ new Date()).toISOString();
208688
208967
  const title = `\u89E3\u51B3\uFF1A${ctx.seed.slice(ctx.seed.indexOf("\uFF1A") + 1, ctx.seed.indexOf("\uFF1A") + 25) || "\u5DE5\u5355\u5361\u70B9"}`;
208689
208968
  const reg0 = (await resolveEngine(currentCompanyId).catch(() => null))?.registry ?? opts.registry;
@@ -209582,7 +209861,7 @@ ${composed}`;
209582
209861
  });
209583
209862
  await rejectedLedger.drain();
209584
209863
  if (rejectedLedger.failures === 0) {
209585
- const { randomUUID: randomUUID48 } = await import("node:crypto");
209864
+ const { randomUUID: randomUUID47 } = await import("node:crypto");
209586
209865
  const now = (/* @__PURE__ */ new Date()).toISOString();
209587
209866
  const rejectedAttachments = effectiveAttachments.map((a) => ({
209588
209867
  name: a.name,
@@ -209590,7 +209869,7 @@ ${composed}`;
209590
209869
  ...a.contentType !== void 0 ? { contentType: a.contentType } : {}
209591
209870
  }));
209592
209871
  await chatStore.appendMessage({
209593
- id: randomUUID48(),
209872
+ id: randomUUID47(),
209594
209873
  sessionId: persistTarget.id,
209595
209874
  role: "user",
209596
209875
  content: persistedUserMessage,
@@ -209668,14 +209947,14 @@ ${composed}`;
209668
209947
  runtimeSessionId: session.nativeSessionId ?? session.id
209669
209948
  }).catch(() => void 0);
209670
209949
  }
209671
- const { randomUUID: randomUUID48 } = await import("node:crypto");
209950
+ const { randomUUID: randomUUID47 } = await import("node:crypto");
209672
209951
  const persistAttachments = effectiveAttachments.map((a) => ({
209673
209952
  name: a.name,
209674
209953
  ...a.blobRef !== void 0 ? { blobRef: a.blobRef } : {},
209675
209954
  ...a.contentType !== void 0 ? { contentType: a.contentType } : {}
209676
209955
  }));
209677
209956
  await chatStore.appendMessage({
209678
- id: randomUUID48(),
209957
+ id: randomUUID47(),
209679
209958
  sessionId: persistTarget.id,
209680
209959
  role: "user",
209681
209960
  content: persistedUserMessage,
@@ -209701,27 +209980,10 @@ ${composed}`;
209701
209980
  const itemLedger = new ChatItemLedger({
209702
209981
  ...itemStore ? { items: itemStore } : {},
209703
209982
  versionSeed,
209704
- /* 用户那条消息一落库就推上 v3 流(ADR-0510 D2 的收口)。
209705
- 不推的话:乐观条没有服务端号、被钉在列表末尾,而 agent 的正文带着更大的号从
209706
- 直播流到达、进「有号」段 ⇒ **回复显示在用户消息上面**,直到下一次快照才跳回去
209707
- (2026-09-09 用户现场)。这里把窗口从「等下一次快照」压到「一次 INSERT 往返」。
209708
- 只推 user:assistant 的每一条本来就走 `emitV3`。 */
209709
- onPersisted: (info) => {
209710
- if (info.role !== "user") return;
209711
- const rowPayload = info.payload && typeof info.payload === "object" && !Array.isArray(info.payload) ? info.payload : void 0;
209712
- const rowCsid = typeof rowPayload?.["clientSubmitId"] === "string" ? rowPayload["clientSubmitId"] : void 0;
209713
- const rowCsids = Array.isArray(rowPayload?.["clientSubmitIds"]) ? rowPayload["clientSubmitIds"].filter((v2) => typeof v2 === "string") : void 0;
209714
- const rowAttachments = Array.isArray(rowPayload?.["attachments"]) ? rowPayload["attachments"] : void 0;
209715
- opts.liveChat?.publishUserItem(persistTarget?.id ?? session.id, {
209716
- itemId: info.itemId,
209717
- ord: info.ord,
209718
- version: info.version,
209719
- text: info.text,
209720
- ...rowCsid ? { clientSubmitId: rowCsid } : {},
209721
- ...rowCsids?.length ? { clientSubmitIds: rowCsids } : {},
209722
- ...rowAttachments?.length ? { attachments: rowAttachments } : {}
209723
- });
209724
- },
209983
+ /* 用户那条消息(首条提交与中途插话)一落库就推上 v3 流(ADR-0510 D2 的收口)——
209984
+ **不在这里接**:`liveChat.start` 带着这本账本开轮时统一订阅
209985
+ (`LiveChatRegistry.start` → `ChatItemLedger.onUserItemPersisted`),开轮前已落库的会补发。
209986
+ 四条开轮入口共用那一处;这里再接一次就是同一行两帧、同一版本号,客户端判 stale 并 resync。 */
209725
209987
  sessionId: persistTarget?.id ?? session.id,
209726
209988
  // 优先账本 id(`chat_session_turns.id`,跨进程唯一、重启不失忆);账本没接入才兜底。
209727
209989
  turnId: heldTurn?.id ?? fallbackTurnId(session.runId),
@@ -209823,52 +210085,27 @@ ${composed}`;
209823
210085
  }, { items: itemLedger }) : null;
209824
210086
  if (live && assistantMsgId) live.setAssistantMessageId(assistantMsgId);
209825
210087
  if (live && chatStore && persistTarget) {
209826
- const { randomUUID: newId } = await import("node:crypto");
209827
210088
  const liveCtl = live;
209828
210089
  const target = persistTarget;
209829
210090
  const store = chatStore;
209830
210091
  liveCtl.onUserInput((text6, insertedAttachments) => {
209831
- const segText = itemLedger.projectContent();
209832
210092
  const segParts = collectedParts();
209833
- const closingId = assistantMsgId;
209834
- const nextId = newId();
209835
- assistantMsgId = nextId;
209836
- itemLedger.beginSegment();
209837
- liveCtl.setAssistantMessageId(nextId);
210093
+ const split = beginInsertedAssistantSegment({
210094
+ chatStore: store,
210095
+ chatSessionId: target.id,
210096
+ ledger: itemLedger,
210097
+ live: liveCtl,
210098
+ closingId: assistantMsgId,
210099
+ segParts,
210100
+ text: text6,
210101
+ attachments: insertedAttachments,
210102
+ ...session.runId ? { runId: session.runId } : {},
210103
+ turnId: heldTurn?.id
210104
+ // 插话切段仍属同一轮(同 turnId)
210105
+ });
210106
+ assistantMsgId = split.nextId;
209838
210107
  segmentBaseSeq = liveCtl.bufferedParts().reduce((max, p2) => p2.seq > max ? p2.seq : max, 0);
209839
- const at = (/* @__PURE__ */ new Date()).toISOString();
209840
- checkpointChain = checkpointChain.then(async () => {
209841
- if (closingId) {
209842
- await store.updateMessage(closingId, {
209843
- content: segText,
209844
- status: "done",
209845
- completedAt: at,
209846
- ...segParts.length ? { parts: segParts } : {}
209847
- }).catch(() => void 0);
209848
- }
209849
- await store.appendMessage({
209850
- id: newId(),
209851
- sessionId: target.id,
209852
- role: "user",
209853
- content: text6,
209854
- createdAt: at,
209855
- // ADR-0166 D9.1:插入带的附件也落在**这条 user 行**上——否则重载后这条气泡
209856
- // 只剩一行字,而同样带图直接发送的那条有缩略图。同 `/api/chat` 那侧只存引用。
209857
- ...insertedAttachments?.length ? { attachments: insertedAttachments } : {},
209858
- ...heldTurn ? { turnId: heldTurn.id } : {}
209859
- // 插话切段仍属同一轮(同 turnId)
209860
- }).catch(() => void 0);
209861
- await store.appendMessage({
209862
- id: nextId,
209863
- sessionId: target.id,
209864
- role: "assistant",
209865
- content: "",
209866
- createdAt: at,
209867
- status: "running",
209868
- ...session.runId ? { runId: session.runId } : {},
209869
- ...heldTurn ? { turnId: heldTurn.id } : {}
209870
- }).then(() => itemLedger.bindMessage(nextId)).catch(() => void 0);
209871
- }).catch(() => void 0);
210108
+ checkpointChain = checkpointChain.then(split.persist).catch(() => void 0);
209872
210109
  });
209873
210110
  }
209874
210111
  let localSeq = 0;
@@ -210151,8 +210388,8 @@ ${composed}`;
210151
210388
  }
210152
210389
  }
210153
210390
  const { spawn: spawn10 } = await import("node:child_process");
210154
- const { randomUUID: randomUUID47 } = await import("node:crypto");
210155
- const sessionId = body2.sessionId ?? randomUUID47();
210391
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
210392
+ const sessionId = body2.sessionId ?? randomUUID46();
210156
210393
  const args = [
210157
210394
  "-p",
210158
210395
  body2.message,
@@ -210448,8 +210685,8 @@ ${composed}`;
210448
210685
  res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: `\u56DE\u8C03\u5730\u5740\u4E0D\u53EF\u7528\uFF0C\u5DF2\u4E2D\u6B62\uFF08\u907F\u514D\u5728 GitHub \u4E0A\u7559\u4E0B\u5B64\u513F App\uFF09\uFF1A${e.message}` }));
210449
210686
  return;
210450
210687
  }
210451
- const { randomUUID: randomUUID47 } = await import("node:crypto");
210452
- const state = randomUUID47();
210688
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
210689
+ const state = randomUUID46();
210453
210690
  const nameSlug = (actorName || actorId?.split(":").pop() || owner?.login || "bot").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "bot";
210454
210691
  const manifest = buildAppManifest({
210455
210692
  employeeSlug: nameSlug,
@@ -211257,6 +211494,7 @@ var init_server3 = __esm({
211257
211494
  init_router();
211258
211495
  init_infra_error();
211259
211496
  init_keyed_mutex();
211497
+ init_initialize_workorder();
211260
211498
  init_live_chat();
211261
211499
  init_chat_turn_wait();
211262
211500
  init_append_now();
@@ -221037,7 +221275,11 @@ function wireRecoveredChatTurn(deps) {
221037
221275
  void handle.kill();
221038
221276
  },
221039
221277
  // ADR-0093 chat 道:重挂的轮同样能被插入——否则「serve 重启过」会变成一条人看不见的能力差异。
221040
- ...typeof handle.appendInput === "function" ? { appendInput: (input) => handle.appendInput({ text: input.text }) } : {},
221278
+ // **整份透传**(2026-09-17):此前只转 `text`,插话链路要的消费回执(`requireConsumptionReceipt`)、
221279
+ // 幂等键(`clientMessageKey`)与附件(`files` / `binaryFiles`)全被剥掉——runtime 照样读到了正文,
221280
+ // 却回不了带键的 `input_consumed`,服务端等到本轮收尾判 `unconfirmed`:agent 处理了那句话,
221281
+ // 对话流里从头到尾没有它,附件也没送到。
221282
+ ...typeof handle.appendInput === "function" ? { appendInput: (input) => handle.appendInput(input) } : {},
221041
221283
  get canAppendInput() {
221042
221284
  return handle.canAppendInput !== false && typeof handle.appendInput === "function";
221043
221285
  }
@@ -221129,40 +221371,25 @@ function wireRecoveredChatTurn(deps) {
221129
221371
  let checkpointChain = Promise.resolve();
221130
221372
  {
221131
221373
  const store = deps.chatStore;
221132
- live.onUserInput((text6) => {
221133
- const segText = itemLedger.projectContent();
221374
+ live.onUserInput((text6, attachments) => {
221134
221375
  const segParts = collectedParts();
221135
- const closingId = currentMsgId;
221136
- const nextId = (0, import_node_crypto49.randomUUID)();
221137
- currentMsgId = nextId;
221138
- itemLedger.beginSegment();
221139
- live.setAssistantMessageId(nextId);
221376
+ const split = beginInsertedAssistantSegment({
221377
+ chatStore: store,
221378
+ chatSessionId: plan.chatSessionId,
221379
+ ledger: itemLedger,
221380
+ live,
221381
+ closingId: currentMsgId,
221382
+ segParts,
221383
+ text: text6,
221384
+ attachments,
221385
+ runId: plan.runId,
221386
+ // 账本那一轮的 `chat_session_turns.id`(与重启前 `/api/chat` 写在开轮行上的同一个);不取 `itemsTurnId`——
221387
+ // 那是 items 上接着写的键,没接轮次账本时是兜底串。
221388
+ turnId: plan.turnId
221389
+ });
221390
+ currentMsgId = split.nextId;
221140
221391
  segmentBaseSeq = live.bufferedParts().reduce((max, p2) => p2.seq > max ? p2.seq : max, 0);
221141
- const at = (/* @__PURE__ */ new Date()).toISOString();
221142
- checkpointChain = checkpointChain.then(async () => {
221143
- await store.updateMessage(closingId, {
221144
- content: segText,
221145
- status: "done",
221146
- completedAt: at,
221147
- ...segParts.length ? { parts: segParts } : {}
221148
- }).catch(() => void 0);
221149
- await store.appendMessage({
221150
- id: (0, import_node_crypto49.randomUUID)(),
221151
- sessionId: plan.chatSessionId,
221152
- role: "user",
221153
- content: text6,
221154
- createdAt: at
221155
- }).catch(() => void 0);
221156
- await store.appendMessage({
221157
- id: nextId,
221158
- sessionId: plan.chatSessionId,
221159
- role: "assistant",
221160
- content: "",
221161
- createdAt: at,
221162
- status: "running",
221163
- runId: plan.runId
221164
- }).then(() => itemLedger.bindMessage(nextId)).catch(() => void 0);
221165
- }).catch(() => void 0);
221392
+ checkpointChain = checkpointChain.then(split.persist).catch(() => void 0);
221166
221393
  });
221167
221394
  }
221168
221395
  const ckptTimer = setInterval(() => {
@@ -221634,12 +221861,11 @@ async function recoverChatTurnsAcrossCompanies(deps) {
221634
221861
  }
221635
221862
  return { ...result, recovered };
221636
221863
  }
221637
- var import_node_crypto49, asObj3, partsOf, maxPartSeq, exitFailed, exitErrorText, runTerminalPatch;
221864
+ var asObj3, partsOf, maxPartSeq, exitFailed, exitErrorText, runTerminalPatch;
221638
221865
  var init_chat_recovery = __esm({
221639
221866
  "../server/src/chat-recovery.ts"() {
221640
221867
  "use strict";
221641
221868
  init_file_restore();
221642
- import_node_crypto49 = require("node:crypto");
221643
221869
  init_chat_parts();
221644
221870
  init_sink();
221645
221871
  init_resilient_appender();
@@ -221670,8 +221896,8 @@ var init_chat_recovery = __esm({
221670
221896
 
221671
221897
  // ../server/src/auth/crypto.ts
221672
221898
  function hashPassword(password) {
221673
- const salt = (0, import_node_crypto50.randomBytes)(16);
221674
- const dk = (0, import_node_crypto50.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
221899
+ const salt = (0, import_node_crypto49.randomBytes)(16);
221900
+ const dk = (0, import_node_crypto49.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
221675
221901
  return `scrypt$${SCRYPT_N}$${salt.toString("base64url")}$${dk.toString("base64url")}`;
221676
221902
  }
221677
221903
  function verifyPassword(password, stored) {
@@ -221688,8 +221914,8 @@ function verifyPassword(password, stored) {
221688
221914
  return false;
221689
221915
  }
221690
221916
  if (expected.length === 0) return false;
221691
- const dk = (0, import_node_crypto50.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
221692
- return dk.length === expected.length && (0, import_node_crypto50.timingSafeEqual)(dk, expected);
221917
+ const dk = (0, import_node_crypto49.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
221918
+ return dk.length === expected.length && (0, import_node_crypto49.timingSafeEqual)(dk, expected);
221693
221919
  }
221694
221920
  function b64urlJson(value2) {
221695
221921
  return Buffer.from(JSON.stringify(value2)).toString("base64url");
@@ -221697,17 +221923,17 @@ function b64urlJson(value2) {
221697
221923
  function signSession(claims, secret) {
221698
221924
  const head = b64urlJson({ alg: "HS256", typ: "JWT" });
221699
221925
  const body2 = b64urlJson(claims);
221700
- const sig = (0, import_node_crypto50.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
221926
+ const sig = (0, import_node_crypto49.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
221701
221927
  return `${head}.${body2}.${sig}`;
221702
221928
  }
221703
221929
  function verifySession(token, secret, nowMs) {
221704
221930
  const parts = token.split(".");
221705
221931
  if (parts.length !== 3) return null;
221706
221932
  const [head, body2, sig] = parts;
221707
- const expected = (0, import_node_crypto50.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
221933
+ const expected = (0, import_node_crypto49.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
221708
221934
  const got = Buffer.from(sig);
221709
221935
  const exp = Buffer.from(expected);
221710
- if (got.length !== exp.length || !(0, import_node_crypto50.timingSafeEqual)(got, exp)) return null;
221936
+ if (got.length !== exp.length || !(0, import_node_crypto49.timingSafeEqual)(got, exp)) return null;
221711
221937
  let claims;
221712
221938
  try {
221713
221939
  claims = JSON.parse(Buffer.from(body2, "base64url").toString("utf8"));
@@ -221719,21 +221945,21 @@ function verifySession(token, secret, nowMs) {
221719
221945
  return claims;
221720
221946
  }
221721
221947
  function generateCode() {
221722
- return String((0, import_node_crypto50.randomInt)(0, 1e6)).padStart(6, "0");
221948
+ return String((0, import_node_crypto49.randomInt)(0, 1e6)).padStart(6, "0");
221723
221949
  }
221724
221950
  function hashCode(code2, secret) {
221725
- return (0, import_node_crypto50.createHmac)("sha256", secret).update(`code:${code2}`).digest("base64url");
221951
+ return (0, import_node_crypto49.createHmac)("sha256", secret).update(`code:${code2}`).digest("base64url");
221726
221952
  }
221727
221953
  function safeEqualHash(a, b2) {
221728
221954
  const ba = Buffer.from(a);
221729
221955
  const bb = Buffer.from(b2);
221730
- return ba.length === bb.length && (0, import_node_crypto50.timingSafeEqual)(ba, bb);
221956
+ return ba.length === bb.length && (0, import_node_crypto49.timingSafeEqual)(ba, bb);
221731
221957
  }
221732
- var import_node_crypto50, SCRYPT_N, SCRYPT_KEYLEN;
221958
+ var import_node_crypto49, SCRYPT_N, SCRYPT_KEYLEN;
221733
221959
  var init_crypto2 = __esm({
221734
221960
  "../server/src/auth/crypto.ts"() {
221735
221961
  "use strict";
221736
- import_node_crypto50 = require("node:crypto");
221962
+ import_node_crypto49 = require("node:crypto");
221737
221963
  SCRYPT_N = 16384;
221738
221964
  SCRYPT_KEYLEN = 32;
221739
221965
  }
@@ -222896,18 +223122,18 @@ function variableKeyFromEnv(env = process.env) {
222896
223122
  return buf;
222897
223123
  }
222898
223124
  if (env.NODE_ENV === "production") throw new Error("OASIS_VAR_KEY is required in production");
222899
- return (0, import_node_crypto51.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
223125
+ return (0, import_node_crypto50.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
222900
223126
  }
222901
223127
  function dedupe(ids2) {
222902
223128
  return [...new Set(ids2.filter((x2) => typeof x2 === "string" && x2.length > 0))];
222903
223129
  }
222904
- var import_node_crypto51, REDACTED_MARKER, ActorsService, mask, changedKeys;
223130
+ var import_node_crypto50, REDACTED_MARKER, ActorsService, mask, changedKeys;
222905
223131
  var init_service5 = __esm({
222906
223132
  "../server/src/domains/actors/service.ts"() {
222907
223133
  "use strict";
222908
223134
  init_file_limits2();
222909
223135
  init_keyed_mutex();
222910
- import_node_crypto51 = require("node:crypto");
223136
+ import_node_crypto50 = require("node:crypto");
222911
223137
  init_skill_fetcher();
222912
223138
  init_identity();
222913
223139
  init_migrate();
@@ -223475,7 +223701,7 @@ ${input.description}
223475
223701
  */
223476
223702
  buildSkillFile(skillId, path40, content3, now) {
223477
223703
  const bytes2 = new TextEncoder().encode(content3);
223478
- const blobHash = (0, import_node_crypto51.createHash)("sha256").update(bytes2).digest("hex");
223704
+ const blobHash = (0, import_node_crypto50.createHash)("sha256").update(bytes2).digest("hex");
223479
223705
  return { skillId, path: path40, content: content3, blobHash, size: bytes2.length, updatedAt: now };
223480
223706
  }
223481
223707
  /**
@@ -224256,15 +224482,15 @@ ${input.description}
224256
224482
  return hit ? { value: hit.value, ...hit.origin ? { origin: hit.origin } : {} } : null;
224257
224483
  }
224258
224484
  encrypt(plain) {
224259
- const iv = (0, import_node_crypto51.randomBytes)(12);
224260
- const cipher = (0, import_node_crypto51.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
224261
- const enc4 = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
224262
- return [iv.toString("base64"), cipher.getAuthTag().toString("base64"), enc4.toString("base64")].join(".");
224485
+ const iv = (0, import_node_crypto50.randomBytes)(12);
224486
+ const cipher = (0, import_node_crypto50.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
224487
+ const enc3 = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
224488
+ return [iv.toString("base64"), cipher.getAuthTag().toString("base64"), enc3.toString("base64")].join(".");
224263
224489
  }
224264
224490
  decrypt(packed) {
224265
224491
  const [iv, tag, data] = packed.split(".");
224266
224492
  if (!iv || !tag || typeof data !== "string") throw new Error("malformed ciphertext");
224267
- const decipher = (0, import_node_crypto51.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
224493
+ const decipher = (0, import_node_crypto50.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
224268
224494
  decipher.setAuthTag(Buffer.from(tag, "base64"));
224269
224495
  return Buffer.concat([decipher.update(Buffer.from(data, "base64")), decipher.final()]).toString("utf8");
224270
224496
  }
@@ -224876,7 +225102,7 @@ async function listSkillMetadataForActor(args) {
224876
225102
  usedDirs.add(dir);
224877
225103
  const sorted = [...metas].sort((a, b2) => a.path.localeCompare(b2.path));
224878
225104
  let latest = "1970-01-01T00:00:00.000Z";
224879
- const hasher = (0, import_node_crypto52.createHash)("sha256");
225105
+ const hasher = (0, import_node_crypto51.createHash)("sha256");
224880
225106
  for (const m2 of sorted) {
224881
225107
  if (m2.updatedAt > latest) latest = m2.updatedAt;
224882
225108
  hasher.update(m2.path);
@@ -224901,11 +225127,11 @@ async function listSkillMetadataForActor(args) {
224901
225127
  const dir = sanitizeSkillDir(c.name, c.slug);
224902
225128
  if (usedDirs.has(dir)) continue;
224903
225129
  usedDirs.add(dir);
224904
- const hasher = (0, import_node_crypto52.createHash)("sha256");
225130
+ const hasher = (0, import_node_crypto51.createHash)("sha256");
224905
225131
  for (const p2 of paths) {
224906
225132
  hasher.update(p2);
224907
225133
  hasher.update("\0");
224908
- hasher.update((0, import_node_crypto52.createHash)("sha256").update(c.files[p2]).digest("hex"));
225134
+ hasher.update((0, import_node_crypto51.createHash)("sha256").update(c.files[p2]).digest("hex"));
224909
225135
  hasher.update("\0");
224910
225136
  }
224911
225137
  out.push({
@@ -224926,11 +225152,11 @@ async function listSkillMetadataForActor(args) {
224926
225152
  const dir = sanitizeSkillDir(b2.name, b2.id);
224927
225153
  if (usedDirs.has(dir)) continue;
224928
225154
  usedDirs.add(dir);
224929
- const hasher = (0, import_node_crypto52.createHash)("sha256");
225155
+ const hasher = (0, import_node_crypto51.createHash)("sha256");
224930
225156
  for (const p2 of paths) {
224931
225157
  hasher.update(p2);
224932
225158
  hasher.update("\0");
224933
- hasher.update((0, import_node_crypto52.createHash)("sha256").update(b2.files[p2]).digest("hex"));
225159
+ hasher.update((0, import_node_crypto51.createHash)("sha256").update(b2.files[p2]).digest("hex"));
224934
225160
  hasher.update("\0");
224935
225161
  }
224936
225162
  out.push({
@@ -224950,7 +225176,7 @@ function builtinSkillToFiles(b2) {
224950
225176
  skillId: `builtin:${b2.id}`,
224951
225177
  path: path40,
224952
225178
  content: content3,
224953
- blobHash: (0, import_node_crypto52.createHash)("sha256").update(content3).digest("hex"),
225179
+ blobHash: (0, import_node_crypto51.createHash)("sha256").update(content3).digest("hex"),
224954
225180
  size: Buffer.byteLength(content3, "utf8"),
224955
225181
  updatedAt: "1970-01-01T00:00:00.000Z"
224956
225182
  }));
@@ -224974,7 +225200,7 @@ function sanitizeSkillDir(name, fallbackId) {
224974
225200
  if (byName) return byName;
224975
225201
  const byId = fallbackId ? fold2(fallbackId) : "";
224976
225202
  if (byId) return byId;
224977
- return `skill-${(0, import_node_crypto52.createHash)("sha256").update(name).digest("hex").slice(0, 8)}`;
225203
+ return `skill-${(0, import_node_crypto51.createHash)("sha256").update(name).digest("hex").slice(0, 8)}`;
224978
225204
  }
224979
225205
  async function materializeSkillFiles(args) {
224980
225206
  const skills = await args.service.listInstalledSkillsForActor(args.actorId);
@@ -225007,7 +225233,7 @@ function connectorSkillToFiles(c) {
225007
225233
  skillId: c.id,
225008
225234
  path: path40,
225009
225235
  content: content3,
225010
- blobHash: (0, import_node_crypto52.createHash)("sha256").update(content3).digest("hex"),
225236
+ blobHash: (0, import_node_crypto51.createHash)("sha256").update(content3).digest("hex"),
225011
225237
  size: Buffer.byteLength(content3, "utf8"),
225012
225238
  updatedAt: "1970-01-01T00:00:00.000Z"
225013
225239
  }));
@@ -225024,11 +225250,11 @@ function connectorSkillDisplayRows(connectorSkills, enabledConnectors) {
225024
225250
  ...s2.version !== void 0 ? { version: s2.version } : {}
225025
225251
  }));
225026
225252
  }
225027
- var import_node_crypto52;
225253
+ var import_node_crypto51;
225028
225254
  var init_skill_materializer = __esm({
225029
225255
  "../server/src/domains/actors/skill-materializer.ts"() {
225030
225256
  "use strict";
225031
- import_node_crypto52 = require("node:crypto");
225257
+ import_node_crypto51 = require("node:crypto");
225032
225258
  }
225033
225259
  });
225034
225260
 
@@ -226596,7 +226822,7 @@ function createActorsDomain(opts) {
226596
226822
  ...kernel !== void 0 ? { onRolesChanged: (a, r) => kernel.setActorRoles(a, r) } : {},
226597
226823
  audit: async (entry) => {
226598
226824
  await opts.audit?.({
226599
- id: `reg_${(0, import_node_crypto53.randomUUID)()}`,
226825
+ id: `reg_${(0, import_node_crypto52.randomUUID)()}`,
226600
226826
  actor: entry.by,
226601
226827
  kind: "registry_change",
226602
226828
  target: entry.actorId,
@@ -226636,11 +226862,11 @@ function createActorsDomain(opts) {
226636
226862
  register: actorsDomain({ resolveCtx, ...opts.resolveTrace ? { resolveTrace: opts.resolveTrace } : {}, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {}, ...opts.getConnectorSkills ? { getConnectorSkills: opts.getConnectorSkills } : {}, ...opts.refreshConnectorSkills ? { refreshConnectorSkills: opts.refreshConnectorSkills } : {}, ...opts.memory ? { memory: opts.memory } : {}, ...opts.resolveDispatchScope ? { resolveDispatchScope: opts.resolveDispatchScope } : {}, ...opts.projectExists ? { projectExists: opts.projectExists } : {}, ...opts.isCompanyManager ? { isCompanyManager: opts.isCompanyManager } : {}, ...opts.credentialAudit ? { credentialAudit: opts.credentialAudit } : {}, ...opts.readCredentialAudit ? { readCredentialAudit: opts.readCredentialAudit } : {}, ...opts.credentialRevealLimits ? { credentialRevealLimits: opts.credentialRevealLimits } : {}, ...opts.skillMarket ? { skillMarket: opts.skillMarket } : {}, ...opts.resolveNodeTenancy ? { resolveNodeTenancy: opts.resolveNodeTenancy } : {} })
226637
226863
  };
226638
226864
  }
226639
- var import_node_crypto53;
226865
+ var import_node_crypto52;
226640
226866
  var init_actors = __esm({
226641
226867
  "../server/src/domains/actors/index.ts"() {
226642
226868
  "use strict";
226643
- import_node_crypto53 = require("node:crypto");
226869
+ import_node_crypto52 = require("node:crypto");
226644
226870
  init_service5();
226645
226871
  init_routes3();
226646
226872
  init_service5();
@@ -228138,6 +228364,7 @@ function buildWorkorderActivity(input) {
228138
228364
  const nodeCancelledAt = node2?.cancelledAt ?? null;
228139
228365
  const name = nodeName(nodeId);
228140
228366
  let fold2 = null;
228367
+ let handoverFold = null;
228141
228368
  for (const [idx, w2] of works.entries()) {
228142
228369
  const next = works[idx + 1];
228143
228370
  const artifacts = artifactsByWork.get(w2.id) ?? [];
@@ -228147,6 +228374,7 @@ function buildWorkorderActivity(input) {
228147
228374
  const reassigned = !!next && next.assigneeActorId !== w2.assigneeActorId;
228148
228375
  const killedByPlan = !!(w2.deadAt || w2.cancelledAt) && !!next && (reassigned || (next.nodeVersion ?? 0) > (w2.nodeVersion ?? 0));
228149
228376
  const failedRound = !w2.conclusion && !interrupt && !removedHere && !killedByPlan && (!!w2.deadAt || !!w2.cancelledAt || st === "failed" || st === "retry");
228377
+ const handedOverRound = !w2.conclusion && !interrupt && !removedHere && !killedByPlan && !failedRound && st === "success" && !!w2.endedAt && !!next;
228150
228378
  if (failedRound && fold2) {
228151
228379
  fold2.traceWorkIds.push(w2.id);
228152
228380
  cardOfWork.set(w2.id, fold2);
@@ -228157,6 +228385,16 @@ function buildWorkorderActivity(input) {
228157
228385
  continue;
228158
228386
  }
228159
228387
  fold2 = null;
228388
+ if (handedOverRound && handoverFold && handoverFold.executorId === w2.assigneeActorId) {
228389
+ handoverFold.traceWorkIds.push(w2.id);
228390
+ cardOfWork.set(w2.id, handoverFold);
228391
+ handoverFold.updatedAt = lastStampOf(w2);
228392
+ handoverFold.artifacts = artifacts;
228393
+ handoverFold.foldedRounds = (handoverFold.foldedRounds ?? 1) + 1;
228394
+ handoverFold.status = ACTIVITY_COPY.foldedStageHandedOver(name, handoverFold.foldedRounds);
228395
+ continue;
228396
+ }
228397
+ handoverFold = null;
228160
228398
  const card2 = {
228161
228399
  /* 卡 id = 这一轮的 workId(连败折叠时取第一轮,React key 与滚动锚点因此稳定)。 */
228162
228400
  id: `work:${w2.id}`,
@@ -228204,6 +228442,10 @@ function buildWorkorderActivity(input) {
228204
228442
  card2.stuckReason = stuckKind;
228205
228443
  card2.status = w2.deadAt || w2.cancelledAt ? ACTIVITY_COPY.killed(name) : st === "retry" ? ACTIVITY_COPY.awaitingRedispatch(nameOf(card2.executorId), name) : stuckKind === "timeout" ? ACTIVITY_COPY.timedOut(nameOf(card2.executorId)) : ACTIVITY_COPY.failed(nameOf(card2.executorId));
228206
228444
  fold2 = card2;
228445
+ } else if (handedOverRound) {
228446
+ card2.phase = "done";
228447
+ card2.status = ACTIVITY_COPY.stageHandedOver(name);
228448
+ handoverFold = card2;
228207
228449
  } else if (w2.endedAt) {
228208
228450
  card2.phase = "running";
228209
228451
  card2.status = ACTIVITY_COPY.awaitingConclude(name);
@@ -228867,8 +229109,20 @@ var init_activity2 = __esm({
228867
229109
  /**
228868
229110
  * 引擎判卷 success、`endedAt` 也写了,却始终没有 `work.conclude`:**交了稿没收口**。
228869
229111
  * 如实说这一档,不冒充「正在处理」——后者把一轮早就结束的执行画成还在跑。
229112
+ * 只剩**节点最新一轮**会落到这里;同节点已有下一轮接手的,见 {@link stageHandedOver}。
228870
229113
  */
228871
229114
  awaitingConclude: (node2) => `\u300A${node2}\u300B\u5DF2\u4EA4\u4ED8\uFF0C\u7B49\u5F85\u6536\u53E3\u3002`,
229115
+ /**
229116
+ * **阶段交付已被接手**:这一轮交了稿、没收口,同节点已经派出下一轮接着做(分批 `propose --checkpoint
229117
+ * --continue`,或派活说明要求先别收口)。这一段到此已经交付——等收口的是接手的那一轮,不是它。
229118
+ * 此前落在 {@link awaitingConclude}、永远「进行中」:生产 2026-09-17 近 7 天 40 张进行中卡全是僵尸,
229119
+ * 38 张是这一档,`ws:wo-da302dca` 一个 QA 节点分批测就留下 24 张(Bug 0053)。
229120
+ *
229121
+ * 不说「已完成」:节点并没有完成,说了会让人以为这一步做完、在等审核。
229122
+ */
229123
+ stageHandedOver: (node2) => `\u300A${node2}\u300B\u5DF2\u63D0\u4EA4\u9636\u6BB5\u6210\u679C\uFF0C\u7531\u4E0B\u4E00\u8F6E\u7EE7\u7EED\u3002`,
229124
+ /** 同一节点上紧挨着的阶段交付轮折成一张(与连败折叠同一套机制),批数写进句子。 */
229125
+ foldedStageHandedOver: (node2, n) => `\u300A${node2}\u300B\u5DF2\u5206 ${n} \u6279\u63D0\u4EA4\u9636\u6BB5\u6210\u679C\uFF0C\u7531\u4E0B\u4E00\u8F6E\u7EE7\u7EED\u3002`,
228872
229126
  /** `works.status=retry`:这一轮没跑成,等引擎重派。谁按的重试由 `plan.node_retry` 补进这句。 */
228873
229127
  awaitingRedispatch: (executor, node2) => `${executor} \u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u6210\u300A${node2}\u300B\uFF0C\u7B49\u5F85\u91CD\u6D3E\u3002`,
228874
229128
  doneAwaitingReview: (name) => `\u300A${name}\u300B\u5DF2\u5B8C\u6210\uFF0C\u7B49\u5F85\u5BA1\u6838\u3002`,
@@ -229840,7 +230094,7 @@ async function buildChatPreview(chatStore, sessionId, who) {
229840
230094
  async function buildChatInbox(chatStore, markers, me, resolveActor, excludeChildSessions) {
229841
230095
  const sessions = await chatStore.listSessions(me);
229842
230096
  const byScope = new Map(markers.map((m2) => [m2.scope, m2]));
229843
- const unread = sessions.filter((s2) => s2.lastTurnQuality !== void 0 && isUnread(s2.touchedAt, byScope.get(chatScope(s2.id)))).sort((a, b2) => a.touchedAt < b2.touchedAt ? 1 : a.touchedAt > b2.touchedAt ? -1 : 0);
230097
+ const unread = sessions.filter((s2) => isChatSessionUnread(s2, byScope.get(chatScope(s2.id)))).sort((a, b2) => a.touchedAt < b2.touchedAt ? 1 : a.touchedAt > b2.touchedAt ? -1 : 0);
229844
230098
  const excluded = excludeChildSessions ? new Set(await excludeChildSessions(unread.map((s2) => s2.id)).catch(() => [])) : /* @__PURE__ */ new Set();
229845
230099
  const out = [];
229846
230100
  let previewsFetched = 0;
@@ -230110,11 +230364,11 @@ function fromRow(row) {
230110
230364
  for (const [k2, v2] of opt) if (v2 !== void 0 && v2 !== null) r[k2] = v2;
230111
230365
  return r;
230112
230366
  }
230113
- var import_node_crypto54, import_node_fs14, DEFAULT_PAGE, MAX_PAGE, pageSize, MemoryNotificationStore, ident3, iso2, str2, PostgresNotificationStore;
230367
+ var import_node_crypto53, import_node_fs14, DEFAULT_PAGE, MAX_PAGE, pageSize, MemoryNotificationStore, ident3, iso2, str2, PostgresNotificationStore;
230114
230368
  var init_notification_store = __esm({
230115
230369
  "../server/src/domains/collab/notification-store.ts"() {
230116
230370
  "use strict";
230117
- import_node_crypto54 = require("node:crypto");
230371
+ import_node_crypto53 = require("node:crypto");
230118
230372
  import_node_fs14 = __toESM(require("node:fs"), 1);
230119
230373
  DEFAULT_PAGE = 50;
230120
230374
  MAX_PAGE = 200;
@@ -230147,7 +230401,7 @@ var init_notification_store = __esm({
230147
230401
  async insert(n) {
230148
230402
  const existing = this.findLive(n.recipient, n.sourceKey, n.category);
230149
230403
  if (existing) return existing;
230150
- const row = { ...n, id: `ntf:${(0, import_node_crypto54.randomUUID)()}`, updatedAt: n.enteredAt };
230404
+ const row = { ...n, id: `ntf:${(0, import_node_crypto53.randomUUID)()}`, updatedAt: n.enteredAt };
230151
230405
  this.rows.set(row.id, row);
230152
230406
  this.save();
230153
230407
  return row;
@@ -230270,7 +230524,7 @@ var init_notification_store = __esm({
230270
230524
  return new _PostgresNotificationStore(sql, schema);
230271
230525
  }
230272
230526
  async insert(n) {
230273
- const id = `ntf:${(0, import_node_crypto54.randomUUID)()}`;
230527
+ const id = `ntf:${(0, import_node_crypto53.randomUUID)()}`;
230274
230528
  const r = await this.sql.query(
230275
230529
  `INSERT INTO ${this.t} (id, recipient, category, source_key, kind, workorder_id, node_id, card_id, actor_id,
230276
230530
  title, workspace_title, entered_at, updated_at, status, read_at, item)
@@ -231221,13 +231475,13 @@ function nameKey(name) {
231221
231475
  function cleanName(name) {
231222
231476
  return name.trim().replace(/\s+/g, " ");
231223
231477
  }
231224
- var import_node_fs15, import_node_path21, import_node_crypto55, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
231478
+ var import_node_fs15, import_node_path21, import_node_crypto54, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
231225
231479
  var init_tags = __esm({
231226
231480
  "../server/src/domains/collab/tags.ts"() {
231227
231481
  "use strict";
231228
231482
  import_node_fs15 = __toESM(require("node:fs"), 1);
231229
231483
  import_node_path21 = __toESM(require("node:path"), 1);
231230
- import_node_crypto55 = __toESM(require("node:crypto"), 1);
231484
+ import_node_crypto54 = __toESM(require("node:crypto"), 1);
231231
231485
  SEP = "::";
231232
231486
  keyOf = (companyId, id) => `${companyId}${SEP}${id}`;
231233
231487
  prefixOf = (companyId) => `${companyId}${SEP}`;
@@ -231245,7 +231499,7 @@ var init_tags = __esm({
231245
231499
  if (!name) return null;
231246
231500
  if (this.entries(companyId).some((t) => nameKey(t.name) === nameKey(name))) return null;
231247
231501
  const tag = {
231248
- id: `tag-${import_node_crypto55.default.randomBytes(6).toString("hex")}`,
231502
+ id: `tag-${import_node_crypto54.default.randomBytes(6).toString("hex")}`,
231249
231503
  name,
231250
231504
  ...input.color?.trim() ? { color: input.color.trim() } : {},
231251
231505
  ...input.description?.trim() ? { description: input.description.trim() } : {}
@@ -232561,11 +232815,11 @@ async function isEmployedElsewhere(store, companyId, accountId) {
232561
232815
  const memberships = await store.listMembershipsForAccount(accountId);
232562
232816
  return memberships.some((m2) => m2.companyId !== companyId);
232563
232817
  }
232564
- var import_node_crypto56, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, INVITE_LINK_TTL_MS, INVITE_LINK_TOKEN_BYTES, INVITE_LINK_ROLE, SLUG_RE, CompanyError, CompaniesService;
232818
+ var import_node_crypto55, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, INVITE_LINK_TTL_MS, INVITE_LINK_TOKEN_BYTES, INVITE_LINK_ROLE, SLUG_RE, CompanyError, CompaniesService;
232565
232819
  var init_service6 = __esm({
232566
232820
  "../server/src/domains/companies/service.ts"() {
232567
232821
  "use strict";
232568
- import_node_crypto56 = require("node:crypto");
232822
+ import_node_crypto55 = require("node:crypto");
232569
232823
  init_console_url();
232570
232824
  ROLES = ["owner", "admin", "member"];
232571
232825
  INVITABLE_ROLES = ["admin", "member"];
@@ -232657,7 +232911,7 @@ var init_service6 = __esm({
232657
232911
  const existing = await this.store.getAccountByEmail(normalized4);
232658
232912
  if (existing) return existing;
232659
232913
  const account = {
232660
- id: `actor:human:${(0, import_node_crypto56.randomUUID)()}`,
232914
+ id: `actor:human:${(0, import_node_crypto55.randomUUID)()}`,
232661
232915
  email: normalized4,
232662
232916
  name: name ?? normalized4,
232663
232917
  status: "active"
@@ -232866,7 +233120,7 @@ var init_service6 = __esm({
232866
233120
  }
232867
233121
  const nowMs = Date.parse(this.now());
232868
233122
  const invitation = {
232869
- id: `invitation:${(0, import_node_crypto56.randomUUID)()}`,
233123
+ id: `invitation:${(0, import_node_crypto55.randomUUID)()}`,
232870
233124
  companyId,
232871
233125
  email: mail,
232872
233126
  role,
@@ -232997,7 +233251,7 @@ var init_service6 = __esm({
232997
233251
  const nowMs = Date.parse(this.now());
232998
233252
  const link = {
232999
233253
  companyId,
233000
- token: (0, import_node_crypto56.randomBytes)(INVITE_LINK_TOKEN_BYTES).toString("base64url"),
233254
+ token: (0, import_node_crypto55.randomBytes)(INVITE_LINK_TOKEN_BYTES).toString("base64url"),
233001
233255
  role: INVITE_LINK_ROLE,
233002
233256
  createdAt: new Date(nowMs).toISOString(),
233003
233257
  expiresAt: new Date(nowMs + INVITE_LINK_TTL_MS).toISOString(),
@@ -233054,7 +233308,7 @@ var init_service6 = __esm({
233054
233308
  const reusable = existing.find((inv) => !this.isExpired(inv));
233055
233309
  if (reusable) return reusable;
233056
233310
  const invitation = {
233057
- id: `invitation:${(0, import_node_crypto56.randomUUID)()}`,
233311
+ id: `invitation:${(0, import_node_crypto55.randomUUID)()}`,
233058
233312
  companyId: link.companyId,
233059
233313
  email: mail,
233060
233314
  role: link.role,
@@ -233292,11 +233546,11 @@ var init_service6 = __esm({
233292
233546
  });
233293
233547
 
233294
233548
  // ../server/src/domains/companies/offboarding.ts
233295
- var import_node_crypto57, MemberOffboardingService;
233549
+ var import_node_crypto56, MemberOffboardingService;
233296
233550
  var init_offboarding = __esm({
233297
233551
  "../server/src/domains/companies/offboarding.ts"() {
233298
233552
  "use strict";
233299
- import_node_crypto57 = require("node:crypto");
233553
+ import_node_crypto56 = require("node:crypto");
233300
233554
  init_service6();
233301
233555
  MemberOffboardingService = class {
233302
233556
  store;
@@ -233368,7 +233622,7 @@ var init_offboarding = __esm({
233368
233622
  const { ownedAgents, inFlight } = await this.snapshot(companyId, accountId);
233369
233623
  const at = this.now();
233370
233624
  const started = await this.store.startMemberOffboarding({
233371
- operationId: `off:${(0, import_node_crypto57.randomUUID)()}`,
233625
+ operationId: `off:${(0, import_node_crypto56.randomUUID)()}`,
233372
233626
  companyId,
233373
233627
  accountId,
233374
233628
  initiatedBy,
@@ -233878,18 +234132,18 @@ var init_routes6 = __esm({
233878
234132
 
233879
234133
  // ../server/src/domains/companies/agent-credentials.ts
233880
234134
  function hashAgentToken(token, secret) {
233881
- return HASH_PREFIX + (0, import_node_crypto58.createHmac)("sha256", secret).update(`agent-token:${token}`).digest("base64url");
234135
+ return HASH_PREFIX + (0, import_node_crypto57.createHmac)("sha256", secret).update(`agent-token:${token}`).digest("base64url");
233882
234136
  }
233883
234137
  function safeEqual(a, b2) {
233884
234138
  const ba = Buffer.from(a);
233885
234139
  const bb = Buffer.from(b2);
233886
- return ba.length === bb.length && (0, import_node_crypto58.timingSafeEqual)(ba, bb);
234140
+ return ba.length === bb.length && (0, import_node_crypto57.timingSafeEqual)(ba, bb);
233887
234141
  }
233888
- var import_node_crypto58, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
234142
+ var import_node_crypto57, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
233889
234143
  var init_agent_credentials = __esm({
233890
234144
  "../server/src/domains/companies/agent-credentials.ts"() {
233891
234145
  "use strict";
233892
- import_node_crypto58 = require("node:crypto");
234146
+ import_node_crypto57 = require("node:crypto");
233893
234147
  init_service6();
233894
234148
  HASH_PREFIX = "ac1$";
233895
234149
  TOKEN_PREFIX = "oak_";
@@ -233914,7 +234168,7 @@ var init_agent_credentials = __esm({
233914
234168
  if (employee.status !== "active") {
233915
234169
  throw new CompanyError(409, "AGENT_LEFT", `\u6570\u5B57\u5458\u5DE5\u5DF2\u79BB\u5F00\uFF0C\u4E0D\u80FD\u7B7E\u53D1\u51ED\u8BC1\uFF1A${agentId}`);
233916
234170
  }
233917
- const token = `${TOKEN_PREFIX}${(0, import_node_crypto58.randomBytes)(32).toString("base64url")}`;
234171
+ const token = `${TOKEN_PREFIX}${(0, import_node_crypto57.randomBytes)(32).toString("base64url")}`;
233918
234172
  const credential = {
233919
234173
  tokenHash: hashAgentToken(token, this.secret),
233920
234174
  companyId,
@@ -234352,7 +234606,7 @@ function gatewayInstanceSlug(gatewayUrl) {
234352
234606
  }
234353
234607
  function connectInstanceName(gatewayUrl, token, companyId) {
234354
234608
  const scope2 = companyId?.trim();
234355
- const suffix = scope2 ? `c${(0, import_node_crypto59.createHash)("sha256").update(`${gatewayUrl}\0${scope2}`).digest("hex").slice(0, 10)}` : token.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase() || "enroll";
234609
+ const suffix = scope2 ? `c${(0, import_node_crypto58.createHash)("sha256").update(`${gatewayUrl}\0${scope2}`).digest("hex").slice(0, 10)}` : token.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase() || "enroll";
234356
234610
  const base = gatewayInstanceSlug(gatewayUrl);
234357
234611
  return base.slice(0, Math.max(1, 64 - suffix.length - 1)) + "-" + suffix;
234358
234612
  }
@@ -234681,12 +234935,12 @@ function buildNpxCmd(p2) {
234681
234935
  const prefix = daemonNpmPrefix();
234682
234936
  return `npm install -g --prefer-online --prefix ${prefix} ${DAEMON_NPM_PACKAGE}@latest && ${prefix}/bin/oasis daemon start --instance ${shellQuote(instanceName)} --server-ws ${shellQuote(p2.gatewayUrl)}${companyPart}${namePart} --pkg ${shellQuote(DAEMON_NPM_PACKAGE)} --upgrade --enroll-token ${shellQuote(p2.token)}`;
234683
234937
  }
234684
- var import_node_crypto59;
234938
+ var import_node_crypto58;
234685
234939
  var init_connect_script = __esm({
234686
234940
  "../server/src/domains/nodes/connect-script.ts"() {
234687
234941
  "use strict";
234688
234942
  init_src();
234689
- import_node_crypto59 = require("node:crypto");
234943
+ import_node_crypto58 = require("node:crypto");
234690
234944
  }
234691
234945
  });
234692
234946
 
@@ -235293,7 +235547,7 @@ function nodesDomain(deps) {
235293
235547
  }
235294
235548
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
235295
235549
  } else {
235296
- nodeId = `node-${(0, import_node_crypto60.randomUUID)().slice(0, 8)}`;
235550
+ nodeId = `node-${(0, import_node_crypto59.randomUUID)().slice(0, 8)}`;
235297
235551
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
235298
235552
  }
235299
235553
  const existingNode = await deps.nodeStore.getNode(nodeId);
@@ -235478,14 +235732,14 @@ function normalizeGatewayUrl(raw) {
235478
235732
  }
235479
235733
  }
235480
235734
  function newNodeId() {
235481
- return "node-" + (0, import_node_crypto60.randomUUID)().replace(/-/g, "").slice(0, 12);
235735
+ return "node-" + (0, import_node_crypto59.randomUUID)().replace(/-/g, "").slice(0, 12);
235482
235736
  }
235483
- var import_node_crypto60, import_node_fs16, import_node_url6, import_node_path22;
235737
+ var import_node_crypto59, import_node_fs16, import_node_url6, import_node_path22;
235484
235738
  var init_routes7 = __esm({
235485
235739
  "../server/src/domains/nodes/routes.ts"() {
235486
235740
  "use strict";
235487
235741
  init_runtime_status();
235488
- import_node_crypto60 = require("node:crypto");
235742
+ import_node_crypto59 = require("node:crypto");
235489
235743
  import_node_fs16 = require("node:fs");
235490
235744
  import_node_url6 = require("node:url");
235491
235745
  import_node_path22 = require("node:path");
@@ -236676,7 +236930,7 @@ function createBundle(root, target, input) {
236676
236930
  }
236677
236931
  }
236678
236932
  const bytes2 = fs25.statSync(absBundle).size;
236679
- const digest = (0, import_node_crypto61.createHash)("sha256").update(fs25.readFileSync(absBundle)).digest("hex");
236933
+ const digest = (0, import_node_crypto60.createHash)("sha256").update(fs25.readFileSync(absBundle)).digest("hex");
236680
236934
  const relBase = `inputs/git-bundles/${safeRepo}-${commit.slice(0, 12)}`;
236681
236935
  return {
236682
236936
  absoluteBundlePath: absBundle,
@@ -236772,12 +237026,12 @@ function classifyGitError(fallback, err, where) {
236772
237026
  function oneLine(msg) {
236773
237027
  return msg.split("\n").map((s2) => s2.trim()).filter(Boolean).slice(0, 3).join(" ");
236774
237028
  }
236775
- var import_node_child_process16, import_node_crypto61, fs25, os7, path24, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
237029
+ var import_node_child_process16, import_node_crypto60, fs25, os7, path24, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
236776
237030
  var init_readonly_bundle = __esm({
236777
237031
  "../server/src/git/readonly-bundle.ts"() {
236778
237032
  "use strict";
236779
237033
  import_node_child_process16 = require("node:child_process");
236780
- import_node_crypto61 = require("node:crypto");
237034
+ import_node_crypto60 = require("node:crypto");
236781
237035
  fs25 = __toESM(require("node:fs"), 1);
236782
237036
  os7 = __toESM(require("node:os"), 1);
236783
237037
  path24 = __toESM(require("node:path"), 1);
@@ -236797,8 +237051,9 @@ var init_readonly_bundle = __esm({
236797
237051
 
236798
237052
  // ../server/src/domains/collab/create-seeded-workorder.ts
236799
237053
  function makeSeededWorkorderCreator(deps) {
236800
- const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto62.randomUUID)().slice(0, 8)}`);
236801
- return async (input) => {
237054
+ const locks = createKeyedMutex();
237055
+ const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto61.randomUUID)().slice(0, 8)}`);
237056
+ const create = async (input) => {
236802
237057
  const company = input.companyId && deps.resolveCompany ? await deps.resolveCompany(input.companyId) : void 0;
236803
237058
  const kernel = company?.kernel ?? deps.kernel;
236804
237059
  const blobs = company?.blobs ?? deps.blobs;
@@ -236806,10 +237061,9 @@ function makeSeededWorkorderCreator(deps) {
236806
237061
  const artifactState = company ? company.artifactState : deps.artifactState;
236807
237062
  const workorderDrafts = company ? company.workorderDrafts : deps.workorderDrafts;
236808
237063
  const workspace = input.idempotencyKey ? workspaceForKey(input.idempotencyKey) : genWorkspace();
236809
- if (input.idempotencyKey) {
236810
- const existing = existingWorkorder(kernel, workspace);
236811
- if (existing) return existing;
236812
- }
237064
+ const snapshot = input.idempotencyKey ? await kernel.getStore().transaction((tx) => tx.loadWorkorder(workspace)) : null;
237065
+ const existing = existingWorkorder(snapshot, workspace, input.actor);
237066
+ if (existing && (await kernel.isWorkorderCreationComplete(workspace) || legacyCreationComplete(snapshot, existing.rootArtifactId))) return existing;
236813
237067
  if (artifactState && input.projectId) {
236814
237068
  await bindWorkorderProject({
236815
237069
  workOrderId: workspace,
@@ -236818,6 +237072,13 @@ function makeSeededWorkorderCreator(deps) {
236818
237072
  ...deps.onWarn ? { onWarn: deps.onWarn } : {}
236819
237073
  });
236820
237074
  }
237075
+ const finishCreation = async (result) => {
237076
+ await seedWorkorderBrief(kernel, blobs, workspace, result.rootArtifactId, input.actor, input.brief);
237077
+ if (input.dispatch === false) await artifactState?.setWorkspaceDispatchHold(workspace, true);
237078
+ await kernel.completeWorkorderCreation(workspace, input.actor, input.dispatch !== false, WORKORDER_HOLD_AT_CREATE_REASON);
237079
+ return result;
237080
+ };
237081
+ if (existing) return await finishCreation(existing);
236821
237082
  const planned = await buildDynamicWorkorderPlan({
236822
237083
  // requestedType 在 agentPlan 路仅用于兜底;给第一个节点类型即可,图仍以 plan 为准。
236823
237084
  requestedType: input.plan.nodes[0]?.type ?? "prd",
@@ -236874,44 +237135,41 @@ function makeSeededWorkorderCreator(deps) {
236874
237135
  }
236875
237136
  };
236876
237137
  }
236877
- await kernel.applyIntervention(planned.plan, preview2.baseSeqs, input.humanActorId ?? input.actor);
236878
- const briefId = planned.rootArtifactId;
236879
- if (input.brief.trim()) {
236880
- const ref2 = await blobs.put(enc2(input.brief));
236881
- await kernel.proposeRevision({ artifactId: briefId, actor: input.actor, contentRef: ref2, reason: "\u5267\u672C\u5EFA\u5355\uFF1A\u843D\u5B9A brief \u9996\u7248" });
236882
- await kernel.advanceQueue(briefId, input.actor);
236883
- await kernel.conclude({ artifactId: briefId, actor: input.actor, note: "\u5267\u672C\u5EFA\u5355\u65F6\u5B9A\u7A3F" });
236884
- }
236885
- if (input.dispatch === false && artifactState) {
236886
- await artifactState.setWorkspaceDispatchHold(workspace, true);
236887
- await kernel.pauseWorkorder?.(workspace, input.actor, WORKORDER_HOLD_AT_CREATE_REASON);
237138
+ await kernel.applyIntervention(planned.plan, preview2.baseSeqs, input.humanActorId ?? input.actor, { deferActivation: true });
237139
+ return await finishCreation({ workspace, rootArtifactId: planned.rootArtifactId, spawned: planned.spawned });
237140
+ };
237141
+ return async (input) => {
237142
+ const release = await locks.acquire(`${input.companyId ?? ""}\0${input.idempotencyKey ?? (0, import_node_crypto61.randomUUID)()}`);
237143
+ try {
237144
+ return await create(input);
237145
+ } finally {
237146
+ release();
236888
237147
  }
236889
- return { workspace, rootArtifactId: briefId, spawned: planned.spawned };
236890
237148
  };
236891
237149
  }
236892
237150
  function workspaceForKey(key2) {
236893
- return `ws:wo-${(0, import_node_crypto62.createHash)("sha256").update(key2).digest("hex").slice(0, 8)}`;
237151
+ return `ws:wo-${(0, import_node_crypto61.createHash)("sha256").update(key2).digest("hex").slice(0, 8)}`;
236894
237152
  }
236895
- function existingWorkorder(kernel, workspace) {
236896
- const arts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === workspace);
236897
- if (arts.length === 0) return null;
236898
- const seed = arts.find((a) => a.inputs.length === 0) ?? arts[0];
237153
+ function existingWorkorder(snapshot, workspace, actor) {
237154
+ if (!snapshot?.nodes.length) return null;
237155
+ const seed = snapshot.nodes.find((node2) => node2.type === "brief") ?? snapshot.nodes.find((node2) => !snapshot.edges.some((edge) => edge.toNodeId === node2.id));
237156
+ if (!seed) throw new Error(`\u65E2\u6709\u5DE5\u5355\u7F3A\u5C11\u603B\u7EB2\u8282\u70B9\uFF1A${workspace}`);
236899
237157
  return {
236900
237158
  workspace,
236901
237159
  rootArtifactId: seed.id,
236902
- // role 取自 planner 的规划结果、不是 artifact 上的字段——重试路径拿不到也无所谓(可省)。
236903
- spawned: arts.map((a) => ({ id: a.id, type: a.type, owner: a.owner }))
237160
+ spawned: snapshot.nodes.map((node2) => ({ id: node2.id, type: node2.type, owner: node2.assigneeActorId ?? actor }))
236904
237161
  };
236905
237162
  }
236906
- var import_node_crypto62, enc2;
237163
+ var import_node_crypto61;
236907
237164
  var init_create_seeded_workorder = __esm({
236908
237165
  "../server/src/domains/collab/create-seeded-workorder.ts"() {
236909
237166
  "use strict";
236910
- import_node_crypto62 = require("node:crypto");
237167
+ import_node_crypto61 = require("node:crypto");
236911
237168
  init_uncategorized_project();
236912
237169
  init_src();
236913
237170
  init_planner();
236914
- enc2 = (s2) => new TextEncoder().encode(s2);
237171
+ init_keyed_mutex();
237172
+ init_initialize_workorder();
236915
237173
  }
236916
237174
  });
236917
237175
 
@@ -238335,12 +238593,12 @@ function stateFromAttempt(attempt, handoff, checkpoint, now, stalledAfterMs, ope
238335
238593
  if (attempt.status === "no-output") return handoff?.unresolvedIssues.length ? "blocked" : "no_change";
238336
238594
  return "failed";
238337
238595
  }
238338
- var import_node_crypto63, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
238596
+ var import_node_crypto62, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
238339
238597
  var init_service8 = __esm({
238340
238598
  "../server/src/domains/execution-continuity/service.ts"() {
238341
238599
  "use strict";
238342
238600
  init_src();
238343
- import_node_crypto63 = require("node:crypto");
238601
+ import_node_crypto62 = require("node:crypto");
238344
238602
  init_src7();
238345
238603
  BLOCKING_STATES = /* @__PURE__ */ new Set(["stalled", "failed", "blocked"]);
238346
238604
  HANDOFF_OUTCOMES2 = /* @__PURE__ */ new Set(["succeeded", "no-output", "failed", "cancelled", "timeout", "orphaned"]);
@@ -238571,7 +238829,7 @@ var init_service8 = __esm({
238571
238829
  if (typeof expectedHash !== "string" || !this.readBundleFile) return null;
238572
238830
  const injectedResume = this.readBundleFile(attempt.id, "execution/RESUME.md");
238573
238831
  if (!injectedResume) return null;
238574
- const actualHash = (0, import_node_crypto63.createHash)("sha256").update(injectedResume).digest("hex");
238832
+ const actualHash = (0, import_node_crypto62.createHash)("sha256").update(injectedResume).digest("hex");
238575
238833
  if (actualHash !== expectedHash) return null;
238576
238834
  const recoverySnapshot = parseResumePackSnapshot(injectedResume);
238577
238835
  if (!recoverySnapshot || recoverySnapshot.scenario !== "redispatch" || !recoverySnapshot.previousAttempt || recoverySnapshot.node.nodeId !== attempt.nodeId || recoverySnapshot.jobKey !== attempt.jobKey || recoverySnapshot.part !== attempt.part) return null;
@@ -239698,7 +239956,7 @@ function createChatSessionsDomain(opts) {
239698
239956
  if (!text6.trim()) throw new ApiError(400, "BAD_REQUEST", "text required");
239699
239957
  const now = Date.now();
239700
239958
  sweepDrafts(now);
239701
- const id = `cd_${(0, import_node_crypto64.randomUUID)()}`;
239959
+ const id = `cd_${(0, import_node_crypto63.randomUUID)()}`;
239702
239960
  drafts.set(id, { text: text6, actor: req.auth.actor, expiresAt: now + DRAFT_TTL_MS });
239703
239961
  return { status: 201, body: { id, expiresInMs: DRAFT_TTL_MS } };
239704
239962
  });
@@ -239730,6 +239988,13 @@ function createChatSessionsDomain(opts) {
239730
239988
  ...runningParentIds.has(s2.id) ? { hasRunningDelegation: true } : {}
239731
239989
  }));
239732
239990
  }
239991
+ async function markSessionUnread(sessions, actor, companyId) {
239992
+ if (!opts.resolveReadMarkers || !isHumanActorId(actor) || sessions.length === 0) return sessions;
239993
+ const markers = await opts.resolveReadMarkers(companyId).then((store2) => store2.listMarkers(actor)).catch(() => null);
239994
+ if (!markers) return sessions;
239995
+ const byScope = new Map(markers.map((m2) => [m2.scope, m2]));
239996
+ return sessions.map((s2) => ({ ...s2, unread: isChatSessionUnread(s2, byScope.get(chatScope(s2.id))) }));
239997
+ }
239733
239998
  router.get("/api/chat-sessions", async (req) => {
239734
239999
  const store2 = await storeFor(req);
239735
240000
  if (req.query?.get("analysis") === "all") {
@@ -239744,7 +240009,8 @@ function createChatSessionsDomain(opts) {
239744
240009
  }
239745
240010
  const analyzedRunId = req.query?.get("analyzedRunId") ?? void 0;
239746
240011
  const sessions = analyzedRunId ? await store2.listSessionsByAnalyzedRun(analyzedRunId) : await store2.listSessions(req.auth.actor);
239747
- const items = await markSessionDelegations(sessions, req.auth.companyId ?? void 0);
240012
+ const marked = await markSessionDelegations(sessions, req.auth.companyId ?? void 0);
240013
+ const items = analyzedRunId ? marked : await markSessionUnread(marked, req.auth.actor, req.auth.companyId ?? void 0);
239748
240014
  return { status: 200, body: { items } };
239749
240015
  });
239750
240016
  router.post("/api/chat-sessions", async (req) => {
@@ -239758,7 +240024,7 @@ function createChatSessionsDomain(opts) {
239758
240024
  const runtimeId = await currentRuntimeId(body2.aiActorId) ?? "unknown";
239759
240025
  const now = (/* @__PURE__ */ new Date()).toISOString();
239760
240026
  const session = {
239761
- id: (0, import_node_crypto64.randomUUID)(),
240027
+ id: (0, import_node_crypto63.randomUUID)(),
239762
240028
  humanActorId: req.auth.actor,
239763
240029
  // 注:这里**刻意不写 companyId**。新会话要不要带公司归属,按 ws:wo-6d588f8e 的 ADR
239764
240030
  // (每公司 schema、最终移除表内 company 列)统一定,不在本节点自行发明。
@@ -239864,7 +240130,7 @@ function createChatSessionsDomain(opts) {
239864
240130
  if (body2.role !== "user" && body2.role !== "assistant") throw new ApiError(400, "BAD_REQUEST", "role must be user or assistant");
239865
240131
  const role = body2.role;
239866
240132
  const msg = await store2.appendMessage({
239867
- id: (0, import_node_crypto64.randomUUID)(),
240133
+ id: (0, import_node_crypto63.randomUUID)(),
239868
240134
  sessionId: req.params.id,
239869
240135
  role,
239870
240136
  content: role === "user" ? stripInjectedChatContext(body2.content) : body2.content,
@@ -239905,7 +240171,7 @@ function createChatSessionsDomain(opts) {
239905
240171
  const known = key2 ? cur.find((x2) => x2.clientKey === key2) : void 0;
239906
240172
  if (!known && cur.length >= QUEUE_MAX) throw new ApiError(409, "QUEUE_FULL", `\u961F\u5217\u5DF2\u6EE1\uFF08\u4E0A\u9650 ${QUEUE_MAX} \u6761\uFF09`);
239907
240173
  const item = await store.enqueuePendingMessage({
239908
- id: (0, import_node_crypto64.randomUUID)(),
240174
+ id: (0, import_node_crypto63.randomUUID)(),
239909
240175
  chatSessionId: sid,
239910
240176
  text: text6,
239911
240177
  ...attachments.length ? { attachments } : {},
@@ -240454,12 +240720,12 @@ function createChatSessionsDomain(opts) {
240454
240720
  });
240455
240721
  };
240456
240722
  }
240457
- var import_node_crypto64, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS;
240723
+ var import_node_crypto63, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS;
240458
240724
  var init_chat_sessions2 = __esm({
240459
240725
  "../server/src/domains/chat-sessions/index.ts"() {
240460
240726
  "use strict";
240461
240727
  init_file_limits2();
240462
- import_node_crypto64 = require("node:crypto");
240728
+ import_node_crypto63 = require("node:crypto");
240463
240729
  init_src();
240464
240730
  init_chat_session();
240465
240731
  init_persistent_files();
@@ -241850,7 +242116,7 @@ function busyError(chatSessionId, blockedBy) {
241850
242116
  return `\u76EE\u6807\u4F1A\u8BDD ${chatSessionId} \u6B63\u6709\u4E00\u8F6E\u5728\u8DD1${blockedBy ? `\uFF08${blockedBy}\uFF09` : ""}\u2014\u2014\u672C\u6B21\u89E6\u53D1\u5DF2\u4E22\u5F03\uFF0C\u4E0D\u6392\u961F\u3001\u4E0D\u8865\u8DD1`;
241851
242117
  }
241852
242118
  function genWebhookToken() {
241853
- return `owt_${(0, import_node_crypto65.randomBytes)(32).toString("base64url")}`;
242119
+ return `owt_${(0, import_node_crypto64.randomBytes)(32).toString("base64url")}`;
241854
242120
  }
241855
242121
  function validateTitleTemplate(tpl) {
241856
242122
  const residue = tpl.replace(DATE_TOKEN, "");
@@ -241906,11 +242172,11 @@ function appendSourceNote(brief, automation, source, atIso) {
241906
242172
  ---
241907
242173
  \u672C\u5DE5\u5355\u7531\u81EA\u52A8\u5316\u300C${automation.name}\u300D\u4E8E ${atIso} \u89E6\u53D1\uFF08\u6765\u6E90\uFF1A${source}\uFF09\u3002\u5F00\u5DE5\u540E\u8BF7\u628A\u5DE5\u5355\u6807\u9898\u6539\u6210\u51C6\u786E\u53CD\u6620\u5B9E\u9645\u5DE5\u4F5C\u7684\u63CF\u8FF0\u3002`;
241908
242174
  }
241909
- var import_node_crypto65, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
242175
+ var import_node_crypto64, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
241910
242176
  var init_service9 = __esm({
241911
242177
  "../server/src/domains/automations/service.ts"() {
241912
242178
  "use strict";
241913
- import_node_crypto65 = require("node:crypto");
242179
+ import_node_crypto64 = require("node:crypto");
241914
242180
  init_src();
241915
242181
  init_registry3();
241916
242182
  init_attribution();
@@ -242724,11 +242990,11 @@ function verifySignature(rawBody, headers, secret) {
242724
242990
  if (!secret) return "not_required";
242725
242991
  const given = header1(headers, "x-hub-signature-256");
242726
242992
  if (!given) return "missing";
242727
- const expected = `sha256=${(0, import_node_crypto66.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
242993
+ const expected = `sha256=${(0, import_node_crypto65.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
242728
242994
  const a = Buffer.from(given);
242729
242995
  const b2 = Buffer.from(expected);
242730
242996
  if (a.length !== b2.length) return "invalid";
242731
- return (0, import_node_crypto66.timingSafeEqual)(a, b2) ? "valid" : "invalid";
242997
+ return (0, import_node_crypto65.timingSafeEqual)(a, b2) ? "valid" : "invalid";
242732
242998
  }
242733
242999
  function eventAllowed(filters, envelope) {
242734
243000
  if (!filters || filters.length === 0) return true;
@@ -242758,11 +243024,11 @@ function splitEvent(envelope) {
242758
243024
  }
242759
243025
  return { eventName, actionCandidates: candidates };
242760
243026
  }
242761
- var import_node_crypto66, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
243027
+ var import_node_crypto65, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
242762
243028
  var init_webhook = __esm({
242763
243029
  "../server/src/domains/automations/webhook.ts"() {
242764
243030
  "use strict";
242765
- import_node_crypto66 = require("node:crypto");
243031
+ import_node_crypto65 = require("node:crypto");
242766
243032
  MAX_BODY_BYTES = 256 * 1024;
242767
243033
  AutomationWebhookService = class {
242768
243034
  store;
@@ -243910,7 +244176,7 @@ function playbooksDomain(opts) {
243910
244176
  opts.overrides.set(companyOf(req), ref2, nodeKey, nextOverride);
243911
244177
  try {
243912
244178
  await opts.audit?.({
243913
- id: `reg_${(0, import_node_crypto67.randomUUID)()}`,
244179
+ id: `reg_${(0, import_node_crypto66.randomUUID)()}`,
243914
244180
  actor: req.auth.actor,
243915
244181
  kind: "registry_change",
243916
244182
  target: `playbook:${ref2}#${nodeKey}`,
@@ -243927,11 +244193,11 @@ function playbooksDomain(opts) {
243927
244193
  });
243928
244194
  };
243929
244195
  }
243930
- var import_node_crypto67;
244196
+ var import_node_crypto66;
243931
244197
  var init_routes12 = __esm({
243932
244198
  "../server/src/domains/playbooks/routes.ts"() {
243933
244199
  "use strict";
243934
- import_node_crypto67 = require("node:crypto");
244200
+ import_node_crypto66 = require("node:crypto");
243935
244201
  init_router();
243936
244202
  init_registry3();
243937
244203
  init_planner();
@@ -244060,17 +244326,17 @@ function completionFingerprint(workspace, members) {
244060
244326
  workspace,
244061
244327
  members: [...members].sort((a, b2) => a.artifactId < b2.artifactId ? -1 : a.artifactId > b2.artifactId ? 1 : 0).map((m2) => ({ artifactId: m2.artifactId, currentRevisionId: m2.currentRevisionId ?? null }))
244062
244328
  });
244063
- return (0, import_node_crypto68.createHash)("sha256").update(canonical2).digest("hex");
244329
+ return (0, import_node_crypto67.createHash)("sha256").update(canonical2).digest("hex");
244064
244330
  }
244065
244331
  function eventConsumptionKey(args) {
244066
244332
  const raw = `project-event:${EVENT_KEY_VERSION}:${args.triggerId}:${args.event}:${args.workspace}:${args.fingerprint}`;
244067
- return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto68.createHash)("sha256").update(raw).digest("hex")}`;
244333
+ return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto67.createHash)("sha256").update(raw).digest("hex")}`;
244068
244334
  }
244069
- var import_node_crypto68, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
244335
+ var import_node_crypto67, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
244070
244336
  var init_project_event_poller = __esm({
244071
244337
  "../server/src/automations/project-event-poller.ts"() {
244072
244338
  "use strict";
244073
- import_node_crypto68 = require("node:crypto");
244339
+ import_node_crypto67 = require("node:crypto");
244074
244340
  EVENT_KEY_VERSION = "v1";
244075
244341
  EMPTY_METRICS = () => ({
244076
244342
  scanDurationMs: 0,
@@ -244281,7 +244547,7 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
244281
244547
  if (input.requireConsumptionReceipt && !hub.supportsInputConsumptionReceipts?.(dispatchId)) {
244282
244548
  return Promise.resolve({ accepted: false, reason: "consumption-unsupported" });
244283
244549
  }
244284
- const requestId = (0, import_node_crypto69.randomUUID)();
244550
+ const requestId = (0, import_node_crypto68.randomUUID)();
244285
244551
  const sent = routeToSession(hub, nodeId, dispatchId, { type: "append_input", dispatchId, input, requestId });
244286
244552
  if (!sent) return Promise.resolve({ accepted: false, reason: "session-closing", ...unknownIfInterrupting });
244287
244553
  return new Promise((resolve10) => {
@@ -244294,11 +244560,11 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
244294
244560
  entry.appendWaiters.push({ requestId, requireConsumptionReceipt: input.requireConsumptionReceipt, resolve: resolve10, timer });
244295
244561
  });
244296
244562
  }
244297
- var import_node_crypto69, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
244563
+ var import_node_crypto68, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
244298
244564
  var init_daemon_adapter = __esm({
244299
244565
  "../server/src/daemon-adapter.ts"() {
244300
244566
  "use strict";
244301
- import_node_crypto69 = require("node:crypto");
244567
+ import_node_crypto68 = require("node:crypto");
244302
244568
  init_src();
244303
244569
  STASH_TTL_MS = 5 * 6e4;
244304
244570
  STASH_MAX_FRAMES_PER_ID = 500;
@@ -244648,7 +244914,7 @@ var init_daemon_adapter = __esm({
244648
244914
  async spawn(job) {
244649
244915
  const nodeId = job.binding?.nodeId;
244650
244916
  if (!nodeId) throw new Error("DaemonHubAdapter \u9700\u8981 job.binding.nodeId\uFF08\u8DEF\u7531\u9519\u8BEF\uFF09");
244651
- const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto69.randomUUID)()}`;
244917
+ const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto68.randomUUID)()}`;
244652
244918
  if (job.dispatchId && this.pending.has(dispatchId)) {
244653
244919
  return this.recover(dispatchId, nodeId, job.binding?.runtimeKind, job.actor);
244654
244920
  }
@@ -244891,7 +245157,7 @@ var init_daemon_adapter = __esm({
244891
245157
  return daemon?.meta.supportsWorkdirWalk === true;
244892
245158
  }
244893
245159
  request(nodeId, buildFrame) {
244894
- const requestId = (0, import_node_crypto69.randomUUID)();
245160
+ const requestId = (0, import_node_crypto68.randomUUID)();
244895
245161
  const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
244896
245162
  if (!sent) return Promise.resolve({ ok: false, code: "NODE_OFFLINE" });
244897
245163
  return new Promise((resolve10) => {
@@ -244999,14 +245265,14 @@ function classifyPage(page, lastPushedHash, serviceAccount) {
244999
245265
  if (sha(body2) === lastPushedHash) return { kind: "echo" };
245000
245266
  return { kind: "human-edit", content: body2, updatedBy: page.updatedBy };
245001
245267
  }
245002
- var import_node_crypto70, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
245268
+ var import_node_crypto69, sha, enc2, dec, MirrorEngine, MapMirrorIdentities;
245003
245269
  var init_engine = __esm({
245004
245270
  "../server/src/mirror/engine.ts"() {
245005
245271
  "use strict";
245006
- import_node_crypto70 = require("node:crypto");
245272
+ import_node_crypto69 = require("node:crypto");
245007
245273
  init_src();
245008
- sha = (s2) => (0, import_node_crypto70.createHash)("sha256").update(s2, "utf8").digest("hex");
245009
- enc3 = (s2) => new TextEncoder().encode(s2);
245274
+ sha = (s2) => (0, import_node_crypto69.createHash)("sha256").update(s2, "utf8").digest("hex");
245275
+ enc2 = (s2) => new TextEncoder().encode(s2);
245010
245276
  dec = (b2) => new TextDecoder().decode(b2);
245011
245277
  MirrorEngine = class {
245012
245278
  constructor(deps) {
@@ -245080,7 +245346,7 @@ var init_engine = __esm({
245080
245346
  continue;
245081
245347
  }
245082
245348
  try {
245083
- const contentRef = await this.deps.blobs.put(enc3(cls.content));
245349
+ const contentRef = await this.deps.blobs.put(enc2(cls.content));
245084
245350
  const rev = await this.deps.kernel.proposeRevision({
245085
245351
  artifactId,
245086
245352
  actor,
@@ -245266,11 +245532,11 @@ function humanExitCause(exit3) {
245266
245532
  const base = (exit3.reason ? label[exit3.reason] : void 0) ?? `\u4F1A\u8BDD\u5F02\u5E38\u7ED3\u675F\uFF08${exit3.reason ?? "\u65E0\u9000\u51FA\u4FE1\u606F"}\uFF09`;
245267
245533
  return exit3.errorMessage ? `${base}\uFF1A${exit3.errorMessage.slice(0, 200)}` : base;
245268
245534
  }
245269
- var import_node_crypto71, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, REJECTED_PROPOSALS_IN_TASK, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
245535
+ var import_node_crypto70, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, REJECTED_PROPOSALS_IN_TASK, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
245270
245536
  var init_worker = __esm({
245271
245537
  "../server/src/coordinator/worker.ts"() {
245272
245538
  "use strict";
245273
- import_node_crypto71 = require("node:crypto");
245539
+ import_node_crypto70 = require("node:crypto");
245274
245540
  init_src7();
245275
245541
  init_identity();
245276
245542
  AUTONOMOUS_ETHOS = `\u4F60\u662F\u8FD9\u4E2A\u5DE5\u5355\u7684**\u7BA1\u7406\u8005**\u2014\u2014\u804C\u8D23\u662F\u8BA9\u5B83\u987A\u7545\u8DD1\u5B8C\u3002\u7CFB\u7EDF\u5728\u67D0\u4E2A\u8282\u70B9\u5361\u4F4F\u3001\u673A\u68B0\u5206\u8BCA\u786E\u8BA4"\u9700\u8981\u4F60"\u65F6\u5524\u8D77\u4F60\uFF08\u65E0\u4EBA\u5728\u573A\uFF0C\u4F60\u8FD9\u4E00\u8F6E\u628A\u80FD\u505A\u7684\u505A\u6389\uFF09\u3002\u4F60\u7684\u624B\u6BB5\u5F88\u5BBD\uFF1A
@@ -245540,7 +245806,7 @@ ${HIGH_RISK_COMMANDS}`;
245540
245806
  return this.deps.kernel.model.lastSeq.get(artifactId) ?? 0;
245541
245807
  }
245542
245808
  evaluationKey(kind, artifactId, evidence) {
245543
- const fingerprint2 = (0, import_node_crypto71.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
245809
+ const fingerprint2 = (0, import_node_crypto70.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
245544
245810
  return `${kind}:${artifactId}:${fingerprint2}`;
245545
245811
  }
245546
245812
  artifactEvidence(id) {
@@ -245929,8 +246195,8 @@ ${ctx.nodeFault}
245929
246195
  const taskWithRejections = rejectedSection ? `${task}
245930
246196
 
245931
246197
  ${rejectedSection}` : task;
245932
- const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto71.randomUUID)();
245933
- const coordinatorRunId = `coordinate:${(0, import_node_crypto71.randomUUID)()}`;
246198
+ const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto70.randomUUID)();
246199
+ const coordinatorRunId = `coordinate:${(0, import_node_crypto70.randomUUID)()}`;
245934
246200
  const taskWithContext = coordinatorContext ? [
245935
246201
  taskWithRejections,
245936
246202
  ``,
@@ -245974,7 +246240,7 @@ ${rejectedSection}` : task;
245974
246240
  const startedAtMs = Date.now();
245975
246241
  let trajectoryStarted = false;
245976
246242
  if (this.deps.trajectory) {
245977
- const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content3]) => [name, (0, import_node_crypto71.createHash)("sha256").update(content3).digest("hex")]));
246243
+ const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content3]) => [name, (0, import_node_crypto70.createHash)("sha256").update(content3).digest("hex")]));
245978
246244
  const session = {
245979
246245
  runId: coordinatorRunId,
245980
246246
  runtimeSessionId,
@@ -246180,11 +246446,11 @@ var init_pg_ident = __esm({
246180
246446
  });
246181
246447
 
246182
246448
  // ../storage/src/postgres.ts
246183
- var import_node_crypto72, ident4, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
246449
+ var import_node_crypto71, ident4, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
246184
246450
  var init_postgres = __esm({
246185
246451
  "../storage/src/postgres.ts"() {
246186
246452
  "use strict";
246187
- import_node_crypto72 = require("node:crypto");
246453
+ import_node_crypto71 = require("node:crypto");
246188
246454
  init_pg_ident();
246189
246455
  init_esm();
246190
246456
  init_src();
@@ -246364,7 +246630,7 @@ var init_postgres = __esm({
246364
246630
  return new _PostgresBlobStore(pool, schema);
246365
246631
  }
246366
246632
  async put(bytes2) {
246367
- const hash3 = (0, import_node_crypto72.createHash)("sha256").update(bytes2).digest("hex");
246633
+ const hash3 = (0, import_node_crypto71.createHash)("sha256").update(bytes2).digest("hex");
246368
246634
  await this.pool.query(
246369
246635
  `INSERT INTO ${this.t} (hash, bytes, size, content_type) VALUES ($1, $2, $3, $4) ON CONFLICT (hash) DO NOTHING`,
246370
246636
  [hash3, Buffer.from(bytes2), bytes2.byteLength, sniffContentType(bytes2) ?? null]
@@ -254035,14 +254301,22 @@ var init_postgres_chat_items = __esm({
254035
254301
  * ——此刻起的 chunk 已经算下一段,但那条行还不存在。先写 `message_id = NULL`、行落库后回填,
254036
254302
  * 比让写入等一次 DB 往返更安全(等 = 丢帧或阻塞流)。
254037
254303
  *
254038
- * `message_id IS NULL` 天然把上一段隔开:上一段的 item 在切段之前就已经绑好了。
254304
+ * `message_id IS NULL` 只能把**已经绑好的**上一段隔开,隔不开**后面**的段:新行落库慢于下一次插话时,
254305
+ * 后一段的 item 同样是 NULL。所以切段回填给 `itemIds`,只更新那一段自己的 item(2026-09-17)。
254039
254306
  * 回填**不动** `version`/`turn_version`——归属关系不是 item 内容的一次修订。
254040
254307
  */
254041
- async bindMessage(sessionId, turnId, messageId) {
254042
- const r = await this.pool.query(
254308
+ async bindMessage(sessionId, turnId, messageId, itemIds) {
254309
+ if (itemIds && itemIds.length === 0) return 0;
254310
+ const r = itemIds ? await this.pool.query(
254311
+ `UPDATE ${this.s}.chat_items
254312
+ SET message_id = $3
254313
+ WHERE session_id = $1 AND turn_id = $2 AND message_id IS NULL AND role = 'assistant'
254314
+ AND id = ANY($4::text[])`,
254315
+ [sessionId, turnId, messageId, [...itemIds]]
254316
+ ) : await this.pool.query(
254043
254317
  `UPDATE ${this.s}.chat_items
254044
- SET message_id = $3
254045
- WHERE session_id = $1 AND turn_id = $2 AND message_id IS NULL AND role = 'assistant'`,
254318
+ SET message_id = $3
254319
+ WHERE session_id = $1 AND turn_id = $2 AND message_id IS NULL AND role = 'assistant'`,
254046
254320
  [sessionId, turnId, messageId]
254047
254321
  );
254048
254322
  return r.rowCount ?? 0;
@@ -255052,11 +255326,11 @@ var init_postgres_delegations = __esm({
255052
255326
  });
255053
255327
 
255054
255328
  // ../storage/src/postgres-nodes.ts
255055
- var import_node_crypto73, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
255329
+ var import_node_crypto72, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
255056
255330
  var init_postgres_nodes = __esm({
255057
255331
  "../storage/src/postgres-nodes.ts"() {
255058
255332
  "use strict";
255059
- import_node_crypto73 = require("node:crypto");
255333
+ import_node_crypto72 = require("node:crypto");
255060
255334
  init_pg_ident();
255061
255335
  init_esm();
255062
255336
  ident13 = (s2) => {
@@ -255273,7 +255547,7 @@ var init_postgres_nodes = __esm({
255273
255547
  return store;
255274
255548
  }
255275
255549
  issue(nodeId) {
255276
- const token = `ont_${(0, import_node_crypto73.randomBytes)(24).toString("base64url")}`;
255550
+ const token = `ont_${(0, import_node_crypto72.randomBytes)(24).toString("base64url")}`;
255277
255551
  this.cache.set(token, nodeId);
255278
255552
  void this.pool.query(`INSERT INTO ${this.s}.node_tokens (token,node_id) VALUES ($1,$2)`, [token, nodeId]);
255279
255553
  return token;
@@ -255539,11 +255813,11 @@ var init_postgres_type_registry = __esm({
255539
255813
  });
255540
255814
 
255541
255815
  // ../storage/src/postgres-actor-memory.ts
255542
- var import_node_crypto74, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
255816
+ var import_node_crypto73, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
255543
255817
  var init_postgres_actor_memory = __esm({
255544
255818
  "../storage/src/postgres-actor-memory.ts"() {
255545
255819
  "use strict";
255546
- import_node_crypto74 = require("node:crypto");
255820
+ import_node_crypto73 = require("node:crypto");
255547
255821
  init_pg_ident();
255548
255822
  init_src();
255549
255823
  matchClause = (q2) => q2.requireMatch && q2.keywords.length > 0 ? " WHERE m > 0" : "";
@@ -255753,7 +256027,7 @@ var init_postgres_actor_memory = __esm({
255753
256027
  }
255754
256028
  async write(input, now) {
255755
256029
  if (input.memId === void 0) {
255756
- const memId = `mem:${(0, import_node_crypto74.randomUUID)()}`;
256030
+ const memId = `mem:${(0, import_node_crypto73.randomUUID)()}`;
255757
256031
  const r2 = await this.pool.query(
255758
256032
  `INSERT INTO ${this.s}.actor_memories
255759
256033
  (mem_id, actor_id, project_id, keywords, content, version, created_at, updated_at, accessed_at, source_artifact_id, source_session_id)
@@ -257200,14 +257474,14 @@ function companySchemaName(companyId) {
257200
257474
  if (typeof companyId !== "string" || !companyId.trim()) {
257201
257475
  throw new Error("company schema mapping \u9700\u8981\u975E\u7A7A companyId\uFF08fail-closed\uFF1A\u4E0D\u56DE\u9000\u9ED8\u8BA4\u516C\u53F8\uFF09");
257202
257476
  }
257203
- const hex = (0, import_node_crypto75.createHash)("sha256").update(companyId, "utf8").digest("hex");
257477
+ const hex = (0, import_node_crypto74.createHash)("sha256").update(companyId, "utf8").digest("hex");
257204
257478
  return `${COMPANY_SCHEMA_PREFIX}${hex.slice(0, DIGEST_HEX_LEN)}`;
257205
257479
  }
257206
- var import_node_crypto75, COMPANY_SCHEMA_PREFIX, DIGEST_HEX_LEN, SchemaMappingConflictError, TenantStorageLocationStore;
257480
+ var import_node_crypto74, COMPANY_SCHEMA_PREFIX, DIGEST_HEX_LEN, SchemaMappingConflictError, TenantStorageLocationStore;
257207
257481
  var init_tenant_schema_mapping = __esm({
257208
257482
  "../storage/src/tenant-schema-mapping.ts"() {
257209
257483
  "use strict";
257210
- import_node_crypto75 = require("node:crypto");
257484
+ import_node_crypto74 = require("node:crypto");
257211
257485
  init_esm();
257212
257486
  init_pg_ident();
257213
257487
  COMPANY_SCHEMA_PREFIX = "c_";
@@ -257281,7 +257555,7 @@ var init_tenant_schema_mapping = __esm({
257281
257555
 
257282
257556
  // ../storage/src/company-data-plane-schema.ts
257283
257557
  function canonicalTypeDefHash(def) {
257284
- return `sha256:${(0, import_node_crypto76.createHash)("sha256").update(JSON.stringify(def), "utf8").digest("hex")}`;
257558
+ return `sha256:${(0, import_node_crypto75.createHash)("sha256").update(JSON.stringify(def), "utf8").digest("hex")}`;
257285
257559
  }
257286
257560
  function defaultArtifactTypeTemplate() {
257287
257561
  return [
@@ -257463,11 +257737,11 @@ async function seedDefaultArtifactTypes(pool, schema, templates = defaultArtifac
257463
257737
  }
257464
257738
  return { inserted };
257465
257739
  }
257466
- var import_node_crypto76, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
257740
+ var import_node_crypto75, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
257467
257741
  var init_company_data_plane_schema = __esm({
257468
257742
  "../storage/src/company-data-plane-schema.ts"() {
257469
257743
  "use strict";
257470
- import_node_crypto76 = require("node:crypto");
257744
+ import_node_crypto75 = require("node:crypto");
257471
257745
  init_esm();
257472
257746
  init_pg_ident();
257473
257747
  DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION = "4c368e5c-types-v1";
@@ -257547,11 +257821,11 @@ function rowToFile2(row) {
257547
257821
  updatedAt: new Date(row.updated_at).toISOString()
257548
257822
  };
257549
257823
  }
257550
- var import_node_crypto77, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
257824
+ var import_node_crypto76, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
257551
257825
  var init_company_owned_registry_store = __esm({
257552
257826
  "../storage/src/company-owned-registry-store.ts"() {
257553
257827
  "use strict";
257554
- import_node_crypto77 = require("node:crypto");
257828
+ import_node_crypto76 = require("node:crypto");
257555
257829
  init_esm();
257556
257830
  init_src();
257557
257831
  init_pg_ident();
@@ -257937,7 +258211,7 @@ var init_company_owned_registry_store = __esm({
257937
258211
  enabled: skill.enabled,
257938
258212
  files: files.map((f2) => ({ path: f2.path, blobHash: f2.blobHash }))
257939
258213
  });
257940
- return (0, import_node_crypto77.createHash)("sha256").update(body2).digest("hex");
258214
+ return (0, import_node_crypto76.createHash)("sha256").update(body2).digest("hex");
257941
258215
  }
257942
258216
  };
257943
258217
  }
@@ -260537,7 +260811,7 @@ function sourceSetConservation(input) {
260537
260811
  return input.sourceFinal === input.copied + input.approvedRuntimeConversion + input.approvedArchive + input.approvedDiscard;
260538
260812
  }
260539
260813
  function payloadChecksum(value2) {
260540
- return (0, import_node_crypto78.createHash)("sha256").update(JSON.stringify(value2)).digest("hex");
260814
+ return (0, import_node_crypto77.createHash)("sha256").update(JSON.stringify(value2)).digest("hex");
260541
260815
  }
260542
260816
  async function copyWorkorderDraftsIdempotent(pool, companyId, companySchema, manifestId, opts) {
260543
260817
  const cs = quoteIdent2(companySchema);
@@ -260925,11 +261199,11 @@ async function copyTraceFamilyIdempotent(pool, companyId, companySchema, manifes
260925
261199
  sequences
260926
261200
  };
260927
261201
  }
260928
- var import_node_crypto78, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
261202
+ var import_node_crypto77, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
260929
261203
  var init_tenant_migration_operator = __esm({
260930
261204
  "../storage/src/tenant-migration-operator.ts"() {
260931
261205
  "use strict";
260932
- import_node_crypto78 = require("node:crypto");
261206
+ import_node_crypto77 = require("node:crypto");
260933
261207
  init_esm();
260934
261208
  init_pg_ident();
260935
261209
  init_tenancy_attribution();
@@ -261161,8 +261435,8 @@ var init_tenant_new_company_provision = __esm({
261161
261435
  // ../storage/src/knowledge-snapshot-crypto.ts
261162
261436
  function sealSnapshot(snapshot, key2) {
261163
261437
  if (snapshot.content.startsWith(PREFIX3)) return structuredClone(snapshot);
261164
- const iv = (0, import_node_crypto79.randomBytes)(12);
261165
- const cipher = (0, import_node_crypto79.createCipheriv)("aes-256-gcm", bytes(key2), iv);
261438
+ const iv = (0, import_node_crypto78.randomBytes)(12);
261439
+ const cipher = (0, import_node_crypto78.createCipheriv)("aes-256-gcm", bytes(key2), iv);
261166
261440
  cipher.setAAD(aad2(snapshot));
261167
261441
  const encrypted = Buffer.concat([cipher.update(snapshot.content, "utf8"), cipher.final()]);
261168
261442
  return { ...structuredClone(snapshot), content: `${PREFIX3}${iv.toString("base64url")}.${cipher.getAuthTag().toString("base64url")}.${encrypted.toString("base64url")}` };
@@ -261172,7 +261446,7 @@ function openSnapshot(snapshot, key2) {
261172
261446
  const packed = snapshot.content.slice(PREFIX3.length).split(".");
261173
261447
  if (packed.length !== 3 || packed.some((part) => !part)) throw new Error(`knowledge snapshot ${snapshot.ref} ciphertext is malformed`);
261174
261448
  const [iv, tag, data] = packed;
261175
- const decipher = (0, import_node_crypto79.createDecipheriv)("aes-256-gcm", bytes(key2), Buffer.from(iv, "base64url"));
261449
+ const decipher = (0, import_node_crypto78.createDecipheriv)("aes-256-gcm", bytes(key2), Buffer.from(iv, "base64url"));
261176
261450
  decipher.setAAD(aad2(snapshot));
261177
261451
  decipher.setAuthTag(Buffer.from(tag, "base64url"));
261178
261452
  return {
@@ -261180,11 +261454,11 @@ function openSnapshot(snapshot, key2) {
261180
261454
  content: Buffer.concat([decipher.update(Buffer.from(data, "base64url")), decipher.final()]).toString("utf8")
261181
261455
  };
261182
261456
  }
261183
- var import_node_crypto79, PREFIX3, bytes, aad2;
261457
+ var import_node_crypto78, PREFIX3, bytes, aad2;
261184
261458
  var init_knowledge_snapshot_crypto = __esm({
261185
261459
  "../storage/src/knowledge-snapshot-crypto.ts"() {
261186
261460
  "use strict";
261187
- import_node_crypto79 = require("node:crypto");
261461
+ import_node_crypto78 = require("node:crypto");
261188
261462
  PREFIX3 = "oasis-knowledge:v1:";
261189
261463
  bytes = (key2) => {
261190
261464
  const value2 = Buffer.from(key2);
@@ -263438,14 +263712,14 @@ init_src();
263438
263712
  var os14 = __toESM(require("node:os"));
263439
263713
  var path39 = __toESM(require("node:path"));
263440
263714
  var import_node_child_process22 = require("node:child_process");
263441
- var import_node_crypto90 = require("node:crypto");
263715
+ var import_node_crypto89 = require("node:crypto");
263442
263716
 
263443
263717
  // ../cli/src/cli.ts
263444
263718
  init_src();
263445
263719
  var fs39 = __toESM(require("node:fs"), 1);
263446
263720
  var os12 = __toESM(require("node:os"), 1);
263447
263721
  var path34 = __toESM(require("node:path"), 1);
263448
- var import_node_crypto88 = require("node:crypto");
263722
+ var import_node_crypto87 = require("node:crypto");
263449
263723
 
263450
263724
  // ../cli/src/serve.ts
263451
263725
  init_src();
@@ -263454,11 +263728,11 @@ var fs33 = __toESM(require("node:fs"), 1);
263454
263728
  var os8 = __toESM(require("node:os"), 1);
263455
263729
  var path28 = __toESM(require("node:path"), 1);
263456
263730
  var import_node_child_process17 = require("node:child_process");
263457
- var import_node_crypto84 = require("node:crypto");
263731
+ var import_node_crypto83 = require("node:crypto");
263458
263732
  var import_node_url7 = require("node:url");
263459
263733
 
263460
263734
  // ../cli/src/artifact-discovery-source.ts
263461
- var import_node_crypto80 = require("node:crypto");
263735
+ var import_node_crypto79 = require("node:crypto");
263462
263736
  init_src7();
263463
263737
  var ArtifactDiscoverySourceUnreadableError = class extends Error {
263464
263738
  constructor(message) {
@@ -263488,7 +263762,7 @@ async function resolveArtifactDiscoverySourceContent(blobs, input) {
263488
263762
  throw new ArtifactDiscoverySourceUnreadableError(`Artifact Revision ${input.revisionId} \u7684 contentKind \u4E0D\u53D7\u652F\u6301\uFF1A${String(input.contentKind)}`);
263489
263763
  }
263490
263764
  const rootBytes = await blobs.get(input.contentRef);
263491
- const sourceFingerprint = (0, import_node_crypto80.createHash)("sha256").update(rootBytes).digest("hex");
263765
+ const sourceFingerprint = (0, import_node_crypto79.createHash)("sha256").update(rootBytes).digest("hex");
263492
263766
  if (input.contentKind === "inline-blob") {
263493
263767
  const sourceContent = decodeText3(rootBytes);
263494
263768
  if (sourceContent === null) {
@@ -263512,7 +263786,7 @@ ${entry.text}`).join("\n\n"),
263512
263786
  }
263513
263787
 
263514
263788
  // ../server/src/domains/discussions/service.ts
263515
- var import_node_crypto81 = require("node:crypto");
263789
+ var import_node_crypto80 = require("node:crypto");
263516
263790
  init_zod();
263517
263791
 
263518
263792
  // ../contract/src/discussions.ts
@@ -263549,7 +263823,7 @@ var generationSchema = external_exports.discriminatedUnion("result", [
263549
263823
  }).strict()
263550
263824
  ]);
263551
263825
  var entryId = (projectId2, id) => JSON.stringify([projectId2, id]);
263552
- var hash2 = (s2) => (0, import_node_crypto81.createHash)("sha256").update(s2).digest("hex").slice(0, 24);
263826
+ var hash2 = (s2) => (0, import_node_crypto80.createHash)("sha256").update(s2).digest("hex").slice(0, 24);
263553
263827
  var canonical = (s2) => s2.normalize("NFKC").trim().toLocaleLowerCase();
263554
263828
  function decisionFingerprint(d) {
263555
263829
  return hash2(JSON.stringify([d.statement, d.rationale, d.alternatives]));
@@ -263704,7 +263978,7 @@ var DiscussionService = class {
263704
263978
  let id = t.topic_id;
263705
263979
  if (id.startsWith("new:")) {
263706
263980
  const existing = [...topicMap.values()].find((x2) => canonical(x2.title) === canonical(t.title));
263707
- id = existing?.topicId ?? `topic_${(0, import_node_crypto81.randomUUID)()}`;
263981
+ id = existing?.topicId ?? `topic_${(0, import_node_crypto80.randomUUID)()}`;
263708
263982
  } else if (!topicMap.has(id)) invalid("topics.topic_id", `\u8BAE\u9898 ${id} \u4E0D\u5B58\u5728\uFF1B\u65B0\u8BAE\u9898\u4F7F\u7528 new: \u524D\u7F00`);
263709
263983
  topicAliases.set(t.topic_id, id);
263710
263984
  const value2 = { projectId: input.projectId, topicId: id, title: t.title };
@@ -263725,7 +263999,7 @@ var DiscussionService = class {
263725
263999
  if (id.startsWith("new:")) {
263726
264000
  const candidateKey = JSON.stringify([topicId, canonical(d.record.statement)]);
263727
264001
  const existing = [...decisionMap.values()].find((x2) => x2.topicId === topicId && canonical(x2.record.statement) === canonical(d.record.statement));
263728
- id = existing?.decisionId ?? candidateIds.get(candidateKey) ?? `decision_${(0, import_node_crypto81.randomUUID)()}`;
264002
+ id = existing?.decisionId ?? candidateIds.get(candidateKey) ?? `decision_${(0, import_node_crypto80.randomUUID)()}`;
263729
264003
  candidateIds.set(candidateKey, id);
263730
264004
  } else if (!decisionMap.has(id)) invalid("decision_id", `\u51B3\u7B56 ${id} \u4E0D\u5B58\u5728\uFF1B\u65B0\u51B3\u7B56\u4F7F\u7528 new: \u524D\u7F00`);
263731
264005
  decisionAliases.set(d.decision_id, id);
@@ -263858,7 +264132,7 @@ var DiscussionService = class {
263858
264132
  });
263859
264133
  }
263860
264134
  async operation(tx, actorId, kind, id, from, to) {
263861
- await tx.put("operation", (0, import_node_crypto81.randomUUID)(), { actorId, kind, id, from, to, at: new Date(this.now()).toISOString() });
264135
+ await tx.put("operation", (0, import_node_crypto80.randomUUID)(), { actorId, kind, id, from, to, at: new Date(this.now()).toISOString() });
263862
264136
  }
263863
264137
  };
263864
264138
  function pickDiscussionContent(d) {
@@ -264004,7 +264278,7 @@ var DiscussionContext = class {
264004
264278
  };
264005
264279
 
264006
264280
  // ../server/src/domains/discussions/worker.ts
264007
- var import_node_crypto82 = require("node:crypto");
264281
+ var import_node_crypto81 = require("node:crypto");
264008
264282
  var DISCUSSION_IDLE_MS = 15 * 6e4;
264009
264283
  var DISCUSSION_MAX_CORRECTIONS = 3;
264010
264284
  var DISCUSSION_LEASE_MS = 2 * 6e4;
@@ -264030,7 +264304,7 @@ var DiscussionWorker = class {
264030
264304
  if (await this.isReplying(sessionId)) return false;
264031
264305
  const cutoff = eligibleCutoff(await this.service.chat.listMessages(sessionId), this.service.now());
264032
264306
  if (!cutoff) return false;
264033
- const owner = (0, import_node_crypto82.randomUUID)();
264307
+ const owner = (0, import_node_crypto81.randomUUID)();
264034
264308
  const claimed = await this.service.store.transaction(async (tx) => {
264035
264309
  const old = (await tx.get("job", sessionId))?.value;
264036
264310
  if (old?.status === "running" && (old.leaseUntil ?? 0) > this.service.now()) return null;
@@ -264463,7 +264737,7 @@ function createDiscussionRuntime(options) {
264463
264737
  }
264464
264738
 
264465
264739
  // ../server/src/domains/discussions/store.ts
264466
- var import_node_crypto83 = require("node:crypto");
264740
+ var import_node_crypto82 = require("node:crypto");
264467
264741
  var import_promises16 = require("node:fs/promises");
264468
264742
  var import_node_path27 = require("node:path");
264469
264743
  var key = (kind, id) => JSON.stringify([kind, id]);
@@ -264501,7 +264775,7 @@ var MemoryDiscussionStore = class _MemoryDiscussionStore {
264501
264775
  if (changed) {
264502
264776
  if (this.filename) {
264503
264777
  await (0, import_promises16.mkdir)((0, import_node_path27.dirname)(this.filename), { recursive: true });
264504
- const temp = `${this.filename}.${(0, import_node_crypto83.randomUUID)()}.tmp`;
264778
+ const temp = `${this.filename}.${(0, import_node_crypto82.randomUUID)()}.tmp`;
264505
264779
  await (0, import_promises16.writeFile)(temp, JSON.stringify(next), "utf8");
264506
264780
  await (0, import_promises16.rename)(temp, this.filename);
264507
264781
  }
@@ -265269,7 +265543,7 @@ init_src9();
265269
265543
  init_src();
265270
265544
  init_trajectory();
265271
265545
  function deterministicUuid2(seed) {
265272
- const h = (0, import_node_crypto84.createHash)("sha256").update(seed).digest();
265546
+ const h = (0, import_node_crypto83.createHash)("sha256").update(seed).digest();
265273
265547
  const b2 = Buffer.from(h.subarray(0, 16));
265274
265548
  b2[6] = b2[6] & 15 | 80;
265275
265549
  b2[8] = b2[8] & 63 | 128;
@@ -265469,7 +265743,7 @@ function oasisWrapperScript() {
265469
265743
  const repoRoot = (0, import_node_url7.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
265470
265744
  const tsx = path28.join(repoRoot, "node_modules/.bin/tsx");
265471
265745
  const main = path28.join(repoRoot, "packages/cli/src/main.ts");
265472
- const key2 = (0, import_node_crypto84.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 12);
265746
+ const key2 = (0, import_node_crypto83.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 12);
265473
265747
  const uid = typeof process.getuid === "function" ? process.getuid() : "na";
265474
265748
  const stable = path28.join(os8.tmpdir(), `oasis-cli-wrapper-${uid}-${key2}`);
265475
265749
  let dir;
@@ -265855,7 +266129,7 @@ async function startServe(opts) {
265855
266129
  if (!hub) return Promise.resolve({ ok: false, reason: "\u672C\u8FDB\u7A0B\u6CA1\u6709\u8282\u70B9\u7F51\u5173" });
265856
266130
  const online = hub.connectedDaemons().some((d) => d.daemonId === nodeId);
265857
266131
  if (!online) return Promise.resolve({ ok: false, reason: "\u8282\u70B9\u5F53\u524D\u4E0D\u5728\u7EBF\uFF0C\u7528\u7684\u662F\u5B83\u4E0A\u6B21\u62A5\u7684\u6E05\u5355" });
265858
- const requestId = (0, import_node_crypto84.randomUUID)();
266132
+ const requestId = (0, import_node_crypto83.randomUUID)();
265859
266133
  const task = new Promise((resolve10) => {
265860
266134
  const timer = setTimeout(() => {
265861
266135
  modelRefreshWaiters.delete(requestId);
@@ -266623,7 +266897,7 @@ async function startServe(opts) {
266623
266897
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
266624
266898
  if (buf || session.runId) {
266625
266899
  await turnStore.appendMessage({
266626
- id: (0, import_node_crypto84.randomUUID)(),
266900
+ id: (0, import_node_crypto83.randomUUID)(),
266627
266901
  sessionId: chatSessionId,
266628
266902
  role: "assistant",
266629
266903
  content: buf,
@@ -266669,7 +266943,7 @@ async function startServe(opts) {
266669
266943
  chatTargets: {
266670
266944
  inspect: inspectChatTarget,
266671
266945
  createTargetSession: async ({ companyId, agentId, ownerHumanActorId, title }) => {
266672
- const sessionId = (0, import_node_crypto84.randomUUID)();
266946
+ const sessionId = (0, import_node_crypto83.randomUUID)();
266673
266947
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
266674
266948
  const binding = await registryStore.getBinding(agentId).catch(() => null);
266675
266949
  await (await chatStoreFor(companyId)).createSession({
@@ -267815,7 +268089,7 @@ async function startServe(opts) {
267815
268089
  }));
267816
268090
  }
267817
268091
  const limits = { wallClockMs: wallClockForKind("planner") };
267818
- const artifactId = `artifact:planner:${(0, import_node_crypto84.randomUUID)()}`;
268092
+ const artifactId = `artifact:planner:${(0, import_node_crypto83.randomUUID)()}`;
267819
268093
  const handle = await chatRemoteAdapter.spawn({
267820
268094
  actor: planner.id,
267821
268095
  // companyId 同协调者:planner 用的就是默认公司的 actors 服务,漏签会让它一调公司域端点就 404。
@@ -268111,7 +268385,9 @@ async function startServe(opts) {
268111
268385
  // v3 快照端点(ws:wo-81de04dc S3 恢复路径唯一入口):`chat_items` 权威读口 +
268112
268386
  // live 注册表算 `frameCursor`。与 server.ts 的双写解析同一层,与 attach 路径共享 registry。
268113
268387
  resolveChatItems: (companyId) => chatItemsFor(companyId),
268114
- liveChat
268388
+ liveChat,
268389
+ // ADR-0587:列表行派生 `unread`,与 collab 域收件箱读同一份按公司的水位(无 PG 时闭包自己回落单实例)。
268390
+ resolveReadMarkers: readMarkerStoreFor
268115
268391
  })] : []
268116
268392
  ],
268117
268393
  workorderDrafts,
@@ -268698,10 +268974,10 @@ async function startServe(opts) {
268698
268974
  if (isDeletedActor(dispatchTarget)) {
268699
268975
  throw new ApiError(409, ACTOR_DELETED_ERROR_CODE, ACTOR_DELETED_CHAT_MESSAGE);
268700
268976
  }
268701
- const runtimeSessionId = sessionId ?? (requestDispatchId ? deterministicUuid2(`chat-runtime:${requestDispatchId}`) : (0, import_node_crypto84.randomUUID)());
268977
+ const runtimeSessionId = sessionId ?? (requestDispatchId ? deterministicUuid2(`chat-runtime:${requestDispatchId}`) : (0, import_node_crypto83.randomUUID)());
268702
268978
  const resumeRuntimeSession = Boolean(sessionId);
268703
- const traceRunId = requestDispatchId ? `chat-run:${deterministicUuid2(`chat-trace:${requestDispatchId}`)}` : `chat-run:${(0, import_node_crypto84.randomUUID)()}`;
268704
- const artifactId = requestDispatchId ? `artifact:chat:${deterministicUuid2(`chat-artifact:${requestDispatchId}`)}` : `artifact:chat:${(0, import_node_crypto84.randomUUID)()}`;
268979
+ const traceRunId = requestDispatchId ? `chat-run:${deterministicUuid2(`chat-trace:${requestDispatchId}`)}` : `chat-run:${(0, import_node_crypto83.randomUUID)()}`;
268980
+ const artifactId = requestDispatchId ? `artifact:chat:${deterministicUuid2(`chat-artifact:${requestDispatchId}`)}` : `artifact:chat:${(0, import_node_crypto83.randomUUID)()}`;
268705
268981
  let stableDispatch = requestDispatchId ? await resolveStableChatDispatch({ store: await traceStoreFor(runCompanyId), runId: traceRunId }) : null;
268706
268982
  if (stableDispatch && !stableDispatch.shouldDispatch) {
268707
268983
  return {
@@ -269881,7 +270157,7 @@ async function startServe(opts) {
269881
270157
  console.log(`[new-engine] dispatchWork ${workId} \u662F human work\u2014\u2014\u4E0D\u6D3E runtime\uFF0C\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406`);
269882
270158
  return;
269883
270159
  }
269884
- const ledgerId = `dispatch:${(0, import_node_crypto84.randomUUID)()}`;
270160
+ const ledgerId = `dispatch:${(0, import_node_crypto83.randomUUID)()}`;
269885
270161
  try {
269886
270162
  await (await dispatchLedgerFor(companyId)).insertOpen({
269887
270163
  id: ledgerId,
@@ -269966,7 +270242,7 @@ async function startServe(opts) {
269966
270242
  const kernelModel = newKernel.model;
269967
270243
  const art = kernelModel.artifacts.get(nodeId);
269968
270244
  const revWid = art?.workspace ?? "";
269969
- const ledgerId = `dispatch:${(0, import_node_crypto84.randomUUID)()}`;
270245
+ const ledgerId = `dispatch:${(0, import_node_crypto83.randomUUID)()}`;
269970
270246
  try {
269971
270247
  await (await dispatchLedgerFor(companyId)).insertOpen({
269972
270248
  id: ledgerId,
@@ -270340,7 +270616,7 @@ async function startServe(opts) {
270340
270616
  resolveSupplementalContext: server.knowledgeGovernance ? async ({ actorId, artifactId, workOrderId, action, part, runtimeKind, taskMarkdown }) => {
270341
270617
  try {
270342
270618
  const governance = server.knowledgeGovernance;
270343
- const taskFingerprint = (0, import_node_crypto84.createHash)("sha256").update(JSON.stringify({ artifactId, workOrderId, action, part: part ?? null, taskMarkdown })).digest("hex");
270619
+ const taskFingerprint = (0, import_node_crypto83.createHash)("sha256").update(JSON.stringify({ artifactId, workOrderId, action, part: part ?? null, taskMarkdown })).digest("hex");
270344
270620
  const requestContext = {
270345
270621
  organizationId: companyId,
270346
270622
  actorId,
@@ -270804,10 +271080,10 @@ async function startServe(opts) {
270804
271080
  if (h < 24) return `${h} \u5C0F\u65F6\u524D`;
270805
271081
  return `${Math.floor(h / 24)} \u5929\u524D`;
270806
271082
  };
270807
- const humanFailCause = (reason, errorMessage) => {
270808
- const quote = errorMessage?.trim().slice(0, 300);
271083
+ const humanFailCause = (reason, errorMessage2) => {
271084
+ const quote = errorMessage2?.trim().slice(0, 300);
270809
271085
  if (reason === "rate-limit") {
270810
- const hint = extractResetHint(errorMessage);
271086
+ const hint = extractResetHint(errorMessage2);
270811
271087
  return `**\u5B83\u7684\u989D\u5EA6\u88AB\u6253\u6EE1\u4E86**${hint ? `\uFF08runtime \u539F\u8BDD\uFF1A\`${hint}\`\uFF09` : ""}\u2014\u2014\u989D\u5EA6\u5230\u70B9\u4F1A\u81EA\u5DF1\u6062\u590D\uFF0C**\u4E0D\u9700\u8981\u4F60\u4FEE\u4EFB\u4F55\u4E1C\u897F**\uFF1B\u4E0D\u60F3\u7B49\u5C31\u7ED9\u8FD9\u4E2A\u5458\u5DE5\u6362\u4E2A runtime\u3002` + (quote ? `
270812
271088
  > ${quote}` : "");
270813
271089
  }
@@ -271957,11 +272233,11 @@ async function runSession(dispatchId, job, deps) {
271957
272233
  } catch (err) {
271958
272234
  deps.onFailure?.();
271959
272235
  log2("[node-cli]", ` spawn failed: ${err}`);
271960
- const errorMessage = String(err?.stack ?? err).slice(0, 8e3);
272236
+ const errorMessage2 = String(err?.stack ?? err).slice(0, 8e3);
271961
272237
  deps.sink.keyFrame({ type: "session_exited", dispatchId, sessionId: "spawn-failed", info: {
271962
272238
  code: null,
271963
272239
  reason: "error",
271964
- errorMessage,
272240
+ errorMessage: errorMessage2,
271965
272241
  ...err instanceof BundleRestoreError ? { fileRestoreError: { path: err.filePath, message: err.message } } : {}
271966
272242
  } });
271967
272243
  }
@@ -271970,7 +272246,6 @@ async function runSession(dispatchId, job, deps) {
271970
272246
  // ../cli/src/daemon/session-process.ts
271971
272247
  var RECONNECT_MIN_MS = 1e3;
271972
272248
  var RECONNECT_MAX_MS = 3e4;
271973
- var EXIT_ACK_WAIT_MS = LINK_WINDOWS.exitAckWaitMs;
271974
272249
  var OUTBOX_MAX_FRAMES = 8e3;
271975
272250
  var isKeyFrame = (m2) => m2.type === "session_started" || m2.type === "session_exited";
271976
272251
  async function runSessionProcess(specFile, adapter) {
@@ -272016,6 +272291,7 @@ var SessionProcessState = class {
272016
272291
  this.hardTtlMs = o.hardTtlMs ?? SESSION_LIMITS.hardTtlMs;
272017
272292
  this.suicideAfterUnreachableMs = o.suicideAfterUnreachableMs ?? SESSION_LIMITS.suicideAfterUnreachableMs;
272018
272293
  this.watchdogIntervalMs = o.watchdogIntervalMs ?? 3e4;
272294
+ this.exitAckWaitMs = o.exitAckWaitMs ?? LINK_WINDOWS.exitAckWaitMs;
272019
272295
  }
272020
272296
  ws = null;
272021
272297
  connected = false;
@@ -272029,6 +272305,8 @@ var SessionProcessState = class {
272029
272305
  hardTtlMs;
272030
272306
  suicideAfterUnreachableMs;
272031
272307
  watchdogIntervalMs;
272308
+ /** 发完 session_exited 后等 ack 的时限;等不到也退——outbox 那份由服务端 L3 时钟扫描兜底。 */
272309
+ exitAckWaitMs;
272032
272310
  watchdog = null;
272033
272311
  exitAckResolve = null;
272034
272312
  /** 整个会话进程「可以退了」的信号:会话正常退出、或被 evict / TTL / 失联自尽。 */
@@ -272251,7 +272529,7 @@ var SessionProcessState = class {
272251
272529
  this.exitAckResolve = null;
272252
272530
  resolve10();
272253
272531
  };
272254
- const timer = setTimeout(finish, EXIT_ACK_WAIT_MS);
272532
+ const timer = setTimeout(finish, this.exitAckWaitMs);
272255
272533
  this.exitAckResolve = finish;
272256
272534
  });
272257
272535
  }
@@ -272274,7 +272552,7 @@ var SessionProcessState = class {
272274
272552
  init_wrapper();
272275
272553
  init_src();
272276
272554
  var import_node_os9 = require("node:os");
272277
- var import_node_crypto85 = require("node:crypto");
272555
+ var import_node_crypto84 = require("node:crypto");
272278
272556
  init_src8();
272279
272557
  init_src2();
272280
272558
 
@@ -273059,7 +273337,7 @@ var DaemonWsClient = class {
273059
273337
  */
273060
273338
  async queryArtifactStates(artifactIds, timeoutMs = 15e3) {
273061
273339
  if (artifactIds.length === 0) return {};
273062
- const requestId = (0, import_node_crypto85.randomUUID)();
273340
+ const requestId = (0, import_node_crypto84.randomUUID)();
273063
273341
  return new Promise((resolve10, reject) => {
273064
273342
  const timer = setTimeout(() => {
273065
273343
  this.gcPending.delete(requestId);
@@ -273465,7 +273743,7 @@ function forwardSignal(child, sig, killGroup = (pgid, signal) => {
273465
273743
  // ../cli/src/daemon/machine-id.ts
273466
273744
  var import_node_child_process20 = require("node:child_process");
273467
273745
  var import_node_fs22 = require("node:fs");
273468
- var import_node_crypto86 = require("node:crypto");
273746
+ var import_node_crypto85 = require("node:crypto");
273469
273747
  var import_node_os12 = require("node:os");
273470
273748
  function linuxMachineId() {
273471
273749
  for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
@@ -273531,7 +273809,7 @@ var defaultSources = {
273531
273809
  function resolveNodeId(sources = {}) {
273532
273810
  const s2 = { ...defaultSources, ...sources };
273533
273811
  const material = `${s2.machineFingerprint()}:${s2.osUser()}`;
273534
- const digest = (0, import_node_crypto86.createHash)("sha256").update(material).digest("hex").slice(0, 12);
273812
+ const digest = (0, import_node_crypto85.createHash)("sha256").update(material).digest("hex").slice(0, 12);
273535
273813
  return `node-${digest}`;
273536
273814
  }
273537
273815
 
@@ -275302,13 +275580,13 @@ function ownFlagsFromDecl(command) {
275302
275580
  }
275303
275581
 
275304
275582
  // ../cli/src/connector-effect-runner.ts
275305
- var import_node_crypto87 = require("node:crypto");
275583
+ var import_node_crypto86 = require("node:crypto");
275306
275584
  var import_node_child_process21 = require("node:child_process");
275307
275585
  init_src8();
275308
275586
  function stableKey(effect) {
275309
275587
  const keyFields = Object.fromEntries(Object.entries(effect.keyFields).sort(([a], [b2]) => a.localeCompare(b2)));
275310
275588
  const canonical2 = JSON.stringify({ kind: effect.kind, target: effect.target, keyFields });
275311
- return `sha256:${(0, import_node_crypto87.createHash)("sha256").update(canonical2).digest("hex")}`;
275589
+ return `sha256:${(0, import_node_crypto86.createHash)("sha256").update(canonical2).digest("hex")}`;
275312
275590
  }
275313
275591
  async function runConnectorEffect(connector, toolArgs, deps) {
275314
275592
  const effect = connector.describeExternalEffect?.(toolArgs) ?? null;
@@ -275545,7 +275823,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
275545
275823
  const timeoutMs = resolveAuthWaitMs();
275546
275824
  const controller = new AbortController();
275547
275825
  const timer = setTimeout(() => controller.abort(), timeoutMs);
275548
- const waiterId = (0, import_node_crypto88.randomUUID)();
275826
+ const waiterId = (0, import_node_crypto87.randomUUID)();
275549
275827
  process.stderr.write(
275550
275828
  `\u7B49\u5F85\u4EBA\u5DE5\u6388\u6743\u4E2D\u2026\u2026\u6700\u591A ${Math.round(timeoutMs / 1e3)} \u79D2\uFF0C\u8D85\u65F6\u4F1A\u81EA\u52A8\u8F6C\u4E3A\u5F85\u5BA1\uFF1B\u8FD9\u4E0D\u662F\u9519\u8BEF\uFF0C\u8BF7\u52FF\u91CD\u8BD5\u540C\u4E00\u6761\u547D\u4EE4\u3002
275551
275829
  `
@@ -275973,7 +276251,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
275973
276251
  const store = createNodeTokenStore(path34.join(dir, "node-tokens.json"));
275974
276252
  const sub = positional[0];
275975
276253
  if (sub === "issue") {
275976
- const id = flags.get("id") ?? `node-${(0, import_node_crypto88.randomUUID)()}`;
276254
+ const id = flags.get("id") ?? `node-${(0, import_node_crypto87.randomUUID)()}`;
275977
276255
  const token2 = store.issue(id);
275978
276256
  println(token2);
275979
276257
  process.stderr.write(
@@ -277004,7 +277282,7 @@ ${t.ref} ${t.name} [\u5267\u672C=${t.playbookRef}]`);
277004
277282
  if (!name) throw new Error("\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
277005
277283
  const goal = flags.get("goal")?.trim();
277006
277284
  const slug6 = flags.get("slug");
277007
- const id = slug6 !== void 0 ? projectSlug(slug6) : projectIdFromUuid((0, import_node_crypto88.randomUUID)());
277285
+ const id = slug6 !== void 0 ? projectSlug(slug6) : projectIdFromUuid((0, import_node_crypto87.randomUUID)());
277008
277286
  const result = await api.request("POST", "/api/projects", {
277009
277287
  ...id ? { id } : {},
277010
277288
  name,
@@ -278996,13 +279274,13 @@ var LEGACY_SYSTEMD_UNIT = "oasis-node.service";
278996
279274
  var LEGACY_LAUNCHD_LABEL = "com.oasis.node";
278997
279275
 
278998
279276
  // src/install.ts
278999
- var import_node_crypto89 = require("node:crypto");
279277
+ var import_node_crypto88 = require("node:crypto");
279000
279278
  var fs43 = __toESM(require("node:fs"));
279001
279279
  var path38 = __toESM(require("node:path"));
279002
279280
  function versionLabel(sourceFile, pkgVersion, npmPrefixDir, pkgName = LEGACY_PKG_NAME) {
279003
279281
  const tag = pkgName === LEGACY_PKG_NAME ? "" : `+pkg.${pkgName.replace(/[^A-Za-z0-9-]/g, "-")}`;
279004
279282
  if (isFromNpmPrefix(sourceFile, npmPrefixDir)) return `${pkgVersion}${tag}`;
279005
- const digest = (0, import_node_crypto89.createHash)("sha256").update(fs43.readFileSync(sourceFile)).digest("hex").slice(0, 8);
279283
+ const digest = (0, import_node_crypto88.createHash)("sha256").update(fs43.readFileSync(sourceFile)).digest("hex").slice(0, 8);
279006
279284
  return tag ? `${pkgVersion}${tag}.local.${digest}` : `${pkgVersion}+local.${digest}`;
279007
279285
  }
279008
279286
  function isFromNpmPrefix(sourceFile, npmPrefixDir) {
@@ -279125,7 +279403,7 @@ function shimScript() {
279125
279403
  }
279126
279404
 
279127
279405
  // src/index.ts
279128
- var PKG_VERSION = true ? "2.2.48" : "dev";
279406
+ var PKG_VERSION = true ? "2.2.49" : "dev";
279129
279407
  var LOCAL_BIN = localBin();
279130
279408
  var NPM_PREFIX = npmPrefix();
279131
279409
  var INSTANCE = DEFAULT_INSTANCE;
@@ -279410,7 +279688,7 @@ function newInstanceName() {
279410
279688
  const existing = new Set(listInstances());
279411
279689
  if (!existing.has(DEFAULT_INSTANCE)) return DEFAULT_INSTANCE;
279412
279690
  for (; ; ) {
279413
- const n = `inst-${(0, import_node_crypto90.randomBytes)(3).toString("hex")}`;
279691
+ const n = `inst-${(0, import_node_crypto89.randomBytes)(3).toString("hex")}`;
279414
279692
  if (!existing.has(n)) return n;
279415
279693
  }
279416
279694
  }