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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/ui-conversation-message-actions",
3
- "version": "0.0.13",
3
+ "version": "0.0.14-alpha.0",
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.3"
38
+ "@morlay/session-branch": "^0.0.4-alpha.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@morlay/session-rdb": "^0.0.13",
41
+ "@morlay/session-rdb": "^0.0.16-alpha.0",
42
42
  "@types/react": "^19.2.18",
43
43
  "@types/react-dom": "^19.2.5",
44
44
  "lightningcss": "^1.32.0",
@@ -47,22 +47,22 @@
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@deepseek-ai/cordis": "^4.0.2",
50
- "@deepseek-ai/dsh-api-session-controller": "^0.1.2-rc.1",
51
- "@deepseek-ai/dsh-attachment": "^0.1.2-rc.1",
52
- "@deepseek-ai/dsh-client-connection": "^0.1.2-rc.1",
53
- "@deepseek-ai/dsh-client-store": "^0.1.2-rc.1",
54
- "@deepseek-ai/dsh-client-ui-attachment": "^0.1.2-rc.1",
55
- "@deepseek-ai/dsh-client-ui-chat": "^0.1.2-rc.1",
56
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
57
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1",
58
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-rc.1",
59
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1",
60
- "@deepseek-ai/dsh-invariants": "^0.1.2-rc.1",
61
- "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
62
- "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
63
- "@deepseek-ai/dsh-session-persistence": "^0.1.2-rc.1",
64
- "@deepseek-ai/dsh-session-projection": "^0.1.2-rc.1",
65
- "@deepseek-ai/dsh-token-meter": "^0.1.2-rc.1"
50
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.3-alpha.2",
51
+ "@deepseek-ai/dsh-attachment": "^0.1.3-alpha.2",
52
+ "@deepseek-ai/dsh-client-connection": "^0.1.3-alpha.2",
53
+ "@deepseek-ai/dsh-client-store": "^0.1.3-alpha.2",
54
+ "@deepseek-ai/dsh-client-ui-attachment": "^0.1.3-alpha.2",
55
+ "@deepseek-ai/dsh-client-ui-chat": "^0.1.3-alpha.2",
56
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.3-alpha.2",
57
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.3-alpha.2",
58
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.3-alpha.2",
59
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.3-alpha.2",
60
+ "@deepseek-ai/dsh-invariants": "^0.1.3-alpha.2",
61
+ "@deepseek-ai/dsh-llm": "^0.1.3-alpha.2",
62
+ "@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
63
+ "@deepseek-ai/dsh-session-persistence": "^0.1.3-alpha.2",
64
+ "@deepseek-ai/dsh-session-projection": "^0.1.3-alpha.2",
65
+ "@deepseek-ai/dsh-token-meter": "^0.1.3-alpha.2"
66
66
  },
