@morlay/ui-conversation-message-actions 0.0.14-alpha.0 → 0.0.14-alpha.1

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.
package/dist/client.js CHANGED
@@ -351,12 +351,12 @@ window.__ModuleLoader__.load({
351
351
  document.head.appendChild(tag);
352
352
  }
353
353
  var MessageIconActions_module_css_default = {
354
- "action": "lKOSUG_action",
355
- "visuallyHidden": "lKOSUG_visuallyHidden",
356
354
  "actions": "lKOSUG_actions",
357
- "runTimeDot": "lKOSUG_runTimeDot",
358
355
  "timeStart": "lKOSUG_timeStart",
359
- "timeEnd": "lKOSUG_timeEnd"
356
+ "timeEnd": "lKOSUG_timeEnd",
357
+ "runTimeDot": "lKOSUG_runTimeDot",
358
+ "action": "lKOSUG_action",
359
+ "visuallyHidden": "lKOSUG_visuallyHidden"
360
360
  };
361
361
  //#endregion
362
362
  //#region src/client/chat-node/MessageIconActions.tsx
@@ -568,34 +568,34 @@ window.__ModuleLoader__.load({
568
568
  document.head.appendChild(tag);
569
569
  }
570
570
  var MessageItem_module_css_default = {
571
- "compactionButton": "okW0Ua_compactionButton",
572
- "retryRow": "okW0Ua_retryRow",
573
- "retry-shimmer": "okW0Ua_retry-shimmer",
574
- "maxTokensTitle": "okW0Ua_maxTokensTitle",
575
- "refChip": "okW0Ua_refChip",
576
- "userRow": "okW0Ua_userRow",
577
- "compactionSummary": "okW0Ua_compactionSummary",
578
- "userStack": "okW0Ua_userStack",
579
- "compactionRow": "okW0Ua_compactionRow",
571
+ "turnErrorTitle": "okW0Ua_turnErrorTitle",
572
+ "retryText": "okW0Ua_retryText",
580
573
  "compactionLeading": "okW0Ua_compactionLeading",
581
- "compactionDisclosureIcon": "okW0Ua_compactionDisclosureIcon",
582
- "retryDetails": "okW0Ua_retryDetails",
574
+ "retryRow": "okW0Ua_retryRow",
583
575
  "contextRow": "okW0Ua_contextRow",
584
- "retryDetailLabel": "okW0Ua_retryDetailLabel",
576
+ "compactionContextIcon": "okW0Ua_compactionContextIcon",
577
+ "retryDetails": "okW0Ua_retryDetails",
578
+ "compactionRow": "okW0Ua_compactionRow",
579
+ "compactionTitle": "okW0Ua_compactionTitle",
585
580
  "turnErrorRow": "okW0Ua_turnErrorRow",
586
- "compactionSep": "okW0Ua_compactionSep",
581
+ "turnErrorMessage": "okW0Ua_turnErrorMessage",
582
+ "confirmActions": "okW0Ua_confirmActions",
583
+ "compactionDisclosureIcon": "okW0Ua_compactionDisclosureIcon",
584
+ "compactionSummary": "okW0Ua_compactionSummary",
587
585
  "turnErrorDot": "okW0Ua_turnErrorDot",
588
586
  "turnErrorCopy": "okW0Ua_turnErrorCopy",
587
+ "userStack": "okW0Ua_userStack",
588
+ "compactionSep": "okW0Ua_compactionSep",
589
+ "userRow": "okW0Ua_userRow",
590
+ "retry-shimmer": "okW0Ua_retry-shimmer",
591
+ "compactionButton": "okW0Ua_compactionButton",
589
592
  "bubble": "okW0Ua_bubble",
590
593
  "compactionBody": "okW0Ua_compactionBody",
591
- "retryText": "okW0Ua_retryText",
592
- "retrySummary": "okW0Ua_retrySummary",
593
- "compactionTitle": "okW0Ua_compactionTitle",
594
- "turnErrorTitle": "okW0Ua_turnErrorTitle",
595
- "turnErrorMessage": "okW0Ua_turnErrorMessage",
594
+ "retryDetailLabel": "okW0Ua_retryDetailLabel",
596
595
  "turnErrorCode": "okW0Ua_turnErrorCode",
597
- "confirmActions": "okW0Ua_confirmActions",
598
- "compactionContextIcon": "okW0Ua_compactionContextIcon"
596
+ "maxTokensTitle": "okW0Ua_maxTokensTitle",
597
+ "refChip": "okW0Ua_refChip",
598
+ "retrySummary": "okW0Ua_retrySummary"
599
599
  };
600
600
  //#endregion
601
601
  //#region src/client/chat-node/MessageItem.tsx
package/dist/testing.mjs CHANGED
@@ -17929,7 +17929,9 @@ function findSurfaceRepairs(events) {
17929
17929
  const degradeToAppend = /* @__PURE__ */ new Set();
17930
17930
  const addAppendMarker = /* @__PURE__ */ new Set();
17931
17931
  const clearSurfaceOp = /* @__PURE__ */ new Set();
17932
- for (const event of events) {
17932
+ const clampEnd = /* @__PURE__ */ new Map();
17933
+ for (let index = 0; index < events.length; index++) {
17934
+ const event = events[index];
17933
17935
  const op = event.surfaceOp;
17934
17936
  if (op === void 0) {
17935
17937
  if (SURFACE_EVENT_TYPES.has(event.type)) {
@@ -17952,7 +17954,12 @@ function findSurfaceRepairs(events) {
17952
17954
  const end = replace?.["end"];
17953
17955
  const shapeOk = replace !== void 0 && replace["op"] === "replace" && isEventSeqLike(start) && isEventSeqLike(end);
17954
17956
  const startIdx = shapeOk ? nodes.indexOf(start) : -1;
17955
- const endIdx = shapeOk ? nodes.indexOf(end) : -1;
17957
+ let endIdx = shapeOk ? nodes.indexOf(end) : -1;
17958
+ let clamped;
17959
+ if (shapeOk && startIdx !== -1 && endIdx === -1) {
17960
+ clamped = clampReplaceEnd(events, index, nodes, startIdx);
17961
+ if (clamped !== void 0) endIdx = nodes.indexOf(clamped);
17962
+ }
17956
17963
  const rangeOk = shapeOk && startIdx !== -1 && endIdx !== -1 && startIdx <= endIdx;
17957
17964
  let rewriteOk = true;
17958
17965
  if (rangeOk && event.type === "tool/result") {
@@ -17968,25 +17975,98 @@ function findSurfaceRepairs(events) {
17968
17975
  nodes.push(event.seq);
17969
17976
  continue;
17970
17977
  }
17978
+ if (clamped !== void 0) clampEnd.set(event.seq, clamped);
17971
17979
  nodes.splice(startIdx, endIdx - startIdx + 1, event.seq);
17972
17980
  }
17973
17981
  return {
17974
17982
  degradeToAppend,
17975
17983
  addAppendMarker,
17976
- clearSurfaceOp
17984
+ clearSurfaceOp,
17985
+ clampEnd
17977
17986
  };
17978
17987
  }
17988
+ /**
17989
+ * 夹取一个 end 落在旧坐标空间的 replace 的结尾。
17990
+ *
17991
+ * 仅当 replace 紧邻一个 metering 事件且其 `shadowedSeqs` 是权威数量时成立;
17992
+ * 夹取点是从 `start` 起的第 `shadowedSeqs.length` 个当前 surface 节点(不足
17993
+ * 则取当前 surface 末尾)。无法夹取(start 也不在当前 surface)时返回
17994
+ * `undefined`,由调用方降级为 append。
17995
+ */
17996
+ function clampReplaceEnd(events, index, nodes, startIdx) {
17997
+ const metering = index > 0 ? events[index - 1] : void 0;
17998
+ if (metering === void 0 || !METERING_EVENT_TYPES.has(metering.type)) return void 0;
17999
+ const shadowedSeqs = metering.data.shadowedSeqs;
18000
+ if (!Array.isArray(shadowedSeqs) || shadowedSeqs.length === 0) return void 0;
18001
+ return nodes[Math.min(startIdx + shadowedSeqs.length - 1, nodes.length - 1)];
18002
+ }
17979
18003
  function repairSurfaceOps(events) {
17980
18004
  const repairs = findSurfaceRepairs(events);
17981
- if (repairs.degradeToAppend.size === 0 && repairs.addAppendMarker.size === 0 && repairs.clearSurfaceOp.size === 0) return;
18005
+ if (repairs.degradeToAppend.size === 0 && repairs.addAppendMarker.size === 0 && repairs.clearSurfaceOp.size === 0 && repairs.clampEnd.size === 0) return;
17982
18006
  for (const event of events) {
17983
18007
  const raw = event;
18008
+ const clamped = repairs.clampEnd.get(event.seq);
17984
18009
  if (repairs.degradeToAppend.has(event.seq)) raw.surfaceOp = "append";
18010
+ else if (clamped !== void 0) raw.surfaceOp = {
18011
+ op: "replace",
18012
+ start: raw.surfaceOp.start,
18013
+ end: clamped
18014
+ };
17985
18015
  else if (repairs.addAppendMarker.has(event.seq)) raw.surfaceOp = "append";
17986
18016
  else if (repairs.clearSurfaceOp.has(event.seq)) delete raw.surfaceOp;
17987
18017
  }
17988
18018
  }
17989
18019
  /**
18020
+ * 读取时补全 assistant 结算字段:旧写入器不落库 `stream`(v2 才把流式记录
18021
+ * 嵌入消息),读回时缺失即补空数组——上游 seed 校验要求 turn/step/stream
18022
+ * 三者齐备,缺失会让整个会话加载失败。
18023
+ */
18024
+ function repairAssistantSettlement(events) {
18025
+ for (const event of events) {
18026
+ if (event.type !== "assistant/message" && event.type !== "assistant/attempt") continue;
18027
+ const data = event.data;
18028
+ if (!Array.isArray(data["stream"])) data["stream"] = [];
18029
+ }
18030
+ }
18031
+ /**
18032
+ * 把紧邻 replace 的 metering 事件的 `shadowedRange` / `shadowedSeqs` 对齐到
18033
+ * replace 最终的稠密 range。
18034
+ *
18035
+ * 旧写入器重编号事件后,metering 与 replace 一起落在旧坐标空间:二者数值
18036
+ * 相等但与当前 surface 无关。夹取(或 range 本身已落在稠密空间而 metering
18037
+ * 仍是旧值)后二者不再相等,而上游 token-meter 契约要求紧邻的 metering
18038
+ * range 与 replace range 完全一致(否则报 no adjacent shadow price)。range
18039
+ * 已一致时不改写——当前写入器保证一致,shadowedSeqs 的额外并发节点因此
18040
+ * 原样保留。
18041
+ */
18042
+ function syncMeteringRanges(events) {
18043
+ for (let i = 1; i < events.length; i++) {
18044
+ const metering = events[i - 1];
18045
+ if (!METERING_EVENT_TYPES.has(metering.type)) continue;
18046
+ const op = events[i].surfaceOp;
18047
+ if (typeof op !== "object" || op === null || op.op !== "replace") continue;
18048
+ const { start, end } = op;
18049
+ const data = metering.data;
18050
+ if (data.shadowedRange?.start === start && data.shadowedRange.end === end) continue;
18051
+ data.shadowedRange = {
18052
+ start,
18053
+ end
18054
+ };
18055
+ data.shadowedSeqs = events.filter((candidate) => candidate.seq >= start && candidate.seq <= end && SURFACE_EVENT_TYPES.has(candidate.type)).map((candidate) => candidate.seq);
18056
+ }
18057
+ }
18058
+ /**
18059
+ * 读取视图修复总入口:结算字段补全 → surface 语义修复 → metering 对齐 →
18060
+ * provenance 重算 → 孤儿 inbox splice 改写。全部只作用于内存视图,不落库。
18061
+ */
18062
+ function repairReadView(events) {
18063
+ repairAssistantSettlement(events);
18064
+ repairSurfaceOps(events);
18065
+ syncMeteringRanges(events);
18066
+ recomputeReplaceProvenance(events);
18067
+ repairOrphanInboxSplices(events);
18068
+ }
18069
+ /**
17990
18070
  * 定位无法从空状态增量重放的 agent/inbox/spliced 事件(孤儿操作)。
17991
18071
  *
17992
18072
  * 上游 Inbox 每次构造都从会话起点重放全部 inbox splice,失败即拒绝整个
@@ -23472,6 +23552,36 @@ function convertLegacyRows(row, eventRows) {
23472
23552
  function isLegacyVersion(version) {
23473
23553
  return version < 2;
23474
23554
  }
23555
+ /**
23556
+ * 迁移链拒绝后,把旧格式行直接当**当前格式**事件视图采用。
23557
+ *
23558
+ * 旧写入器只在建会话时落一次 header version,之后跟随上游演进继续追加
23559
+ * 事件——同一个 log 因此可能是混合世代(v0 时代的行 + 新版本字段),不是
23560
+ * 任何单一已发布格式,严格迁移链必然拒绝。这些行的数据形状(消息包装、
23561
+ * 类型名)已经是当前格式,只需 header 版本归一到当前、按稠密 seq 读取;
23562
+ * 事件数据的补全与 surface 修复由读取视图修复(repairReadView)负责。
23563
+ *
23564
+ * 调用方须在此之后执行 `validateStoredEvents`:真正属于旧格式的类型
23565
+ * (`assistant/chunk` / `compact/*` 等)仍会被 fail-loud 拒绝。
23566
+ *
23567
+ * @param row - 会话行(f_version < 2)。
23568
+ * @param eventRows - 该会话全部桥接行(按 f_sequence 升序)。
23569
+ * @returns 当前格式 header(version 归一到 SESSION_FORMAT_VERSION)、收缩后的
23570
+ * 继承前缀长度、稠密事件与可选 torn tail 起点。
23571
+ * @throws scanRows 的已提交区损坏错误(seq gap / 不可解析行)。
23572
+ */
23573
+ function adoptLegacyRows(row, eventRows) {
23574
+ const { preserved, tornFrom } = scanRows(eventRows, 0);
23575
+ return {
23576
+ meta: {
23577
+ ...rowToMeta(row),
23578
+ version: SESSION_FORMAT_VERSION
23579
+ },
23580
+ inheritedEventCount: Math.min(row.fSeedLength ?? 0, preserved.length),
23581
+ events: preserved,
23582
+ ...tornFrom !== void 0 ? { tornFrom } : {}
23583
+ };
23584
+ }
23475
23585
  //#endregion
23476
23586
  //#region ../session-rdb/src/index.ts
23477
23587
  /** 会话级写所有权与 live 路由簿记。 */
@@ -23615,9 +23725,7 @@ var RdbSessionHandle = class RdbSessionHandle {
23615
23725
  };
23616
23726
  throw new SessionPersistenceNotFoundError(this.id);
23617
23727
  }
23618
- repairSurfaceOps(log.events);
23619
- recomputeReplaceProvenance(log.events);
23620
- repairOrphanInboxSplices(log.events);
23728
+ repairReadView(log.events);
23621
23729
  return {
23622
23730
  eventState: "detached",
23623
23731
  events: log.events.slice(offset, offset + length)
@@ -23830,6 +23938,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
23830
23938
  const log = await this.readLog(id, {}, options?.signal);
23831
23939
  if (log === void 0) throw new SessionPersistenceNotFoundError(id);
23832
23940
  validateStoredEvents(log.meta, log.events);
23941
+ if (log.migrated && log.events.length !== log.storedCount) await this.rewriteMigratedLog(id, log);
23833
23942
  this.writeGuard.confirmHead(id, log.events.at(-1)?.seq ?? -1);
23834
23943
  return this.tracker.adopt(new RdbSessionHandle(this, id, log.meta, "write", {
23835
23944
  cursor: log.events.length,
@@ -23893,8 +24002,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
23893
24002
  signal?.throwIfAborted();
23894
24003
  const log = await this.readLog(id, {}, signal);
23895
24004
  if (log === void 0) return void 0;
23896
- repairSurfaceOps(log.events);
23897
- recomputeReplaceProvenance(log.events);
24005
+ repairReadView(log.events);
23898
24006
  const inheritedEventCount = Math.min(log.inheritedEventCount, log.events.length);
23899
24007
  return {
23900
24008
  meta: log.meta,
@@ -23962,14 +24070,29 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
23962
24070
  const meta = rowToMeta(row);
23963
24071
  const eventRows = options.fromSeq === void 0 ? await this.backend.getEventRows(id) : await this.backend.getEventRows(id, options.fromSeq);
23964
24072
  signal?.throwIfAborted();
23965
- if (isLegacyVersion(row.fVersion)) {
24073
+ if (isLegacyVersion(row.fVersion)) try {
23966
24074
  const converted = convertLegacyRows(row, eventRows);
23967
24075
  return {
23968
24076
  meta: converted.meta,
23969
24077
  inheritedEventCount: converted.inheritedEventCount,
23970
24078
  events: converted.events,
23971
24079
  incarnation: row.fIncarnation,
23972
- revision: row.fRevision
24080
+ revision: row.fRevision,
24081
+ storedCount: eventRows.length,
24082
+ migrated: true
24083
+ };
24084
+ } catch (error) {
24085
+ this.ctx.logger.warn(`session-rdb: session "${id}" is not a single released format; adopting its stored rows as current-format data (${error instanceof Error ? error.message : String(error)})`);
24086
+ const adopted = adoptLegacyRows(row, eventRows);
24087
+ return {
24088
+ meta: adopted.meta,
24089
+ inheritedEventCount: adopted.inheritedEventCount,
24090
+ events: adopted.events,
24091
+ incarnation: row.fIncarnation,
24092
+ revision: row.fRevision,
24093
+ storedCount: eventRows.length,
24094
+ migrated: false,
24095
+ ...adopted.tornFrom !== void 0 ? { tornFrom: adopted.tornFrom } : {}
23973
24096
  };
23974
24097
  }
23975
24098
  const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0);
@@ -23979,9 +24102,35 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
23979
24102
  events: preserved,
23980
24103
  incarnation: row.fIncarnation,
23981
24104
  revision: row.fRevision,
24105
+ storedCount: eventRows.length,
24106
+ migrated: false,
23982
24107
  ...tornFrom !== void 0 ? { tornFrom } : {}
23983
24108
  };
23984
24109
  }
24110
+ /**
24111
+ * 把迁移链读出的 v2 视图整体落库(旧格式会话写打开时的一次性迁移)。
24112
+ *
24113
+ * 迁移链会生成/合并事件(end-seed / attempt / chunk 合并),事件 seq 空间
24114
+ * 与存储桥接行数不再相等;写路径以存储 head 为锚点重编号,二者不一致会让
24115
+ * append 撞上已有行。这里在同一事务内删光本会话桥接行、按迁移视图重建
24116
+ * (新事件行,完整信封),并更新 head 与 revision;旧事件行保留(可能被
24117
+ * fork 子会话引用,孤儿由惰性 GC 处理)。
24118
+ */
24119
+ async rewriteMigratedLog(id, log) {
24120
+ await this.backend.transaction(async (tx) => {
24121
+ await tx.deleteBridgeTail(id, 0);
24122
+ await tx.upsertSession({
24123
+ meta: log.meta,
24124
+ inheritedEventCount: SessionLogOffset(log.inheritedEventCount)
24125
+ }, randomUUID());
24126
+ const { headEventId, headSequence } = await appendEventTail(tx, log.meta, log.events, {
24127
+ parentId: "",
24128
+ nextSeq: 0
24129
+ });
24130
+ await tx.updateHead(id, headEventId, headSequence);
24131
+ await tx.bumpRevision(id);
24132
+ });
24133
+ }
23985
24134
  async listSnapshots(signal) {
23986
24135
  signal?.throwIfAborted();
23987
24136
  await this.ready;
@@ -24167,6 +24316,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
24167
24316
  if (stored.meta.cwd !== session.header.cwd) throw new Error(`session "${id}" is already persisted at a different cwd (persisted: ${String(stored.meta.cwd)}, live: ${String(session.header.cwd)}) (id collision)`);
24168
24317
  if (stored.inheritedEventCount !== session.inheritedEventCount) throw new Error(`session "${id}" is already persisted with a different inherited event count (id collision)`);
24169
24318
  assertVersion(stored.meta);
24319
+ repairReadView(stored.events);
24170
24320
  const seed = session.snapshotEvents();
24171
24321
  if (!seedCoversPrefix(seed, stored.events)) throw new Error(`session "${id}" already has a persisted log on disk that does not match this live session (id collision)`);
24172
24322
  handle = await this.open(id, "write");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/ui-conversation-message-actions",
3
- "version": "0.0.14-alpha.0",
3
+ "version": "0.0.14-alpha.1",
4
4
  "description": "Rewind / retry / fork orchestration (host) plus conversation.chat.node renderer replacement (browser) — message edit / retry actions on user bubbles.",
5
5
  "keywords": [
6
6
  "conversation-chat-node",
@@ -35,10 +35,10 @@
35
35
  "./client": "./dist/client.js"
36
36
  },
37
37
  "dependencies": {
38
- "@morlay/session-branch": "^0.0.4-alpha.0"
38
+ "@morlay/session-branch": "^0.0.4-alpha.1"
39
39
  },
40
40
  "devDependencies": {
41
- "@morlay/session-rdb": "^0.0.16-alpha.0",
41
+ "@morlay/session-rdb": "^0.0.16-alpha.1",
42
42
  "@types/react": "^19.2.18",
43
43
  "@types/react-dom": "^19.2.5",
44
44
  "lightningcss": "^1.32.0",