oasis_test_v2 2.2.47 → 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 +1004 -638
  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
  };
@@ -5952,6 +5955,9 @@ var init_protocol_session = __esm({
5952
5955
  function codexNum(v2) {
5953
5956
  return typeof v2 === "number" && Number.isFinite(v2) ? v2 : 0;
5954
5957
  }
5958
+ function codexOutputTokens(output, reasoning) {
5959
+ return Math.max(codexNum(output), codexNum(reasoning));
5960
+ }
5955
5961
  function parseCodexAppServerTurnUsage(params) {
5956
5962
  const p2 = params;
5957
5963
  const last = p2?.tokenUsage?.last;
@@ -5960,7 +5966,7 @@ function parseCodexAppServerTurnUsage(params) {
5960
5966
  input: codexNum(last["inputTokens"]),
5961
5967
  cacheRead: codexNum(last["cachedInputTokens"]),
5962
5968
  cacheWrite: codexNum(last["cacheWriteInputTokens"]),
5963
- output: codexNum(last["outputTokens"]) + codexNum(last["reasoningOutputTokens"])
5969
+ output: codexOutputTokens(last["outputTokens"], last["reasoningOutputTokens"])
5964
5970
  };
5965
5971
  }
5966
5972
  function addCodexBuckets(acc, add) {
@@ -5992,8 +5998,8 @@ function bucketsOf(usage) {
5992
5998
  // cache 字段名跨版本有两种
5993
5999
  cacheRead: codexNum(usage["cached_input_tokens"]) || codexNum(usage["cache_read_input_tokens"]),
5994
6000
  cacheWrite: codexNum(usage["cache_write_input_tokens"]),
5995
- // reasoning 计入 output(对齐 Multica:codex 的 output_tokens 不含 reasoning)。
5996
- output: codexNum(usage["output_tokens"]) + codexNum(usage["reasoning_output_tokens"])
6001
+ // output_tokens 已含 reasoning,见 codexOutputTokens。
6002
+ output: codexOutputTokens(usage["output_tokens"], usage["reasoning_output_tokens"])
5997
6003
  };
5998
6004
  }
5999
6005
  function usageFromBuckets(b2) {
@@ -6903,7 +6909,7 @@ ${task}` : task;
6903
6909
  const runtimeSessionId = telemetry.sessionId ?? resumeSessionId;
6904
6910
  const sawStreamError = streamErrorMessages.length > 0;
6905
6911
  const isError = !killReason && (code2 !== 0 || sawStreamError);
6906
- 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;
6907
6913
  const sessionNotFound = !killReason && isResume && isSessionNotFoundError([...streamErrorMessages, ...stdoutErrorLines, ...stderrTail2].join("\n"));
6908
6914
  const reason = killReason ? killReason : sessionNotFound ? "session-not-found" : sawStreamError ? "error" : code2 === 0 ? "clean" : "error";
6909
6915
  finish({
@@ -6916,7 +6922,7 @@ ${task}` : task;
6916
6922
  ...telemetry.usage ? { usage: telemetry.usage } : {},
6917
6923
  ...model ? { model } : {},
6918
6924
  ...runtimeSessionId ? { runtimeSessionId } : {},
6919
- ...errorMessage ? { errorMessage } : {},
6925
+ ...errorMessage2 ? { errorMessage: errorMessage2 } : {},
6920
6926
  // 跑在哪个镜像上要落账:同一题换了镜像,分数就不可比(ADR-0147 M5)
6921
6927
  ...containerized?.imageDigest ? { imageDigest: containerized.imageDigest } : {}
6922
6928
  });
@@ -8236,8 +8242,8 @@ function classifyHermesPromptFailure(turn) {
8236
8242
  if (errors.length === 0 && !failedSentence && !silent) return void 0;
8237
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)}`);
8238
8244
  }
8239
- function failure(errorMessage) {
8240
- 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 };
8241
8247
  }
8242
8248
  function promptResultHasUsage(result) {
8243
8249
  if (typeof result !== "object" || result === null) return false;
@@ -9000,12 +9006,12 @@ async function runStreamJson(job, cfg) {
9000
9006
  child.on("exit", (code2) => {
9001
9007
  clearTimeout(timer);
9002
9008
  const isError = !killReason && typeof code2 === "number" && code2 !== 0;
9003
- 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;
9004
9010
  finish({
9005
9011
  code: killReason ? null : code2,
9006
9012
  reason: killReason ?? (code2 === 0 ? "clean" : "error"),
9007
9013
  transcriptRef: transcriptRefPtr.value,
9008
- ...errorMessage ? { errorMessage } : {},
9014
+ ...errorMessage2 ? { errorMessage: errorMessage2 } : {},
9009
9015
  // ADR-0147:容器化会话带上镜像 digest(可复现凭据)。
9010
9016
  ...containerized?.imageDigest ? { imageDigest: containerized.imageDigest } : {}
9011
9017
  });
@@ -9103,12 +9109,12 @@ async function runOneShotText(job, cfg) {
9103
9109
  child.on("exit", (code2) => {
9104
9110
  clearTimeout(timer);
9105
9111
  const isError = !killReason && typeof code2 === "number" && code2 !== 0;
9106
- 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;
9107
9113
  finish({
9108
9114
  code: killReason ? null : code2,
9109
9115
  reason: killReason ?? (code2 === 0 ? "clean" : "error"),
9110
9116
  transcriptRef: transcriptRefPtr.value,
9111
- ...errorMessage ? { errorMessage } : {},
9117
+ ...errorMessage2 ? { errorMessage: errorMessage2 } : {},
9112
9118
  // ADR-0147:容器化会话带上镜像 digest(可复现凭据)。
9113
9119
  ...containerized?.imageDigest ? { imageDigest: containerized.imageDigest } : {}
9114
9120
  });
@@ -10493,6 +10499,9 @@ var init_chat_item_ledger = __esm({
10493
10499
  finished = false;
10494
10500
  /** 上一次见到的归一层 turnId——变了就清映射(provider 开了新 turn)。 */
10495
10501
  lastTurnId = null;
10502
+ /** 本账本已经落库的 user 行(按落库顺序)。{@link onUserItemPersisted} 的晚到订阅者从这里补。 */
10503
+ persistedUserItems = [];
10504
+ userItemListeners = /* @__PURE__ */ new Set();
10496
10505
  /** 会话逻辑时钟走一格。**唯一发号点**(ADR-0510 附录 A.4)——别在别处自己数。 */
10497
10506
  tick() {
10498
10507
  return ++this.clock;
@@ -10505,6 +10514,42 @@ var init_chat_item_ledger = __esm({
10505
10514
  const ord = this.rows.get(itemId)?.ord;
10506
10515
  return ord === null ? void 0 : ord;
10507
10516
  }
10517
+ /** 展示时间与持久行同源;不以浏览器收到帧的时间代替。 */
10518
+ timestampsFor(itemId) {
10519
+ const row = this.rows.get(itemId);
10520
+ return row ? { createdAt: row.createdAt, completedAt: row.completedAt } : void 0;
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
+ }
10508
10553
  get failures() {
10509
10554
  return this.failureCount;
10510
10555
  }
@@ -10708,6 +10753,8 @@ var init_chat_item_ledger = __esm({
10708
10753
  if (this.rows.has(row.id)) continue;
10709
10754
  const { text: text6, attrs } = restoredTextAndAttrs(row);
10710
10755
  const item = {
10756
+ createdAt: row.createdAt,
10757
+ completedAt: row.completedAt,
10711
10758
  id: row.id,
10712
10759
  seq: ++this.seqCounter,
10713
10760
  ord: row.ord,
@@ -10767,6 +10814,9 @@ var init_chat_item_ledger = __esm({
10767
10814
  /**
10768
10815
  * 切段(人中途插入):此刻起建的 item 属于**下一条** assistant 行。
10769
10816
  * 已有 provider item 保留身份与位置;新 item 才归下一段,迟到快照不能复制旧块。
10817
+ *
10818
+ * 返回新段的段号。那条 assistant 行落库后回填归属时**必须带上它**({@link bindMessage}):
10819
+ * 行是异步落库的,回填到达时后面可能已经又切过段。
10770
10820
  */
10771
10821
  beginSegment() {
10772
10822
  if (this.segmentEndEmitted) {
@@ -10778,6 +10828,7 @@ var init_chat_item_ledger = __esm({
10778
10828
  this.messageId = null;
10779
10829
  this.segment += 1;
10780
10830
  this.segmentItemCount = 0;
10831
+ return this.segment;
10781
10832
  }
10782
10833
  /**
10783
10834
  * 人插话时,先把「这一段到此为止」落定;**必须在那条 user 行之前调用**。
@@ -10814,15 +10865,31 @@ var init_chat_item_ledger = __esm({
10814
10865
  payload: { code: "segment_end", note: SEGMENT_END_NOTE }
10815
10866
  });
10816
10867
  }
10817
- /** 行落库了,把本段还挂着 NULL 的 assistant item 认领回去,并让后续 item 直接带上它。 */
10818
- bindMessage(messageId) {
10819
- 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 = [];
10820
10885
  for (const row of this.rows.values()) {
10821
- 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);
10822
10889
  }
10823
- if (!this.persistEnabled) return;
10890
+ if (!this.persistEnabled || itemIds.length === 0) return;
10824
10891
  this.enqueue(async () => {
10825
- 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);
10826
10893
  }, "bindMessage");
10827
10894
  }
10828
10895
  /**
@@ -10962,7 +11029,10 @@ var init_chat_item_ledger = __esm({
10962
11029
  const id = input.deterministicId ?? this.newId();
10963
11030
  const startedVersion = this.tick();
10964
11031
  const writeVersion = this.tick();
11032
+ const createdAt = this.now();
10965
11033
  const row = {
11034
+ createdAt,
11035
+ completedAt: null,
10966
11036
  id,
10967
11037
  seq: ++this.seqCounter,
10968
11038
  ord: null,
@@ -10986,7 +11056,6 @@ var init_chat_item_ledger = __esm({
10986
11056
  if (!input.forceNullMessage) this.segmentItemCount += 1;
10987
11057
  if (!input.persist || !this.persistEnabled) return id;
10988
11058
  const messageId = row.messageId;
10989
- const createdAt = this.now();
10990
11059
  const payload2 = input.payload ?? this.payloadOf(row);
10991
11060
  const metadata = input.metadata ?? { [CHAT_ITEM_META_LEGACY_CONTENT]: input.inContent };
10992
11061
  this.enqueue(async () => {
@@ -11006,17 +11075,27 @@ var init_chat_item_ledger = __esm({
11006
11075
  version: writeVersion
11007
11076
  });
11008
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
+ };
11009
11086
  try {
11010
- this.deps.onPersisted?.({
11011
- itemId: id,
11012
- ord: created.ord,
11013
- version: writeVersion,
11014
- role: input.role,
11015
- text: input.text,
11016
- payload: payload2
11017
- });
11087
+ this.deps.onPersisted?.(info);
11018
11088
  } catch {
11019
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
+ }
11020
11099
  }, `create:${input.kind}`);
11021
11100
  return id;
11022
11101
  }
@@ -11054,9 +11133,11 @@ var init_chat_item_ledger = __esm({
11054
11133
  persist(row, op, what) {
11055
11134
  const version2 = this.tick();
11056
11135
  row.wireVersion = version2;
11136
+ const at = this.now();
11137
+ if (op.op === "set_status" && op.status !== "streaming") row.completedAt = at;
11057
11138
  if (!row.persist || !this.persistEnabled) return;
11058
11139
  this.enqueue(async () => {
11059
- await this.deps.items.applyOp(row.id, op, { at: this.now(), version: version2 });
11140
+ await this.deps.items.applyOp(row.id, op, { at, version: version2 });
11060
11141
  }, what);
11061
11142
  }
11062
11143
  /** 串行链 + 失败吞掉计数。**串行是必须的**:append_text 依赖建行已经落地。 */
@@ -11202,6 +11283,46 @@ async function finalizeAssistantRow(deps) {
11202
11283
  } catch {
11203
11284
  }
11204
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
+ }
11205
11326
  function outputsPartProvider(scan2, chatSessionId, companyId) {
11206
11327
  if (!scan2) return void 0;
11207
11328
  return async () => {
@@ -11293,6 +11414,8 @@ function runBackgroundAssistantChatTurn(deps) {
11293
11414
  chatStore: deps.chatStore,
11294
11415
  chatSessionId: deps.chatSessionId,
11295
11416
  ...runId ? { runId } : {},
11417
+ // 与 `/api/chat` 同口径(ADR 0300 I1):消息行带这一轮的 `chat_session_turns.id`。此前后台轮不带。
11418
+ ...deps.turnId ? { turnId: deps.turnId } : {},
11296
11419
  now
11297
11420
  });
11298
11421
  const itemLedger = new ChatItemLedger({
@@ -11325,9 +11448,34 @@ function runBackgroundAssistantChatTurn(deps) {
11325
11448
  );
11326
11449
  const localParts = [];
11327
11450
  let localSeq = 0;
11328
- 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 = [];
11329
11455
  let checkpointChain = Promise.resolve();
11330
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
+ }
11331
11479
  normalizedSource.onNormalizedProviderEvent((event) => {
11332
11480
  try {
11333
11481
  if (liveTurn) liveTurn.applyNormalizedEvent(event);
@@ -11355,11 +11503,12 @@ function runBackgroundAssistantChatTurn(deps) {
11355
11503
  let ckptTimer = null;
11356
11504
  if (checkpointMs > 0 && assistantMsgId) {
11357
11505
  ckptTimer = setInterval(() => {
11358
- if (!ckptDirty) return;
11506
+ if (!ckptDirty || !currentMsgId) return;
11359
11507
  ckptDirty = false;
11360
11508
  const parts = collectedParts();
11361
11509
  const content3 = itemLedger.projectContent();
11362
- checkpointChain = checkpointChain.then(() => deps.chatStore.updateMessage(assistantMsgId, {
11510
+ const target = currentMsgId;
11511
+ checkpointChain = checkpointChain.then(() => deps.chatStore.updateMessage(target, {
11363
11512
  content: content3,
11364
11513
  status: "running",
11365
11514
  ...parts.length ? { parts } : {}
@@ -11379,11 +11528,13 @@ function runBackgroundAssistantChatTurn(deps) {
11379
11528
  await finalizeAssistantRow({
11380
11529
  chatStore: deps.chatStore,
11381
11530
  chatSessionId: deps.chatSessionId,
11382
- assistantMsgId,
11531
+ // 插话切过段就收最后一段那一行;前面各段在切段时已转 done。
11532
+ assistantMsgId: currentMsgId,
11383
11533
  content: projectedContent,
11384
11534
  parts,
11385
11535
  status,
11386
11536
  ...runId ? { runId } : {},
11537
+ ...deps.turnId ? { turnId: deps.turnId } : {},
11387
11538
  ...deps.outputsPart ? { outputsPart: deps.outputsPart } : {},
11388
11539
  now
11389
11540
  });
@@ -11399,7 +11550,8 @@ function runBackgroundAssistantChatTurn(deps) {
11399
11550
  }
11400
11551
  }
11401
11552
  if (liveTurn) {
11402
- if (projectedContent) liveTurn.emit({ type: "text", text: projectedContent });
11553
+ const turnText = [...closedSegmentsText, projectedContent].join("");
11554
+ if (turnText) liveTurn.emit({ type: "text", text: turnText });
11403
11555
  liveTurn.emitLive({
11404
11556
  protocolVersion: 2,
11405
11557
  streamId: `background:${deps.chatSessionId}`,
@@ -11407,7 +11559,7 @@ function runBackgroundAssistantChatTurn(deps) {
11407
11559
  itemId: `background:${assistantMsgId ?? runId}`,
11408
11560
  itemType: "message",
11409
11561
  operation: "completed",
11410
- payload: { role: "assistant", text: projectedContent }
11562
+ payload: { role: "assistant", text: turnText }
11411
11563
  });
11412
11564
  }
11413
11565
  try {
@@ -11881,11 +12033,11 @@ function decryptFeishu(encrypt, encryptKey) {
11881
12033
  function decodeBody(body2, encryptKey) {
11882
12034
  if (typeof body2 !== "object" || body2 === null) return null;
11883
12035
  const rec = body2;
11884
- const enc4 = rec["encrypt"];
11885
- if (typeof enc4 === "string") {
12036
+ const enc3 = rec["encrypt"];
12037
+ if (typeof enc3 === "string") {
11886
12038
  if (!encryptKey) return null;
11887
12039
  try {
11888
- const parsed = JSON.parse(decryptFeishu(enc4, encryptKey));
12040
+ const parsed = JSON.parse(decryptFeishu(enc3, encryptKey));
11889
12041
  return typeof parsed === "object" && parsed !== null ? parsed : null;
11890
12042
  } catch {
11891
12043
  return null;
@@ -121002,11 +121154,11 @@ var require_lib2 = __commonJS({
121002
121154
  var DEFAULT_INITIAL = "Thinking...";
121003
121155
  var DEFAULT_EMPTY = "(no content)";
121004
121156
  var INITIAL_SUMMARY = "[Generating...]";
121005
- var SUMMARY_MAX_CHARS = 50;
121157
+ var SUMMARY_MAX_CHARS2 = 50;
121006
121158
  var ERROR_FOOTER = "\n\n\u2014 _(Generation interrupted)_";
121007
121159
  var DEFAULT_MAX_ELEMENT_CHARS = 3e4;
121008
121160
  var ELEMENT_ID = "stream_md";
121009
- function truncateSummary(text6, max = SUMMARY_MAX_CHARS) {
121161
+ function truncateSummary(text6, max = SUMMARY_MAX_CHARS2) {
121010
121162
  if (!text6)
121011
121163
  return "";
121012
121164
  const cleaned = text6.replace(/\s+/g, " ").trim();
@@ -125204,6 +125356,90 @@ var init_state = __esm({
125204
125356
  }
125205
125357
  });
125206
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
+
125207
125443
  // ../engine/src/letters.ts
125208
125444
  function repliesSorted(state, issueId) {
125209
125445
  return [...state.repliesOf(issueId)].sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
@@ -125578,8 +125814,17 @@ function scan(state, ctx) {
125578
125814
  continue;
125579
125815
  }
125580
125816
  }
125581
- const terminated = events.length === 0 && checkTerminated(state);
125582
- 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 };
125583
125828
  }
125584
125829
  function checkRunningWorkTimeout(state, node2, work, ctx, events) {
125585
125830
  const start = work.lastActivityAt ?? work.startedAt ?? work.createdAt;
@@ -125670,6 +125915,18 @@ function scanBlockersForNode(state, node2, latest) {
125670
125915
  }
125671
125916
  return blockers;
125672
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
+ }
125673
125930
  function tryToRun(state, nodeId, ctx, events) {
125674
125931
  const node2 = state.node(nodeId);
125675
125932
  if (!node2) return;
@@ -125695,15 +125952,6 @@ function tryToRun(state, nodeId, ctx, events) {
125695
125952
  }
125696
125953
  return;
125697
125954
  }
125698
- const inbound = state.edgesInto(nodeId);
125699
- for (const e of inbound) {
125700
- if (e.kind !== "data" || !e.required) continue;
125701
- if (isEdgeInputUnobtainable(state, e)) continue;
125702
- const upstream = state.node(e.fromNodeId);
125703
- if (!upstream?.latestAcceptId) return;
125704
- if (isBusinessDependencyEdge(state, e) && !businessHandoffBarrierSatisfied(state, upstream.id, upstream.latestAcceptId)) return;
125705
- }
125706
- if (findUnstableRequiredUpstream(state, nodeId, events)) return;
125707
125955
  if (state.hasQueuedWorkFor(nodeId)) return;
125708
125956
  const workId = deriveWorkId(nodeId, state.allWorksIncludingReplies(nodeId).length + 1);
125709
125957
  const letters = pendingLetters(state, node2);
@@ -125719,6 +125967,8 @@ function tryToRun(state, nodeId, ctx, events) {
125719
125967
  if (deliveredNow.has(g2.id)) continue;
125720
125968
  reasons.push({ kind: "issue", issueId: g2.id, issueKind: g2.kind });
125721
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;
125722
125972
  events.push({
125723
125973
  kind: "work.create",
125724
125974
  workorderId: state.workorder.id,
@@ -125868,59 +126118,6 @@ function collectStallTrips(state, node2, ctx) {
125868
126118
  }
125869
126119
  return out;
125870
126120
  }
125871
- function inputsMatch(state, node2, latestWork) {
125872
- const inbound = state.edgesInto(node2.id);
125873
- const currInputs = new Set(
125874
- inbound.filter((e) => e.kind === "data" && e.pinnedWorkId).map((e) => e.pinnedWorkId)
125875
- );
125876
- const latestInputs = new Set(state.workInputsOf(latestWork.id).map((i) => i.upstreamWorkId));
125877
- return currInputs.size === latestInputs.size && [...currInputs].every((x2) => latestInputs.has(x2));
125878
- }
125879
- function findUnstableRequiredUpstream(state, nodeId, pendingEvents = []) {
125880
- const memo = /* @__PURE__ */ new Map();
125881
- const visiting = /* @__PURE__ */ new Set();
125882
- const mayStillChange2 = (candidateId) => {
125883
- const cached3 = memo.get(candidateId);
125884
- if (cached3 !== void 0) return cached3;
125885
- if (visiting.has(candidateId)) return true;
125886
- visiting.add(candidateId);
125887
- const candidate = state.node(candidateId);
125888
- let unstable = false;
125889
- if (!candidate) {
125890
- unstable = true;
125891
- } else if (candidate.cancelledAt && candidate.latestAcceptId) {
125892
- unstable = false;
125893
- } else if (!candidate.latestAcceptId) {
125894
- unstable = true;
125895
- } else {
125896
- const accepted = state.work(candidate.latestAcceptId);
125897
- if (!accepted || accepted.nodeVersion !== candidate.version || !inputsMatch(state, candidate, accepted)) {
125898
- unstable = true;
125899
- } else {
125900
- const acceptedIsCurrent = candidate.latestAcceptId === candidate.latestWorkId || candidate.latestAcceptId === candidate.latestProposedWorkId;
125901
- const queuedThisScan = pendingEvents.some((event) => event.kind === "work.create" && event.nodeId === candidateId);
125902
- unstable = !acceptedIsCurrent || state.hasQueuedWorkFor(candidateId) || queuedThisScan;
125903
- }
125904
- if (!unstable) {
125905
- for (const edge of state.edgesInto(candidateId)) {
125906
- if (edge.kind !== "data" || !edge.required || isEdgeInputUnobtainable(state, edge)) continue;
125907
- if (mayStillChange2(edge.fromNodeId)) {
125908
- unstable = true;
125909
- break;
125910
- }
125911
- }
125912
- }
125913
- }
125914
- visiting.delete(candidateId);
125915
- memo.set(candidateId, unstable);
125916
- return unstable;
125917
- };
125918
- for (const edge of state.edgesInto(nodeId)) {
125919
- if (edge.kind !== "data" || !edge.required || isEdgeInputUnobtainable(state, edge)) continue;
125920
- if (mayStillChange2(edge.fromNodeId)) return edge.fromNodeId;
125921
- }
125922
- return null;
125923
- }
125924
126121
  function reviewMaxRetriesOf(policy) {
125925
126122
  const act = policy.resolve({ executor: "agent", scenario: "review" }).timeoutAction;
125926
126123
  return act.kind === "kill-rebuild" ? act.maxRetries : AGENT_REVIEW_MAX_RETRIES;
@@ -126082,10 +126279,6 @@ function mergedClosureRequirements(state, nodeId, closureActors) {
126082
126279
  const kept = state.requirementsOf(nodeId).filter((r) => r.source !== "closure").map((r) => ({ reviewerActorId: r.reviewerActorId, reviewGroup: r.reviewGroup, source: r.source }));
126083
126280
  return [...kept, ...closureReviewerSpecs(wantedClosureActors(state, nodeId, closureActors))];
126084
126281
  }
126085
- function isEdgeInputUnobtainable(state, edge) {
126086
- const upstream = state.node(edge.fromNodeId);
126087
- return !!upstream?.cancelledAt && !upstream.latestAcceptId;
126088
- }
126089
126282
  function isBusinessDependencyEdge(state, edge) {
126090
126283
  const downstream = state.node(edge.toNodeId);
126091
126284
  return edge.kind === "data" && edge.required && downstream?.fields?.[BUSINESS_HANDOFF_POLICY_FIELD] !== BUSINESS_HANDOFF_POLICY_EXEMPT;
@@ -126192,6 +126385,8 @@ function assessConvergence(state, ctx) {
126192
126385
  var init_activate = __esm({
126193
126386
  "../engine/src/activate.ts"() {
126194
126387
  "use strict";
126388
+ init_upstream_stability();
126389
+ init_upstream_stability();
126195
126390
  init_continuation();
126196
126391
  init_src4();
126197
126392
  init_src5();
@@ -130083,7 +130278,7 @@ var require_split2 = __commonJS({
130083
130278
  var { StringDecoder } = require("string_decoder");
130084
130279
  var kLast = Symbol("last");
130085
130280
  var kDecoder = Symbol("decoder");
130086
- function transform2(chunk, enc4, cb) {
130281
+ function transform2(chunk, enc3, cb) {
130087
130282
  let list2;
130088
130283
  if (this.overflow) {
130089
130284
  const buf = this[kDecoder].write(chunk);
@@ -132699,7 +132894,9 @@ var init_store_postgres = __esm({
132699
132894
  WHERE (
132700
132895
  (e.status = 'pending' AND (e.available_at IS NULL OR e.available_at <= $1))
132701
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
132702
- 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)))
132703
132900
  )
132704
132901
  AND NOT EXISTS (
132705
132902
  SELECT 1 FROM ${this.s}.events p
@@ -132716,13 +132913,23 @@ var init_store_postgres = __esm({
132716
132913
  async markEvent(seq, status, failedReason, at) {
132717
132914
  await this.c.query(
132718
132915
  `UPDATE ${this.s}.events