67
67
  "dsh": {
68
68
  "bundle": {
@@ -8,7 +8,7 @@ import type { ReactNode } from "react";
8
8
  import type { InjectFace } from "@deepseek-ai/dsh-client-ui-slots";
9
9
  import { Button, Modal } from "@deepseek-ai/dsh-client-ui-primitives";
10
10
  import type { UserMessageNode } from "@deepseek-ai/dsh-client-ui-chat/client";
11
- import { JsonBlock, MessageText } from "@deepseek-ai/dsh-client-ui-primitives";
11
+ import { JsonBlock } from "@deepseek-ai/dsh-client-ui-primitives";
12
12
  import type { ChatNodeViewProps, ChatViewSlotProps } from "@deepseek-ai/dsh-client-ui-chat/client";
13
13
  import type { RenderMessageImages } from "@deepseek-ai/dsh-client-ui-conversation/client";
14
14
  import { MessageIconActions } from "./MessageIconActions.tsx";
@@ -45,8 +45,7 @@ function projectUserText(text: string): ReactNode {
45
45
  while ((m = re.exec(text)) !== null) {
46
46
  const tokenStart = m.index + (m[1]?.length ?? 0);
47
47
  const label = m[2] ?? "";
48
- if (tokenStart > cursor)
49
- parts.push(<MessageText key={cursor} text={text.slice(cursor, tokenStart)} />);
48
+ if (tokenStart > cursor) parts.push(<span key={cursor}>{text.slice(cursor, tokenStart)}</span>);
50
49
  parts.push(
51
50
  <span
52
51
  key={tokenStart}
@@ -58,8 +57,8 @@ function projectUserText(text: string): ReactNode {
58
57
  );
59
58
  cursor = tokenStart + label.length;
60
59
  }
61
- if (parts.length === 0) return <MessageText text={text} />;
62
- if (cursor < text.length) parts.push(<MessageText key={cursor} text={text.slice(cursor)} />);
60
+ if (parts.length === 0) return <span>{text}</span>;
61
+ if (cursor < text.length) parts.push(<span key={cursor}>{text.slice(cursor)}</span>);
63
62
  return <>{parts}</>;
64
63
  }
65
64
 
package/src/index.ts CHANGED
@@ -142,10 +142,9 @@ function appendManualTurn(
142
142
  appendSurfaceSeedEvent(
143
143
  events,
144
144
  "assistant/message",
145
- { turn, step: 1, message: assistant },
145
+ { turn, step: 1, message: assistant, stream: [] },
146
146
  {
147
147
  surfaceOp: "append",
148
- sourceEventSeqs: [],
149
148
  },
150
149
  );
151
150
  appendLogSeedEvent(events, "step/end", { turn, step: 1 });
@@ -155,7 +154,11 @@ function appendManualTurn(
155
154
  });
156
155
  }
157
156
 
158
- function appendSeedSuffixLive(session: Session, seedSuffix: readonly SessionEvent[]): void {
157
+ async function appendSeedSuffixLive(
158
+ session: Session,
159
+ seedSuffix: readonly SessionEvent[],
160
+ appendDirect: (events: readonly SessionEvent[]) => Promise<void>,
161
+ ): Promise<void> {
159
162
  for (const event of seedSuffix) {
160
163
  // 版本效果事件携带 ignorable 标记(上游类型无此字段,duck-type 读取)。
161
164
  const ignorable = (event as { ignorable?: boolean }).ignorable === true;
@@ -164,10 +167,13 @@ function appendSeedSuffixLive(session: Session, seedSuffix: readonly SessionEven
164
167
  log: SessionEvent[];
165
168
  eventsSnapshot?: unknown;
166
169
  };
167
- // ignorable 语义:live log 保留、不落 canonical log。session.append 不
168
- // 保留 ignorable 标记,因此直接 push 内存 log(不发布);seq 按 log
169
- // 续接重编号(seedSuffix 内部编号从 0 起)。
170
- s.log.push({ ...event, seq: s.log.length } as SessionEvent);
170
+ // 原样存储语义:版本效果事件经 RDB write handle 直接落库(带
171
+ // ignorable 信封,与 JSONL 一致)。session.append 不保留 ignorable
172
+ // 标记且类型系统禁止 append 未知类型,因此不发布、直接 push 内存
173
+ // log(seq log 续接重编号,与 handle cursor 同空间)。
174
+ const seq = s.log.length;
175
+ await appendDirect([{ ...event, seq } as SessionEvent]);
176
+ s.log.push({ ...event, seq } as SessionEvent);
171
177
  s.eventsSnapshot = undefined;
172
178
  continue;
173
179
  }
@@ -308,10 +314,25 @@ export class SessionEditor extends Service {
308
314
  await this.ctx.sessionBranch.rewind(operation.sessionId, boundary, signal);
309
315
  if (seedSuffix.length > 0) {
310
316
  if (live !== undefined) {
311
- // live:版本效果 push 内存 log(ignorable 保留)、manualTurn append,
312
- // 同步 cursor 后显式 flush(push 不发布、不进缓冲,cursor 会落后)。
313
- appendSeedSuffixLive(live, seedSuffix);
314
- this.ctx.sessionBranch.syncLiveCursor(operation.sessionId);
317
+ // live:版本效果经 RDB write handle 直接落库(带 ignorable 信封,
318
+ // JSONL 一致)、manualTurn 走 append;同步 cursor 后显式 flush
319
+ // (直接落库不发布、不进缓冲,cursor 会落后)。
320
+ const persistence = this.ctx.sessionPersistence as unknown as {
321
+ tracker: {
322
+ writerOf(id: SessionId):
323
+ | {
324
+ append(events: readonly SessionEvent[]): Promise<void>;
325
+ }
326
+ | undefined;
327
+ };
328
+ };
329
+ const handle = persistence.tracker.writerOf(operation.sessionId);
330
+ if (handle === undefined) {
331
+ throw new Error(
332
+ `session "${operation.sessionId}" has no live write handle for the version effect`,
333
+ );
334
+ }
335
+ await appendSeedSuffixLive(live, seedSuffix, (events) => handle.append(events));
315
336
  await this.ctx.sessions.flush(live);
316
337
  } else {
317
338
  // cold:续写 seq 从平衡后的保留前缀接续(exclusive 截断可能残留
@@ -325,7 +346,12 @@ export class SessionEditor extends Service {
325
346
  seq: keepLength + index,
326
347
  }) as SessionEvent,
327
348
  );
328
- await this.ctx.sessionPersistence.append(operation.sessionId, renumbered);
349
+ const handle = await this.ctx.sessionPersistence.open(operation.sessionId, "write");
350
+ try {
351
+ await handle.append(renumbered);
352
+ } finally {
353
+ await handle.close();
354
+ }
329
355
  }
330
356
  }
331
357
 
@@ -401,10 +427,7 @@ export class SessionEditor extends Service {
401
427
  const fallbackProvider = config?.provider ?? "";
402
428
  const fallbackModel = config?.model ?? "";
403
429
  if (fallbackProvider.length === 0 || fallbackModel.length === 0) {
404
- throw new SessionBranchError(
405
- "无法重放:会话没有可解析的模型配置。",
406
- "INVALID_BOUNDARY",
407
- );
430
+ throw new SessionBranchError("无法重放:会话没有可解析的模型配置。", "INVALID_BOUNDARY");
408
431
  }
409
432
  const handle = await agents.resume({
410
433
  resumeSessionId: sessionId,
package/src/plan.ts CHANGED
@@ -298,7 +298,10 @@ export function retryPlan(operation: RetryOperation, turns: readonly ClosedTurn[
298
298
  };
299
299
  }
300
300
 
301
- export function rerollPlan(operation: RerollOperation, turns: readonly ClosedTurn[]): OperationPlan {
301
+ export function rerollPlan(
302
+ operation: RerollOperation,
303
+ turns: readonly ClosedTurn[],
304
+ ): OperationPlan {
302
305
  for (let index = turns.length - 1; index >= 0; index -= 1) {
303
306
  const turn = turns[index];
304
307
  // 未闭合轮次无已落定的助手回复可重生成。
package/src/testing.ts CHANGED
@@ -65,17 +65,16 @@ export async function createPersisted(
65
65
  events: readonly SessionEvent[],
66
66
  header: SessionHeader = meta(id),
67
67
  ): Promise<void> {
68
- await ctx.sessionPersistence.create(header);
69
- await ctx.sessionPersistence.append(SessionIdBrand(id), [...events]);
68
+ const handle = await ctx.sessionPersistence.create(header);
69
+ try {
70
+ await handle.append([...events]);
71
+ } finally {
72
+ await handle.close();
73
+ }
70
74
  }
71
75
 
72
76
  /** 构造一条 user/message 事件。 */
73
- export function userMessage(
74
- seq: number,
75
- id: string,
76
- text: string,
77
- time = seq,
78
- ): SessionEvent {
77
+ export function userMessage(seq: number, id: string, text: string, time = seq): SessionEvent {
79
78
  return {
80
79
  type: "user/message",
81
80
  seq: SessionSeq(seq),
@@ -112,9 +111,10 @@ export function assistantMessage(
112
111
  content: [{ type: "text", text }],
113
112
  source: { kind: "model", provider: "mock", model: "mock" },
114
113
  },
114
+ stream: [] as const,
115
115
  },
116
116
  surfaceOp: "append",
117
- } as SessionEvent;
117
+ } as unknown as SessionEvent;
118
118
  }
119
119
 
120
120
  /** 真实 agent-loop 形状的一轮:轮首输入 + 可选 followup,可闭合。 */