@khorsheed/dsh-client-message-tools 0.1.0 → 0.3.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.en.md +16 -12
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +35 -31
  5. package/lib/client.js +295 -135
  6. package/lib/index.js +63 -67
  7. package/lib/typert.host.js +3 -3
  8. package/lib/typert.remote-client.js +3 -3
  9. package/lib/types/client/RestoredMessageView.d.ts +1 -1
  10. package/lib/types/client/RestoredMessageView.js +20 -1
  11. package/lib/types/client/UserMessageView.d.ts +1 -1
  12. package/lib/types/client/UserMessageView.js +16 -10
  13. package/lib/types/client/WithdrawnDividerView.d.ts +1 -1
  14. package/lib/types/client/WithdrawnDividerView.js +13 -9
  15. package/lib/types/client/chat-hook.d.ts +36 -0
  16. package/lib/types/client/chat-hook.js +51 -0
  17. package/lib/types/client/dom-hider.d.ts +3 -2
  18. package/lib/types/client/dom-hider.js +20 -13
  19. package/lib/types/client/edit-in-place.d.ts +2 -1
  20. package/lib/types/client/index.d.ts +6 -4
  21. package/lib/types/client/index.js +42 -8
  22. package/lib/types/client/locales.d.ts +8 -0
  23. package/lib/types/client/locales.js +8 -0
  24. package/lib/types/client/withdrawn-node.d.ts +3 -2
  25. package/lib/types/client/withdrawn-node.js +45 -54
  26. package/lib/types/index.js +10 -8
  27. package/lib/types/marker.d.ts +2 -2
  28. package/lib/types/withdraw.js +76 -80
  29. package/package.json +19 -18
  30. package/lib/client.js.map +0 -1
  31. package/lib/tsconfig.client.tsbuildinfo +0 -1
  32. package/lib/tsconfig.host.tsbuildinfo +0 -1
  33. package/lib/typert.remote-client.d.ts.map +0 -1
  34. package/lib/types/client/ModelChip.d.ts.map +0 -1
  35. package/lib/types/client/RestoredMessageView.d.ts.map +0 -1
  36. package/lib/types/client/UserMessageView.d.ts.map +0 -1
  37. package/lib/types/client/WithdrawnDividerView.d.ts.map +0 -1
  38. package/lib/types/client/backfill.d.ts.map +0 -1
  39. package/lib/types/client/dom-hider.d.ts.map +0 -1
  40. package/lib/types/client/edit-in-place.d.ts.map +0 -1
  41. package/lib/types/client/icons.d.ts.map +0 -1
  42. package/lib/types/client/index.d.ts.map +0 -1
  43. package/lib/types/client/locales.d.ts.map +0 -1
  44. package/lib/types/client/slots.d.ts.map +0 -1
  45. package/lib/types/client/withdraw-backfill.d.ts.map +0 -1
  46. package/lib/types/client/withdrawn-node.d.ts.map +0 -1
  47. package/lib/types/index.d.ts.map +0 -1
  48. package/lib/types/invariant.d.ts.map +0 -1
  49. package/lib/types/marker.d.ts.map +0 -1
  50. package/lib/types/types.d.ts.map +0 -1
  51. package/lib/types/withdraw.d.ts.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { chatSourceOf } from './chat-hook.js';
1
2
  import { MESSAGE_TOOLS_PLUGIN, messageToolsOp } from '../marker.js';
2
3
  import { foldHiddenRanges, isSeqHidden } from './withdrawn-node.js';