132719
- SET status = $2, failed_reason = $3,
132916
+ SET status = $2, failed_reason = $3, available_at = NULL,
132720
132917
  applied_at = CASE WHEN $2 = 'applied' THEN $4::timestamptz ELSE applied_at END,
132721
132918
  done_at = CASE WHEN $2 = 'done' THEN $4::timestamptz ELSE done_at END
132722
132919
  WHERE seq = $1`,
132723
132920
  [seq, status, failedReason, at]
132724
132921
  );
132725
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
+ }
132726
132933
  async isHandlerDone(seq, handlerName) {
132727
132934
  const res = await this.c.query(
132728
132935
  `SELECT 1 FROM ${this.s}.event_handler_log WHERE event_seq = $1 AND handler_name = $2`,
@@ -132756,7 +132963,9 @@ var init_store_postgres = __esm({
132756
132963
  WHERE (
132757
132964
  (e.status = 'pending' AND (e.available_at IS NULL OR e.available_at <= $1))
132758
132965
  -- \u5D29\u6E83\u81EA\u6108\uFF1A\u89C1 claimNextEvent \u7684\u6CE8\u91CA
132759
- 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)))
132760
132969
  )
132761
132970
  AND NOT EXISTS (SELECT 1 FROM ${this.s}.events p WHERE p.workorder_id = e.workorder_id
132762
132971
  AND p.status IN ('pending','applied') AND p.seq < e.seq)
@@ -133139,12 +133348,29 @@ var init_plan = __esm({
133139
133348
  });
133140
133349
 
133141
133350
  // ../engine/src/handlers/workorder.ts
133142
- var workorderCreate, workorderMeta, workorderPause, workorderResume, workorderPing;
133351
+ var workorderInitialize, workorderCreate, workorderMeta, workorderPause, workorderResume, workorderPing;
133143
133352
  var init_workorder = __esm({
133144
133353
  "../engine/src/handlers/workorder.ts"() {
133145
133354
  "use strict";
133146
133355
  init_kill();
133147
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
+ };
133148
133374
  workorderCreate = {
133149
133375
  name: "workorder/create",
133150
133376
  kind: "workorder.created",
@@ -133181,6 +133407,7 @@ var init_workorder = __esm({
133181
133407
  workorderPause = {
133182
133408
  name: "workorder/pause",
133183
133409
  kind: "workorder.paused",
133410
+ retryFailedEffect: true,
133184
133411
  apply(e, state, ctx) {
133185
133412
  state.updateWorkorder({ dispatchPaused: true, pausedBy: e.by, pausedReason: e.reason, updatedAt: ctx.at });
133186
133413
  killAllWorks(state, ctx.at);
@@ -133226,6 +133453,7 @@ var workCreate, workRedispatch, workSnapshotRecorded, workKill, workResponse, wo
133226
133453
  var init_work = __esm({
133227
133454
  "../engine/src/handlers/work.ts"() {
133228
133455
  "use strict";
133456
+ init_upstream_stability();
133229
133457
  init_continuation();
133230
133458
  init_views2();
133231
133459
  init_kill();
@@ -133538,6 +133766,10 @@ var init_work = __esm({
133538
133766
  apply(e, state, ctx) {
133539
133767
  const w2 = state.work(e.workId);
133540
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
+ }
133541
133773
  if (e.artifacts.length === 0 && w2.acceptanceState === "accepted" && state.artifactsOf(e.workId).length > 0) {
133542
133774
  return;
133543
133775
  }
@@ -134150,8 +134382,9 @@ var init_handlers = __esm({
134150
134382
  init_continuation2();
134151
134383
  init_types();
134152
134384
  HANDLERS = [
134153
- /* ── 工作区级(5)── */
134385
+ /* ── 工作区级(6)── */
134154
134386
  erase(workorderCreate),
134387
+ erase(workorderInitialize),
134155
134388
  erase(workorderMeta),
134156
134389
  erase(workorderPause),
134157
134390
  erase(workorderResume),
@@ -134204,6 +134437,7 @@ var init_handlers = __esm({
134204
134437
 
134205
134438
  // ../engine/src/bus.ts
134206
134439
  function versionCommandEvent(event) {
134440
+ if (event.kind === "work.submit_output") return { ...event, upstreamStabilityVersion: 1 };
134207
134441
  if (event.kind === "work.create" || event.kind === "review.create") {
134208
134442
  return { ...event, pauseGateVersion: 1, ...event.kind === "work.create" ? { continuationGateVersion: 1 } : {} };
134209
134443
  }
@@ -134228,6 +134462,9 @@ function versionCommandEvent(event) {
134228
134462
  return event;
134229
134463
  }
134230
134464
  }
134465
+ function errorMessage(err) {
134466
+ return err instanceof Error ? err.message : String(err);
134467
+ }
134231
134468
  function emptySnapshotPlaceholder(record8) {
134232
134469
  return {
134233
134470
  workorder: {
@@ -134258,7 +134495,7 @@ function emptySnapshotPlaceholder(record8) {
134258
134495
  reviews: []
134259
134496
  };
134260
134497
  }
134261
- 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;
134262
134499
  var init_bus = __esm({
134263
134500
  "../engine/src/bus.ts"() {
134264
134501
  "use strict";
@@ -134268,6 +134505,7 @@ var init_bus = __esm({
134268
134505
  init_model();
134269
134506
  init_handlers();
134270
134507
  init_state();
134508
+ init_upstream_stability();
134271
134509
  init_continuation();
134272
134510
  SYSTEM_ACTOR = "actor:system:engine";
134273
134511
  NOOP_IO = {
@@ -134299,6 +134537,7 @@ var init_bus = __esm({
134299
134537
  };
134300
134538
  DEFAULT_STUCK_APPLIED_RECOVERY_MS = 5 * 6e4;
134301
134539
  SETTLE_FOREIGN_PROGRESS_FALLBACK_MS = 200;
134540
+ EVENT_RETRY_DELAY_MS = 1e3;
134302
134541
  EngineBus = class {
134303
134542
  store;
134304
134543
  io;
@@ -134458,6 +134697,7 @@ var init_bus = __esm({
134458
134697
  if (!await this.drainOne()) break;
134459
134698
  }
134460
134699
  }
134700
+ await this.assertProcessed([seq]);
134461
134701
  }
134462
134702
  /** 同一命令的一组最终修改一起入队。scan-on-empty 在末项之前始终看到 pending,不能派发中间状态。 */
134463
134703
  async submitBatchAndProcess(inputs) {
@@ -134484,6 +134724,17 @@ var init_bus = __esm({
134484
134724
  } else {
134485
134725
  for (let i = 0; i < 2e3; i++) if (!await this.drainOne()) break;
134486
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
+ });
134487
134738
  }
134488
134739
  /* ═══════════════════════ 调度器主循环 ═══════════════════════ */
134489
134740
  /**
@@ -134533,8 +134784,7 @@ var init_bus = __esm({
134533
134784
  }
134534
134785
  touched.add(record8.workorderId);
134535
134786
  try {
134536
- await this.applyRecord(record8);
134537
- await this.effectRecord(record8);
134787
+ if (await this.applyRecord(record8)) await this.effectRecord(record8);
134538
134788
  } catch (err) {
134539
134789
  console.error(`[bus] event ${record8.seq} ${record8.event.kind}:`, err);
134540
134790
  }
@@ -134665,9 +134915,10 @@ var init_bus = __esm({
134665
134915
  /* ═══════════════════════ 旧 drain(测试兼容) ═══════════════════════ */
134666
134916
  /** 取一条事件 apply+effect,然后 scan 其工单。仅测试用——生产代码走 run() 循环。 */
134667
134917
  async drainOne() {
134668
- const record8 = await this.applyPhase();
134669
- if (!record8) return false;
134670
- 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);
134671
134922
  this.emitEvents([record8]);
134672
134923
  try {
134673
134924
  const result = await this.scan(record8.workorderId);
@@ -134684,10 +134935,11 @@ var init_bus = __esm({
134684
134935
  async drain(limit = 1e3) {
134685
134936
  let processed = 0, failed = 0;
134686
134937
  for (let i = 0; i < limit; i++) {
134687
- const record8 = await this.applyPhase();
134688
- if (!record8) break;
134938
+ const phase = await this.applyPhase();
134939
+ if (!phase) break;
134940
+ const { record: record8, applied } = phase;
134689
134941
  processed++;
134690
- const ok3 = await this.effectPhase(record8);
134942
+ const ok3 = applied && await this.effectPhase(record8);
134691
134943
  if (!ok3) failed++;
134692
134944
  this.emitEvents([record8]);
134693
134945
  }
@@ -134699,7 +134951,7 @@ var init_bus = __esm({
134699
134951
  * 公开方法,供 kernel-bridge 和 run() 共用。
134700
134952
  */
134701
134953
  async applyRecord(record8) {
134702
- await this.store.transaction(async (tx) => {
134954
+ const applied = await this.store.transaction(async (tx) => {
134703
134955
  const snapshot = await tx.loadWorkorder(record8.workorderId);
134704
134956
  const state = new WorkorderState(snapshot ?? emptySnapshotPlaceholder(record8));
134705
134957
  const ctx = {
@@ -134708,10 +134960,15 @@ var init_bus = __esm({
134708
134960
  actorId: record8.actorId,
134709
134961
  config: this.config
134710
134962
  };
134963
+ const completedHandlers = [];
134711
134964
  for (const h of this.registry.for(record8.event.kind)) {
134712
134965
  if (await tx.isHandlerDone(record8.seq, h.name)) continue;
134713
- h.apply(record8.event, state, ctx);
134714
- 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);
134715
134972
  }
134716
134973
  await tx.applyMutations(record8.workorderId, state.mutations);
134717
134974
  if (state.emitted.length) {
@@ -134728,20 +134985,28 @@ var init_bus = __esm({
134728
134985
  ...eventAnchors(event)
134729
134986
  })));
134730
134987
  }
134988
+ for (const name of completedHandlers) await tx.markHandlerDone(record8.seq, name);
134731
134989
  await tx.markEvent(record8.seq, "applied", null, this.clock.now());
134990
+ return true;
134732
134991
  }).catch(async (err) => {
134733
- if (err instanceof ApplyFailure) {
134734
- await this.store.transaction(
134735
- (tx) => tx.markEvent(err.record.seq, "failed", err.reason, this.clock.now())
134736
- );
134737
- return;
134738
- }
134739
- 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;
134740
134999
  });
134741
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();
134742
135006
  }
134743
135007
  /** 事务二:跑 IO + 收尾。 */
134744
135008
  async effectRecord(record8) {
135009
+ if (record8.status !== "applied") return false;
134745
135010
  const handlers = this.registry.for(record8.event.kind).filter((h) => h.effect);
134746
135011
  const buffered = [];
134747
135012
  const ctx = {
@@ -134753,32 +135018,53 @@ var init_bus = __esm({
134753
135018
  dispatchReview: (id, nodeId, reviewerActorId) => this.io.dispatchReview(id, nodeId, reviewerActorId),
134754
135019
  canDispatchWork: async (workorderId, workId, opts) => {
134755
135020
  const snap = await this.store.transaction((tx) => tx.loadWorkorder(workorderId));
134756
- if (!snap || snap.workorder.dispatchPaused) return false;
135021
+ if (!snap || snap.workorder.lifecycle !== "active" || snap.workorder.dispatchPaused) return false;
134757
135022
  const work = snap.works.find((row) => row.id === workId);
134758
135023
  if (work && work.lane === "main" && !work.startedAt && !work.sessionRef && !work.lastActivityAt) {
134759
- const node2 = snap.nodes.find((row) => row.id === work.nodeId);
134760
- 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) {
134761
135026
  const policy = await this.io.continuationPolicy(work.assigneeActorId);
134762
135027
  if (policy) {
134763
135028
  if (!Number.isSafeInteger(policy.maxRounds) || policy.maxRounds < 1 || policy.maxRounds > 100) {
134764
135029
  throw new Error(`invalid continuation round limit for ${work.assigneeActorId}`);
134765
135030
  }
134766
- 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 } });
134767
135032
  buffered.push({ event: { kind: "work.redispatch", workorderId, workId } });
134768
135033
  return false;
134769
135034
  }
134770
135035
  }
134771
- if (node2 && continuationExhausted(new WorkorderState(snap), node2)) return false;
135036
+ if (node3 && continuationExhausted(new WorkorderState(snap), node3)) return false;
134772
135037
  }
134773
135038
  if (opts?.neverStarted && work) {
134774
- const node2 = snap.nodes.find((row) => row.id === work.nodeId);
134775
- 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;
134776
135041
  }
134777
- return work?.status === "running" && !work.deadAt && !work.cancelledAt && !work.endedAt;
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
+ }
135062
+ }
135063
+ return true;
134778
135064
  },
134779
135065
  canDispatchReview: async (workorderId, reviewId) => {
134780
135066
  const snap = await this.store.transaction((tx) => tx.loadWorkorder(workorderId));
134781
- if (!snap || snap.workorder.dispatchPaused) return false;
135067
+ if (!snap || snap.workorder.lifecycle !== "active" || snap.workorder.dispatchPaused) return false;
134782
135068
  const review = snap.reviews.find((row) => row.id === reviewId);
134783
135069
  return review?.status === "running" && !review.cancelledAt && !review.endedAt;
134784
135070
  },
@@ -134795,19 +135081,14 @@ var init_bus = __esm({
134795
135081
  // apply 已把被杀 work/review 置 deadAt/cancelledAt,这里按 store 找它们仍挂着的会话 cancel
134796
135082
  //(deadAt 且 endedAt 未置 = 刚被杀未退出的在途会话)。没会话 / 已退 → no-op。
134797
135083
  cancelKilledSessions: async (_workorderId, reviewScope) => {
134798
- let snap;
134799
- try {
134800
- snap = await this.store.transaction((tx) => tx.loadWorkorder(record8.workorderId));
134801
- } catch (err) {
134802
- console.warn(`[bus] cancelKilledSessions ${record8.workorderId} \u8BFB\u5FEB\u7167\u5931\u8D25: ${String(err)}`);
134803
- return;
134804
- }
135084
+ const snap = await this.store.transaction((tx) => tx.loadWorkorder(record8.workorderId));
134805
135085
  if (!snap) return;
135086
+ const failures = [];
134806
135087
  const cancel = async (id, what) => {
134807
135088
  try {
134808
135089
  await this.io.cancelSession(id);
134809
135090
  } catch (err) {
134810
- console.warn(`[bus] cancelKilledSessions ${what} ${id}: ${String(err)}`);
135091
+ failures.push(`${what} ${id}: ${errorMessage(err)}`);
134811
135092
  }
134812
135093
  };
134813
135094
  for (const w2 of reviewScope === void 0 ? snap.works : []) {
@@ -134819,10 +135100,12 @@ var init_bus = __esm({
134819
135100
  ))) continue;
134820
135101
  if (r.cancelledAt && r.endedAt === null) await cancel(r.id, "review");
134821
135102
  }
135103
+ if (failures.length) throw new Error(`session cancellation incomplete: ${failures.join("; ")}`);
134822
135104
  },
134823
135105
  notify: (ids2, payload2) => this.io.notify(ids2, payload2)
134824
135106
  };
134825
135107
  let ok3 = true;
135108
+ let failureReason = null;
134826
135109
  for (const h of handlers) {
134827
135110
  const effectKey = `${h.name}#effect`;
134828
135111
  if (record8.appliedAt && await this.store.transaction((tx) => tx.isHandlerDone(record8.seq, effectKey))) {
@@ -134830,6 +135113,7 @@ var init_bus = __esm({
134830
135113
  }
134831
135114
  let lastErr = "";
134832
135115
  for (let attempt = 1; attempt <= this.maxEffectAttempts; attempt++) {
135116
+ buffered.length = 0;
134833
135117
  try {
134834
135118
  await h.effect(record8.event, ctx);
134835
135119
  lastErr = "";
@@ -134840,27 +135124,30 @@ var init_bus = __esm({
134840
135124
  }
134841
135125
  if (lastErr) {
134842
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;
134843
135132
  } else {
134844
- 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
+ });
134845
135148
  }
134846
135149
  }
134847
- await this.store.transaction(async (tx) => {
134848
- if (buffered.length) {
134849
- await tx.appendEvents(buffered.map((b2) => ({
134850
- companyId: record8.companyId,
134851
- workorderId: record8.workorderId,
134852
- actorId: b2.actorId ?? SYSTEM_ACTOR,
134853
- handActorId: null,
134854
- event: versionCommandEvent(b2.event),
134855
- causedBySeq: record8.seq,
134856
- origin: "effect",
134857
- dedupKey: b2.dedupKey ?? null,
134858
- createdAt: this.clock.now(),
134859
- ...eventAnchors(b2.event)
134860
- })));
134861
- }
134862
- await tx.markEvent(record8.seq, "done", null, this.clock.now());
134863
- });
135150
+ await this.store.transaction((tx) => tx.markEvent(record8.seq, "done", failureReason, this.clock.now()));
134864
135151
  return ok3;
134865
135152
  }
134866
135153
  /* ═══════════════════════ scan:唯一的调度入口 ═══════════════════════ */
@@ -134926,57 +135213,10 @@ var init_bus = __esm({
134926
135213
  }
134927
135214
  /* ═══════════════════════ 内部:旧 applyPhase(claim + apply 一体) ═══════════════════════ */
134928
135215
  async applyPhase() {
134929
- return this.store.transaction(async (tx) => {
134930
- const record8 = await tx.claimNextEvent(this.clock.now(), this.recoverAppliedBefore());
134931
- if (!record8) return null;
134932
- const snapshot = await tx.loadWorkorder(record8.workorderId);
134933
- if (snapshot?.workorder.doneAt && record8.event.kind !== "work.content_edited") {
134934
- await tx.applyMutations(record8.workorderId, [{
134935
- t: "workorder.update",
134936
- patch: { doneAt: null }
134937
- }]);
134938
- }
134939
- const state = new WorkorderState(snapshot ?? emptySnapshotPlaceholder(record8));
134940
- const ctx = {
134941
- seq: record8.seq,
134942
- at: record8.createdAt,
134943
- actorId: record8.actorId,
134944
- config: this.config
134945
- };
134946
- for (const h of this.registry.for(record8.event.kind)) {
134947
- if (await tx.isHandlerDone(record8.seq, h.name)) continue;
134948
- h.apply(record8.event, state, ctx);
134949
- await tx.markHandlerDone(record8.seq, h.name);
134950
- }
134951
- await tx.applyMutations(record8.workorderId, state.mutations);
134952
- if (state.emitted.length) {
134953
- await tx.appendEvents(state.emitted.map((event) => ({
134954
- companyId: record8.companyId,
134955
- workorderId: record8.workorderId,
134956
- actorId: SYSTEM_ACTOR,
134957
- handActorId: null,
134958
- event: versionCommandEvent(event),
134959
- causedBySeq: record8.seq,
134960
- origin: "apply",
134961
- dedupKey: null,
134962
- createdAt: record8.createdAt,
134963
- ...eventAnchors(event)
134964
- })));
134965
- }
134966
- await tx.markEvent(record8.seq, "applied", null, this.clock.now());
134967
- return record8;
134968
- }).catch(async (err) => {
134969
- if (err instanceof ApplyFailure) {
134970
- await this.store.transaction(
134971
- (tx) => tx.markEvent(err.record.seq, "failed", err.reason, this.clock.now())
134972
- );
134973
- return err.record;
134974
- }
134975
- throw err;
134976
- }).then((record8) => {
134977
- if (record8) this.emitApplied(record8.workorderId);
134978
- return record8;
134979
- });
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) };
134980
135220
  }