3
4
  /**
@@ -77,9 +78,9 @@ export function installDomHider(ctx, options = {}) {
77
78
  let retryObserver;
78
79
  let retryTimer;
79
80
  let latest;
80
- const applyRules = (snapshot) => {
81
- latest = snapshot;
82
- const nodes = snapshot.chat.nodes.values();
81
+ const applyRules = (chat) => {
82
+ latest = chat;
83
+ const nodes = chat.nodes.values();
83
84
  const ranges = foldHiddenRanges(nodes);
84
85
  const nextSignature = `${ranges.join(',')}:${nodes.length}`;
85
86
  if (nextSignature === signature)
@@ -141,17 +142,17 @@ export function installDomHider(ctx, options = {}) {
141
142
  else
142
143
  failProbe(); }, probeRetryWindowMs);
143
144
  };
144
- const onSnapshot = (snapshot) => {
145
- latest = snapshot;
145
+ const onSnapshot = (chat) => {
146
+ latest = chat;
146
147
  if (disabled)
147
148
  return;
148
149
  if (probed) {
149
- applyRules(snapshot);
150
+ applyRules(chat);
150
151
  return;
151
152
  }
152
153
  // Probe on the first non-empty rule set, one frame out so React has
153
154
  // committed the rows the rules target.
154
- if (hiddenFlowKeys(snapshot.chat.nodes.values()).length === 0)
155
+ if (hiddenFlowKeys(chat.nodes.values()).length === 0)
155
156
  return;
156
157
  if (probeScheduled || retrying)
157
158
  return;
@@ -176,18 +177,24 @@ export function installDomHider(ctx, options = {}) {
176
177
  style.textContent = '';
177
178
  if (current === undefined)
178
179
  return;
179
- const binding = ctx.sessions.binding(current);
180
- if (binding === undefined)
180
+ // Chat data lives in the uiConversation binding's chat target (host
181
+ // 0.1.2's split — see chat-hook.ts); its snapshot is undefined until the
182
+ // first subscription activates the view.
183
+ const source = chatSourceOf(ctx, current);
184
+ if (source === undefined)
181
185
  return;
182
- onSnapshot(binding.session.getSnapshot());
183
- stopSession = binding.session.subscribe(() => { onSnapshot(binding.session.getSnapshot()); });
186
+ const publish = () => {
187
+ const chat = source.getSnapshot();
188
+ if (chat !== undefined)
189
+ onSnapshot(chat);
190
+ };
191
+ publish();
192
+ stopSession = source.subscribe(publish);
184
193
  };
185
194
  const stopList = ctx.sessions.list.subscribe(bindCurrent);
186
- const stopProvide = ctx.sessions.currentProvideInfo.subscribe(bindCurrent);
187
195
  bindCurrent();
188
196
  return () => {
189
197
  stopList();
190
- stopProvide();
191
198
  stopSession?.();
192
199
  stopRetry();
193
200
  style.remove();
@@ -51,7 +51,8 @@ export interface WithdrawInPlaceSteps {
51
51
  export declare function withdrawInPlace(steps: WithdrawInPlaceSteps, running: boolean): Promise<void>;
52
52
  /**
53
53
  * The minimal slice of the client conversation snapshot the settle probe
54
- * reads (structurally satisfied by `ConversationSnapshot`, so the unit tests
54
+ * reads (structurally satisfied by the ui-chat snapshot's timeline slice, so
55
+ * the unit tests stub only these fields).
55
56
  * stub only these fields).
56
57
  */
57
58
  export interface TurnSettleSnapshot {
@@ -13,19 +13,21 @@
13
13
  * adds.
14
14
  * @module @khorsheed/dsh-client-message-tools/client
15
15
  */
16
- import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
16
+ import type { Context } from '@deepseek-ai/cordis';
17
17
  import type { TypertRemoteNamespaceMap } from '@deepseek-ai/dsh-typert-protocol';
18
18
  /** The messageTools Remote namespace, as mounted by this plugin. */
19
19
  export type MessageToolsRemote = TypertRemoteNamespaceMap['messageTools'];
20
20
  /**
21
21
  * Required services: slots, sessions, the remote channel, the scope-addressed
22
- * conversation service, conversation node registration, and the copy.
22
+ * conversation service, and the copy.
23
23
  * `remote.messageTools` is deliberately NOT an inject: this plugin both
24
24
  * mounts the namespace (through `$mount` below) and consumes it, and the
25
25
  * Cordis property proxy only resolves services declared in `inject` or
26
26
  * provided by an ancestor fiber. Declaring it would deadlock the loader; the
27
27
  * namespace is read back from the global store with `ctx.get` after the
28
- * awaited mount.
28
+ * awaited mount. The event-Definition registry (`uiConversation.events`) is
29
+ * likewise NOT a static inject: the deferred ctx.inject arm keeps the
30
+ * registration independent of ui-conversation's own mount order.
29
31
  */
30
32
  export declare const inject: string[];
31
33
  /**
@@ -35,5 +37,5 @@ export declare const inject: string[];
35
37
  * @param ctx - client root context.
36
38
  * @returns disposer unwinding the mounted Remote namespace.
37
39
  */
38
- export declare function apply(ctx: ClientContext): Promise<() => Promise<void>>;
40
+ export declare function apply(ctx: Context): Promise<() => Promise<void>>;
39
41
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import messageToolsRemote from '@khorsheed/dsh-client-message-tools/remote';
2
2
  import { installDomHider } from './dom-hider.js';
3
3
  import { mergedDraft } from './backfill.js';
4
+ import { chatSourceOf } from './chat-hook.js';
4
5
  import { editInPlace, waitForTurnSettled, withdrawInPlace } from './edit-in-place.js';
5
6
  import { en, zh } from './locales.js';
6
7
  import { editedMessageDefinition, restoredAssistantMessageDefinition, restoredMessageDefinition, withdrawnDividerDefinition, } from './withdrawn-node.js';
@@ -19,15 +20,35 @@ const EMPTY_MODEL_DIRECTORY_STATE = Object.freeze({
19
20
  });
20
21
  /**
21
22
  * Required services: slots, sessions, the remote channel, the scope-addressed
22
- * conversation service, conversation node registration, and the copy.
23
+ * conversation service, and the copy.
23
24
  * `remote.messageTools` is deliberately NOT an inject: this plugin both
24
25
  * mounts the namespace (through `$mount` below) and consumes it, and the
25
26
  * Cordis property proxy only resolves services declared in `inject` or
26
27
  * provided by an ancestor fiber. Declaring it would deadlock the loader; the
27
28
  * namespace is read back from the global store with `ctx.get` after the
28
- * awaited mount.
29
+ * awaited mount. The event-Definition registry (`uiConversation.events`) is
30
+ * likewise NOT a static inject: the deferred ctx.inject arm keeps the
31
+ * registration independent of ui-conversation's own mount order.
29
32
  */
30
- export const inject = ['slots', 'sessions', 'remote', 'conversation', 'conversationEvents', 'locale'];
33
+ export const inject = ['slots', 'sessions', 'remote', 'conversation', 'locale'];
34
+ /**
35
+ * Register the four message-tools event Definitions against ui-conversation's
36
+ * event registry (host 0.1.2 folded the standalone registry service into
37
+ * `uiConversation.events`, upstream commit be531688f3). The registry is live
38
+ * (a registration re-assembles mounted views), so the deferred arm needs no
39
+ * ordering against ui-conversation's own apply; a composition without the
40
+ * service never fires it and simply has no chat projection at all.
41
+ * @param ctx - client root context.
42
+ */
43
+ function registerEventDefinitions(ctx) {
44
+ ctx.inject(['uiConversation'], (lctx) => {
45
+ const registry = lctx.uiConversation.events;
46
+ registry.register(withdrawnDividerDefinition);
47
+ registry.register(restoredMessageDefinition);
48
+ registry.register(restoredAssistantMessageDefinition);
49
+ registry.register(editedMessageDefinition);
50
+ });
51
+ }
31
52
  /**
32
53
  * Client plugin body: mount the Remote, register the dictionaries, the
33
54
  * withdrawal divider Definition, and the two `conversation.chat.node` entries
@@ -72,10 +93,7 @@ export async function apply(ctx) {
72
93
  };
73
94
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'message-tools: dictionaries');
74
95
  const t = ctx.locale.bind(NS);
75
- ctx.conversationEvents.register(withdrawnDividerDefinition);
76
- ctx.conversationEvents.register(restoredMessageDefinition);
77
- ctx.conversationEvents.register(restoredAssistantMessageDefinition);
78
- ctx.conversationEvents.register(editedMessageDefinition);
96
+ registerEventDefinitions(ctx);
79
97
  ctx.effect(() => installDomHider(ctx), 'message-tools: dom hider');
80
98
  ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
81
99
  name: 'conversation.chat.node',
@@ -125,7 +143,23 @@ export async function apply(ctx) {
125
143
  // turn/end last); a replacement's span must cover all of it, so wait
126
144
  // for the turn to actually close. Bounded: a turn that never settles
127
145
  // rejects instead of letting the edit/withdraw race the stragglers.
128
- const waitIdle = () => waitForTurnSettled(() => ctx.sessions.binding(sessionId)?.session.getSnapshot());
146
+ // `running` rides the Session snapshot; the chat slice (timeline, and
147
+ // runningCalls under `legacy`) rides the conversation binding's chat
148
+ // target (host 0.1.2's split, see chat-hook.ts).
149
+ const settleSnapshot = () => {
150
+ const session = ctx.sessions.binding(sessionId)?.session.getSnapshot();
151
+ if (session === undefined)
152
+ return undefined;
153
+ const chat = chatSourceOf(ctx, sessionId)?.getSnapshot();
154
+ if (chat === undefined)
155
+ return undefined;
156
+ return {
157
+ running: session.running,
158
+ runningCalls: chat.legacy.runningCalls,
159
+ chat,
160
+ };
161
+ };
162
+ const waitIdle = () => waitForTurnSettled(settleSnapshot);
129
163
  return {
130
164
  editMessage: (targetSeq, text) => editInPlace({
131
165
  cancel: () => conversation.cancel(),
@@ -29,6 +29,10 @@ export declare const zh: {
29
29
  'model.empty': string;
30
30
  'model.selectFailed': string;
31
31
  'message.extraBlock': string;
32
+ close: string;
33
+ 'markdown.copy': string;
34
+ 'markdown.copied': string;
35
+ 'markdown.footnotes': string;
32
36
  'json.truncated': string;
33
37
  };
34
38
  /** The message-tools namespace key union. */
@@ -69,6 +73,10 @@ export declare const en: {
69
73
  'model.empty': string;
70
74
  'model.selectFailed': string;
71
75
  'message.extraBlock': string;
76
+ close: string;
77
+ 'markdown.copy': string;
78
+ 'markdown.copied': string;
79
+ 'markdown.footnotes': string;
72
80
  'json.truncated': string;
73
81
  };
74
82
  //# sourceMappingURL=locales.d.ts.map
@@ -29,6 +29,10 @@ export const zh = {
29
29
  'model.empty': '暂无可用模型',
30
30
  'model.selectFailed': '模型切换失败',
31
31
  'message.extraBlock': '附加内容块',
32
+ 'close': '关闭',
33
+ 'markdown.copy': '复制',
34
+ 'markdown.copied': '已复制',
35
+ 'markdown.footnotes': '脚注',
32
36
  'json.truncated': '… 已截断,共 {total} 字符',
33
37
  };
34
38
  /** English dictionary, checked complete against the zh key set. */
@@ -61,5 +65,9 @@ export const en = {
61
65
  'model.empty': 'No models available',
62
66
  'model.selectFailed': 'Could not switch model',
63
67
  'message.extraBlock': 'Extra content block',
68
+ 'close': 'Close',
69
+ 'markdown.copy': 'Copy',
70
+ 'markdown.copied': 'Copied',
71
+ 'markdown.footnotes': 'Footnotes',
64
72
  'json.truncated': '… truncated, {total} characters total',
65
73
  };
@@ -8,7 +8,8 @@
8
8
  * Definition per event and offers no suppression seam; hiding happens in the
9
9
  * renderers and the DOM hider.
10
10
  */
11
- import type { ChatConversationViewNode, ConversationNodeDefinition } from '@deepseek-ai/dsh-client-runtime/client';
11
+ import type { ChatConversationViewNode } from '@deepseek-ai/dsh-client-ui-chat/client';
12
+ import type { ConversationNodeDefinition } from '@deepseek-ai/dsh-client-ui-conversation/client';
12
13
  /** Chat node data of one withdrawal divider row. */
13
14
  export interface WithdrawnDividerData {
14
15
  /** Seq of the replacement event; the divider anchors here and the hidden span ends (exclusive). */
@@ -51,7 +52,7 @@ export interface RestoredAssistantMessageData {
51
52
  /** The assistant reply text (model-facing frame stripped for display). */
52
53
  readonly text: string;
53
54
  }
54
- declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
55
+ declare module '@deepseek-ai/dsh-client-ui-chat/client' {
55
56
  interface ChatNodeDataMap {
56
57
  /** message-tools withdrawal divider marking where a span was hidden. */
57
58
  'message-tools-withdrawn': WithdrawnDividerData;
@@ -1,4 +1,22 @@
1
1
  import { isMessageToolsEdit, isMessageToolsReplacement, isMessageToolsRestore, isMessageToolsRestoreAssistant, stripRestoreAssistantFrame, } from '../marker.js';
2
+ /**
3
+ * Materialize one final chat-target node. The Definition contract's
4
+ * buildViewNode returns the ConversationViewNode base (key/kind/id/target/
5
+ * data), so the chat extras (anchorSeq/location/visibility) go through this
6
+ * explicitly typed builder instead of an excess-checked literal.
7
+ */
8
+ function chatViewNode(context, kind, anchorSeq, data) {
9
+ return {
10
+ key: context.key,
11
+ kind,
12
+ id: context.id,
13
+ target: 'chat',
14
+ anchorSeq,
15
+ location: context.start?.location ?? { kind: 'unresolved' },
16
+ visibility: 'visible',
17
+ data,
18
+ };
19
+ }
2
20
  /**
3
21
  * The withdrawal divider Definition: one single-event Context per
4
22
  * message-tools replacement event, anchored at the replacement's seq so the
@@ -7,29 +25,23 @@ import { isMessageToolsEdit, isMessageToolsReplacement, isMessageToolsRestore, i
7
25
  export const withdrawnDividerDefinition = {
8
26
  kind: 'message-tools-withdrawn',
9
27
  target: 'chat',
10
- match: event => isMessageToolsReplacement(event)
11
- ? { id: String(event.seq), role: 'start' }
12
- : null,
28
+ match: (event) => {
29
+ // Attempt and boundary rows share the bus; the guards speak SessionEvent only.
30
+ if (event.type !== 'user/message')
31
+ return null;
32
+ return isMessageToolsReplacement(event) ? { id: String(event.seq), role: 'start' } : null;
33
+ },
13
34
  start: (_context, match) => {
14
35
  const event = match.event;
15
36
  if (!isMessageToolsReplacement(event)) {
16
37
  throw new Error('message-tools-withdrawn start requires a message-tools replacement event');
17
38
  }
18
- return { seq: event.seq, hiddenStartSeq: event.surfaceOp.start };
39
+ return { seq: event.seq, hiddenStartSeq: event.surfaceOp.startSeq };
19
40
  },
20
41
  update: context => context.state,
21
42
  buildViewNode: context => context.state === undefined
22
43
  ? null
23
- : {
24
- key: context.key,
25
- kind: 'message-tools-withdrawn',
26
- id: context.id,
27
- target: 'chat',
28
- anchorSeq: context.state.seq,
29
- location: context.start?.location ?? { kind: 'unresolved' },
30
- visibility: 'visible',
31
- data: context.state,
32
- },
44
+ : chatViewNode(context, 'message-tools-withdrawn', context.state.seq, context.state),
33
45
  };
34
46
  /**
35
47
  * The edited-bubble Definition: one single-event Context per message-tools
@@ -40,9 +52,11 @@ export const withdrawnDividerDefinition = {
40
52
  export const editedMessageDefinition = {
41
53
  kind: 'message-tools-edited',
42
54
  target: 'chat',
43
- match: event => isMessageToolsEdit(event)
44
- ? { id: String(event.seq), role: 'start' }
45
- : null,
55
+ match: (event) => {
56
+ if (event.type !== 'user/message')
57
+ return null;
58
+ return isMessageToolsEdit(event) ? { id: String(event.seq), role: 'start' } : null;
59
+ },
46
60
  start: (_context, match) => {
47
61
  const event = match.event;
48
62
  if (!isMessageToolsEdit(event)) {
@@ -52,22 +66,13 @@ export const editedMessageDefinition = {
52
66
  seq: event.seq,
53
67
  time: event.time,
54
68
  content: event.data.content,
55
- hiddenStartSeq: event.surfaceOp.start,
69
+ hiddenStartSeq: event.surfaceOp.startSeq,
56
70
  };
57
71
  },
58
72
  update: context => context.state,
59
73
  buildViewNode: context => context.state === undefined
60
74
  ? null
61
- : {
62
- key: context.key,
63
- kind: 'message-tools-edited',
64
- id: context.id,
65
- target: 'chat',
66
- anchorSeq: context.state.seq,
67
- location: context.start?.location ?? { kind: 'unresolved' },
68
- visibility: 'visible',
69
- data: context.state,
70
- },
75
+ : chatViewNode(context, 'message-tools-edited', context.state.seq, context.state),
71
76
  };
72
77
  /**
73
78
  * Fold the hidden seq spans out of the live chat nodes, flattened to
@@ -122,9 +127,11 @@ function joinAssistantText(blocks) {
122
127
  export const restoredMessageDefinition = {
123
128
  kind: 'message-tools-restored',
124
129
  target: 'chat',
125
- match: event => isMessageToolsRestore(event)
126
- ? { id: String(event.seq), role: 'start' }
127
- : null,
130
+ match: (event) => {
131
+ if (event.type !== 'user/message')
132
+ return null;
133
+ return isMessageToolsRestore(event) ? { id: String(event.seq), role: 'start' } : null;
134
+ },
128
135
  start: (_context, match) => {
129
136
  const event = match.event;
130
137
  if (!isMessageToolsRestore(event)) {
@@ -141,16 +148,7 @@ export const restoredMessageDefinition = {
141
148
  update: context => context.state,
142
149
  buildViewNode: context => context.state === undefined
143
150
  ? null
144
- : {
145
- key: context.key,
146
- kind: 'message-tools-restored',
147
- id: context.id,
148
- target: 'chat',
149
- anchorSeq: context.state.seq,
150
- location: context.start?.location ?? { kind: 'unresolved' },
151
- visibility: 'visible',
152
- data: context.state,
153
- },
151
+ : chatViewNode(context, 'message-tools-restored', context.state.seq, context.state),
154
152
  };
155
153
  /**
156
154
  * The restore-assistant-line Definition: one single-event Context per
@@ -162,9 +160,11 @@ export const restoredMessageDefinition = {
162
160
  export const restoredAssistantMessageDefinition = {
163
161
  kind: 'message-tools-restored-assistant',
164
162
  target: 'chat',
165
- match: event => isMessageToolsRestoreAssistant(event)
166
- ? { id: String(event.seq), role: 'start' }
167
- : null,
163
+ match: (event) => {
164
+ if (event.type !== 'user/message')
165
+ return null;
166
+ return isMessageToolsRestoreAssistant(event) ? { id: String(event.seq), role: 'start' } : null;
167
+ },
168
168
  start: (_context, match) => {
169
169
  const event = match.event;
170
170
  if (!isMessageToolsRestoreAssistant(event)) {
@@ -180,16 +180,7 @@ export const restoredAssistantMessageDefinition = {
180
180
  update: context => context.state,
181
181
  buildViewNode: context => context.state === undefined
182
182
  ? null
183
- : {
184
- key: context.key,
185
- kind: 'message-tools-restored-assistant',
186
- id: context.id,
187
- target: 'chat',
188
- anchorSeq: context.state.seq,
189
- location: context.start?.location ?? { kind: 'unresolved' },
190
- visibility: 'visible',
191
- data: context.state,
192
- },
183
+ : chatViewNode(context, 'message-tools-restored-assistant', context.state.seq, context.state),
193
184
  };
194
185
  /**
195
186
  * Count the user-visible messages hidden by one withdrawn span. This mirrors
@@ -32,6 +32,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
32
32
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
33
  done = true;
34
34
  };
35
+ // Value: the branded seq constructor the surface markers require.
36
+ import { SessionSeq } from '@deepseek-ai/dsh-session';
35
37
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
36
38
  import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
37
39
  import { EDIT_TRIGGER_NOTICE, MESSAGE_TOOLS_PLUGIN, WITHDRAWN_NOTICE, editReplacementSource, editTriggerSource, restoreAssistantSource, } from './marker.js';
@@ -77,15 +79,15 @@ let MessageToolsService = (() => {
77
79
  const session = this.ctx.sessions.get(request.sessionId);
78
80
  if (session === undefined)
79
81
  return { ok: false, error: { code: 'session-not-found' } };
80
- const planned = planWithdrawal(session.events, session.surface.nodes, request.targetSeq);
82
+ const planned = planWithdrawal(session.snapshotEvents(), session.surface.nodes, request.targetSeq);
81
83
  if (!planned.ok)
82
84
  return { ok: false, error: { code: planned.code } };
83
85
  const replacement = session.append('user/message', createUserMessage({
84
86
  content: [{ type: 'text', text: WITHDRAWN_NOTICE }],
85
87
  source: { kind: 'plugin', plugin: MESSAGE_TOOLS_PLUGIN },
86
88
  }), {
87
- surfaceOp: { op: 'replace', start: planned.plan.start, end: planned.plan.end },
88
- sourceEventSeqs: [...planned.plan.sourceEventSeqs],
89
+ surfaceOp: { op: 'replace', startSeq: SessionSeq(planned.plan.start), endSeq: SessionSeq(planned.plan.end) },
90
+ sourceEventSeqs: planned.plan.sourceEventSeqs.map(seq => SessionSeq(seq)),
89
91
  });
90
92
  await this.ctx.sessions.flush(session);
91
93
  return {
@@ -111,7 +113,7 @@ let MessageToolsService = (() => {
111
113
  const session = this.ctx.sessions.get(request.sessionId);
112
114
  if (session === undefined)
113
115
  return { ok: false, error: { code: 'session-not-found' } };
114
- const planned = planRestore(session.events, session.surface.nodes, request.targetSeq);
116
+ const planned = planRestore(session.snapshotEvents(), session.surface.nodes, request.targetSeq);
115
117
  if (!planned.ok)
116
118
  return { ok: false, error: { code: planned.code } };
117
119
  const appendedSeqs = [];
@@ -127,7 +129,7 @@ let MessageToolsService = (() => {
127
129
  });
128
130
  appendedSeqs.push(session.append('user/message', message, {
129
131
  surfaceOp: 'append',
130
- sourceEventSeqs: [entry.sourceSeq],
132
+ sourceEventSeqs: [SessionSeq(entry.sourceSeq)],
131
133
  }).seq);
132
134
  }
133
135
  await this.ctx.sessions.flush(session);
@@ -149,15 +151,15 @@ let MessageToolsService = (() => {
149
151
  const session = this.ctx.sessions.get(request.sessionId);
150
152
  if (session === undefined)
151
153
  return { ok: false, error: { code: 'session-not-found' } };
152
- const planned = planEdit(session.events, session.surface.nodes, request.targetSeq, request.text);
154
+ const planned = planEdit(session.snapshotEvents(), session.surface.nodes, request.targetSeq, request.text);
153
155
  if (!planned.ok)
154
156
  return { ok: false, error: { code: planned.code } };
155
157
  const replacement = session.append('user/message', createUserMessage({
156
158
  content: [{ type: 'text', text: request.text }],
157
159
  source: editReplacementSource(),
158
160
  }), {
159
- surfaceOp: { op: 'replace', start: planned.plan.start, end: planned.plan.end },
160
- sourceEventSeqs: [...planned.plan.sourceEventSeqs],
161
+ surfaceOp: { op: 'replace', startSeq: SessionSeq(planned.plan.start), endSeq: SessionSeq(planned.plan.end) },
162
+ sourceEventSeqs: planned.plan.sourceEventSeqs.map(seq => SessionSeq(seq)),
161
163
  });
162
164
  await this.ctx.sessions.flush(session);
163
165
  const agent = this.ctx.agents.get(request.sessionId);
@@ -44,8 +44,8 @@ export declare function stripRestoreAssistantFrame(text: string): string;
44
44
  export type MessageToolsReplacementEvent = SessionEvent<'user/message'> & {
45
45
  readonly surfaceOp: {
46
46
  readonly op: 'replace';
47
- readonly start: number;
48
- readonly end: number;
47
+ readonly startSeq: number;
48
+ readonly endSeq: number;
49
49
  };
50
50
  };
51
51
  /** One message-tools edit replacement event, narrowed (same shape, `op: 'edit'` source). */