134981
135221
  async effectPhase(record8) {
134982
135222
  return this.effectRecord(record8);
@@ -134998,7 +135238,7 @@ var init_bus = __esm({
134998
135238
  const events = await this.store.transaction((tx) => tx.listEvents(workorderId));
134999
135239
  let state = null;
135000
135240
  for (const record8 of events) {
135001
- if (record8.origin === "effect") continue;
135241
+ if (record8.status === "failed" || record8.status === "pending" || record8.origin === "effect") continue;
135002
135242
  if (!state) state = new WorkorderState(emptySnapshotPlaceholder(record8));
135003
135243
  for (const h of this.registry.for(record8.event.kind)) {
135004
135244
  h.apply(record8.event, state, {
@@ -138992,16 +139232,16 @@ function classifyExitReason(info) {
138992
139232
  if (AUTH_RE.test(msg)) return "auth-error";
138993
139233
  return info.reason;
138994
139234
  }
138995
- function quotaResetHorizon(errorMessage) {
138996
- if (!errorMessage) return "unknown";
138997
- 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";
138998
- if (/\d{4}-\d{2}-\d{2}/.test(errorMessage)) return "long";
138999
- 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";
139000
139240
  return "unknown";
139001
139241
  }
139002
- function extractResetHint(errorMessage) {
139003
- if (!errorMessage) return void 0;
139004
- 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);
139005
139245
  return m2?.[1]?.trim().replace(/[.,;]$/, "");
139006
139246
  }
139007
139247
  var QUOTA_RE2, UNREACHABLE_RE, AUTH_RE;
@@ -140312,7 +140552,7 @@ var init_dispatcher = __esm({
140312
140552
  assertSpawnAttemptCurrent(jobKey, attempt) {
140313
140553
  if (!this.isSpawnAttemptCurrent(jobKey, attempt)) throw new SpawnAttemptStale(jobKey);
140314
140554
  }
140315
- async recordSpawnFailure(jobKey, spec, reason, errorMessage, bypassScheduling = false) {
140555
+ async recordSpawnFailure(jobKey, spec, reason, errorMessage2, bypassScheduling = false) {
140316
140556
  if (bypassScheduling) return;
140317
140557
  if (!this.strikeLedgerLive) return;
140318
140558
  const seqAtVerdict = this.opts.kernel.model.lastSeq.get(spec.artifactId) ?? 0;
@@ -140341,7 +140581,7 @@ var init_dispatcher = __esm({
140341
140581
  firstFailedAt: prev?.firstFailedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
140342
140582
  lastReason: reason,
140343
140583
  totalFailures: (prev?.totalFailures ?? prev?.count ?? 0) + 1,
140344
- ...errorMessage ? { lastErrorMessage: errorMessage.slice(0, 400) } : {},
140584
+ ...errorMessage2 ? { lastErrorMessage: errorMessage2.slice(0, 400) } : {},
140345
140585
  // 半开一旦开启就**粘着**,直到真的成功(自己落了 op)才随整条记录清掉。
140346
140586
  // 不粘的话:试探失败却因自愈类/宽限期没能立刻回熔断时,标记会丢,等于白送回满血 3 次。
140347
140587
  ...prev?.halfOpen ? { halfOpen: true } : {},
@@ -142800,6 +143040,10 @@ var init_kernel_bridge = __esm({
142800
143040
  * 用来兜住"本进程没收到事件"的写入——多实例部署下别的实例改的工单就属于这种。
142801
143041
  */
142802
143042
  async refreshModel(opts) {
143043
+ if (opts?.workorderId) {
143044
+ this.trackedWorkorders.add(opts.workorderId);
143045
+ this.markWorkorderDirty(opts.workorderId);
143046
+ }
142803
143047
  if (opts?.resync) this.resyncRequested = true;
142804
143048
  await this.refreshTracked();
142805
143049
  }
@@ -144289,6 +144533,24 @@ var init_kernel_bridge = __esm({
144289
144533
  }
144290
144534
  });
144291
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
+ }
144292
144554
  /** 工单级恢复:workorder.resumed handler 解冻全部节点 + scan 重建 work。 */
144293
144555
  async resumeWorkorder(workorderId, actor) {
144294
144556
  this.trackedWorkorders.add(workorderId);
@@ -144586,7 +144848,7 @@ var init_kernel_bridge = __esm({
144586
144848
  const scopedModel = ws ? filterReadModelByWorkspace(this.model, ws) : this.model;
144587
144849
  return previewIntervention(scopedModel, plan, { schema: this.schema, roleIndex: this.roleIndex });
144588
144850
  }
144589
- async applyIntervention(plan, _baseSeqs, _actor) {
144851
+ async applyIntervention(plan, _baseSeqs, _actor, options = {}) {
144590
144852
  const ws = this.effectiveWorkspace(plan);
144591
144853
  const scopedModel = ws ? filterReadModelByWorkspace(this.model, ws) : this.model;
144592
144854
  plan = resolveInterventionRefs(scopedModel, plan, this.schema);
@@ -144835,7 +145097,7 @@ var init_kernel_bridge = __esm({
144835
145097
  break;
144836
145098
  }
144837
145099
  }
144838
- await queue({
145100
+ if (!options.deferActivation) await queue({
144839
145101
  companyId: "",
144840
145102
  workorderId: wid,
144841
145103
  actorId: _actor,
@@ -145662,15 +145924,15 @@ var init_parseUtil = __esm({
145662
145924
  message: issueData.message
145663
145925
  };
145664
145926
  }
145665
- let errorMessage = "";
145927
+ let errorMessage2 = "";
145666
145928
  const maps = errorMaps.filter((m2) => !!m2).slice().reverse();
145667
145929
  for (const map of maps) {
145668
- errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message;
145930
+ errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
145669
145931
  }
145670
145932
  return {
145671
145933
  ...issueData,
145672
145934
  path: fullPath,
145673
- message: errorMessage
145935
+ message: errorMessage2
145674
145936
  };
145675
145937
  };
145676
145938
  EMPTY_PATH = [];
@@ -162237,7 +162499,52 @@ var init_keyed_mutex = __esm({
162237
162499
  }
162238
162500
  });
162239
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
+
162240
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
+ }
162241
162548
  function normalizedKindToItemType(kind) {
162242
162549
  if (kind === "text") return "message";
162243
162550
  if (kind === "thinking") return "thinking";
@@ -162329,6 +162636,8 @@ var init_live_chat = __esm({
162329
162636
  };
162330
162637
  this.turns.set(chatSessionId, turn);
162331
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;
162332
162641
  const waiters = this.turnStartWaiters.get(chatSessionId);
162333
162642
  this.turnStartWaiters.delete(chatSessionId);
162334
162643
  for (const wake of waiters ?? []) {
@@ -162379,6 +162688,7 @@ var init_live_chat = __esm({
162379
162688
  };
162380
162689
  },
162381
162690
  finish: (status) => {
162691
+ stopUserItems?.();
162382
162692
  for (const receipt of turn.inputReceipts.values()) receipt.abandon();
162383
162693
  turn.inputReceipts.clear();
162384
162694
  try {
@@ -162579,8 +162889,12 @@ var init_live_chat = __esm({
162579
162889
  payload: item.payload
162580
162890
  });
162581
162891
  }
162582
- assignV3Seq(turn, frame) {
162583
- return { ...frame, epoch: turn.liveEpoch, seq: ++turn.v3Seq };
162892
+ assignV3Seq(turn, frame, times = turn.items?.timestampsFor(frame.itemId)) {
162893
+ const payload2 = times ? {
162894
+ ...frame.payload && typeof frame.payload === "object" && !Array.isArray(frame.payload) ? frame.payload : {},
162895
+ ...times
162896
+ } : frame.payload;
162897
+ return { ...frame, ...payload2 !== void 0 ? { payload: payload2 } : {}, epoch: turn.liveEpoch, seq: ++turn.v3Seq };
162584
162898
  }
162585
162899
  /**
162586
162900
  * v3 帧的**唯一**发布出口:按 24ms 窗口合并同一 item 上相邻的 `append_text`,其余一律是顺序屏障。
@@ -162664,11 +162978,11 @@ var init_live_chat = __esm({
162664
162978
  * 空对话首次发送时那个最大号是 0,随后到达的历史(号更大)会全部排到乐观条**后面**——
162665
162979
  * 正是 ADR D9 点名必须验的「空桶先发送」那一档。号只能由服务端给。
162666
162980
  *
162667
- * ## 拿不到轮就静默跳过
162981
+ * ## 谁来调
162668
162982
  *
162669
- * 本方法由账本的落库链回调触发,而那一刻这条会话的 live 轮可能还没 `start`
162670
- * (`server.ts` 里 `recordUserSubmission` 在 `liveChat.start` 之前)。拿不到就不发——
162671
- * 前端退回「等下一次快照」,与修复前一致,不会更坏。
162983
+ * 生产上只有 {@link start} 订阅账本(`ChatItemLedger.onUserItemPersisted`)之后经
162984
+ * {@link publishPersistedUserItem} 调,四条开轮入口一处接好;「落库早于开轮」由账本订阅时补发兜住。
162985
+ * 下面按会话号暂存的那条路只服务直接调本方法、手上又没有轮的调用方。
162672
162986
  */
162673
162987
  /**
162674
162988
  * 「user item 已落库、但这条会话的 live 轮还没 start」时的暂存位(见 {@link publishUserItem})。
@@ -162718,6 +163032,18 @@ var init_live_chat = __esm({
162718
163032
  this.publishV3(turn, this.assignV3Seq(turn, frame));
162719
163033
  return true;
162720
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
+ }
162721
163047
  /** 恢复路在 DB 收口旧 item 后,将同一身份/版本的终态发给已挂上的观众。 */
162722
163048
  publishItemStatus(chatSessionId, item) {
162723
163049
  const turn = this.turns.get(chatSessionId);
@@ -162733,7 +163059,7 @@ var init_live_chat = __esm({
162733
163059
  operation: item.status === "completed" ? "completed" : "failed",
162734
163060
  itemVersion: item.version,
162735
163061
  ...item.ord !== null ? { ord: item.ord } : {}
162736
- }));
163062
+ }, { createdAt: item.createdAt, completedAt: item.completedAt }));
162737
163063
  return true;
162738
163064
  }
162739
163065
  emitV3TurnTerminal(turn, status) {
@@ -200837,7 +201163,34 @@ function registerDelegatedChildTurn(liveChat, childSessionId, session, deps = {}
200837
201163
  } catch {
200838
201164
  }
200839
201165
  });
200840
- const settle = async (signal) => {
201166
+ const recordFailure = (err) => {
201167
+ const chatExit = err instanceof Error ? err.chatExit : void 0;
201168
+ const text6 = chatExit?.summary ?? (err instanceof Error ? err.message : String(err));
201169
+ const extra = {
201170
+ ...chatExit?.reason ? { reason: chatExit.reason } : {},
201171
+ ...chatExit?.detail ? { detail: chatExit.detail } : {}
201172
+ };
201173
+ try {
201174
+ ctrl.emit({ type: "error", text: text6, ...extra });
201175
+ } catch {
201176
+ }
201177
+ try {
201178
+ const outcome = ledger.recordError(text6, extra);
201179
+ ctrl.publishServerItem({
201180
+ itemId: outcome.itemId,
201181
+ itemType: "control",
201182
+ startedVersion: outcome.startedVersion,
201183
+ version: outcome.version,
201184
+ ord: outcome.ord,
201185
+ status: "failed",
201186
+ payload: { code: "error", note: text6, ...extra }
201187
+ });
201188
+ } catch (e) {
201189
+ log3(`[delegation] \u5B50\u4F1A\u8BDD ${childSessionId} \u5931\u8D25\u539F\u56E0\u843D\u8D26\u5931\u8D25: ${String(e)}`);
201190
+ }
201191
+ };
201192
+ const settle = async (signal, err) => {
201193
+ if (signal === "failed") recordFailure(err);
200841
201194
  try {
200842
201195
  normalized4?.finishTurn(signal);
200843
201196
  } catch {
@@ -200845,12 +201198,12 @@ function registerDelegatedChildTurn(liveChat, childSessionId, session, deps = {}
200845
201198
  try {
200846
201199
  ledger.finish(signal);
200847
201200
  await ledger.drain();
200848
- } catch (err) {
200849
- log3(`[delegation] \u5B50\u4F1A\u8BDD ${childSessionId} \u8D26\u672C\u6536\u5C3E\u5931\u8D25: ${String(err)}`);
201201
+ } catch (err2) {
201202
+ log3(`[delegation] \u5B50\u4F1A\u8BDD ${childSessionId} \u8D26\u672C\u6536\u5C3E\u5931\u8D25: ${String(err2)}`);
200850
201203
  }
200851
201204
  ctrl.finish(signal === "completed" ? "done" : "error");
200852
201205
  };
200853
- void session.done.then(() => settle("completed"), () => settle("failed"));
201206
+ void session.done.then(() => settle("completed"), (err) => settle("failed", err));
200854
201207
  }
200855
201208
  var init_child_live_turn = __esm({
200856
201209
  "../server/src/domains/delegations/child-live-turn.ts"() {
@@ -205328,15 +205681,6 @@ async function loadWorkorderSnapshot(kernel, workorderId) {
205328
205681
  if (!store) return null;
205329
205682
  return store.transaction((tx) => tx.loadWorkorder(workorderId));
205330
205683
  }
205331
- function workorderCreationState(snap, briefArtifactId) {
205332
- const briefWorkIds = new Set(
205333
- snap.works.filter((w2) => w2.nodeId === briefArtifactId).map((w2) => w2.id)
205334
- );
205335
- return {
205336
- hasNodes: snap.nodes.length > 0,
205337
- briefHasBody: snap.artifacts.some((a) => briefWorkIds.has(a.workId))
205338
- };
205339
- }
205340
205684
  function wantsChatParts(req) {
205341
205685
  const accept = req.headers.accept;
205342
205686
  return typeof accept === "string" && accept.includes("application/x-ndjson");
@@ -205985,18 +206329,12 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
205985
206329
  }
205986
206330
  };
205987
206331
  }
205988
- const applied = await kernel.applyIntervention(planned.plan, preview2.baseSeqs, actor);
206332
+ const applied = await kernel.applyIntervention(planned.plan, preview2.baseSeqs, actor, { deferActivation: true });
205989
206333
  const briefArtifactId = planned.rootArtifactId;
205990
- if (brief && brief.trim()) {
205991
- const briefRef = await blobs.put(enc(brief));
205992
- await kernel.proposeRevision({ artifactId: briefArtifactId, actor, contentRef: briefRef, reason: "\u5EFA\u5355\uFF1A\u843D\u5B9A\u53D1\u8D77\u4EBA\u6572\u5B9A\u7684 brief \u9996\u7248" });
205993
- await kernel.advanceQueue(briefArtifactId, actor);
205994
- await kernel.conclude({ artifactId: briefArtifactId, actor, note: "\u5EFA\u5355\u65F6\u7531\u53D1\u8D77\u4EBA\u62CD\u677F\u5B9A\u7A3F" });
205995
- }
205996
- if (args["dispatch"] === false) {
205997
- await ctx.artifactState?.setWorkspaceDispatchHold(workspace, true);
205998
- await kernel.pauseWorkorder?.(workspace, actor, WORKORDER_HOLD_AT_CREATE_REASON);
205999
- }
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);
206000
206338
  return {
206001
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`,
206002
206340
  data: {
@@ -207246,10 +207584,10 @@ async function startOasisServer(opts) {
207246
207584
  ...fp(p2)
207247
207585
  }))
207248
207586
  ] : [];
207249
- const { randomUUID: randomUUID47 } = await import("node:crypto");
207587
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
207250
207588
  try {
207251
207589
  await store.enqueuePendingMessage({
207252
- id: randomUUID47(),
207590
+ id: randomUUID46(),
207253
207591
  chatSessionId: input.chatSessionId,
207254
207592
  text: input.text,
207255
207593
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -207282,11 +207620,11 @@ async function startOasisServer(opts) {
207282
207620
  ...f2?.mtime ? { srcMtime: f2.mtime } : {}
207283
207621
  };
207284
207622
  });
207285
- const { randomUUID: randomUUID47 } = await import("node:crypto");
207623
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
207286
207624
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
207287
207625
  try {
207288
207626
  await store.enqueuePendingMessage({
207289
- id: randomUUID47(),
207627
+ id: randomUUID46(),
207290
207628
  chatSessionId: input.chatSessionId,
207291
207629
  text: "",
207292
207630
  createdAt: nowIso,
@@ -208214,14 +208552,13 @@ async function startOasisServer(opts) {
208214
208552
  }
208215
208553
  }
208216
208554
  const existingSnap = targetWs ? await loadWorkorderSnapshot(engine.kernel, targetWs) : null;
208217
- const resumeState = existingSnap ? workorderCreationState(existingSnap, body2.briefArtifactId) : { hasNodes: false, briefHasBody: false };
208218
- if (resumeState.hasNodes && resumeState.briefHasBody) {
208555
+ if (targetWs && (await engine.kernel.isWorkorderCreationComplete(targetWs) || legacyCreationComplete(existingSnap, body2.briefArtifactId))) {
208219
208556
  res.writeHead(409, { "content-type": "application/json" }).end(
208220
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 })
208221
208558
  );
208222
208559
  return;
208223
208560
  }
208224
- const resuming = resumeState.hasNodes;
208561
+ const resuming = (existingSnap?.nodes.length ?? 0) > 0;
208225
208562
  let sessionProjectId;
208226
208563
  let sessionBindingState;
208227
208564
  if (body2.chatSessionId && opts.chatSession) {
@@ -208285,10 +208622,7 @@ async function startOasisServer(opts) {
208285
208622
  if (targetWs && sessionProjectId) {
208286
208623
  await sessionBindingState.upsertWorkspaceBinding({ workOrderId: targetWs, projectId: sessionProjectId });
208287
208624
  }
208288
- const result = resuming ? { interventionId: "", applied: 0, resumed: true } : await engine.kernel.applyIntervention(body2.plan, body2.baseSeqs, actor);
208289
- if (resuming) {
208290
- await engine.kernel.activateWorkorder?.(targetWs, actor);
208291
- }
208625
+ const result = resuming ? { interventionId: "", applied: 0, resumed: true } : await engine.kernel.applyIntervention(body2.plan, body2.baseSeqs, actor, { deferActivation: true });
208292
208626
  const briefSpawn = body2.plan.ops.find(
208293
208627
  (op) => op.action === "spawn" && op.id === body2.briefArtifactId
208294
208628
  );
@@ -208299,26 +208633,15 @@ async function startOasisServer(opts) {
208299
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`);
208300
208634
  }
208301
208635
  const warnings = [];
208302
- if (briefText) {
208303
- const briefRef = await engine.blobs.put(new TextEncoder().encode(briefText));
208304
- 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" });
208305
- await engine.kernel.advanceQueue(body2.briefArtifactId, actor);
208306
- await engine.kernel.conclude({ artifactId: body2.briefArtifactId, actor, note: "\u5EFA\u5355\u65F6\u7531\u53D1\u8D77\u4EBA\u62CD\u677F\u5B9A\u7A3F" });
208307
- } else {
208308
- warnings.push("\u5DE5\u5355\u603B\u7EB2\u6CA1\u6709\u6B63\u6587\uFF0C\u672A\u843D\u9996\u7248");
208309
- }
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");
208310
208638
  if (body2.dispatch === false) {
208311
- await opts.artifactState?.setWorkspaceDispatchHold(workspace, true);
208312
- await engine.kernel.pauseWorkorder?.(workspace, actor, WORKORDER_HOLD_AT_CREATE_REASON);
208639
+ await (await artifactStateNow())?.setWorkspaceDispatchHold?.(workspace, true);
208313
208640
  }
208314
208641
  await wodraftPlannerIssues.remove(workspace);
208315
208642
  if (opts.chatSession && body2.chatSessionId && workspace !== "ws:default") {
208316
208643
  const applyLinkStore = await chatStoreNow();
208317
- await applyLinkStore.linkWorkOrder(body2.chatSessionId, workspace, "origin").catch((err) => {
208318
- const detail = err instanceof Error ? err.message : String(err);
208319
- console.error(`[chat-session] Failed to link work order ${workspace} to session ${body2.chatSessionId}:`, err);
208320
- warnings.push(`\u672A\u80FD\u628A\u5DE5\u5355\u5173\u8054\u56DE\u672C\u6B21\u5BF9\u8BDD\uFF1A${detail}`);
208321
- });
208644
+ await applyLinkStore.linkWorkOrder(body2.chatSessionId, workspace, "origin");
208322
208645
  const handoffArtifacts = await artifactStateNow();
208323
208646
  if (handoffArtifacts?.addWorkOrderFile) {
208324
208647
  const handoff = await linkChatAttachmentsToWorkOrder({
@@ -208348,6 +208671,7 @@ async function startOasisServer(opts) {
208348
208671
  const missing = plannedSpawnIds.filter((id) => !present.has(id));
208349
208672
  if (missing.length > 0) warnings.push(`\u534F\u4F5C\u56FE\u7F3A ${missing.length} \u4E2A\u8282\u70B9\u672A\u5EFA\u51FA\uFF1A${missing.join("\u3001")}`);
208350
208673
  }
208674
+ await engine.kernel.completeWorkorderCreation(workspace, actor, body2.dispatch !== false, WORKORDER_HOLD_AT_CREATE_REASON);
208351
208675
  res.writeHead(200, { "content-type": "application/json" }).end(
208352
208676
  JSON.stringify({
208353
208677
  ...result,
@@ -208629,7 +208953,7 @@ async function startOasisServer(opts) {
208629
208953
  const openRoom = async () => {
208630
208954
  let chatSessionId2;
208631
208955
  if (chatStore) {
208632
- const { randomUUID: randomUUID47 } = await import("node:crypto");
208956
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
208633
208957
  const known = discussSessions.get(itemKey);
208634
208958
  const knownSession = known ? await chatStore.getSession(known) : null;
208635
208959
  if (knownSession && knownSession.workspace === ctx.workspace && knownSession.humanActorId === ctx.ownerId && (!body2.escalationId || knownSession.escalationId === body2.escalationId)) chatSessionId2 = known;
@@ -208638,7 +208962,7 @@ async function startOasisServer(opts) {
208638
208962
  chatSessionId2 = existing[0]?.id;
208639
208963
  }
208640
208964
  if (!chatSessionId2) {
208641
- chatSessionId2 = randomUUID47();
208965
+ chatSessionId2 = randomUUID46();
208642
208966
  const now = (/* @__PURE__ */ new Date()).toISOString();
208643
208967
  const title = `\u89E3\u51B3\uFF1A${ctx.seed.slice(ctx.seed.indexOf("\uFF1A") + 1, ctx.seed.indexOf("\uFF1A") + 25) || "\u5DE5\u5355\u5361\u70B9"}`;
208644
208968
  const reg0 = (await resolveEngine(currentCompanyId).catch(() => null))?.registry ?? opts.registry;
@@ -209537,7 +209861,7 @@ ${composed}`;
209537
209861
  });
209538
209862
  await rejectedLedger.drain();
209539
209863
  if (rejectedLedger.failures === 0) {
209540
- const { randomUUID: randomUUID48 } = await import("node:crypto");
209864
+ const { randomUUID: randomUUID47 } = await import("node:crypto");
209541
209865
  const now = (/* @__PURE__ */ new Date()).toISOString();
209542
209866
  const rejectedAttachments = effectiveAttachments.map((a) => ({
209543
209867
  name: a.name,
@@ -209545,7 +209869,7 @@ ${composed}`;
209545
209869
  ...a.contentType !== void 0 ? { contentType: a.contentType } : {}
209546
209870
  }));
209547
209871
  await chatStore.appendMessage({
209548
- id: randomUUID48(),
209872
+ id: randomUUID47(),
209549
209873
  sessionId: persistTarget.id,
209550
209874
  role: "user",
209551
209875
  content: persistedUserMessage,
@@ -209623,14 +209947,14 @@ ${composed}`;
209623
209947
  runtimeSessionId: session.nativeSessionId ?? session.id
209624
209948
  }).catch(() => void 0);
209625
209949
  }
209626
- const { randomUUID: randomUUID48 } = await import("node:crypto");
209950
+ const { randomUUID: randomUUID47 } = await import("node:crypto");
209627
209951
  const persistAttachments = effectiveAttachments.map((a) => ({
209628
209952
  name: a.name,
209629
209953
  ...a.blobRef !== void 0 ? { blobRef: a.blobRef } : {},
209630
209954
  ...a.contentType !== void 0 ? { contentType: a.contentType } : {}
209631
209955
  }));
209632
209956
  await chatStore.appendMessage({
209633
- id: randomUUID48(),
209957
+ id: randomUUID47(),
209634
209958
  sessionId: persistTarget.id,
209635
209959
  role: "user",
209636
209960
  content: persistedUserMessage,
@@ -209656,27 +209980,10 @@ ${composed}`;
209656
209980
  const itemLedger = new ChatItemLedger({
209657
209981
  ...itemStore ? { items: itemStore } : {},
209658
209982
  versionSeed,
209659
- /* 用户那条消息一落库就推上 v3 流(ADR-0510 D2 的收口)。
209660
- 不推的话:乐观条没有服务端号、被钉在列表末尾,而 agent 的正文带着更大的号从
209661
- 直播流到达、进「有号」段 ⇒ **回复显示在用户消息上面**,直到下一次快照才跳回去
209662
- (2026-09-09 用户现场)。这里把窗口从「等下一次快照」压到「一次 INSERT 往返」。
209663
- 只推 user:assistant 的每一条本来就走 `emitV3`。 */
209664
- onPersisted: (info) => {
209665
- if (info.role !== "user") return;
209666
- const rowPayload = info.payload && typeof info.payload === "object" && !Array.isArray(info.payload) ? info.payload : void 0;
209667
- const rowCsid = typeof rowPayload?.["clientSubmitId"] === "string" ? rowPayload["clientSubmitId"] : void 0;
209668
- const rowCsids = Array.isArray(rowPayload?.["clientSubmitIds"]) ? rowPayload["clientSubmitIds"].filter((v2) => typeof v2 === "string") : void 0;
209669
- const rowAttachments = Array.isArray(rowPayload?.["attachments"]) ? rowPayload["attachments"] : void 0;
209670
- opts.liveChat?.publishUserItem(persistTarget?.id ?? session.id, {
209671
- itemId: info.itemId,
209672
- ord: info.ord,
209673
- version: info.version,
209674
- text: info.text,
209675
- ...rowCsid ? { clientSubmitId: rowCsid } : {},
209676
- ...rowCsids?.length ? { clientSubmitIds: rowCsids } : {},
209677
- ...rowAttachments?.length ? { attachments: rowAttachments } : {}
209678
- });
209679
- },
209983
+ /* 用户那条消息(首条提交与中途插话)一落库就推上 v3 流(ADR-0510 D2 的收口)——
209984
+ **不在这里接**:`liveChat.start` 带着这本账本开轮时统一订阅
209985
+ (`LiveChatRegistry.start` → `ChatItemLedger.onUserItemPersisted`),开轮前已落库的会补发。
209986
+ 四条开轮入口共用那一处;这里再接一次就是同一行两帧、同一版本号,客户端判 stale 并 resync。 */
209680
209987
  sessionId: persistTarget?.id ?? session.id,
209681
209988
  // 优先账本 id(`chat_session_turns.id`,跨进程唯一、重启不失忆);账本没接入才兜底。
209682
209989
  turnId: heldTurn?.id ?? fallbackTurnId(session.runId),
@@ -209778,52 +210085,27 @@ ${composed}`;
209778
210085
  }, { items: itemLedger }) : null;
209779
210086
  if (live && assistantMsgId) live.setAssistantMessageId(assistantMsgId);
209780
210087
  if (live && chatStore && persistTarget) {
209781
- const { randomUUID: newId } = await import("node:crypto");
209782
210088
  const liveCtl = live;
209783
210089
  const target = persistTarget;
209784
210090
  const store = chatStore;
209785
210091
  liveCtl.onUserInput((text6, insertedAttachments) => {
209786
- const segText = itemLedger.projectContent();
209787
210092
  const segParts = collectedParts();
209788
- const closingId = assistantMsgId;
209789
- const nextId = newId();
209790
- assistantMsgId = nextId;
209791
- itemLedger.beginSegment();
209792
- 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;
209793
210107
  segmentBaseSeq = liveCtl.bufferedParts().reduce((max, p2) => p2.seq > max ? p2.seq : max, 0);
209794
- const at = (/* @__PURE__ */ new Date()).toISOString();
209795
- checkpointChain = checkpointChain.then(async () => {
209796
- if (closingId) {
209797
- await store.updateMessage(closingId, {
209798
- content: segText,
209799
- status: "done",
209800
- completedAt: at,
209801
- ...segParts.length ? { parts: segParts } : {}
209802
- }).catch(() => void 0);
209803
- }
209804
- await store.appendMessage({
209805
- id: newId(),
209806
- sessionId: target.id,
209807
- role: "user",
209808
- content: text6,
209809
- createdAt: at,
209810
- // ADR-0166 D9.1:插入带的附件也落在**这条 user 行**上——否则重载后这条气泡
209811
- // 只剩一行字,而同样带图直接发送的那条有缩略图。同 `/api/chat` 那侧只存引用。
209812
- ...insertedAttachments?.length ? { attachments: insertedAttachments } : {},
209813
- ...heldTurn ? { turnId: heldTurn.id } : {}
209814
- // 插话切段仍属同一轮(同 turnId)
209815
- }).catch(() => void 0);
209816
- await store.appendMessage({
209817
- id: nextId,
209818
- sessionId: target.id,
209819
- role: "assistant",
209820
- content: "",
209821
- createdAt: at,
209822
- status: "running",
209823
- ...session.runId ? { runId: session.runId } : {},
209824
- ...heldTurn ? { turnId: heldTurn.id } : {}
209825
- }).then(() => itemLedger.bindMessage(nextId)).catch(() => void 0);
209826
- }).catch(() => void 0);
210108
+ checkpointChain = checkpointChain.then(split.persist).catch(() => void 0);
209827
210109
  });
209828
210110
  }
209829
210111
  let localSeq = 0;
@@ -210106,8 +210388,8 @@ ${composed}`;
210106
210388
  }
210107
210389
  }
210108
210390
  const { spawn: spawn10 } = await import("node:child_process");
210109
- const { randomUUID: randomUUID47 } = await import("node:crypto");
210110
- const sessionId = body2.sessionId ?? randomUUID47();
210391
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
210392
+ const sessionId = body2.sessionId ?? randomUUID46();
210111
210393
  const args = [
210112
210394
  "-p",
210113
210395
  body2.message,
@@ -210403,8 +210685,8 @@ ${composed}`;
210403
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}` }));
210404
210686
  return;
210405
210687
  }
210406
- const { randomUUID: randomUUID47 } = await import("node:crypto");
210407
- const state = randomUUID47();
210688
+ const { randomUUID: randomUUID46 } = await import("node:crypto");
210689
+ const state = randomUUID46();
210408
210690
  const nameSlug = (actorName || actorId?.split(":").pop() || owner?.login || "bot").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "bot";
210409
210691
  const manifest = buildAppManifest({
210410
210692
  employeeSlug: nameSlug,
@@ -211212,6 +211494,7 @@ var init_server3 = __esm({
211212
211494
  init_router();
211213
211495
  init_infra_error();
211214
211496
  init_keyed_mutex();
211497
+ init_initialize_workorder();
211215
211498
  init_live_chat();
211216
211499
  init_chat_turn_wait();
211217
211500
  init_append_now();
@@ -220992,7 +221275,11 @@ function wireRecoveredChatTurn(deps) {
220992
221275
  void handle.kill();
220993
221276
  },
220994
221277
  // ADR-0093 chat 道:重挂的轮同样能被插入——否则「serve 重启过」会变成一条人看不见的能力差异。
220995
- ...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) } : {},
220996
221283
  get canAppendInput() {
220997
221284
  return handle.canAppendInput !== false && typeof handle.appendInput === "function";
220998
221285
  }
@@ -221084,40 +221371,25 @@ function wireRecoveredChatTurn(deps) {
221084
221371
  let checkpointChain = Promise.resolve();
221085
221372
  {
221086
221373
  const store = deps.chatStore;
221087
- live.onUserInput((text6) => {
221088
- const segText = itemLedger.projectContent();
221374
+ live.onUserInput((text6, attachments) => {
221089
221375
  const segParts = collectedParts();
221090
- const closingId = currentMsgId;
221091
- const nextId = (0, import_node_crypto49.randomUUID)();
221092
- currentMsgId = nextId;
221093
- itemLedger.beginSegment();
221094
- 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;
221095
221391
  segmentBaseSeq = live.bufferedParts().reduce((max, p2) => p2.seq > max ? p2.seq : max, 0);
221096
- const at = (/* @__PURE__ */ new Date()).toISOString();
221097
- checkpointChain = checkpointChain.then(async () => {
221098
- await store.updateMessage(closingId, {
221099
- content: segText,
221100
- status: "done",
221101
- completedAt: at,
221102
- ...segParts.length ? { parts: segParts } : {}
221103
- }).catch(() => void 0);
221104
- await store.appendMessage({
221105
- id: (0, import_node_crypto49.randomUUID)(),
221106
- sessionId: plan.chatSessionId,
221107
- role: "user",
221108
- content: text6,
221109
- createdAt: at
221110
- }).catch(() => void 0);
221111
- await store.appendMessage({
221112
- id: nextId,
221113
- sessionId: plan.chatSessionId,
221114
- role: "assistant",
221115
- content: "",
221116
- createdAt: at,
221117
- status: "running",
221118
- runId: plan.runId
221119
- }).then(() => itemLedger.bindMessage(nextId)).catch(() => void 0);
221120
- }).catch(() => void 0);
221392
+ checkpointChain = checkpointChain.then(split.persist).catch(() => void 0);
221121
221393
  });
221122
221394
  }
221123
221395
  const ckptTimer = setInterval(() => {
@@ -221589,12 +221861,11 @@ async function recoverChatTurnsAcrossCompanies(deps) {
221589
221861
  }
221590
221862
  return { ...result, recovered };
221591
221863
  }
221592
- var import_node_crypto49, asObj3, partsOf, maxPartSeq, exitFailed, exitErrorText, runTerminalPatch;
221864
+ var asObj3, partsOf, maxPartSeq, exitFailed, exitErrorText, runTerminalPatch;
221593
221865
  var init_chat_recovery = __esm({
221594
221866
  "../server/src/chat-recovery.ts"() {
221595
221867
  "use strict";
221596
221868
  init_file_restore();
221597
- import_node_crypto49 = require("node:crypto");
221598
221869
  init_chat_parts();
221599
221870
  init_sink();
221600
221871
  init_resilient_appender();
@@ -221625,8 +221896,8 @@ var init_chat_recovery = __esm({
221625
221896
 
221626
221897
  // ../server/src/auth/crypto.ts
221627
221898
  function hashPassword(password) {
221628
- const salt = (0, import_node_crypto50.randomBytes)(16);
221629
- 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 });
221630
221901
  return `scrypt$${SCRYPT_N}$${salt.toString("base64url")}$${dk.toString("base64url")}`;
221631
221902
  }
221632
221903
  function verifyPassword(password, stored) {
@@ -221643,8 +221914,8 @@ function verifyPassword(password, stored) {
221643
221914
  return false;
221644
221915
  }
221645
221916
  if (expected.length === 0) return false;
221646
- const dk = (0, import_node_crypto50.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
221647
- 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);
221648
221919
  }
221649
221920
  function b64urlJson(value2) {
221650
221921
  return Buffer.from(JSON.stringify(value2)).toString("base64url");
@@ -221652,17 +221923,17 @@ function b64urlJson(value2) {
221652
221923
  function signSession(claims, secret) {
221653
221924
  const head = b64urlJson({ alg: "HS256", typ: "JWT" });
221654
221925
  const body2 = b64urlJson(claims);
221655
- 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");
221656
221927
  return `${head}.${body2}.${sig}`;
221657
221928
  }
221658
221929
  function verifySession(token, secret, nowMs) {
221659
221930
  const parts = token.split(".");
221660
221931
  if (parts.length !== 3) return null;
221661
221932
  const [head, body2, sig] = parts;
221662
- 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");
221663
221934
  const got = Buffer.from(sig);
221664
221935
  const exp = Buffer.from(expected);
221665
- 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;
221666
221937
  let claims;
221667
221938
  try {
221668
221939
  claims = JSON.parse(Buffer.from(body2, "base64url").toString("utf8"));
@@ -221674,21 +221945,21 @@ function verifySession(token, secret, nowMs) {
221674
221945
  return claims;
221675
221946
  }
221676
221947
  function generateCode() {
221677
- 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");
221678
221949
  }
221679
221950
  function hashCode(code2, secret) {
221680
- 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");
221681
221952
  }
221682
221953
  function safeEqualHash(a, b2) {
221683
221954
  const ba = Buffer.from(a);
221684
221955
  const bb = Buffer.from(b2);
221685
- 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);
221686
221957
  }
221687
- var import_node_crypto50, SCRYPT_N, SCRYPT_KEYLEN;
221958
+ var import_node_crypto49, SCRYPT_N, SCRYPT_KEYLEN;
221688
221959
  var init_crypto2 = __esm({
221689
221960
  "../server/src/auth/crypto.ts"() {
221690
221961
  "use strict";
221691
- import_node_crypto50 = require("node:crypto");
221962
+ import_node_crypto49 = require("node:crypto");
221692
221963
  SCRYPT_N = 16384;
221693
221964
  SCRYPT_KEYLEN = 32;
221694
221965
  }
@@ -222851,18 +223122,18 @@ function variableKeyFromEnv(env = process.env) {
222851
223122
  return buf;
222852
223123
  }
222853
223124
  if (env.NODE_ENV === "production") throw new Error("OASIS_VAR_KEY is required in production");
222854
- 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();
222855
223126
  }
222856
223127
  function dedupe(ids2) {
222857
223128
  return [...new Set(ids2.filter((x2) => typeof x2 === "string" && x2.length > 0))];
222858
223129
  }
222859
- var import_node_crypto51, REDACTED_MARKER, ActorsService, mask, changedKeys;
223130
+ var import_node_crypto50, REDACTED_MARKER, ActorsService, mask, changedKeys;
222860
223131
  var init_service5 = __esm({
222861
223132
  "../server/src/domains/actors/service.ts"() {
222862
223133
  "use strict";
222863
223134
  init_file_limits2();
222864
223135
  init_keyed_mutex();
222865
- import_node_crypto51 = require("node:crypto");
223136
+ import_node_crypto50 = require("node:crypto");
222866
223137
  init_skill_fetcher();
222867
223138
  init_identity();
222868
223139
  init_migrate();
@@ -223430,7 +223701,7 @@ ${input.description}
223430
223701
  */
223431
223702
  buildSkillFile(skillId, path40, content3, now) {
223432
223703
  const bytes2 = new TextEncoder().encode(content3);
223433
- 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");
223434
223705
  return { skillId, path: path40, content: content3, blobHash, size: bytes2.length, updatedAt: now };
223435
223706
  }
223436
223707
  /**
@@ -224211,15 +224482,15 @@ ${input.description}
224211
224482
  return hit ? { value: hit.value, ...hit.origin ? { origin: hit.origin } : {} } : null;
224212
224483
  }
224213
224484
  encrypt(plain) {
224214
- const iv = (0, import_node_crypto51.randomBytes)(12);
224215
- const cipher = (0, import_node_crypto51.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
224216
- const enc4 = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
224217
- 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(".");
224218
224489
  }
224219
224490
  decrypt(packed) {
224220
224491
  const [iv, tag, data] = packed.split(".");
224221
224492
  if (!iv || !tag || typeof data !== "string") throw new Error("malformed ciphertext");
224222
- 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"));
224223
224494
  decipher.setAuthTag(Buffer.from(tag, "base64"));
224224
224495
  return Buffer.concat([decipher.update(Buffer.from(data, "base64")), decipher.final()]).toString("utf8");
224225
224496
  }
@@ -224831,7 +225102,7 @@ async function listSkillMetadataForActor(args) {
224831
225102
  usedDirs.add(dir);
224832
225103
  const sorted = [...metas].sort((a, b2) => a.path.localeCompare(b2.path));
224833
225104
  let latest = "1970-01-01T00:00:00.000Z";
224834
- const hasher = (0, import_node_crypto52.createHash)("sha256");
225105
+ const hasher = (0, import_node_crypto51.createHash)("sha256");
224835
225106
  for (const m2 of sorted) {
224836
225107
  if (m2.updatedAt > latest) latest = m2.updatedAt;
224837
225108
  hasher.update(m2.path);
@@ -224856,11 +225127,11 @@ async function listSkillMetadataForActor(args) {
224856
225127
  const dir = sanitizeSkillDir(c.name, c.slug);
224857
225128
  if (usedDirs.has(dir)) continue;
224858
225129
  usedDirs.add(dir);
224859
- const hasher = (0, import_node_crypto52.createHash)("sha256");
225130
+ const hasher = (0, import_node_crypto51.createHash)("sha256");
224860
225131
  for (const p2 of paths) {
224861
225132
  hasher.update(p2);
224862
225133
  hasher.update("\0");
224863
- 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"));
224864
225135
  hasher.update("\0");
224865
225136
  }
224866
225137
  out.push({
@@ -224881,11 +225152,11 @@ async function listSkillMetadataForActor(args) {
224881
225152
  const dir = sanitizeSkillDir(b2.name, b2.id);
224882
225153
  if (usedDirs.has(dir)) continue;
224883
225154
  usedDirs.add(dir);
224884
- const hasher = (0, import_node_crypto52.createHash)("sha256");
225155
+ const hasher = (0, import_node_crypto51.createHash)("sha256");
224885
225156
  for (const p2 of paths) {
224886
225157
  hasher.update(p2);
224887
225158
  hasher.update("\0");
224888
- 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"));
224889
225160
  hasher.update("\0");
224890
225161
  }
224891
225162
  out.push({
@@ -224905,7 +225176,7 @@ function builtinSkillToFiles(b2) {
224905
225176
  skillId: `builtin:${b2.id}`,
224906
225177
  path: path40,
224907
225178
  content: content3,
224908
- blobHash: (0, import_node_crypto52.createHash)("sha256").update(content3).digest("hex"),
225179
+ blobHash: (0, import_node_crypto51.createHash)("sha256").update(content3).digest("hex"),
224909
225180
  size: Buffer.byteLength(content3, "utf8"),
224910
225181
  updatedAt: "1970-01-01T00:00:00.000Z"
224911
225182
  }));
@@ -224929,7 +225200,7 @@ function sanitizeSkillDir(name, fallbackId) {
224929
225200
  if (byName) return byName;
224930
225201
  const byId = fallbackId ? fold2(fallbackId) : "";
224931
225202
  if (byId) return byId;
224932
- 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)}`;
224933
225204
  }
224934
225205
  async function materializeSkillFiles(args) {
224935
225206
  const skills = await args.service.listInstalledSkillsForActor(args.actorId);
@@ -224962,7 +225233,7 @@ function connectorSkillToFiles(c) {
224962
225233
  skillId: c.id,
224963
225234
  path: path40,
224964
225235
  content: content3,
224965
- blobHash: (0, import_node_crypto52.createHash)("sha256").update(content3).digest("hex"),
225236
+ blobHash: (0, import_node_crypto51.createHash)("sha256").update(content3).digest("hex"),
224966
225237
  size: Buffer.byteLength(content3, "utf8"),
224967
225238
  updatedAt: "1970-01-01T00:00:00.000Z"
224968
225239
  }));
@@ -224979,11 +225250,11 @@ function connectorSkillDisplayRows(connectorSkills, enabledConnectors) {
224979
225250
  ...s2.version !== void 0 ? { version: s2.version } : {}
224980
225251
  }));
224981
225252
  }
224982
- var import_node_crypto52;
225253
+ var import_node_crypto51;
224983
225254
  var init_skill_materializer = __esm({
224984
225255
  "../server/src/domains/actors/skill-materializer.ts"() {
224985
225256
  "use strict";
224986
- import_node_crypto52 = require("node:crypto");
225257
+ import_node_crypto51 = require("node:crypto");
224987
225258
  }
224988
225259
  });
224989
225260
 
@@ -226551,7 +226822,7 @@ function createActorsDomain(opts) {
226551
226822
  ...kernel !== void 0 ? { onRolesChanged: (a, r) => kernel.setActorRoles(a, r) } : {},
226552
226823
  audit: async (entry) => {
226553
226824
  await opts.audit?.({
226554
- id: `reg_${(0, import_node_crypto53.randomUUID)()}`,
226825
+ id: `reg_${(0, import_node_crypto52.randomUUID)()}`,
226555
226826
  actor: entry.by,
226556
226827
  kind: "registry_change",
226557
226828
  target: entry.actorId,
@@ -226591,11 +226862,11 @@ function createActorsDomain(opts) {
226591
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 } : {} })
226592
226863
  };
226593
226864
  }
226594
- var import_node_crypto53;
226865
+ var import_node_crypto52;
226595
226866
  var init_actors = __esm({
226596
226867
  "../server/src/domains/actors/index.ts"() {
226597
226868
  "use strict";
226598
- import_node_crypto53 = require("node:crypto");
226869
+ import_node_crypto52 = require("node:crypto");
226599
226870
  init_service5();
226600
226871
  init_routes3();
226601
226872
  init_service5();
@@ -228093,6 +228364,7 @@ function buildWorkorderActivity(input) {
228093
228364
  const nodeCancelledAt = node2?.cancelledAt ?? null;
228094
228365
  const name = nodeName(nodeId);
228095
228366
  let fold2 = null;
228367
+ let handoverFold = null;
228096
228368
  for (const [idx, w2] of works.entries()) {
228097
228369
  const next = works[idx + 1];
228098
228370
  const artifacts = artifactsByWork.get(w2.id) ?? [];
@@ -228102,6 +228374,7 @@ function buildWorkorderActivity(input) {
228102
228374
  const reassigned = !!next && next.assigneeActorId !== w2.assigneeActorId;
228103
228375
  const killedByPlan = !!(w2.deadAt || w2.cancelledAt) && !!next && (reassigned || (next.nodeVersion ?? 0) > (w2.nodeVersion ?? 0));
228104
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;
228105
228378
  if (failedRound && fold2) {
228106
228379
  fold2.traceWorkIds.push(w2.id);
228107
228380
  cardOfWork.set(w2.id, fold2);
@@ -228112,6 +228385,16 @@ function buildWorkorderActivity(input) {
228112
228385
  continue;
228113
228386
  }
228114
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;
228115
228398
  const card2 = {
228116
228399
  /* 卡 id = 这一轮的 workId(连败折叠时取第一轮,React key 与滚动锚点因此稳定)。 */
228117
228400
  id: `work:${w2.id}`,
@@ -228159,6 +228442,10 @@ function buildWorkorderActivity(input) {
228159
228442
  card2.stuckReason = stuckKind;
228160
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));
228161
228444
  fold2 = card2;
228445
+ } else if (handedOverRound) {
228446
+ card2.phase = "done";
228447
+ card2.status = ACTIVITY_COPY.stageHandedOver(name);
228448
+ handoverFold = card2;
228162
228449
  } else if (w2.endedAt) {
228163
228450
  card2.phase = "running";
228164
228451
  card2.status = ACTIVITY_COPY.awaitingConclude(name);
@@ -228822,8 +229109,20 @@ var init_activity2 = __esm({
228822
229109
  /**
228823
229110
  * 引擎判卷 success、`endedAt` 也写了,却始终没有 `work.conclude`:**交了稿没收口**。
228824
229111
  * 如实说这一档,不冒充「正在处理」——后者把一轮早就结束的执行画成还在跑。
229112
+ * 只剩**节点最新一轮**会落到这里;同节点已有下一轮接手的,见 {@link stageHandedOver}。
228825
229113
  */
228826
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`,
228827
229126
  /** `works.status=retry`:这一轮没跑成,等引擎重派。谁按的重试由 `plan.node_retry` 补进这句。 */
228828
229127
  awaitingRedispatch: (executor, node2) => `${executor} \u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u6210\u300A${node2}\u300B\uFF0C\u7B49\u5F85\u91CD\u6D3E\u3002`,
228829
229128
  doneAwaitingReview: (name) => `\u300A${name}\u300B\u5DF2\u5B8C\u6210\uFF0C\u7B49\u5F85\u5BA1\u6838\u3002`,
@@ -229795,7 +230094,7 @@ async function buildChatPreview(chatStore, sessionId, who) {
229795
230094
  async function buildChatInbox(chatStore, markers, me, resolveActor, excludeChildSessions) {
229796
230095
  const sessions = await chatStore.listSessions(me);
229797
230096
  const byScope = new Map(markers.map((m2) => [m2.scope, m2]));
229798
- 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);
229799
230098
  const excluded = excludeChildSessions ? new Set(await excludeChildSessions(unread.map((s2) => s2.id)).catch(() => [])) : /* @__PURE__ */ new Set();
229800
230099
  const out = [];
229801
230100
  let previewsFetched = 0;
@@ -230065,11 +230364,11 @@ function fromRow(row) {
230065
230364
  for (const [k2, v2] of opt) if (v2 !== void 0 && v2 !== null) r[k2] = v2;
230066
230365
  return r;
230067
230366
  }
230068
- 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;
230069
230368
  var init_notification_store = __esm({
230070
230369
  "../server/src/domains/collab/notification-store.ts"() {
230071
230370
  "use strict";
230072
- import_node_crypto54 = require("node:crypto");
230371
+ import_node_crypto53 = require("node:crypto");
230073
230372
  import_node_fs14 = __toESM(require("node:fs"), 1);
230074
230373
  DEFAULT_PAGE = 50;
230075
230374
  MAX_PAGE = 200;
@@ -230102,7 +230401,7 @@ var init_notification_store = __esm({
230102
230401
  async insert(n) {
230103
230402
  const existing = this.findLive(n.recipient, n.sourceKey, n.category);
230104
230403
  if (existing) return existing;
230105
- 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 };
230106
230405
  this.rows.set(row.id, row);
230107
230406
  this.save();
230108
230407
  return row;
@@ -230225,7 +230524,7 @@ var init_notification_store = __esm({
230225
230524
  return new _PostgresNotificationStore(sql, schema);
230226
230525
  }
230227
230526
  async insert(n) {
230228
- const id = `ntf:${(0, import_node_crypto54.randomUUID)()}`;
230527
+ const id = `ntf:${(0, import_node_crypto53.randomUUID)()}`;
230229
230528
  const r = await this.sql.query(
230230
230529
  `INSERT INTO ${this.t} (id, recipient, category, source_key, kind, workorder_id, node_id, card_id, actor_id,
230231
230530
  title, workspace_title, entered_at, updated_at, status, read_at, item)
@@ -231176,13 +231475,13 @@ function nameKey(name) {
231176
231475
  function cleanName(name) {
231177
231476
  return name.trim().replace(/\s+/g, " ");
231178
231477
  }
231179
- 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;
231180
231479
  var init_tags = __esm({
231181
231480
  "../server/src/domains/collab/tags.ts"() {
231182
231481
  "use strict";
231183
231482
  import_node_fs15 = __toESM(require("node:fs"), 1);
231184
231483
  import_node_path21 = __toESM(require("node:path"), 1);
231185
- import_node_crypto55 = __toESM(require("node:crypto"), 1);
231484
+ import_node_crypto54 = __toESM(require("node:crypto"), 1);
231186
231485
  SEP = "::";
231187
231486
  keyOf = (companyId, id) => `${companyId}${SEP}${id}`;
231188
231487
  prefixOf = (companyId) => `${companyId}${SEP}`;
@@ -231200,7 +231499,7 @@ var init_tags = __esm({
231200
231499
  if (!name) return null;
231201
231500
  if (this.entries(companyId).some((t) => nameKey(t.name) === nameKey(name))) return null;
231202
231501
  const tag = {
231203
- id: `tag-${import_node_crypto55.default.randomBytes(6).toString("hex")}`,
231502
+ id: `tag-${import_node_crypto54.default.randomBytes(6).toString("hex")}`,
231204
231503
  name,
231205
231504
  ...input.color?.trim() ? { color: input.color.trim() } : {},
231206
231505
  ...input.description?.trim() ? { description: input.description.trim() } : {}
@@ -232516,11 +232815,11 @@ async function isEmployedElsewhere(store, companyId, accountId) {
232516
232815
  const memberships = await store.listMembershipsForAccount(accountId);
232517
232816
  return memberships.some((m2) => m2.companyId !== companyId);
232518
232817
  }
232519
- 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;
232520
232819
  var init_service6 = __esm({
232521
232820
  "../server/src/domains/companies/service.ts"() {
232522
232821
  "use strict";
232523
- import_node_crypto56 = require("node:crypto");
232822
+ import_node_crypto55 = require("node:crypto");
232524
232823
  init_console_url();
232525
232824
  ROLES = ["owner", "admin", "member"];
232526
232825
  INVITABLE_ROLES = ["admin", "member"];
@@ -232612,7 +232911,7 @@ var init_service6 = __esm({
232612
232911
  const existing = await this.store.getAccountByEmail(normalized4);
232613
232912
  if (existing) return existing;
232614
232913
  const account = {
232615
- id: `actor:human:${(0, import_node_crypto56.randomUUID)()}`,
232914
+ id: `actor:human:${(0, import_node_crypto55.randomUUID)()}`,
232616
232915
  email: normalized4,
232617
232916
  name: name ?? normalized4,
232618
232917
  status: "active"
@@ -232821,7 +233120,7 @@ var init_service6 = __esm({
232821
233120
  }
232822
233121
  const nowMs = Date.parse(this.now());
232823
233122
  const invitation = {
232824
- id: `invitation:${(0, import_node_crypto56.randomUUID)()}`,
233123
+ id: `invitation:${(0, import_node_crypto55.randomUUID)()}`,
232825
233124
  companyId,
232826
233125
  email: mail,
232827
233126
  role,
@@ -232952,7 +233251,7 @@ var init_service6 = __esm({
232952
233251
  const nowMs = Date.parse(this.now());
232953
233252
  const link = {
232954
233253
  companyId,
232955
- 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"),
232956
233255
  role: INVITE_LINK_ROLE,
232957
233256
  createdAt: new Date(nowMs).toISOString(),
232958
233257
  expiresAt: new Date(nowMs + INVITE_LINK_TTL_MS).toISOString(),
@@ -233009,7 +233308,7 @@ var init_service6 = __esm({
233009
233308
  const reusable = existing.find((inv) => !this.isExpired(inv));
233010
233309
  if (reusable) return reusable;
233011
233310
  const invitation = {
233012
- id: `invitation:${(0, import_node_crypto56.randomUUID)()}`,
233311
+ id: `invitation:${(0, import_node_crypto55.randomUUID)()}`,
233013
233312
  companyId: link.companyId,
233014
233313
  email: mail,
233015
233314
  role: link.role,
@@ -233247,11 +233546,11 @@ var init_service6 = __esm({
233247
233546
  });
233248
233547
 
233249
233548
  // ../server/src/domains/companies/offboarding.ts
233250
- var import_node_crypto57, MemberOffboardingService;
233549
+ var import_node_crypto56, MemberOffboardingService;
233251
233550
  var init_offboarding = __esm({
233252
233551
  "../server/src/domains/companies/offboarding.ts"() {
233253
233552
  "use strict";
233254
- import_node_crypto57 = require("node:crypto");
233553
+ import_node_crypto56 = require("node:crypto");
233255
233554
  init_service6();
233256
233555
  MemberOffboardingService = class {
233257
233556
  store;
@@ -233323,7 +233622,7 @@ var init_offboarding = __esm({
233323
233622
  const { ownedAgents, inFlight } = await this.snapshot(companyId, accountId);
233324
233623
  const at = this.now();
233325
233624
  const started = await this.store.startMemberOffboarding({
233326
- operationId: `off:${(0, import_node_crypto57.randomUUID)()}`,
233625
+ operationId: `off:${(0, import_node_crypto56.randomUUID)()}`,
233327
233626
  companyId,
233328
233627
  accountId,
233329
233628
  initiatedBy,
@@ -233833,18 +234132,18 @@ var init_routes6 = __esm({
233833
234132
 
233834
234133
  // ../server/src/domains/companies/agent-credentials.ts
233835
234134
  function hashAgentToken(token, secret) {
233836
- 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");
233837
234136
  }
233838
234137
  function safeEqual(a, b2) {
233839
234138
  const ba = Buffer.from(a);
233840
234139
  const bb = Buffer.from(b2);
233841
- 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);
233842
234141
  }
233843
- var import_node_crypto58, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
234142
+ var import_node_crypto57, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
233844
234143
  var init_agent_credentials = __esm({
233845
234144
  "../server/src/domains/companies/agent-credentials.ts"() {
233846
234145
  "use strict";
233847
- import_node_crypto58 = require("node:crypto");
234146
+ import_node_crypto57 = require("node:crypto");
233848
234147
  init_service6();
233849
234148
  HASH_PREFIX = "ac1$";
233850
234149
  TOKEN_PREFIX = "oak_";
@@ -233869,7 +234168,7 @@ var init_agent_credentials = __esm({
233869
234168
  if (employee.status !== "active") {
233870
234169
  throw new CompanyError(409, "AGENT_LEFT", `\u6570\u5B57\u5458\u5DE5\u5DF2\u79BB\u5F00\uFF0C\u4E0D\u80FD\u7B7E\u53D1\u51ED\u8BC1\uFF1A${agentId}`);
233871
234170
  }
233872
- 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")}`;
233873
234172
  const credential = {
233874
234173
  tokenHash: hashAgentToken(token, this.secret),
233875
234174
  companyId,
@@ -234307,7 +234606,7 @@ function gatewayInstanceSlug(gatewayUrl) {
234307
234606
  }
234308
234607
  function connectInstanceName(gatewayUrl, token, companyId) {
234309
234608
  const scope2 = companyId?.trim();
234310
- 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";
234311
234610
  const base = gatewayInstanceSlug(gatewayUrl);
234312
234611
  return base.slice(0, Math.max(1, 64 - suffix.length - 1)) + "-" + suffix;
234313
234612
  }
@@ -234636,12 +234935,12 @@ function buildNpxCmd(p2) {
234636
234935
  const prefix = daemonNpmPrefix();
234637
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)}`;
234638
234937
  }
234639
- var import_node_crypto59;
234938
+ var import_node_crypto58;
234640
234939
  var init_connect_script = __esm({
234641
234940
  "../server/src/domains/nodes/connect-script.ts"() {
234642
234941
  "use strict";
234643
234942
  init_src();
234644
- import_node_crypto59 = require("node:crypto");
234943
+ import_node_crypto58 = require("node:crypto");
234645
234944
  }
234646
234945
  });
234647
234946
 
@@ -235248,7 +235547,7 @@ function nodesDomain(deps) {
235248
235547
  }
235249
235548
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
235250
235549
  } else {
235251
- nodeId = `node-${(0, import_node_crypto60.randomUUID)().slice(0, 8)}`;
235550
+ nodeId = `node-${(0, import_node_crypto59.randomUUID)().slice(0, 8)}`;
235252
235551
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
235253
235552
  }
235254
235553
  const existingNode = await deps.nodeStore.getNode(nodeId);
@@ -235433,14 +235732,14 @@ function normalizeGatewayUrl(raw) {
235433
235732
  }
235434
235733
  }
235435
235734
  function newNodeId() {
235436
- 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);
235437
235736
  }
235438
- 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;
235439
235738
  var init_routes7 = __esm({
235440
235739
  "../server/src/domains/nodes/routes.ts"() {
235441
235740
  "use strict";
235442
235741
  init_runtime_status();
235443
- import_node_crypto60 = require("node:crypto");
235742
+ import_node_crypto59 = require("node:crypto");
235444
235743
  import_node_fs16 = require("node:fs");
235445
235744
  import_node_url6 = require("node:url");
235446
235745
  import_node_path22 = require("node:path");
@@ -236631,7 +236930,7 @@ function createBundle(root, target, input) {
236631
236930
  }
236632
236931
  }
236633
236932
  const bytes2 = fs25.statSync(absBundle).size;
236634
- 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");
236635
236934
  const relBase = `inputs/git-bundles/${safeRepo}-${commit.slice(0, 12)}`;
236636
236935
  return {
236637
236936
  absoluteBundlePath: absBundle,
@@ -236727,12 +237026,12 @@ function classifyGitError(fallback, err, where) {
236727
237026
  function oneLine(msg) {
236728
237027
  return msg.split("\n").map((s2) => s2.trim()).filter(Boolean).slice(0, 3).join(" ");
236729
237028
  }
236730
- 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;
236731
237030
  var init_readonly_bundle = __esm({
236732
237031
  "../server/src/git/readonly-bundle.ts"() {
236733
237032
  "use strict";
236734
237033
  import_node_child_process16 = require("node:child_process");
236735
- import_node_crypto61 = require("node:crypto");
237034
+ import_node_crypto60 = require("node:crypto");
236736
237035
  fs25 = __toESM(require("node:fs"), 1);
236737
237036
  os7 = __toESM(require("node:os"), 1);
236738
237037
  path24 = __toESM(require("node:path"), 1);
@@ -236752,8 +237051,9 @@ var init_readonly_bundle = __esm({
236752
237051
 
236753
237052
  // ../server/src/domains/collab/create-seeded-workorder.ts
236754
237053
  function makeSeededWorkorderCreator(deps) {
236755
- const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto62.randomUUID)().slice(0, 8)}`);
236756
- 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) => {
236757
237057
  const company = input.companyId && deps.resolveCompany ? await deps.resolveCompany(input.companyId) : void 0;
236758
237058
  const kernel = company?.kernel ?? deps.kernel;
236759
237059
  const blobs = company?.blobs ?? deps.blobs;
@@ -236761,10 +237061,9 @@ function makeSeededWorkorderCreator(deps) {
236761
237061
  const artifactState = company ? company.artifactState : deps.artifactState;
236762
237062
  const workorderDrafts = company ? company.workorderDrafts : deps.workorderDrafts;
236763
237063
  const workspace = input.idempotencyKey ? workspaceForKey(input.idempotencyKey) : genWorkspace();
236764
- if (input.idempotencyKey) {
236765
- const existing = existingWorkorder(kernel, workspace);
236766
- if (existing) return existing;
236767
- }
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;
236768
237067
  if (artifactState && input.projectId) {
236769
237068
  await bindWorkorderProject({
236770
237069
  workOrderId: workspace,
@@ -236773,6 +237072,13 @@ function makeSeededWorkorderCreator(deps) {
236773
237072
  ...deps.onWarn ? { onWarn: deps.onWarn } : {}
236774
237073
  });
236775
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);
236776
237082
  const planned = await buildDynamicWorkorderPlan({
236777
237083
  // requestedType 在 agentPlan 路仅用于兜底;给第一个节点类型即可,图仍以 plan 为准。
236778
237084
  requestedType: input.plan.nodes[0]?.type ?? "prd",
@@ -236829,44 +237135,41 @@ function makeSeededWorkorderCreator(deps) {
236829
237135
  }
236830
237136
  };
236831
237137
  }
236832
- await kernel.applyIntervention(planned.plan, preview2.baseSeqs, input.humanActorId ?? input.actor);
236833
- const briefId = planned.rootArtifactId;
236834
- if (input.brief.trim()) {
236835
- const ref2 = await blobs.put(enc2(input.brief));
236836
- await kernel.proposeRevision({ artifactId: briefId, actor: input.actor, contentRef: ref2, reason: "\u5267\u672C\u5EFA\u5355\uFF1A\u843D\u5B9A brief \u9996\u7248" });
236837
- await kernel.advanceQueue(briefId, input.actor);
236838
- await kernel.conclude({ artifactId: briefId, actor: input.actor, note: "\u5267\u672C\u5EFA\u5355\u65F6\u5B9A\u7A3F" });
236839
- }
236840
- if (input.dispatch === false && artifactState) {
236841
- await artifactState.setWorkspaceDispatchHold(workspace, true);
236842
- 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();
236843
237147
  }
236844
- return { workspace, rootArtifactId: briefId, spawned: planned.spawned };
236845
237148
  };
236846
237149
  }
236847
237150
  function workspaceForKey(key2) {
236848
- 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)}`;
236849
237152
  }
236850
- function existingWorkorder(kernel, workspace) {
236851
- const arts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === workspace);
236852
- if (arts.length === 0) return null;
236853
- 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}`);
236854
237157
  return {
236855
237158
  workspace,
236856
237159
  rootArtifactId: seed.id,
236857
- // role 取自 planner 的规划结果、不是 artifact 上的字段——重试路径拿不到也无所谓(可省)。
236858
- 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 }))
236859
237161
  };
236860
237162
  }
236861
- var import_node_crypto62, enc2;
237163
+ var import_node_crypto61;
236862
237164
  var init_create_seeded_workorder = __esm({
236863
237165
  "../server/src/domains/collab/create-seeded-workorder.ts"() {
236864
237166
  "use strict";
236865
- import_node_crypto62 = require("node:crypto");
237167
+ import_node_crypto61 = require("node:crypto");
236866
237168
  init_uncategorized_project();
236867
237169
  init_src();
236868
237170
  init_planner();
236869
- enc2 = (s2) => new TextEncoder().encode(s2);
237171
+ init_keyed_mutex();
237172
+ init_initialize_workorder();
236870
237173
  }
236871
237174
  });
236872
237175
 
@@ -238290,12 +238593,12 @@ function stateFromAttempt(attempt, handoff, checkpoint, now, stalledAfterMs, ope
238290
238593
  if (attempt.status === "no-output") return handoff?.unresolvedIssues.length ? "blocked" : "no_change";
238291
238594
  return "failed";
238292
238595
  }
238293
- 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;
238294
238597
  var init_service8 = __esm({
238295
238598
  "../server/src/domains/execution-continuity/service.ts"() {
238296
238599
  "use strict";
238297
238600
  init_src();
238298
- import_node_crypto63 = require("node:crypto");
238601
+ import_node_crypto62 = require("node:crypto");
238299
238602
  init_src7();
238300
238603
  BLOCKING_STATES = /* @__PURE__ */ new Set(["stalled", "failed", "blocked"]);
238301
238604
  HANDOFF_OUTCOMES2 = /* @__PURE__ */ new Set(["succeeded", "no-output", "failed", "cancelled", "timeout", "orphaned"]);
@@ -238526,7 +238829,7 @@ var init_service8 = __esm({
238526
238829
  if (typeof expectedHash !== "string" || !this.readBundleFile) return null;
238527
238830
  const injectedResume = this.readBundleFile(attempt.id, "execution/RESUME.md");
238528
238831
  if (!injectedResume) return null;
238529
- 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");
238530
238833
  if (actualHash !== expectedHash) return null;
238531
238834
  const recoverySnapshot = parseResumePackSnapshot(injectedResume);
238532
238835
  if (!recoverySnapshot || recoverySnapshot.scenario !== "redispatch" || !recoverySnapshot.previousAttempt || recoverySnapshot.node.nodeId !== attempt.nodeId || recoverySnapshot.jobKey !== attempt.jobKey || recoverySnapshot.part !== attempt.part) return null;
@@ -239394,6 +239697,25 @@ var init_escalation_opening = __esm({
239394
239697
  }
239395
239698
  });
239396
239699
 
239700
+ // ../server/src/domains/delegations/reply-anchors.ts
239701
+ function delegationReplyAnchors(record8) {
239702
+ const bySequence = /* @__PURE__ */ new Map();
239703
+ for (const summary of record8.roundSummaries) {
239704
+ const sequence = summary.settledSeq;
239705
+ if (typeof sequence !== "number" || !Number.isSafeInteger(sequence) || sequence < 1 || !Number.isSafeInteger(summary.round) || summary.round < 1) continue;
239706
+ const ids2 = bySequence.get(sequence) ?? /* @__PURE__ */ new Set();
239707
+ ids2.add(childRoundMessageIds(record8.childSessionId, summary.round).placeholderId);
239708
+ bySequence.set(sequence, ids2);
239709
+ }
239710
+ return [...bySequence].flatMap(([settledSeq, ids2]) => ids2.size === 1 ? [{ settledSeq, childMessageId: [...ids2][0] }] : []);
239711
+ }
239712
+ var init_reply_anchors = __esm({
239713
+ "../server/src/domains/delegations/reply-anchors.ts"() {
239714
+ "use strict";
239715
+ init_service3();
239716
+ }
239717
+ });
239718
+
239397
239719
  // ../server/src/domains/chat-sessions/project-bundle.ts
239398
239720
  async function buildChatProjectBundle(projectId2, projects, readBlob) {
239399
239721
  const project = await projects.getProject(projectId2);
@@ -239493,7 +239815,11 @@ function mapChatItemToSnapshotEntry(row) {
239493
239815
  ord: row.ord,
239494
239816
  messageId: row.messageId,
239495
239817
  updatedAt: row.updatedAt,
239496
- payload: row.payload
239818
+ payload: {
239819
+ ...row.payload && typeof row.payload === "object" && !Array.isArray(row.payload) ? row.payload : {},
239820
+ createdAt: row.createdAt,
239821
+ completedAt: row.completedAt
239822
+ }
239497
239823
  };
239498
239824
  }
239499
239825
  function throwWorkdirError(code2) {
@@ -239630,7 +239956,7 @@ function createChatSessionsDomain(opts) {
239630
239956
  if (!text6.trim()) throw new ApiError(400, "BAD_REQUEST", "text required");
239631
239957
  const now = Date.now();
239632
239958
  sweepDrafts(now);
239633
- const id = `cd_${(0, import_node_crypto64.randomUUID)()}`;
239959
+ const id = `cd_${(0, import_node_crypto63.randomUUID)()}`;
239634
239960
  drafts.set(id, { text: text6, actor: req.auth.actor, expiresAt: now + DRAFT_TTL_MS });
239635
239961
  return { status: 201, body: { id, expiresInMs: DRAFT_TTL_MS } };
239636
239962
  });
@@ -239662,6 +239988,13 @@ function createChatSessionsDomain(opts) {
239662
239988
  ...runningParentIds.has(s2.id) ? { hasRunningDelegation: true } : {}
239663
239989
  }));
239664
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
+ }
239665
239998
  router.get("/api/chat-sessions", async (req) => {
239666
239999
  const store2 = await storeFor(req);
239667
240000
  if (req.query?.get("analysis") === "all") {
@@ -239676,7 +240009,8 @@ function createChatSessionsDomain(opts) {
239676
240009
  }
239677
240010
  const analyzedRunId = req.query?.get("analyzedRunId") ?? void 0;
239678
240011
  const sessions = analyzedRunId ? await store2.listSessionsByAnalyzedRun(analyzedRunId) : await store2.listSessions(req.auth.actor);
239679
- 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);
239680
240014
  return { status: 200, body: { items } };
239681
240015
  });
239682
240016
  router.post("/api/chat-sessions", async (req) => {
@@ -239690,7 +240024,7 @@ function createChatSessionsDomain(opts) {
239690
240024
  const runtimeId = await currentRuntimeId(body2.aiActorId) ?? "unknown";
239691
240025
  const now = (/* @__PURE__ */ new Date()).toISOString();
239692
240026
  const session = {
239693
- id: (0, import_node_crypto64.randomUUID)(),
240027
+ id: (0, import_node_crypto63.randomUUID)(),
239694
240028
  humanActorId: req.auth.actor,
239695
240029
  // 注:这里**刻意不写 companyId**。新会话要不要带公司归属,按 ws:wo-6d588f8e 的 ADR
239696
240030
  // (每公司 schema、最终移除表内 company 列)统一定,不在本节点自行发明。
@@ -239796,7 +240130,7 @@ function createChatSessionsDomain(opts) {
239796
240130
  if (body2.role !== "user" && body2.role !== "assistant") throw new ApiError(400, "BAD_REQUEST", "role must be user or assistant");
239797
240131
  const role = body2.role;
239798
240132
  const msg = await store2.appendMessage({
239799
- id: (0, import_node_crypto64.randomUUID)(),
240133
+ id: (0, import_node_crypto63.randomUUID)(),
239800
240134
  sessionId: req.params.id,
239801
240135
  role,
239802
240136
  content: role === "user" ? stripInjectedChatContext(body2.content) : body2.content,
@@ -239837,7 +240171,7 @@ function createChatSessionsDomain(opts) {
239837
240171
  const known = key2 ? cur.find((x2) => x2.clientKey === key2) : void 0;
239838
240172
  if (!known && cur.length >= QUEUE_MAX) throw new ApiError(409, "QUEUE_FULL", `\u961F\u5217\u5DF2\u6EE1\uFF08\u4E0A\u9650 ${QUEUE_MAX} \u6761\uFF09`);
239839
240173
  const item = await store.enqueuePendingMessage({
239840
- id: (0, import_node_crypto64.randomUUID)(),
240174
+ id: (0, import_node_crypto63.randomUUID)(),
239841
240175
  chatSessionId: sid,
239842
240176
  text: text6,
239843
240177
  ...attachments.length ? { attachments } : {},
@@ -240015,6 +240349,7 @@ function createChatSessionsDomain(opts) {
240015
240349
  /* 与协调者读口**同一个函数**——两侧 artifacts 有「逐字同源」的既有断言。它顺带给每份产物
240016
240350
  填上 `childMessageId`(子会话面板把产出卡挂到产出它的那一轮下面要用)。 */
240017
240351
  artifacts: delegationArtifactsView(record8),
240352
+ replyAnchors: delegationReplyAnchors(record8),
240018
240353
  createdAt: record8.createdAt,
240019
240354
  ...record8.settledAt ? { settledAt: record8.settledAt } : {}
240020
240355
  });
@@ -240385,12 +240720,12 @@ function createChatSessionsDomain(opts) {
240385
240720
  });
240386
240721
  };
240387
240722
  }
240388
- var import_node_crypto64, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS;
240723
+ var import_node_crypto63, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS;
240389
240724
  var init_chat_sessions2 = __esm({
240390
240725
  "../server/src/domains/chat-sessions/index.ts"() {
240391
240726
  "use strict";
240392
240727
  init_file_limits2();
240393
- import_node_crypto64 = require("node:crypto");
240728
+ import_node_crypto63 = require("node:crypto");
240394
240729
  init_src();
240395
240730
  init_chat_session();
240396
240731
  init_persistent_files();
@@ -240402,6 +240737,7 @@ var init_chat_sessions2 = __esm({
240402
240737
  init_workorders();
240403
240738
  init_chat_parts();
240404
240739
  init_service3();
240740
+ init_reply_anchors();
240405
240741
  init_injected_context();
240406
240742
  init_deletion();
240407
240743
  init_chat_turn_gate();
@@ -241780,7 +242116,7 @@ function busyError(chatSessionId, blockedBy) {
241780
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`;
241781
242117
  }
241782
242118
  function genWebhookToken() {
241783
- return `owt_${(0, import_node_crypto65.randomBytes)(32).toString("base64url")}`;
242119
+ return `owt_${(0, import_node_crypto64.randomBytes)(32).toString("base64url")}`;
241784
242120
  }
241785
242121
  function validateTitleTemplate(tpl) {
241786
242122
  const residue = tpl.replace(DATE_TOKEN, "");
@@ -241836,11 +242172,11 @@ function appendSourceNote(brief, automation, source, atIso) {
241836
242172
  ---
241837
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`;
241838
242174
  }
241839
- 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;
241840
242176
  var init_service9 = __esm({
241841
242177
  "../server/src/domains/automations/service.ts"() {
241842
242178
  "use strict";
241843
- import_node_crypto65 = require("node:crypto");
242179
+ import_node_crypto64 = require("node:crypto");
241844
242180
  init_src();
241845
242181
  init_registry3();
241846
242182
  init_attribution();
@@ -242654,11 +242990,11 @@ function verifySignature(rawBody, headers, secret) {
242654
242990
  if (!secret) return "not_required";
242655
242991
  const given = header1(headers, "x-hub-signature-256");
242656
242992
  if (!given) return "missing";
242657
- 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")}`;
242658
242994
  const a = Buffer.from(given);
242659
242995
  const b2 = Buffer.from(expected);
242660
242996
  if (a.length !== b2.length) return "invalid";
242661
- return (0, import_node_crypto66.timingSafeEqual)(a, b2) ? "valid" : "invalid";
242997
+ return (0, import_node_crypto65.timingSafeEqual)(a, b2) ? "valid" : "invalid";
242662
242998
  }
242663
242999
  function eventAllowed(filters, envelope) {
242664
243000
  if (!filters || filters.length === 0) return true;
@@ -242688,11 +243024,11 @@ function splitEvent(envelope) {
242688
243024
  }
242689
243025
  return { eventName, actionCandidates: candidates };
242690
243026
  }
242691
- var import_node_crypto66, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
243027
+ var import_node_crypto65, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
242692
243028
  var init_webhook = __esm({
242693
243029
  "../server/src/domains/automations/webhook.ts"() {
242694
243030
  "use strict";
242695
- import_node_crypto66 = require("node:crypto");
243031
+ import_node_crypto65 = require("node:crypto");
242696
243032
  MAX_BODY_BYTES = 256 * 1024;
242697
243033
  AutomationWebhookService = class {
242698
243034
  store;
@@ -243840,7 +244176,7 @@ function playbooksDomain(opts) {
243840
244176
  opts.overrides.set(companyOf(req), ref2, nodeKey, nextOverride);
243841
244177
  try {
243842
244178
  await opts.audit?.({
243843
- id: `reg_${(0, import_node_crypto67.randomUUID)()}`,
244179
+ id: `reg_${(0, import_node_crypto66.randomUUID)()}`,
243844
244180
  actor: req.auth.actor,
243845
244181
  kind: "registry_change",
243846
244182
  target: `playbook:${ref2}#${nodeKey}`,
@@ -243857,11 +244193,11 @@ function playbooksDomain(opts) {
243857
244193
  });
243858
244194
  };
243859
244195
  }
243860
- var import_node_crypto67;
244196
+ var import_node_crypto66;
243861
244197
  var init_routes12 = __esm({
243862
244198
  "../server/src/domains/playbooks/routes.ts"() {
243863
244199
  "use strict";
243864
- import_node_crypto67 = require("node:crypto");
244200
+ import_node_crypto66 = require("node:crypto");
243865
244201
  init_router();
243866
244202
  init_registry3();
243867
244203
  init_planner();
@@ -243990,17 +244326,17 @@ function completionFingerprint(workspace, members) {
243990
244326
  workspace,
243991
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 }))
243992
244328
  });
243993
- return (0, import_node_crypto68.createHash)("sha256").update(canonical2).digest("hex");
244329
+ return (0, import_node_crypto67.createHash)("sha256").update(canonical2).digest("hex");
243994
244330
  }
243995
244331
  function eventConsumptionKey(args) {
243996
244332
  const raw = `project-event:${EVENT_KEY_VERSION}:${args.triggerId}:${args.event}:${args.workspace}:${args.fingerprint}`;
243997
- 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")}`;
243998
244334
  }
243999
- 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;
244000
244336
  var init_project_event_poller = __esm({
244001
244337
  "../server/src/automations/project-event-poller.ts"() {
244002
244338
  "use strict";
244003
- import_node_crypto68 = require("node:crypto");
244339
+ import_node_crypto67 = require("node:crypto");
244004
244340
  EVENT_KEY_VERSION = "v1";
244005
244341
  EMPTY_METRICS = () => ({
244006
244342
  scanDurationMs: 0,
@@ -244211,7 +244547,7 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
244211
244547
  if (input.requireConsumptionReceipt && !hub.supportsInputConsumptionReceipts?.(dispatchId)) {
244212
244548
  return Promise.resolve({ accepted: false, reason: "consumption-unsupported" });
244213
244549
  }
244214
- const requestId = (0, import_node_crypto69.randomUUID)();
244550
+ const requestId = (0, import_node_crypto68.randomUUID)();
244215
244551
  const sent = routeToSession(hub, nodeId, dispatchId, { type: "append_input", dispatchId, input, requestId });
244216
244552
  if (!sent) return Promise.resolve({ accepted: false, reason: "session-closing", ...unknownIfInterrupting });
244217
244553
  return new Promise((resolve10) => {
@@ -244224,11 +244560,11 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
244224
244560
  entry.appendWaiters.push({ requestId, requireConsumptionReceipt: input.requireConsumptionReceipt, resolve: resolve10, timer });
244225
244561
  });
244226
244562
  }
244227
- 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;
244228
244564
  var init_daemon_adapter = __esm({
244229
244565
  "../server/src/daemon-adapter.ts"() {
244230
244566
  "use strict";
244231
- import_node_crypto69 = require("node:crypto");
244567
+ import_node_crypto68 = require("node:crypto");
244232
244568
  init_src();
244233
244569
  STASH_TTL_MS = 5 * 6e4;
244234
244570
  STASH_MAX_FRAMES_PER_ID = 500;
@@ -244578,7 +244914,7 @@ var init_daemon_adapter = __esm({
244578
244914
  async spawn(job) {
244579
244915
  const nodeId = job.binding?.nodeId;
244580
244916
  if (!nodeId) throw new Error("DaemonHubAdapter \u9700\u8981 job.binding.nodeId\uFF08\u8DEF\u7531\u9519\u8BEF\uFF09");
244581
- const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto69.randomUUID)()}`;
244917
+ const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto68.randomUUID)()}`;
244582
244918
  if (job.dispatchId && this.pending.has(dispatchId)) {
244583
244919
  return this.recover(dispatchId, nodeId, job.binding?.runtimeKind, job.actor);
244584
244920
  }
@@ -244821,7 +245157,7 @@ var init_daemon_adapter = __esm({
244821
245157
  return daemon?.meta.supportsWorkdirWalk === true;
244822
245158
  }
244823
245159
  request(nodeId, buildFrame) {
244824
- const requestId = (0, import_node_crypto69.randomUUID)();
245160
+ const requestId = (0, import_node_crypto68.randomUUID)();
244825
245161
  const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
244826
245162
  if (!sent) return Promise.resolve({ ok: false, code: "NODE_OFFLINE" });
244827
245163
  return new Promise((resolve10) => {
@@ -244929,14 +245265,14 @@ function classifyPage(page, lastPushedHash, serviceAccount) {
244929
245265
  if (sha(body2) === lastPushedHash) return { kind: "echo" };
244930
245266
  return { kind: "human-edit", content: body2, updatedBy: page.updatedBy };
244931
245267
  }
244932
- var import_node_crypto70, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
245268
+ var import_node_crypto69, sha, enc2, dec, MirrorEngine, MapMirrorIdentities;
244933
245269
  var init_engine = __esm({
244934
245270
  "../server/src/mirror/engine.ts"() {
244935
245271
  "use strict";
244936
- import_node_crypto70 = require("node:crypto");
245272
+ import_node_crypto69 = require("node:crypto");
244937
245273
  init_src();
244938
- sha = (s2) => (0, import_node_crypto70.createHash)("sha256").update(s2, "utf8").digest("hex");
244939
- 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);
244940
245276
  dec = (b2) => new TextDecoder().decode(b2);
244941
245277
  MirrorEngine = class {
244942
245278
  constructor(deps) {
@@ -245010,7 +245346,7 @@ var init_engine = __esm({
245010
245346
  continue;
245011
245347
  }
245012
245348
  try {
245013
- const contentRef = await this.deps.blobs.put(enc3(cls.content));
245349
+ const contentRef = await this.deps.blobs.put(enc2(cls.content));
245014
245350
  const rev = await this.deps.kernel.proposeRevision({
245015
245351
  artifactId,
245016
245352
  actor,
@@ -245196,11 +245532,11 @@ function humanExitCause(exit3) {
245196
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`;
245197
245533
  return exit3.errorMessage ? `${base}\uFF1A${exit3.errorMessage.slice(0, 200)}` : base;
245198
245534
  }
245199
- 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;
245200
245536
  var init_worker = __esm({
245201
245537
  "../server/src/coordinator/worker.ts"() {
245202
245538
  "use strict";
245203
- import_node_crypto71 = require("node:crypto");
245539
+ import_node_crypto70 = require("node:crypto");
245204
245540
  init_src7();
245205
245541
  init_identity();
245206
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
@@ -245470,7 +245806,7 @@ ${HIGH_RISK_COMMANDS}`;
245470
245806
  return this.deps.kernel.model.lastSeq.get(artifactId) ?? 0;
245471
245807
  }
245472
245808
  evaluationKey(kind, artifactId, evidence) {
245473
- 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");
245474
245810
  return `${kind}:${artifactId}:${fingerprint2}`;
245475
245811
  }
245476
245812
  artifactEvidence(id) {
@@ -245859,8 +246195,8 @@ ${ctx.nodeFault}
245859
246195
  const taskWithRejections = rejectedSection ? `${task}
245860
246196
 
245861
246197
  ${rejectedSection}` : task;
245862
- const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto71.randomUUID)();
245863
- 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)()}`;
245864
246200
  const taskWithContext = coordinatorContext ? [
245865
246201
  taskWithRejections,
245866
246202
  ``,
@@ -245904,7 +246240,7 @@ ${rejectedSection}` : task;
245904
246240
  const startedAtMs = Date.now();
245905
246241
  let trajectoryStarted = false;
245906
246242
  if (this.deps.trajectory) {
245907
- 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")]));
245908
246244
  const session = {
245909
246245
  runId: coordinatorRunId,
245910
246246
  runtimeSessionId,
@@ -246110,11 +246446,11 @@ var init_pg_ident = __esm({
246110
246446
  });
246111
246447
 
246112
246448
  // ../storage/src/postgres.ts
246113
- var import_node_crypto72, ident4, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
246449
+ var import_node_crypto71, ident4, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
246114
246450
  var init_postgres = __esm({
246115
246451
  "../storage/src/postgres.ts"() {
246116
246452
  "use strict";
246117
- import_node_crypto72 = require("node:crypto");
246453
+ import_node_crypto71 = require("node:crypto");
246118
246454
  init_pg_ident();
246119
246455
  init_esm();
246120
246456
  init_src();
@@ -246294,7 +246630,7 @@ var init_postgres = __esm({
246294
246630
  return new _PostgresBlobStore(pool, schema);
246295
246631
  }
246296
246632
  async put(bytes2) {
246297
- 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");
246298
246634
  await this.pool.query(
246299
246635
  `INSERT INTO ${this.t} (hash, bytes, size, content_type) VALUES ($1, $2, $3, $4) ON CONFLICT (hash) DO NOTHING`,
246300
246636
  [hash3, Buffer.from(bytes2), bytes2.byteLength, sniffContentType(bytes2) ?? null]
@@ -253965,14 +254301,22 @@ var init_postgres_chat_items = __esm({
253965
254301
  * ——此刻起的 chunk 已经算下一段,但那条行还不存在。先写 `message_id = NULL`、行落库后回填,
253966
254302
  * 比让写入等一次 DB 往返更安全(等 = 丢帧或阻塞流)。
253967
254303
  *
253968
- * `message_id IS NULL` 天然把上一段隔开:上一段的 item 在切段之前就已经绑好了。
254304
+ * `message_id IS NULL` 只能把**已经绑好的**上一段隔开,隔不开**后面**的段:新行落库慢于下一次插话时,
254305
+ * 后一段的 item 同样是 NULL。所以切段回填给 `itemIds`,只更新那一段自己的 item(2026-09-17)。
253969
254306
  * 回填**不动** `version`/`turn_version`——归属关系不是 item 内容的一次修订。
253970
254307
  */
253971
- async bindMessage(sessionId, turnId, messageId) {
253972
- 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(
253973
254311
  `UPDATE ${this.s}.chat_items
253974
- SET message_id = $3
253975
- WHERE session_id = $1 AND turn_id = $2 AND message_id IS NULL AND role = 'assistant'`,
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(
254317
+ `UPDATE ${this.s}.chat_items
254318
+ SET message_id = $3
254319
+ WHERE session_id = $1 AND turn_id = $2 AND message_id IS NULL AND role = 'assistant'`,
253976
254320
  [sessionId, turnId, messageId]
253977
254321
  );
253978
254322
  return r.rowCount ?? 0;
@@ -254982,11 +255326,11 @@ var init_postgres_delegations = __esm({
254982
255326
  });
254983
255327
 
254984
255328
  // ../storage/src/postgres-nodes.ts
254985
- var import_node_crypto73, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
255329
+ var import_node_crypto72, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
254986
255330
  var init_postgres_nodes = __esm({
254987
255331
  "../storage/src/postgres-nodes.ts"() {
254988
255332
  "use strict";
254989
- import_node_crypto73 = require("node:crypto");
255333
+ import_node_crypto72 = require("node:crypto");
254990
255334
  init_pg_ident();
254991
255335
  init_esm();
254992
255336
  ident13 = (s2) => {
@@ -255203,7 +255547,7 @@ var init_postgres_nodes = __esm({
255203
255547
  return store;
255204
255548
  }
255205
255549
  issue(nodeId) {
255206
- const token = `ont_${(0, import_node_crypto73.randomBytes)(24).toString("base64url")}`;
255550
+ const token = `ont_${(0, import_node_crypto72.randomBytes)(24).toString("base64url")}`;
255207
255551
  this.cache.set(token, nodeId);
255208
255552
  void this.pool.query(`INSERT INTO ${this.s}.node_tokens (token,node_id) VALUES ($1,$2)`, [token, nodeId]);
255209
255553
  return token;
@@ -255469,11 +255813,11 @@ var init_postgres_type_registry = __esm({
255469
255813
  });
255470
255814
 
255471
255815
  // ../storage/src/postgres-actor-memory.ts
255472
- var import_node_crypto74, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
255816
+ var import_node_crypto73, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
255473
255817
  var init_postgres_actor_memory = __esm({
255474
255818
  "../storage/src/postgres-actor-memory.ts"() {
255475
255819
  "use strict";
255476
- import_node_crypto74 = require("node:crypto");
255820
+ import_node_crypto73 = require("node:crypto");
255477
255821
  init_pg_ident();
255478
255822
  init_src();
255479
255823
  matchClause = (q2) => q2.requireMatch && q2.keywords.length > 0 ? " WHERE m > 0" : "";
@@ -255683,7 +256027,7 @@ var init_postgres_actor_memory = __esm({
255683
256027
  }
255684
256028
  async write(input, now) {
255685
256029
  if (input.memId === void 0) {
255686
- const memId = `mem:${(0, import_node_crypto74.randomUUID)()}`;
256030
+ const memId = `mem:${(0, import_node_crypto73.randomUUID)()}`;
255687
256031
  const r2 = await this.pool.query(
255688
256032
  `INSERT INTO ${this.s}.actor_memories
255689
256033
  (mem_id, actor_id, project_id, keywords, content, version, created_at, updated_at, accessed_at, source_artifact_id, source_session_id)
@@ -257130,14 +257474,14 @@ function companySchemaName(companyId) {
257130
257474
  if (typeof companyId !== "string" || !companyId.trim()) {
257131
257475
  throw new Error("company schema mapping \u9700\u8981\u975E\u7A7A companyId\uFF08fail-closed\uFF1A\u4E0D\u56DE\u9000\u9ED8\u8BA4\u516C\u53F8\uFF09");
257132
257476
  }
257133
- 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");
257134
257478
  return `${COMPANY_SCHEMA_PREFIX}${hex.slice(0, DIGEST_HEX_LEN)}`;
257135
257479
  }
257136
- 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;
257137
257481
  var init_tenant_schema_mapping = __esm({
257138
257482
  "../storage/src/tenant-schema-mapping.ts"() {
257139
257483
  "use strict";
257140
- import_node_crypto75 = require("node:crypto");
257484
+ import_node_crypto74 = require("node:crypto");
257141
257485
  init_esm();
257142
257486
  init_pg_ident();
257143
257487
  COMPANY_SCHEMA_PREFIX = "c_";
@@ -257211,7 +257555,7 @@ var init_tenant_schema_mapping = __esm({
257211
257555
 
257212
257556
  // ../storage/src/company-data-plane-schema.ts
257213
257557
  function canonicalTypeDefHash(def) {
257214
- 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")}`;
257215
257559
  }
257216
257560
  function defaultArtifactTypeTemplate() {
257217
257561
  return [
@@ -257393,11 +257737,11 @@ async function seedDefaultArtifactTypes(pool, schema, templates = defaultArtifac
257393
257737
  }
257394
257738
  return { inserted };
257395
257739
  }
257396
- var import_node_crypto76, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
257740
+ var import_node_crypto75, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
257397
257741
  var init_company_data_plane_schema = __esm({
257398
257742
  "../storage/src/company-data-plane-schema.ts"() {
257399
257743
  "use strict";
257400
- import_node_crypto76 = require("node:crypto");
257744
+ import_node_crypto75 = require("node:crypto");
257401
257745
  init_esm();
257402
257746
  init_pg_ident();
257403
257747
  DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION = "4c368e5c-types-v1";
@@ -257477,11 +257821,11 @@ function rowToFile2(row) {
257477
257821
  updatedAt: new Date(row.updated_at).toISOString()
257478
257822
  };
257479
257823
  }
257480
- var import_node_crypto77, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
257824
+ var import_node_crypto76, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
257481
257825
  var init_company_owned_registry_store = __esm({
257482
257826
  "../storage/src/company-owned-registry-store.ts"() {
257483
257827
  "use strict";
257484
- import_node_crypto77 = require("node:crypto");
257828
+ import_node_crypto76 = require("node:crypto");
257485
257829
  init_esm();
257486
257830
  init_src();
257487
257831
  init_pg_ident();
@@ -257867,7 +258211,7 @@ var init_company_owned_registry_store = __esm({
257867
258211
  enabled: skill.enabled,
257868
258212
  files: files.map((f2) => ({ path: f2.path, blobHash: f2.blobHash }))
257869
258213
  });
257870
- return (0, import_node_crypto77.createHash)("sha256").update(body2).digest("hex");
258214
+ return (0, import_node_crypto76.createHash)("sha256").update(body2).digest("hex");
257871
258215
  }
257872
258216
  };
257873
258217
  }
@@ -260467,7 +260811,7 @@ function sourceSetConservation(input) {
260467
260811
  return input.sourceFinal === input.copied + input.approvedRuntimeConversion + input.approvedArchive + input.approvedDiscard;
260468
260812
  }
260469
260813
  function payloadChecksum(value2) {
260470
- 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");
260471
260815
  }
260472
260816
  async function copyWorkorderDraftsIdempotent(pool, companyId, companySchema, manifestId, opts) {
260473
260817
  const cs = quoteIdent2(companySchema);
@@ -260855,11 +261199,11 @@ async function copyTraceFamilyIdempotent(pool, companyId, companySchema, manifes
260855
261199
  sequences
260856
261200
  };
260857
261201
  }
260858
- var import_node_crypto78, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
261202
+ var import_node_crypto77, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
260859
261203
  var init_tenant_migration_operator = __esm({
260860
261204
  "../storage/src/tenant-migration-operator.ts"() {
260861
261205
  "use strict";
260862
- import_node_crypto78 = require("node:crypto");
261206
+ import_node_crypto77 = require("node:crypto");
260863
261207
  init_esm();
260864
261208
  init_pg_ident();
260865
261209
  init_tenancy_attribution();
@@ -261091,8 +261435,8 @@ var init_tenant_new_company_provision = __esm({
261091
261435
  // ../storage/src/knowledge-snapshot-crypto.ts
261092
261436
  function sealSnapshot(snapshot, key2) {
261093
261437
  if (snapshot.content.startsWith(PREFIX3)) return structuredClone(snapshot);
261094
- const iv = (0, import_node_crypto79.randomBytes)(12);
261095
- 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);
261096
261440
  cipher.setAAD(aad2(snapshot));
261097
261441
  const encrypted = Buffer.concat([cipher.update(snapshot.content, "utf8"), cipher.final()]);
261098
261442
  return { ...structuredClone(snapshot), content: `${PREFIX3}${iv.toString("base64url")}.${cipher.getAuthTag().toString("base64url")}.${encrypted.toString("base64url")}` };
@@ -261102,7 +261446,7 @@ function openSnapshot(snapshot, key2) {
261102
261446
  const packed = snapshot.content.slice(PREFIX3.length).split(".");
261103
261447
  if (packed.length !== 3 || packed.some((part) => !part)) throw new Error(`knowledge snapshot ${snapshot.ref} ciphertext is malformed`);
261104
261448
  const [iv, tag, data] = packed;
261105
- 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"));
261106
261450
  decipher.setAAD(aad2(snapshot));
261107
261451
  decipher.setAuthTag(Buffer.from(tag, "base64url"));
261108
261452
  return {
@@ -261110,11 +261454,11 @@ function openSnapshot(snapshot, key2) {
261110
261454
  content: Buffer.concat([decipher.update(Buffer.from(data, "base64url")), decipher.final()]).toString("utf8")
261111
261455
  };
261112
261456
  }
261113
- var import_node_crypto79, PREFIX3, bytes, aad2;
261457
+ var import_node_crypto78, PREFIX3, bytes, aad2;
261114
261458
  var init_knowledge_snapshot_crypto = __esm({
261115
261459
  "../storage/src/knowledge-snapshot-crypto.ts"() {
261116
261460
  "use strict";
261117
- import_node_crypto79 = require("node:crypto");
261461
+ import_node_crypto78 = require("node:crypto");
261118
261462
  PREFIX3 = "oasis-knowledge:v1:";
261119
261463
  bytes = (key2) => {
261120
261464
  const value2 = Buffer.from(key2);
@@ -263368,14 +263712,14 @@ init_src();
263368
263712
  var os14 = __toESM(require("node:os"));
263369
263713
  var path39 = __toESM(require("node:path"));
263370
263714
  var import_node_child_process22 = require("node:child_process");
263371
- var import_node_crypto90 = require("node:crypto");
263715
+ var import_node_crypto89 = require("node:crypto");
263372
263716
 
263373
263717
  // ../cli/src/cli.ts
263374
263718
  init_src();
263375
263719
  var fs39 = __toESM(require("node:fs"), 1);
263376
263720
  var os12 = __toESM(require("node:os"), 1);
263377
263721
  var path34 = __toESM(require("node:path"), 1);
263378
- var import_node_crypto88 = require("node:crypto");
263722
+ var import_node_crypto87 = require("node:crypto");
263379
263723
 
263380
263724
  // ../cli/src/serve.ts
263381
263725
  init_src();
@@ -263384,11 +263728,11 @@ var fs33 = __toESM(require("node:fs"), 1);
263384
263728
  var os8 = __toESM(require("node:os"), 1);
263385
263729
  var path28 = __toESM(require("node:path"), 1);
263386
263730
  var import_node_child_process17 = require("node:child_process");
263387
- var import_node_crypto84 = require("node:crypto");
263731
+ var import_node_crypto83 = require("node:crypto");
263388
263732
  var import_node_url7 = require("node:url");
263389
263733
 
263390
263734
  // ../cli/src/artifact-discovery-source.ts
263391
- var import_node_crypto80 = require("node:crypto");
263735
+ var import_node_crypto79 = require("node:crypto");
263392
263736
  init_src7();
263393
263737
  var ArtifactDiscoverySourceUnreadableError = class extends Error {
263394
263738
  constructor(message) {
@@ -263418,7 +263762,7 @@ async function resolveArtifactDiscoverySourceContent(blobs, input) {
263418
263762
  throw new ArtifactDiscoverySourceUnreadableError(`Artifact Revision ${input.revisionId} \u7684 contentKind \u4E0D\u53D7\u652F\u6301\uFF1A${String(input.contentKind)}`);
263419
263763
  }
263420
263764
  const rootBytes = await blobs.get(input.contentRef);
263421
- 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");
263422
263766
  if (input.contentKind === "inline-blob") {
263423
263767
  const sourceContent = decodeText3(rootBytes);
263424
263768
  if (sourceContent === null) {
@@ -263442,7 +263786,7 @@ ${entry.text}`).join("\n\n"),
263442
263786
  }
263443
263787
 
263444
263788
  // ../server/src/domains/discussions/service.ts
263445
- var import_node_crypto81 = require("node:crypto");
263789
+ var import_node_crypto80 = require("node:crypto");
263446
263790
  init_zod();
263447
263791
 
263448
263792
  // ../contract/src/discussions.ts
@@ -263479,7 +263823,7 @@ var generationSchema = external_exports.discriminatedUnion("result", [
263479
263823
  }).strict()
263480
263824
  ]);
263481
263825
  var entryId = (projectId2, id) => JSON.stringify([projectId2, id]);
263482
- 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);
263483
263827
  var canonical = (s2) => s2.normalize("NFKC").trim().toLocaleLowerCase();
263484
263828
  function decisionFingerprint(d) {
263485
263829
  return hash2(JSON.stringify([d.statement, d.rationale, d.alternatives]));
@@ -263634,7 +263978,7 @@ var DiscussionService = class {
263634
263978
  let id = t.topic_id;
263635
263979
  if (id.startsWith("new:")) {
263636
263980
  const existing = [...topicMap.values()].find((x2) => canonical(x2.title) === canonical(t.title));
263637
- id = existing?.topicId ?? `topic_${(0, import_node_crypto81.randomUUID)()}`;
263981
+ id = existing?.topicId ?? `topic_${(0, import_node_crypto80.randomUUID)()}`;
263638
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`);
263639
263983
  topicAliases.set(t.topic_id, id);
263640
263984
  const value2 = { projectId: input.projectId, topicId: id, title: t.title };
@@ -263655,7 +263999,7 @@ var DiscussionService = class {
263655
263999
  if (id.startsWith("new:")) {
263656
264000
  const candidateKey = JSON.stringify([topicId, canonical(d.record.statement)]);
263657
264001
  const existing = [...decisionMap.values()].find((x2) => x2.topicId === topicId && canonical(x2.record.statement) === canonical(d.record.statement));
263658
- 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)()}`;
263659
264003
  candidateIds.set(candidateKey, id);
263660
264004
  } else if (!decisionMap.has(id)) invalid("decision_id", `\u51B3\u7B56 ${id} \u4E0D\u5B58\u5728\uFF1B\u65B0\u51B3\u7B56\u4F7F\u7528 new: \u524D\u7F00`);
263661
264005
  decisionAliases.set(d.decision_id, id);
@@ -263788,7 +264132,7 @@ var DiscussionService = class {
263788
264132
  });
263789
264133
  }
263790
264134
  async operation(tx, actorId, kind, id, from, to) {
263791
- 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() });
263792
264136
  }
263793
264137
  };
263794
264138
  function pickDiscussionContent(d) {
@@ -263800,6 +264144,16 @@ function pickDecisionContent(d) {
263800
264144
 
263801
264145
  // ../server/src/domains/discussions/context.ts
263802
264146
  init_project_bundle();
264147
+ var decisionLabels = {
264148
+ proposed: "\u63D0\u8BAE\u4E2D",
264149
+ accepted: "\u5DF2\u786E\u8BA4",
264150
+ rejected: "\u5DF2\u5426\u51B3",
264151
+ superseded: "\u5DF2\u88AB\u66FF\u4EE3",
264152
+ withdrawn: "\u5DF2\u64A4\u9500"
264153
+ };
264154
+ function listField(label, items) {
264155
+ return items.length > 1 ? [`- ${label}\uFF1A`, ...items.map((item) => ` - ${item}`)] : [`- ${label}\uFF1A${items[0] ?? "\u65E0"}`];
264156
+ }
263803
264157
  function renderDiscussionContext(input) {
263804
264158
  const lines = [
263805
264159
  "# \u5F53\u524D\u4F1A\u8BDD\u4E0A\u4E0B\u6587",
@@ -263820,44 +264174,37 @@ function renderDiscussionContext(input) {
263820
264174
  if (!input.discussions.length) lines.push("\u5F53\u524D\u6682\u65E0\u5386\u53F2\u8BA8\u8BBA\u8BB0\u5F55\u3002", "");
263821
264175
  for (const row of input.discussions) {
263822
264176
  const d = row.value.record;
264177
+ const topicNames = d.topics.map((t) => input.topics.find((topic) => topic.value.projectId === d.project_id && topic.value.topicId === t.topic_id)?.value.title ?? "\u8BAE\u9898\u540D\u79F0\u7F3A\u5931");
263823
264178
  lines.push(
263824
- `### ${d.session_id}\uFF1A${d.title}`,
263825
- `- \u9879\u76EE\u5F52\u5C5E\uFF1A${d.project_id ?? "\u975E\u9879\u76EE\u8BA8\u8BBA"}`,
263826
- `- \u53C2\u4E0E\u7528\u6237\uFF1A${d.participant_user}`,
263827
- `- \u53C2\u4E0E\u667A\u80FD\u4F53\uFF1A${d.participant_agent}`,
263828
- `- \u6700\u540E\u8986\u76D6\u7684\u5BF9\u8BDD\u65F6\u95F4\uFF1A${row.value.discussedAt}`,
263829
- `- \u6D88\u606F\u622A\u6B62\u70B9\uFF1A${d.source_cutoff}`,
264179
+ `### ${d.title}`,
264180
+ "",
264181
+ ...!input.projectId ? [`- \u9879\u76EE\u5F52\u5C5E\uFF1A${d.project_id ?? "\u975E\u9879\u76EE\u8BA8\u8BBA"}`] : [],
264182
+ `- \u8BA8\u8BBA\u65E5\u671F\uFF1A${row.value.discussedAt.slice(0, 10)}`,
263830
264183
  `- \u6458\u8981\uFF1A${d.summary}`,
263831
- "- \u6D89\u53CA\u8BAE\u9898\uFF1A",
263832
- ...d.topics.map((t) => ` - ${t.topic_id}\uFF1A${t.summary}`),
263833
- "- \u672A\u51B3\u95EE\u9898\uFF1A",
263834
- ...d.open_questions.map((q2) => ` - ${q2}`),
263835
- "- \u4E0B\u4E00\u6B65\uFF1A",
263836
- ...d.next_steps.map((n) => ` - ${n}`),
264184
+ `- \u6D89\u53CA\u8BAE\u9898\uFF1A${topicNames.join("\u3001") || "\u65E0"}`,
264185
+ ...listField("\u672A\u51B3\u95EE\u9898", d.open_questions),
264186
+ ...listField("\u4E0B\u4E00\u6B65", d.next_steps),
263837
264187
  ""
263838
264188
  );
263839
264189
  }
263840
264190
  if (input.projectId) {
263841
- lines.push("## \u9879\u76EE\u8BAE\u9898\u4E0E\u51B3\u7B56", "", "\u4EC5 accepted \u8868\u793A\u5DF2\u786E\u8BA4\uFF1Bproposed \u5C1A\u672A\u786E\u8BA4\uFF1Brejected / withdrawn / superseded \u4E3A\u5931\u6548\u6216\u5386\u53F2\u65B9\u6848\u3002", "");
264191
+ lines.push("## \u9879\u76EE\u8BAE\u9898\u4E0E\u51B3\u7B56", "", "\u4EC5\u201C\u5DF2\u786E\u8BA4\u201D\u8868\u793A\u5DF2\u786E\u8BA4\u65B9\u6848\uFF1B\u201C\u63D0\u8BAE\u4E2D\u201D\u5C1A\u672A\u786E\u8BA4\uFF1B\u201C\u5DF2\u5426\u51B3\u201D\u201C\u5DF2\u64A4\u9500\u201D\u201C\u5DF2\u88AB\u66FF\u4EE3\u201D\u4E3A\u5931\u6548\u6216\u5386\u53F2\u65B9\u6848\u3002", "");
263842
264192
  if (!input.topics.length) lines.push("\u5F53\u524D\u6682\u65E0\u9879\u76EE\u8BAE\u9898\u4E0E\u51B3\u7B56\u3002", "");
263843
264193
  for (const t of input.topics) {
263844
- lines.push(`### ${t.value.topicId}\uFF1A${t.value.title}`, "");
264194
+ lines.push(`### \u8BAE\u9898\uFF1A${t.value.title}`, "");
263845
264195
  const decisions = input.decisions.filter((d) => d.value.topicId === t.value.topicId);
263846
264196
  if (!decisions.length) lines.push("\u6682\u65E0\u51B3\u7B56\u3002", "");
263847
- for (const d of decisions) {
264197
+ for (const [index2, d] of decisions.entries()) {
263848
264198
  const r = d.value.record;
263849
264199
  lines.push(
263850
- `#### ${d.value.decisionId}`,
263851
- `- \u72B6\u6001\uFF1A${r.state}`,
263852
- `- \u65B9\u6848\uFF1A${r.statement}`,
263853
- `- \u539F\u56E0\uFF1A${r.rationale}`,
264200
+ `#### \u51B3\u7B56 ${index2 + 1}\uFF1A${r.statement}`,
264201
+ "",
264202
+ `- \u72B6\u6001\uFF1A${decisionLabels[r.state]}`,
264203
+ `- \u539F\u56E0\uFF1A${r.rationale || "\u672A\u8BF4\u660E"}`,
263854
264204
  `- \u786E\u8BA4\u4EBA\uFF1A${r.confirmer ?? "\u672A\u786E\u8BA4"}`,
263855
- "- \u5907\u9009\u65B9\u6848\u53CA\u7406\u7531\uFF1A",
263856
- ...r.alternatives.map((a) => ` - ${a.statement}\uFF1A${a.reason}`),
263857
- "- \u6D88\u606F\u6765\u6E90\uFF1A",
263858
- ...r.source_refs.map((s2) => ` - ${s2.session_id} / ${s2.message_id}\uFF1A${JSON.stringify(s2.content)}`),
263859
- `- \u66FF\u4EE3\u5173\u7CFB\uFF1A${r.supersedes.join("\u3001") || "\u65E0"}`,
263860
- `- \u6700\u8FD1\u66F4\u65B0\u65F6\u95F4\uFF1A${r.updated_at}`,
264205
+ ...listField("\u5907\u9009\u65B9\u6848", r.alternatives.map((a) => `${a.statement}\uFF0C${a.reason || "\u672A\u8BF4\u660E\u539F\u56E0"}`)),
264206
+ ...r.supersedes.length ? listField("\u66FF\u4EE3\u51B3\u7B56", r.supersedes.map((id) => input.decisions.find((other) => other.value.decisionId === id)?.value.record.statement ?? "\u672A\u627E\u5230\u5BF9\u5E94\u51B3\u7B56")) : [],
264207
+ `- \u66F4\u65B0\u65F6\u95F4\uFF1A${r.updated_at.slice(0, 10)}`,
263861
264208
  ""
263862
264209
  );
263863
264210
  }
@@ -263890,7 +264237,12 @@ var DiscussionContext = class {
263890
264237
  let discussions = (await tx.list("discussion")).filter((r) => r.value.userId === session.humanActorId);
263891
264238
  discussions.sort((a, b2) => Date.parse(a.value.discussedAt) - Date.parse(b2.value.discussedAt) || a.id.localeCompare(b2.id));
263892
264239
  discussions = projectId2 ? discussions.filter((r) => r.value.record.project_id === projectId2) : discussions.slice(-10);
263893
- const view = projectId2 ? await this.service.view(projectId2, tx) : { topics: [], decisions: [] };
264240
+ const discussionProjects = new Set(discussions.map((d) => d.value.record.project_id));
264241
+ const view = projectId2 ? await this.service.view(projectId2, tx) : {
264242
+ // Resolve names for the selected personal history without loading project decisions.
264243
+ topics: (await tx.list("topic")).filter((t) => discussionProjects.has(t.value.projectId)),
264244
+ decisions: []
264245
+ };
263894
264246
  const markdown = renderDiscussionContext({
263895
264247
  userName,
263896
264248
  projectId: projectId2,
@@ -263926,7 +264278,7 @@ var DiscussionContext = class {
263926
264278
  };
263927
264279
 
263928
264280
  // ../server/src/domains/discussions/worker.ts
263929
- var import_node_crypto82 = require("node:crypto");
264281
+ var import_node_crypto81 = require("node:crypto");
263930
264282
  var DISCUSSION_IDLE_MS = 15 * 6e4;
263931
264283
  var DISCUSSION_MAX_CORRECTIONS = 3;
263932
264284
  var DISCUSSION_LEASE_MS = 2 * 6e4;
@@ -263952,7 +264304,7 @@ var DiscussionWorker = class {
263952
264304
  if (await this.isReplying(sessionId)) return false;
263953
264305
  const cutoff = eligibleCutoff(await this.service.chat.listMessages(sessionId), this.service.now());
263954
264306
  if (!cutoff) return false;
263955
- const owner = (0, import_node_crypto82.randomUUID)();
264307
+ const owner = (0, import_node_crypto81.randomUUID)();
263956
264308
  const claimed = await this.service.store.transaction(async (tx) => {
263957
264309
  const old = (await tx.get("job", sessionId))?.value;
263958
264310
  if (old?.status === "running" && (old.leaseUntil ?? 0) > this.service.now()) return null;
@@ -264385,7 +264737,7 @@ function createDiscussionRuntime(options) {
264385
264737
  }
264386
264738
 
264387
264739
  // ../server/src/domains/discussions/store.ts
264388
- var import_node_crypto83 = require("node:crypto");
264740
+ var import_node_crypto82 = require("node:crypto");
264389
264741
  var import_promises16 = require("node:fs/promises");
264390
264742
  var import_node_path27 = require("node:path");
264391
264743
  var key = (kind, id) => JSON.stringify([kind, id]);
@@ -264423,7 +264775,7 @@ var MemoryDiscussionStore = class _MemoryDiscussionStore {
264423
264775
  if (changed) {
264424
264776
  if (this.filename) {
264425
264777
  await (0, import_promises16.mkdir)((0, import_node_path27.dirname)(this.filename), { recursive: true });
264426
- const temp = `${this.filename}.${(0, import_node_crypto83.randomUUID)()}.tmp`;
264778
+ const temp = `${this.filename}.${(0, import_node_crypto82.randomUUID)()}.tmp`;
264427
264779
  await (0, import_promises16.writeFile)(temp, JSON.stringify(next), "utf8");
264428
264780
  await (0, import_promises16.rename)(temp, this.filename);
264429
264781
  }
@@ -264568,6 +264920,15 @@ function transcriptTail(ref2) {
264568
264920
  return "";
264569
264921
  }
264570
264922
  }
264923
+ var SUMMARY_MAX_CHARS = 500;
264924
+ function clipLine(line) {
264925
+ return line.length > SUMMARY_MAX_CHARS ? `${line.slice(0, SUMMARY_MAX_CHARS)}\u2026` : line;
264926
+ }
264927
+ function providerMessage(line) {
264928
+ const m2 = /['"]message['"]\s*:\s*(?:'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)")/.exec(line);
264929
+ const text6 = (m2?.[1] ?? m2?.[2])?.trim();
264930
+ return text6 ? text6 : void 0;
264931
+ }
264571
264932
  function buildChatExitError(info) {
264572
264933
  const genericLine = `runtime exited with code ${info.code ?? "null"}${info.reason ? ` (${info.reason})` : ""}`;
264573
264934
  let detail = "";
@@ -264577,8 +264938,9 @@ function buildChatExitError(info) {
264577
264938
  }
264578
264939
  const firstDetailLine = detail.split("\n").map((line) => line.trim()).find(Boolean) ?? "";
264579
264940
  const firstErrorLine = info.errorMessage?.split("\n").map((line) => line.trim()).find(Boolean) ?? "";
264941
+ const readableError = firstErrorLine ? clipLine(providerMessage(firstErrorLine) ?? firstErrorLine) : "";
264580
264942
  const fixed = info.reason ? SUMMARY_BY_REASON[info.reason] : void 0;
264581
- const summary = fixed ? firstErrorLine && info.reason !== "cancelled" && info.reason !== "timeout" ? `${fixed}\uFF1A${firstErrorLine.slice(0, 200)}` : fixed : firstDetailLine ? `\u5BF9\u8BDD\u5931\u8D25\uFF1A${firstDetailLine.slice(0, 200)}` : `\u5BF9\u8BDD\u5931\u8D25\uFF08${genericLine}\uFF09`;
264943
+ const summary = fixed ? readableError && info.reason !== "cancelled" && info.reason !== "timeout" ? `${fixed}\uFF1A${readableError}` : fixed : readableError ? `\u5BF9\u8BDD\u5931\u8D25\uFF1A${readableError}` : firstDetailLine ? `\u5BF9\u8BDD\u5931\u8D25\uFF1A${clipLine(firstDetailLine)}` : `\u5BF9\u8BDD\u5931\u8D25\uFF08${genericLine}\uFF09`;
264582
264944
  const chatExit = {
264583
264945
  ...info.reason ? { reason: info.reason } : {},
264584
264946
  summary,
@@ -265181,7 +265543,7 @@ init_src9();
265181
265543
  init_src();
265182
265544
  init_trajectory();
265183
265545
  function deterministicUuid2(seed) {
265184
- const h = (0, import_node_crypto84.createHash)("sha256").update(seed).digest();
265546
+ const h = (0, import_node_crypto83.createHash)("sha256").update(seed).digest();
265185
265547
  const b2 = Buffer.from(h.subarray(0, 16));
265186
265548
  b2[6] = b2[6] & 15 | 80;
265187
265549
  b2[8] = b2[8] & 63 | 128;
@@ -265381,7 +265743,7 @@ function oasisWrapperScript() {
265381
265743
  const repoRoot = (0, import_node_url7.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
265382
265744
  const tsx = path28.join(repoRoot, "node_modules/.bin/tsx");
265383
265745
  const main = path28.join(repoRoot, "packages/cli/src/main.ts");
265384
- 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);
265385
265747
  const uid = typeof process.getuid === "function" ? process.getuid() : "na";
265386
265748
  const stable = path28.join(os8.tmpdir(), `oasis-cli-wrapper-${uid}-${key2}`);
265387
265749
  let dir;
@@ -265767,7 +266129,7 @@ async function startServe(opts) {
265767
266129
  if (!hub) return Promise.resolve({ ok: false, reason: "\u672C\u8FDB\u7A0B\u6CA1\u6709\u8282\u70B9\u7F51\u5173" });
265768
266130
  const online = hub.connectedDaemons().some((d) => d.daemonId === nodeId);
265769
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" });
265770
- const requestId = (0, import_node_crypto84.randomUUID)();
266132
+ const requestId = (0, import_node_crypto83.randomUUID)();
265771
266133
  const task = new Promise((resolve10) => {
265772
266134
  const timer = setTimeout(() => {
265773
266135
  modelRefreshWaiters.delete(requestId);
@@ -266535,7 +266897,7 @@ async function startServe(opts) {
266535
266897
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
266536
266898
  if (buf || session.runId) {
266537
266899
  await turnStore.appendMessage({
266538
- id: (0, import_node_crypto84.randomUUID)(),
266900
+ id: (0, import_node_crypto83.randomUUID)(),
266539
266901
  sessionId: chatSessionId,
266540
266902
  role: "assistant",
266541
266903
  content: buf,
@@ -266581,7 +266943,7 @@ async function startServe(opts) {
266581
266943
  chatTargets: {
266582
266944
  inspect: inspectChatTarget,
266583
266945
  createTargetSession: async ({ companyId, agentId, ownerHumanActorId, title }) => {
266584
- const sessionId = (0, import_node_crypto84.randomUUID)();
266946
+ const sessionId = (0, import_node_crypto83.randomUUID)();
266585
266947
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
266586
266948
  const binding = await registryStore.getBinding(agentId).catch(() => null);
266587
266949
  await (await chatStoreFor(companyId)).createSession({
@@ -267727,7 +268089,7 @@ async function startServe(opts) {
267727
268089
  }));
267728
268090
  }
267729
268091
  const limits = { wallClockMs: wallClockForKind("planner") };
267730
- const artifactId = `artifact:planner:${(0, import_node_crypto84.randomUUID)()}`;
268092
+ const artifactId = `artifact:planner:${(0, import_node_crypto83.randomUUID)()}`;
267731
268093
  const handle = await chatRemoteAdapter.spawn({
267732
268094
  actor: planner.id,
267733
268095
  // companyId 同协调者:planner 用的就是默认公司的 actors 服务,漏签会让它一调公司域端点就 404。
@@ -268023,7 +268385,9 @@ async function startServe(opts) {
268023
268385
  // v3 快照端点(ws:wo-81de04dc S3 恢复路径唯一入口):`chat_items` 权威读口 +
268024
268386
  // live 注册表算 `frameCursor`。与 server.ts 的双写解析同一层,与 attach 路径共享 registry。
268025
268387
  resolveChatItems: (companyId) => chatItemsFor(companyId),
268026
- liveChat
268388
+ liveChat,
268389
+ // ADR-0587:列表行派生 `unread`,与 collab 域收件箱读同一份按公司的水位(无 PG 时闭包自己回落单实例)。
268390
+ resolveReadMarkers: readMarkerStoreFor
268027
268391
  })] : []
268028
268392
  ],
268029
268393
  workorderDrafts,
@@ -268610,10 +268974,10 @@ async function startServe(opts) {
268610
268974
  if (isDeletedActor(dispatchTarget)) {
268611
268975
  throw new ApiError(409, ACTOR_DELETED_ERROR_CODE, ACTOR_DELETED_CHAT_MESSAGE);
268612
268976
  }
268613
- 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)());
268614
268978
  const resumeRuntimeSession = Boolean(sessionId);
268615
- const traceRunId = requestDispatchId ? `chat-run:${deterministicUuid2(`chat-trace:${requestDispatchId}`)}` : `chat-run:${(0, import_node_crypto84.randomUUID)()}`;
268616
- 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)()}`;
268617
268981
  let stableDispatch = requestDispatchId ? await resolveStableChatDispatch({ store: await traceStoreFor(runCompanyId), runId: traceRunId }) : null;
268618
268982
  if (stableDispatch && !stableDispatch.shouldDispatch) {
268619
268983
  return {
@@ -269793,7 +270157,7 @@ async function startServe(opts) {
269793
270157
  console.log(`[new-engine] dispatchWork ${workId} \u662F human work\u2014\u2014\u4E0D\u6D3E runtime\uFF0C\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406`);
269794
270158
  return;
269795
270159
  }
269796
- const ledgerId = `dispatch:${(0, import_node_crypto84.randomUUID)()}`;
270160
+ const ledgerId = `dispatch:${(0, import_node_crypto83.randomUUID)()}`;
269797
270161
  try {
269798
270162
  await (await dispatchLedgerFor(companyId)).insertOpen({
269799
270163
  id: ledgerId,
@@ -269878,7 +270242,7 @@ async function startServe(opts) {
269878
270242
  const kernelModel = newKernel.model;
269879
270243
  const art = kernelModel.artifacts.get(nodeId);
269880
270244
  const revWid = art?.workspace ?? "";
269881
- const ledgerId = `dispatch:${(0, import_node_crypto84.randomUUID)()}`;
270245
+ const ledgerId = `dispatch:${(0, import_node_crypto83.randomUUID)()}`;
269882
270246
  try {
269883
270247
  await (await dispatchLedgerFor(companyId)).insertOpen({
269884
270248
  id: ledgerId,
@@ -270252,7 +270616,7 @@ async function startServe(opts) {
270252
270616
  resolveSupplementalContext: server.knowledgeGovernance ? async ({ actorId, artifactId, workOrderId, action, part, runtimeKind, taskMarkdown }) => {
270253
270617
  try {
270254
270618
  const governance = server.knowledgeGovernance;
270255
- 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");
270256
270620
  const requestContext = {
270257
270621
  organizationId: companyId,
270258
270622
  actorId,
@@ -270716,10 +271080,10 @@ async function startServe(opts) {
270716
271080
  if (h < 24) return `${h} \u5C0F\u65F6\u524D`;
270717
271081
  return `${Math.floor(h / 24)} \u5929\u524D`;
270718
271082
  };
270719
- const humanFailCause = (reason, errorMessage) => {
270720
- const quote = errorMessage?.trim().slice(0, 300);
271083
+ const humanFailCause = (reason, errorMessage2) => {
271084
+ const quote = errorMessage2?.trim().slice(0, 300);
270721
271085
  if (reason === "rate-limit") {
270722
- const hint = extractResetHint(errorMessage);
271086
+ const hint = extractResetHint(errorMessage2);
270723
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 ? `
270724
271088
  > ${quote}` : "");
270725
271089
  }
@@ -271869,11 +272233,11 @@ async function runSession(dispatchId, job, deps) {
271869
272233
  } catch (err) {
271870
272234
  deps.onFailure?.();
271871
272235
  log2("[node-cli]", ` spawn failed: ${err}`);
271872
- const errorMessage = String(err?.stack ?? err).slice(0, 8e3);
272236
+ const errorMessage2 = String(err?.stack ?? err).slice(0, 8e3);
271873
272237
  deps.sink.keyFrame({ type: "session_exited", dispatchId, sessionId: "spawn-failed", info: {
271874
272238
  code: null,
271875
272239
  reason: "error",
271876
- errorMessage,
272240
+ errorMessage: errorMessage2,
271877
272241
  ...err instanceof BundleRestoreError ? { fileRestoreError: { path: err.filePath, message: err.message } } : {}
271878
272242
  } });
271879
272243
  }
@@ -271882,7 +272246,6 @@ async function runSession(dispatchId, job, deps) {
271882
272246
  // ../cli/src/daemon/session-process.ts
271883
272247
  var RECONNECT_MIN_MS = 1e3;
271884
272248
  var RECONNECT_MAX_MS = 3e4;
271885
- var EXIT_ACK_WAIT_MS = LINK_WINDOWS.exitAckWaitMs;
271886
272249
  var OUTBOX_MAX_FRAMES = 8e3;
271887
272250
  var isKeyFrame = (m2) => m2.type === "session_started" || m2.type === "session_exited";
271888
272251
  async function runSessionProcess(specFile, adapter) {
@@ -271928,6 +272291,7 @@ var SessionProcessState = class {
271928
272291
  this.hardTtlMs = o.hardTtlMs ?? SESSION_LIMITS.hardTtlMs;
271929
272292
  this.suicideAfterUnreachableMs = o.suicideAfterUnreachableMs ?? SESSION_LIMITS.suicideAfterUnreachableMs;
271930
272293
  this.watchdogIntervalMs = o.watchdogIntervalMs ?? 3e4;
272294
+ this.exitAckWaitMs = o.exitAckWaitMs ?? LINK_WINDOWS.exitAckWaitMs;
271931
272295
  }
271932
272296
  ws = null;
271933
272297
  connected = false;
@@ -271941,6 +272305,8 @@ var SessionProcessState = class {
271941
272305
  hardTtlMs;
271942
272306
  suicideAfterUnreachableMs;
271943
272307
  watchdogIntervalMs;
272308
+ /** 发完 session_exited 后等 ack 的时限;等不到也退——outbox 那份由服务端 L3 时钟扫描兜底。 */
272309
+ exitAckWaitMs;
271944
272310
  watchdog = null;
271945
272311
  exitAckResolve = null;
271946
272312
  /** 整个会话进程「可以退了」的信号:会话正常退出、或被 evict / TTL / 失联自尽。 */
@@ -272163,7 +272529,7 @@ var SessionProcessState = class {
272163
272529
  this.exitAckResolve = null;
272164
272530
  resolve10();
272165
272531
  };
272166
- const timer = setTimeout(finish, EXIT_ACK_WAIT_MS);
272532
+ const timer = setTimeout(finish, this.exitAckWaitMs);
272167
272533
  this.exitAckResolve = finish;
272168
272534
  });
272169
272535
  }
@@ -272186,7 +272552,7 @@ var SessionProcessState = class {
272186
272552
  init_wrapper();
272187
272553
  init_src();
272188
272554
  var import_node_os9 = require("node:os");
272189
- var import_node_crypto85 = require("node:crypto");
272555
+ var import_node_crypto84 = require("node:crypto");
272190
272556
  init_src8();
272191
272557
  init_src2();
272192
272558
 
@@ -272971,7 +273337,7 @@ var DaemonWsClient = class {
272971
273337
  */
272972
273338
  async queryArtifactStates(artifactIds, timeoutMs = 15e3) {
272973
273339
  if (artifactIds.length === 0) return {};
272974
- const requestId = (0, import_node_crypto85.randomUUID)();
273340
+ const requestId = (0, import_node_crypto84.randomUUID)();
272975
273341
  return new Promise((resolve10, reject) => {
272976
273342
  const timer = setTimeout(() => {
272977
273343
  this.gcPending.delete(requestId);
@@ -273377,7 +273743,7 @@ function forwardSignal(child, sig, killGroup = (pgid, signal) => {
273377
273743
  // ../cli/src/daemon/machine-id.ts
273378
273744
  var import_node_child_process20 = require("node:child_process");
273379
273745
  var import_node_fs22 = require("node:fs");
273380
- var import_node_crypto86 = require("node:crypto");
273746
+ var import_node_crypto85 = require("node:crypto");
273381
273747
  var import_node_os12 = require("node:os");
273382
273748
  function linuxMachineId() {
273383
273749
  for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
@@ -273443,7 +273809,7 @@ var defaultSources = {
273443
273809
  function resolveNodeId(sources = {}) {
273444
273810
  const s2 = { ...defaultSources, ...sources };
273445
273811
  const material = `${s2.machineFingerprint()}:${s2.osUser()}`;
273446
- 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);
273447
273813
  return `node-${digest}`;
273448
273814
  }
273449
273815
 
@@ -275214,13 +275580,13 @@ function ownFlagsFromDecl(command) {
275214
275580
  }
275215
275581
 
275216
275582
  // ../cli/src/connector-effect-runner.ts
275217
- var import_node_crypto87 = require("node:crypto");
275583
+ var import_node_crypto86 = require("node:crypto");
275218
275584
  var import_node_child_process21 = require("node:child_process");
275219
275585
  init_src8();
275220
275586
  function stableKey(effect) {
275221
275587
  const keyFields = Object.fromEntries(Object.entries(effect.keyFields).sort(([a], [b2]) => a.localeCompare(b2)));
275222
275588
  const canonical2 = JSON.stringify({ kind: effect.kind, target: effect.target, keyFields });
275223
- 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")}`;
275224
275590
  }
275225
275591
  async function runConnectorEffect(connector, toolArgs, deps) {
275226
275592
  const effect = connector.describeExternalEffect?.(toolArgs) ?? null;
@@ -275457,7 +275823,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
275457
275823
  const timeoutMs = resolveAuthWaitMs();
275458
275824
  const controller = new AbortController();
275459
275825
  const timer = setTimeout(() => controller.abort(), timeoutMs);
275460
- const waiterId = (0, import_node_crypto88.randomUUID)();
275826
+ const waiterId = (0, import_node_crypto87.randomUUID)();
275461
275827
  process.stderr.write(
275462
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
275463
275829
  `
@@ -275885,7 +276251,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
275885
276251
  const store = createNodeTokenStore(path34.join(dir, "node-tokens.json"));
275886
276252
  const sub = positional[0];
275887
276253
  if (sub === "issue") {
275888
- const id = flags.get("id") ?? `node-${(0, import_node_crypto88.randomUUID)()}`;
276254
+ const id = flags.get("id") ?? `node-${(0, import_node_crypto87.randomUUID)()}`;
275889
276255
  const token2 = store.issue(id);
275890
276256
  println(token2);
275891
276257
  process.stderr.write(
@@ -276916,7 +277282,7 @@ ${t.ref} ${t.name} [\u5267\u672C=${t.playbookRef}]`);
276916
277282
  if (!name) throw new Error("\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
276917
277283
  const goal = flags.get("goal")?.trim();
276918
277284
  const slug6 = flags.get("slug");
276919
- 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)());
276920
277286
  const result = await api.request("POST", "/api/projects", {
276921
277287
  ...id ? { id } : {},
276922
277288
  name,
@@ -278908,13 +279274,13 @@ var LEGACY_SYSTEMD_UNIT = "oasis-node.service";
278908
279274
  var LEGACY_LAUNCHD_LABEL = "com.oasis.node";
278909
279275
 
278910
279276
  // src/install.ts
278911
- var import_node_crypto89 = require("node:crypto");
279277
+ var import_node_crypto88 = require("node:crypto");
278912
279278
  var fs43 = __toESM(require("node:fs"));
278913
279279
  var path38 = __toESM(require("node:path"));
278914
279280
  function versionLabel(sourceFile, pkgVersion, npmPrefixDir, pkgName = LEGACY_PKG_NAME) {
278915
279281
  const tag = pkgName === LEGACY_PKG_NAME ? "" : `+pkg.${pkgName.replace(/[^A-Za-z0-9-]/g, "-")}`;
278916
279282
  if (isFromNpmPrefix(sourceFile, npmPrefixDir)) return `${pkgVersion}${tag}`;
278917
- 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);
278918
279284
  return tag ? `${pkgVersion}${tag}.local.${digest}` : `${pkgVersion}+local.${digest}`;
278919
279285
  }
278920
279286
  function isFromNpmPrefix(sourceFile, npmPrefixDir) {
@@ -279037,7 +279403,7 @@ function shimScript() {
279037
279403
  }
279038
279404
 
279039
279405
  // src/index.ts
279040
- var PKG_VERSION = true ? "2.2.47" : "dev";
279406
+ var PKG_VERSION = true ? "2.2.49" : "dev";
279041
279407
  var LOCAL_BIN = localBin();
279042
279408
  var NPM_PREFIX = npmPrefix();
279043
279409
  var INSTANCE = DEFAULT_INSTANCE;
@@ -279322,7 +279688,7 @@ function newInstanceName() {
279322
279688
  const existing = new Set(listInstances());
279323
279689
  if (!existing.has(DEFAULT_INSTANCE)) return DEFAULT_INSTANCE;
279324
279690
  for (; ; ) {
279325
- const n = `inst-${(0, import_node_crypto90.randomBytes)(3).toString("hex")}`;
279691
+ const n = `inst-${(0, import_node_crypto89.randomBytes)(3).toString("hex")}`;
279326
279692
  if (!existing.has(n)) return n;
279327
279693
  }
279328
279694
  }