@deepseek-ai/dsh-client-ui-trajectory 0.1.2-rc.1 → 0.1.5-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/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-trajectory/README.md
5
- README.md: d947787c123fca77dc7978456f4cfdc4e99951bf
6
- README.zh.md: 3b281c6c1e90108d66f050e07a3b08208ed8c802
5
+ README.md: 2b8ecff50b498be529ccf7e35e528f7554084940
6
+ README.zh.md: 8463bd0a68c32753138425d968db229726b5bd1a
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-client-ui-trajectory` is the Trajectory view of the dsh web client: it renders a turn-aware event ledger with selectable User, Assistant, Tool, and nested Subtool records, plus an interactive timing overview. Thick rules mark Turn boundaries, compact inline markers identify Steps, and selecting a record opens a local inspector for token usage, duration, Input, Output, Timing, and durable images from user, assistant, or tool content. The view is a pure consumer: it registers target-specific Event Definitions, a Trajectory view builder, and one tab in the conversation's `conversation.view` slot ring, and provides no service and declares no Context merge. Its typed `trajectory` locale namespace owns every product-authored ledger, timeline, inspector, tooltip, and accessibility phrase; event content, tool names, identifiers, and provider diagnostics remain verbatim data. Long ledgers open at the current tail, page older history on demand, and mount only the visible row window.
12
+ The Trajectory tab lets you inspect agent activity as a turn-aware ledger and interactive timing overview. It groups User, Assistant, Tool, nested Subtool, and compaction records, marks turn and step boundaries, and opens a record inspector for token usage, duration, input, output, timing, images, and attachment summaries. Long histories open at the current tail, load older pages on demand, and render only visible rows. During streaming, the view follows the tail until you scroll upward, and in-flight records show a start marker without inventing elapsed time.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -29,7 +29,7 @@ Open the Trajectory tab in the conversation's view ring to inspect agent activit
29
29
 
30
30
  ### Inspecting records
31
31
 
32
- Selection, timeline navigation, folding, and search cover the React-visible window. Request numbers and cumulative usage cover the complete resident snapshot. Selecting a record opens a local inspector for token usage, duration, Input, Output, Timing, and durable images. Image URLs use the Conversation-owned per-session cache, so Chat and Trajectory share one authorized read per attachment. A record without text labels its row with the image count. A standalone compaction request appears chronologically in its own `Between turns` section, while a numbered compaction remains inside its owning turn.
32
+ Selection, timeline navigation, folding, and search cover the React-visible window. Request numbers and cumulative usage cover the complete resident snapshot. Selecting a record opens a local inspector for token usage, duration, Input, Output, Timing, and durable images. Image URLs use the Conversation-owned per-session cache, so Chat and Trajectory share one authorized read per attachment. A user record shows the generic-file count beside its text, while a record without text shows its image and file counts. A standalone compaction request appears chronologically in its own `Between turns` section, while a numbered compaction remains inside its owning turn.
33
33
 
34
34
  ### The timing overview
35
35
 
@@ -45,9 +45,11 @@ A fixed Overview above the ledger projects real record start/duration timing fro
45
45
 
46
46
  The view is a pure projection: Trajectory-owned Definitions assemble business records from the shared Session window — including durable cancellation-finalized prefixes, chunk-only interruption fallbacks, and interrupted Tool records — so Trajectory neither reads nor changes the Chat conversation snapshot. Its steering classifier retains only next-step Inbox IDs through persistent splice state and shares each current claimed batch across later Contexts.
47
47
 
48
+ A complete appended prompt without a loaded request header appears as a standalone system row; only its known text is available, with no inferred request options or tool catalog. Prepending its request history replaces that standalone presentation without duplicating the prompt. In-history system prompt changes compare against the most recent request state, including earlier prompt updates without a new request header. Each request retains the prompt and change that applied at its own position. Surface replacements, including compaction, restore the last nonempty surviving system prompt even without a new system event; an unloaded prompt remains unavailable until its page arrives.
49
+
48
50
  ### Virtual rows
49
51
 
50
- Long ledgers initially derive React data from 50 target Nodes ending at the mount-time tail. Later Nodes extend that anchored window without evicting its prefix, and the existing load control reveals earlier resident Nodes before requesting another Session page. Virtualization mounts only the visible row window plus a small overscan; request-only separators share the next measurable virtual item, while semantic row keys and ARIA indexes survive prepends. Content-only stream frames preserve virtual row keys and heights, reuse measurements, and do not issue repeated tail-scroll writes. Completed replies retain assembled blocks, timing, and usage in Trajectory target State, while the shared Session window keeps the raw Events.
52
+ Long ledgers initially derive React data from 50 target Nodes ending at the mount-time tail. Later Nodes extend that anchored window without evicting its prefix, and the existing load control reveals earlier resident Nodes before requesting another Session page. Virtualization mounts only the visible row window plus a small overscan; request-only separators share the next measurable virtual item, while semantic row keys and ARIA indexes survive prepends. The virtualizer owns bottom following for structural appends; non-virtual ledgers use a direct tail-position write. Content-only stream frames preserve virtual row keys and heights, reuse measurements, and do not issue repeated tail-scroll writes. Completed replies retain assembled blocks, timing, and usage in Trajectory target State, while the shared Session window keeps the raw Events.
51
53
 
52
54
  ### Layout
53
55
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-client-ui-trajectory` dsh Web 客户端的 Trajectory 视图:它渲染按轮次组织的事件记录表,其中可选择用户、助手、工具与嵌套子工具记录,并带交互式时间概览。较粗的分割线标示轮次边界,紧凑的行内标记标识步骤;选择记录会打开局部检查器,查看 token 用量、耗时、输入、输出、计时,以及用户、助手或工具内容中的持久图片。该视图是纯消费方:它注册 target 专属 Event Definition、Trajectory view builder 以及对话 `conversation.view` slot 环中的一个视图标签页,不提供 service,也不声明 Context 合并。带类型的 `trajectory` locale namespace 拥有所有产品编写的 ledger、timeline、inspector、tooltip 与无障碍文案;事件内容、工具名称、标识符与 provider 诊断保持原始数据。长记录表打开时定位于当前尾部、按需加载更早历史,并且只挂载可见行窗口。
12
+ Trajectory 标签页让你以按轮次组织的事件记录表和交互式时间概览检查 agent 活动。它对用户、助手、工具、嵌套子工具和压缩记录分组,标示轮次与步骤边界,并为所选记录打开检查器,显示 token 用量、耗时、输入、输出、计时、图片和附件摘要。较长历史打开时定位于当前尾部,按需加载更早页面,并且只渲染可见行。流式输出期间,视图会跟随尾部,直到你向上滚动;进行中的记录只显示开始标记,不会虚构耗时。
13
13
 
14
14
  ## 目录
15
15
 
@@ -29,7 +29,7 @@ kind: "package-reference"
29
29
 
30
30
  ### 检查记录
31
31
 
32
- 选择、时间线导航、折叠与搜索只覆盖 React 可见窗口。请求编号与累计用量覆盖完整的驻留 snapshot。选择记录会打开局部检查器,查看 token 用量、耗时、输入、输出、计时与持久图片。图片 URL 使用 Conversation 拥有的逐会话缓存,因此 Chat 与 Trajectory 对每个附件共享一次已授权读取。没有文本的记录会用图片数量标记其行。独立运行的压缩请求会按时间顺序显示在自己的 `Between turns` 区段中,而带编号的压缩仍位于其所属轮次内。
32
+ 选择、时间线导航、折叠与搜索只覆盖 React 可见窗口。请求编号与累计用量覆盖完整的驻留 snapshot。选择记录会打开局部检查器,查看 token 用量、耗时、输入、输出、计时与持久图片。图片 URL 使用 Conversation 拥有的逐会话缓存,因此 Chat 与 Trajectory 对每个附件共享一次已授权读取。用户记录会在文本旁显示通用文件数量;记录没有文本时,则显示图片与文件数量。独立运行的压缩请求会按时间顺序显示在自己的 `Between turns` 区段中,而带编号的压缩仍位于其所属轮次内。
33
33
 
34
34
  ### 时间概览
35
35
 
@@ -45,9 +45,11 @@ kind: "package-reference"
45
45
 
46
46
  视图是纯投影:Trajectory 自有的 Definition 从共享 Session 窗口组装业务记录——包括持久化的取消定稿前缀、只能从分片恢复的打断前缀与被打断的工具记录——因此 Trajectory 既不读取也不改变 Chat 会话快照。其 steering 分类器通过持久 splice state 只保留 next-step Inbox ID,并让后续 Context 共享当前 claimed batch。
47
47
 
48
+ 完整的追加提示词在请求头未加载时显示为独立系统行;仅提供已知文本,不推断请求选项或工具目录。补入其请求历史后,该独立展示被替代而不重复提示词。历史中的系统提示词变更与最近的请求状态比较,包括没有新请求头的先前提示词更新。每个请求保留其所在位置生效的提示词与变更。包括压缩在内的 surface 替换会恢复最后一个非空的存活系统提示词,即使没有新的系统事件;未加载的提示词在对应分页到达前仍不可用。
49
+
48
50
  ### 虚拟行
49
51
 
50
- 长记录表最初只从挂载时尾部结束的 50 个 target Node 派生 React 数据。后续 Node 会扩展这个固定起点的窗口而不会逐出其前缀;现有加载控件会先显露更早的驻留 Node,再请求下一个 Session 页面。虚拟化只挂载可见行窗口加少量缓冲;仅含请求的分隔行并入下一个具备可测高度的虚拟项,语义行键与 ARIA 索引在向前补页后保持不变。仅含内容更新的流式帧会保持虚拟行的键与高度、复用测量结果,并且不会重复写入末尾滚动位置。已完成的回复会在 Trajectory target State 中保留组装后的 blocks、计时与用量,共享 Session 窗口则保留原始 Event。
52
+ 长记录表最初只从挂载时尾部结束的 50 个 target Node 派生 React 数据。后续 Node 会扩展这个固定起点的窗口而不会逐出其前缀;现有加载控件会先显露更早的驻留 Node,再请求下一个 Session 页面。虚拟化只挂载可见行窗口加少量缓冲;仅含请求的分隔行并入下一个具备可测高度的虚拟项,语义行键与 ARIA 索引在向前补页后保持不变。虚拟化器负责结构性追加后的底部跟随;非虚拟记录表会直接写入末尾位置。仅含内容更新的流式帧会保持虚拟行的键与高度、复用测量结果,并且不会重复写入末尾滚动位置。已完成的回复会在 Trajectory target State 中保留组装后的 blocks、计时与用量,共享 Session 窗口则保留原始 Event。
51
53
 
52
54
  ### 布局
53
55
 
package/lib/client.js CHANGED
@@ -216,6 +216,7 @@ window.__ModuleLoader__.load({
216
216
  "layout.compacted": "上下文已压缩",
217
217
  "layout.toolCallOnly": "仅工具调用",
218
218
  "layout.imageOnly": "图片 ×{count}",
219
+ "layout.fileAttachments": "文件 ×{count}",
219
220
  "layout.initialSystemPrompt": "初始系统提示词",
220
221
  "layout.systemPromptUpdated": "系统提示词已更新",
221
222
  "layout.toolsUpdated": "工具已更新",
@@ -393,6 +394,7 @@ window.__ModuleLoader__.load({
393
394
  "layout.compacted": "Context compacted",
394
395
  "layout.toolCallOnly": "Tool call only",
395
396
  "layout.imageOnly": "Images ×{count}",
397
+ "layout.fileAttachments": "Files ×{count}",
396
398
  "layout.initialSystemPrompt": "Initial System Prompt",
397
399
  "layout.systemPromptUpdated": "System Prompt Updated",
398
400
  "layout.toolsUpdated": "Tools Updated",
@@ -597,9 +599,6 @@ window.__ModuleLoader__.load({
597
599
  }
598
600
  //#endregion
599
601
  //#region lib/types/client/trajectory-assistant-definition.js
600
- function isChunkRunEvent(event) {
601
- return event.type === "chunkrow/text-chunks" || event.type === "chunkrow/reasoning-chunks" || event.type === "chunkrow/tool-call-chunks";
602
- }
603
602
  function initialState(turn, step, startSeq, startTime, started) {
604
603
  return {
605
604
  turn,
@@ -647,9 +646,7 @@ window.__ModuleLoader__.load({
647
646
  ...current?.reasoningTokens === void 0 && next.reasoningTokens === void 0 ? {} : { reasoningTokens: (current?.reasoningTokens ?? 0) + (next.reasoningTokens ?? 0) }
648
647
  };
649
648
  }
650
- function updateChunk(state, match) {
651
- if (match.event.type !== "assistant/chunk") return state;
652
- const chunk = match.event.data.chunk;
649
+ function updateChunk(state, chunk, seq, time) {
653
650
  if (chunk.type === "usage") return {
654
651
  ...state,
655
652
  sawChunk: true,
@@ -719,78 +716,21 @@ window.__ModuleLoader__.load({
719
716
  blocks,
720
717
  visibleBlocks,
721
718
  ...visibleBlocks > 0 && state.firstVisibleSeq === void 0 ? {
722
- firstVisibleSeq: match.event.seq,
723
- firstVisibleTime: match.event.time
719
+ firstVisibleSeq: seq,
720
+ firstVisibleTime: time
724
721
  } : {},
725
- ...isTokenDelta(chunk) && state.firstTokenTime === void 0 ? { firstTokenTime: match.event.time } : {}
722
+ ...isTokenDelta(chunk) && state.firstTokenTime === void 0 ? { firstTokenTime: time } : {}
726
723
  };
727
724
  }
728
- function chunkRunBoundaries(event, needsToken, needsVisible, visibleFromStart) {
729
- const fragments = event.type === "chunkrow/tool-call-chunks" ? event.data.args : event.data.texts;
730
- const nameStartsToken = event.type === "chunkrow/tool-call-chunks" && Object.hasOwn(event.data, "name");
731
- let firstTokenTime;
732
- let firstVisible;
733
- let time = event.time;
734
- for (let index = 0; index < fragments.length; index++) {
735
- const fragment = fragments[index];
736
- if (needsToken && firstTokenTime === void 0 && (nameStartsToken || fragment !== "")) firstTokenTime = time;
737
- if (needsVisible && firstVisible === void 0 && (visibleFromStart || event.type !== "chunkrow/tool-call-chunks" && fragment.trim() !== "")) firstVisible = {
738
- seq: event.seq + index,
739
- time
740
- };
741
- if ((!needsToken || firstTokenTime !== void 0) && (!needsVisible || firstVisible !== void 0)) break;
742
- time += event.data.dt[index] ?? 0;
743
- }
744
- return {
745
- firstTokenTime,
746
- firstVisible
747
- };
748
- }
749
- function updateChunkRun(state, event) {
750
- const blocks = [...state.blocks];
751
- const previous = blocks[event.data.index];
752
- const previousVisible = blockIsVisible(previous);
753
- let visibleFromStart = state.visibleBlocks - Number(previousVisible) > 0;
754
- if (event.type === "chunkrow/text-chunks") {
755
- const text = previous?.kind === "text" ? previous.text : "";
756
- visibleFromStart ||= text.trim() !== "";
757
- blocks[event.data.index] = {
758
- kind: "text",
759
- text: text + event.data.texts.join("")
760
- };
761
- } else if (event.type === "chunkrow/reasoning-chunks") {
762
- const text = previous?.kind === "reasoning" ? previous.text : "";
763
- visibleFromStart ||= text.trim() !== "";
764
- blocks[event.data.index] = {
765
- kind: "reasoning",
766
- text: text + event.data.texts.join("")
767
- };
768
- } else {
769
- const base = previous?.kind === "tool-call" ? previous : {
770
- kind: "tool-call",
771
- callId: "",
772
- name: "",
773
- argsRaw: ""
774
- };
775
- blocks[event.data.index] = {
776
- kind: "tool-call",
777
- callId: base.callId || String(event.data.id),
778
- name: Object.hasOwn(event.data, "name") ? event.data.name : base.name,
779
- argsRaw: base.argsRaw + event.data.args.join("")
780
- };
781
- }
782
- const boundaries = chunkRunBoundaries(event, state.firstTokenTime === void 0, state.firstVisibleSeq === void 0, visibleFromStart);
783
- const visibleBlocks = state.visibleBlocks - Number(previousVisible) + Number(blockIsVisible(blocks[event.data.index]));
725
+ function settleMessage(state, match, event) {
726
+ const blocks = toAssistantBlocks(event.data.message.content);
784
727
  return {
785
728
  ...state,
786
- sawChunk: true,
729
+ sawChunk: false,
787
730
  blocks,
788
- visibleBlocks,
789
- ...boundaries.firstVisible === void 0 ? {} : {
790
- firstVisibleSeq: boundaries.firstVisible.seq,
791
- firstVisibleTime: boundaries.firstVisible.time
792
- },
793
- ...boundaries.firstTokenTime === void 0 ? {} : { firstTokenTime: boundaries.firstTokenTime }
731
+ visibleBlocks: countVisibleBlocks(blocks),
732
+ final: match,
733
+ usage: event.data.usage
794
734
  };
795
735
  }
796
736
  function closedBoundary(context) {
@@ -802,25 +742,13 @@ window.__ModuleLoader__.load({
802
742
  function fallbackState$1(context) {
803
743
  let state;
804
744
  for (const match of context.matches) {
805
- if (isChunkRunEvent(match.event)) {
806
- state ??= initialState(match.event.data.turn, match.event.data.step, match.event.seq, match.event.time, false);
807
- state = updateChunkRun(state, match.event);
808
- continue;
809
- }
810
745
  const event = match.event;
811
- if (event.type === "assistant/chunk") {
746
+ if (event.type === "assistant/live-chunk") {
812
747
  state ??= initialState(event.data.turn, event.data.step, event.seq, event.time, false);
813
- state = updateChunk(state, match);
748
+ state = updateChunk(state, event.data.chunk, event.seq, event.time);
814
749
  } else if (event.type === "assistant/message") {
815
750
  state ??= initialState(event.data.turn, event.data.step, event.seq, event.time, false);
816
- const blocks = toAssistantBlocks(event.data.message.content);
817
- state = {
818
- ...state,
819
- blocks,
820
- visibleBlocks: countVisibleBlocks(blocks),
821
- final: match,
822
- usage: state.usage ?? event.data.usage
823
- };
751
+ state = settleMessage(state, match, event);
824
752
  } else if (event.type === "step/end" && state !== void 0) state = {
825
753
  ...state,
826
754
  stepEnd: match
@@ -901,11 +829,7 @@ window.__ModuleLoader__.load({
901
829
  id: `${event.data.turn}:${event.data.step}`,
902
830
  role: "start"
903
831
  };
904
- if (event.type === "assistant/chunk" || event.type === "assistant/message" || event.type === "llm/retry" || event.type === "step/end") return {
905
- id: `${event.data.turn}:${event.data.step}`,
906
- role: "update"
907
- };
908
- if (isChunkRunEvent(event)) return {
832
+ if (event.type === "assistant/live-chunk" || event.type === "assistant/message" || event.type === "llm/retry" || event.type === "step/end") return {
909
833
  id: `${event.data.turn}:${event.data.step}`,
910
834
  role: "update"
911
835
  };
@@ -916,18 +840,8 @@ window.__ModuleLoader__.load({
916
840
  return initialState(match.event.data.turn, match.event.data.step, match.event.seq, match.event.time, true);
917
841
  },
918
842
  update: (context, match) => {
919
- if (isChunkRunEvent(match.event)) return updateChunkRun(context.state, match.event);
920
- if (match.event.type === "assistant/chunk") return updateChunk(context.state, match);
921
- if (match.event.type === "assistant/message") {
922
- const blocks = toAssistantBlocks(match.event.data.message.content);
923
- return {
924
- ...context.state,
925
- blocks,
926
- visibleBlocks: countVisibleBlocks(blocks),
927
- final: match,
928
- usage: context.state.usage ?? match.event.data.usage
929
- };
930
- }
843
+ if (match.event.type === "assistant/live-chunk") return updateChunk(context.state, match.event.data.chunk, match.event.seq, match.event.time);
844
+ if (match.event.type === "assistant/message") return settleMessage(context.state, match, match.event);
931
845
  if (match.event.type === "step/end") return {
932
846
  ...context.state,
933
847
  stepEnd: match
@@ -950,8 +864,7 @@ window.__ModuleLoader__.load({
950
864
  },
951
865
  publication: (match) => {
952
866
  if (match.event.type === "step/start") return "none";
953
- if (isChunkRunEvent(match.event)) return "animation-frame";
954
- if (match.event.type !== "assistant/chunk") return "immediate";
867
+ if (match.event.type !== "assistant/live-chunk") return "immediate";
955
868
  const type = match.event.data.chunk.type;
956
869
  return type === "usage" || type === "finish" ? "none" : "animation-frame";
957
870
  },
@@ -1257,6 +1170,88 @@ window.__ModuleLoader__.load({
1257
1170
  //#endregion
1258
1171
  //#region lib/types/client/trajectory-request-header-definition.js
1259
1172
  /**
1173
+ * Definition retaining each `system/message` surface node for the Trajectory
1174
+ * request-header Definition, which reads it through `reader.previous` and
1175
+ * presents the prompt through the request's `system` cell. A node that
1176
+ * lacks a loaded header contributes known text without request config or tools; an in-history
1177
+ * update contributes a request-header fact at its own position, since no
1178
+ * `request/header` follows a prompt change that keeps the cached history.
1179
+ * Surface replacements also contribute prompt changes when they remove the
1180
+ * effective node; earlier request facts remain historical.
1181
+ * @param inspect - Pure surface interpretation supplied by uiConversation.
1182
+ * @returns The Trajectory system-prompt Definition.
1183
+ */
1184
+ function trajectorySystemMessageDefinition(inspect) {
1185
+ return {
1186
+ kind: "trajectory-system-message",
1187
+ target: "trajectory",
1188
+ match: (event) => event.type === "system/message" || "surfaceOp" in event && event.surfaceOp !== "append" ? {
1189
+ id: String(event.seq),
1190
+ role: "start"
1191
+ } : null,
1192
+ start: (_context, match, reader) => {
1193
+ const prior = reader.previous("trajectory-system-message")?.state;
1194
+ const state = inspect(prior, match.event);
1195
+ const node = state.effective;
1196
+ if (state.uncertain) {
1197
+ const header = reader.previous("trajectory-request-header")?.state;
1198
+ if (header === void 0) return state;
1199
+ return {
1200
+ ...state,
1201
+ header: {
1202
+ seq: match.event.seq,
1203
+ time: match.event.time,
1204
+ location: match.location,
1205
+ prompt: {
1206
+ ...header.prompt,
1207
+ system: ""
1208
+ }
1209
+ }
1210
+ };
1211
+ }
1212
+ if (node === void 0 || node.text === prior?.effective?.text || state.introduced !== void 0 && !state.introduced.update) return {
1213
+ ...state,
1214
+ ...prior?.header === void 0 ? {} : { header: prior.header }
1215
+ };
1216
+ const header = reader.previous("trajectory-request-header")?.state;
1217
+ const systemHeader = prior?.header;
1218
+ const previous = systemHeader !== void 0 && (header === void 0 || systemHeader.seq > header.seq) ? systemHeader : header;
1219
+ if (previous === void 0) return state;
1220
+ return {
1221
+ ...state,
1222
+ header: {
1223
+ seq: node.seq,
1224
+ time: node.time,
1225
+ prompt: {
1226
+ ...previous.prompt,
1227
+ system: node.text
1228
+ },
1229
+ change: {
1230
+ seq: node.seq,
1231
+ time: node.time,
1232
+ kind: "system",
1233
+ previous: previous.prompt
1234
+ },
1235
+ location: match.location
1236
+ }
1237
+ };
1238
+ },
1239
+ update: (context) => context.state,
1240
+ buildViewNode: (context) => {
1241
+ const state = context.state;
1242
+ if (state?.header !== void 0 && state.header.seq === context.start?.event.seq) return trajectoryNode(context, state.header.seq, {
1243
+ kind: "request-header",
1244
+ header: state.header
1245
+ });
1246
+ const prompt = state?.introduced;
1247
+ return prompt !== void 0 && prompt.text !== "" && context.start?.event.type === "system/message" && context.start.event.surfaceOp === "append" ? trajectoryNode(context, prompt.seq, {
1248
+ kind: "system-prompt",
1249
+ prompt
1250
+ }) : null;
1251
+ }
1252
+ };
1253
+ }
1254
+ /**
1260
1255
  * Request-header fact Definition for the Trajectory target.
1261
1256
  * @param inspect - the shared prompt interpretation, supplied by the
1262
1257
  * uiConversation service (a client bundle cannot value-import it).
@@ -1272,8 +1267,14 @@ window.__ModuleLoader__.load({
1272
1267
  } : null,
1273
1268
  start: (_context, match, reader) => {
1274
1269
  if (match.event.type !== "request/header") throw new Error("trajectory-request-header start requires request/header");
1275
- const previous = reader.previous("trajectory-request-header")?.state.prompt;
1276
- const { prompt, change } = inspect(previous, match.event);
1270
+ const header = reader.previous("trajectory-request-header")?.state;
1271
+ const state = reader.previous("trajectory-system-message")?.state;
1272
+ const systemHeader = state?.header;
1273
+ const previous = systemHeader !== void 0 && (header === void 0 || systemHeader.seq > header.seq) ? systemHeader.prompt : header?.prompt;
1274
+ const system = state?.effective;
1275
+ const inspection = inspect(previous, match.event, system);
1276
+ const { prompt } = inspection;
1277
+ const change = inspection.change ?? (systemHeader !== void 0 && (header === void 0 || systemHeader.seq > header.seq) ? systemHeader.change : void 0);
1277
1278
  return {
1278
1279
  seq: match.event.seq,
1279
1280
  time: match.event.time,
@@ -1290,12 +1291,13 @@ window.__ModuleLoader__.load({
1290
1291
  };
1291
1292
  }
1292
1293
  /**
1293
- * Register Trajectory request-header facts.
1294
+ * Register Trajectory system-prompt node and request-header facts.
1294
1295
  *
1295
- * @param ctx - Plugin context receiving the Definition.
1296
+ * @param ctx - Plugin context receiving the Definitions.
1296
1297
  */
1297
1298
  function registerTrajectoryRequestHeaderDefinition(ctx) {
1298
- ctx.uiConversation.events.register(trajectoryRequestHeaderDefinition((previous, event) => ctx.uiConversation.inspectRequestPrompt(previous, event)));
1299
+ ctx.uiConversation.events.register(trajectorySystemMessageDefinition((previous, event) => ctx.uiConversation.inspectSystemPrompt(previous, event)));
1300
+ ctx.uiConversation.events.register(trajectoryRequestHeaderDefinition((previous, event, system) => ctx.uiConversation.inspectRequestPrompt(previous, event, system)));
1299
1301
  }
1300
1302
  //#endregion
1301
1303
  //#region lib/types/client/copy-codes.js
@@ -1431,6 +1433,8 @@ window.__ModuleLoader__.load({
1431
1433
  ...contribution.data.header.change !== void 0 ? { change: contribution.data.header.change } : previous?.change === void 0 ? {} : { change: previous.change }
1432
1434
  });
1433
1435
  }
1436
+ const representedPrompts = new Set(this.contributions.flatMap((node) => node.data.kind === "request-header" && node.data.header.change !== void 0 ? [node.data.header.change.seq] : []));
1437
+ const systemPrompts = [];
1434
1438
  const finalized = [];
1435
1439
  const eventLocations = /* @__PURE__ */ new Map();
1436
1440
  const requests = [];
@@ -1444,6 +1448,10 @@ window.__ModuleLoader__.load({
1444
1448
  const runningCalls = [];
1445
1449
  for (const contribution of this.contributions) {
1446
1450
  const data = contribution.data;
1451
+ if (data.kind === "system-prompt") {
1452
+ if (!representedPrompts.has(data.prompt.seq)) systemPrompts.push(data.prompt);
1453
+ continue;
1454
+ }
1447
1455
  if (data.kind === "request-header") {
1448
1456
  previousHeader = data.header;
1449
1457
  previousTools = indexTools(data.header.prompt.tools);
@@ -1496,6 +1504,7 @@ window.__ModuleLoader__.load({
1496
1504
  finalized.sort((left, right) => left.seq - right.seq);
1497
1505
  return {
1498
1506
  eventNodes: finalized,
1507
+ ...systemPrompts.length > 0 ? { systemPrompts } : {},
1499
1508
  eventLocations,
1500
1509
  requests,
1501
1510
  callSchemas,
@@ -1622,16 +1631,16 @@ window.__ModuleLoader__.load({
1622
1631
  }
1623
1632
  function updateDispatch(state, match) {
1624
1633
  const event = match.event;
1625
- if (event.type !== "tool/code-dispatch-start" && event.type !== "tool/code-dispatch") return state;
1634
+ if (event.type !== "tool/ptc-dispatch-start" && event.type !== "tool/ptc-dispatch") return state;
1626
1635
  const data = event.data;
1627
1636
  const parentId = String(data.parentCallId);
1628
1637
  const childId = String(data.subCallId);
1629
1638
  const siblings = state.children.get(parentId) ?? [];
1630
1639
  const index = siblings.indexOf(childId);
1631
1640
  if (index < 0 && !acceptsEdge(state, parentId, childId)) return state;
1632
- if (event.type === "tool/code-dispatch-start" && index >= 0) return state;
1641
+ if (event.type === "tool/ptc-dispatch-start" && index >= 0) return state;
1633
1642
  const calls = new Map(state.calls);
1634
- calls.set(childId, event.type === "tool/code-dispatch-start" ? childCall(match, data) : childResult(match, data, calls.get(childId)));
1643
+ calls.set(childId, event.type === "tool/ptc-dispatch-start" ? childCall(match, data) : childResult(match, data, calls.get(childId)));
1635
1644
  if (index >= 0) return {
1636
1645
  ...state,
1637
1646
  calls
@@ -1702,7 +1711,7 @@ window.__ModuleLoader__.load({
1702
1711
  for (const match of context.matches) state = updateDispatch(state, match);
1703
1712
  return state;
1704
1713
  }
1705
- /** Trajectory-owned root Tool lifecycle with nested Code Dispatch calls. */
1714
+ /** Trajectory-owned root Tool lifecycle with nested PTC dispatch calls. */
1706
1715
  const trajectoryToolDefinition = {
1707
1716
  kind: "trajectory-tool-call",
1708
1717
  target: "trajectory",
@@ -1715,7 +1724,7 @@ window.__ModuleLoader__.load({
1715
1724
  id: String(event.data.message.source.callId),
1716
1725
  role: "update"
1717
1726
  };
1718
- if (event.type === "tool/code-dispatch-start" || event.type === "tool/code-dispatch") {
1727
+ if (event.type === "tool/ptc-dispatch-start" || event.type === "tool/ptc-dispatch") {
1719
1728
  const rootCallId = event.data.rootCallId;
1720
1729
  return typeof rootCallId === "string" && rootCallId !== "" ? {
1721
1730
  id: rootCallId,
@@ -4382,7 +4391,10 @@ window.__ModuleLoader__.load({
4382
4391
  if (record.cell.kind === "message" || record.cell.kind === "compacted") return record.cell.outputDetail;
4383
4392
  }
4384
4393
  function detailTabs(record) {
4385
- if (record.cell.kind === "system") return record.cell.previousPromptDetail === void 0 ? SYSTEM_PROMPT_TABS : SYSTEM_UPDATE_TABS;
4394
+ if (record.cell.kind === "system") {
4395
+ if (record.cell.promptDetail === void 0 && record.cell.systemPromptDetail !== void 0) return SYSTEM_PROMPT_TABS.filter((tab) => tab.id === "system-prompt");
4396
+ return record.cell.previousPromptDetail === void 0 ? SYSTEM_PROMPT_TABS : SYSTEM_UPDATE_TABS;
4397
+ }
4386
4398
  if (record.cell.kind === "compacted") return [{
4387
4399
  id: "overview",
4388
4400
  labelKey: "tab.summary"
@@ -5082,7 +5094,8 @@ window.__ModuleLoader__.load({
5082
5094
  anchorTo: "end",
5083
5095
  overscan: VIRTUAL_OVERSCAN_ROWS,
5084
5096
  scrollMargin: virtualScrollMargin,
5085
- scrollEndThreshold: BOTTOM_FOLLOW_THRESHOLD_PX
5097
+ scrollEndThreshold: BOTTOM_FOLLOW_THRESHOLD_PX,
5098
+ followOnAppend: "auto"
5086
5099
  });
5087
5100
  const virtualIndexByRecordId = (0, react.useMemo)(() => {
5088
5101
  const indexes = /* @__PURE__ */ new Map();
@@ -5108,7 +5121,8 @@ window.__ModuleLoader__.load({
5108
5121
  const requestBoundaryRuns = (0, react.useMemo)(() => indexRequestBoundaryRuns(records, requestGroups), [records, requestGroups]);
5109
5122
  const selectedPrompt = selected?.cell.kind === "system" ? selected.cell.promptDetail : void 0;
5110
5123
  const selectedPreviousPrompt = selected?.cell.kind === "system" ? selected.cell.previousPromptDetail : void 0;
5111
- const promptSelected = selectedPrompt !== void 0;
5124
+ const selectedSystemPrompt = selectedPrompt?.system ?? selected?.cell.systemPromptDetail;
5125
+ const promptSelected = selectedSystemPrompt !== void 0;
5112
5126
  const selectedState = selected === void 0 ? void 0 : stateOf(selected);
5113
5127
  const selectedRequestInfo = selectedRequest === null ? void 0 : sessionRequestNumbers?.find((request) => requestIdentity(request) === selectedRequest.identity);
5114
5128
  const selectedRequestRecords = (0, react.useMemo)(() => selectedRequestInfo === void 0 ? [] : allRecords.filter((record) => record.turn === selectedRequestInfo.turn && record.group === selectedRequestInfo.group), [allRecords, selectedRequestInfo]).map(currentRecord);
@@ -5339,8 +5353,7 @@ window.__ModuleLoader__.load({
5339
5353
  return;
5340
5354
  }
5341
5355
  if (!followsTableTail.current) return;
5342
- if (virtualizationEnabled) rowVirtualizer.scrollToEnd({ behavior: "auto" });
5343
- else pane.scrollTop = pane.scrollHeight;
5356
+ if (!virtualizationEnabled) pane.scrollTop = pane.scrollHeight;
5344
5357
  }, [
5345
5358
  historyLoading,
5346
5359
  historyStartSeq,
@@ -5839,22 +5852,22 @@ window.__ModuleLoader__.load({
5839
5852
  request: selectedRequestInfo,
5840
5853
  t
5841
5854
  }),
5842
- promptSelected && selectedPreviousPrompt !== void 0 && activeTab === "diff" && (0, react_jsx_runtime.jsx)(SystemPromptDiff, {
5855
+ selectedPrompt !== void 0 && selectedPreviousPrompt !== void 0 && activeTab === "diff" && (0, react_jsx_runtime.jsx)(SystemPromptDiff, {
5843
5856
  before: selectedPreviousPrompt,
5844
5857
  after: selectedPrompt,
5845
5858
  t
5846
5859
  }),
5847
- promptSelected && activeTab === "system-prompt" && (selectedPrompt.system === "" ? (0, react_jsx_runtime.jsx)("p", {
5860
+ promptSelected && activeTab === "system-prompt" && (selectedSystemPrompt === "" ? (0, react_jsx_runtime.jsx)("p", {
5848
5861
  className: TrajectoryTable_module_css_default.noPayload,
5849
5862
  children: t("record.systemPromptMissing")
5850
5863
  }) : (0, react_jsx_runtime.jsx)("div", {
5851
5864
  className: `${TrajectoryTable_module_css_default.markdownPayload} ${TrajectoryTable_module_css_default.systemPrompt}`,
5852
5865
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
5853
- text: selectedPrompt.system,
5866
+ text: selectedSystemPrompt,
5854
5867
  labels: markdownLabels(t)
5855
5868
  })
5856
5869
  })),
5857
- promptSelected && activeTab === "tools" && (0, react_jsx_runtime.jsx)(ToolCatalog, {
5870
+ selectedPrompt !== void 0 && activeTab === "tools" && (0, react_jsx_runtime.jsx)(ToolCatalog, {
5858
5871
  tools: selectedPrompt.tools,
5859
5872
  t
5860
5873
  }),
@@ -6859,8 +6872,9 @@ window.__ModuleLoader__.load({
6859
6872
  const preview = previewContent(node.content);
6860
6873
  const previewMarkdown = preview === "" ? void 0 : preview;
6861
6874
  const images = imageBlockCount(node.content);
6875
+ const files = fileBlockCount(node.content);
6862
6876
  return {
6863
- text: previewMarkdown === void 0 && images > 0 ? t("layout.imageOnly", { count: images }) : "",
6877
+ text: [previewMarkdown === void 0 && images > 0 ? t("layout.imageOnly", { count: images }) : void 0, files > 0 ? t("layout.fileAttachments", { count: files }) : void 0].filter((value) => value !== void 0).join(" · "),
6864
6878
  ...previewMarkdown === void 0 ? {} : { previewMarkdown },
6865
6879
  sourceSeq: node.seq,
6866
6880
  messageSource: node.source,
@@ -6952,6 +6966,16 @@ window.__ModuleLoader__.load({
6952
6966
  if (partial !== null && partial.step > 0) representedRequests.add(`${partial.turn}\u0000${partial.step}`);
6953
6967
  for (const call of runningCalls) if (call.step > 0) representedRequests.add(`${call.turn}\u0000${call.step}`);
6954
6968
  const entries = [
6969
+ ...(input.systemPrompts ?? []).map((prompt) => ({
6970
+ kind: "system",
6971
+ seq: prompt.seq,
6972
+ systemPrompt: prompt.text,
6973
+ change: {
6974
+ seq: prompt.seq,
6975
+ time: prompt.time,
6976
+ kind: prompt.update ? "system" : "initial"
6977
+ }
6978
+ })),
6955
6979
  ...nodes.map((node, nodeIndex) => ({
6956
6980
  kind: "node",
6957
6981
  seq: node.seq,
@@ -7003,7 +7027,8 @@ window.__ModuleLoader__.load({
7003
7027
  kind: "system",
7004
7028
  text: promptChangeLabel(change, t),
7005
7029
  sourceSeq: change.seq,
7006
- ...request.prompt === void 0 ? {} : { promptDetail: request.prompt },
7030
+ ...request?.prompt === void 0 ? {} : { promptDetail: request.prompt },
7031
+ ...entry.systemPrompt === void 0 ? {} : { systemPromptDetail: entry.systemPrompt },
7007
7032
  ...change.previous === void 0 ? {} : { previousPromptDetail: change.previous },
7008
7033
  timeSeconds: 0,
7009
7034
  startedAt: finiteTime(change.time)
@@ -7431,6 +7456,9 @@ window.__ModuleLoader__.load({
7431
7456
  function imageBlockCount(content) {
7432
7457
  return content.filter((block) => block.type === "image").length;
7433
7458
  }
7459
+ function fileBlockCount(content) {
7460
+ return content.filter((block) => block.type === "file").length;
7461
+ }
7434
7462
  function stringifySourceValue(value) {
7435
7463
  return JSON.stringify(value, null, 2) || String(value);
7436
7464
  }
@@ -7938,6 +7966,7 @@ window.__ModuleLoader__.load({
7938
7966
  },
7939
7967
  runningCalls,
7940
7968
  requests,
7969
+ systemPrompts: inspection.systemPrompts,
7941
7970
  callSchemas
7942
7971
  }, t);
7943
7972
  return {
@@ -7951,6 +7980,7 @@ window.__ModuleLoader__.load({
7951
7980
  partialStep,
7952
7981
  runningCalls,
7953
7982
  requests,
7983
+ inspection.systemPrompts,
7954
7984
  callSchemas,
7955
7985
  t
7956
7986
  ]);
@@ -19,6 +19,7 @@ export interface TrajectoryTurnModel {
19
19
  }
20
20
  /** Snapshot slice the trajectory view folds. */
21
21
  export interface TrajectoryLayoutInput {
22
+ systemPrompts?: TrajectorySnapshot['systemPrompts'];
22
23
  nodes: TrajectorySnapshot['eventNodes'];
23
24
  eventLocations?: ReadonlyMap<number, ConversationLocation>;
24
25
  partial: TrajectorySnapshot['partial'];
@@ -172,6 +172,7 @@ export declare const zh: {
172
172
  readonly 'layout.compacted': "上下文已压缩";
173
173
  readonly 'layout.toolCallOnly': "仅工具调用";
174
174
  readonly 'layout.imageOnly': "图片 ×{count}";
175
+ readonly 'layout.fileAttachments': "文件 ×{count}";
175
176
  readonly 'layout.initialSystemPrompt': "初始系统提示词";
176
177
  readonly 'layout.systemPromptUpdated': "系统提示词已更新";
177
178
  readonly 'layout.toolsUpdated': "工具已更新";
@@ -1,4 +1,4 @@
1
- import type { AssistantMessageNode, ConversationLocation, ConversationNode, ConversationPromptSnapshot, ConversationViewNode, MessageImagesOwnerProps, PartialAssistant, RequestPromptChange, RequestView, RunningToolCall, ToolCallBlock } from '@deepseek-ai/dsh-client-ui-conversation/client';
1
+ import type { AssistantMessageNode, ConversationLocation, ConversationNode, ConversationPromptSnapshot, ConversationViewNode, MessageImagesOwnerProps, PartialAssistant, RequestPromptChange, RequestView, RunningToolCall, SystemPromptNode, ToolCallBlock } from '@deepseek-ai/dsh-client-ui-conversation/client';
2
2
  import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
3
3
  /** Request-header facts retained by the Trajectory target. */
4
4
  export interface TrajectoryRequestHeaderState {
@@ -10,6 +10,9 @@ export interface TrajectoryRequestHeaderState {
10
10
  }
11
11
  /** One independently assembled contribution to the legacy Trajectory ledger. */
12
12
  export type TrajectoryContribution = {
13
+ readonly kind: 'system-prompt';
14
+ readonly prompt: SystemPromptNode;
15
+ } | {
13
16
  readonly kind: 'node';
14
17
  readonly node: ConversationNode;
15
18
  } | {
@@ -50,6 +53,8 @@ export interface TrajectoryConversationViewNode extends ConversationViewNode {
50
53
  }
51
54
  /** Stage-oriented Trajectory data assembled from registered business Contexts. */
52
55
  export interface TrajectorySnapshot {
56
+ /** Complete loaded prompt text whose request header is outside the window. */
57
+ readonly systemPrompts?: readonly SystemPromptNode[];
53
58
  readonly eventNodes: readonly ConversationNode[];
54
59
  readonly eventLocations: ReadonlyMap<number, ConversationLocation>;
55
60
  readonly requests: readonly RequestView[];
@@ -46,6 +46,8 @@ export interface TrajectoryCellProps extends HTMLAttributes<HTMLDivElement> {
46
46
  inputDetail?: string;
47
47
  /** Complete system-prompt/tool-catalog state introduced by a SYSTEM record. */
48
48
  promptDetail?: ConversationPromptSnapshot;
49
+ /** Known prompt text without a loaded request config or tool catalog. */
50
+ systemPromptDetail?: string;
49
51
  /** System-prompt/tool-catalog state replaced by a SYSTEM update. */
50
52
  previousPromptDetail?: ConversationPromptSnapshot;
51
53
  /** Full assistant/tool result content for the details panel. */
@@ -1,8 +1,18 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
+ import type { SystemPromptState } from '@deepseek-ai/dsh-client-ui-conversation/client';
3
+ import type { TrajectoryRequestHeaderState } from './trajectory-contract.ts';
4
+ /** Loaded system surface plus the latest request facts changed by an append or compaction. */
5
+ export interface TrajectorySystemMessageState extends SystemPromptState {
6
+ /**
7
+ * Latest synthetic header, retained across unrelated replacements. Only the
8
+ * Context whose start seq equals this header's seq contributes a view Node.
9
+ */
10
+ readonly header?: TrajectoryRequestHeaderState;
11
+ }
2
12
  /**
3
- * Register Trajectory request-header facts.
13
+ * Register Trajectory system-prompt node and request-header facts.
4
14
  *
5
- * @param ctx - Plugin context receiving the Definition.
15
+ * @param ctx - Plugin context receiving the Definitions.
6
16
  */
7
17
  export declare function registerTrajectoryRequestHeaderDefinition(ctx: Context): void;
8
18
  //# sourceMappingURL=trajectory-request-header-definition.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-trajectory",
3
3
  "description": "Trajectory event ledger with an interactive timing overview: pure-consumer plugin registering into the conversation ViewMap (no service)",
4
- "version": "0.1.2-rc.1",
4
+ "version": "0.1.5-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -50,23 +50,23 @@
50
50
  "@types/react-dom": "~18.3.0",
51
51
  "react": "^18.2.0",
52
52
  "react-dom": "^18.2.0",
53
- "@deepseek-ai/dsh-client-locale": "^0.1.2-rc.1",
54
- "@deepseek-ai/dsh-agent": "^0.1.2-rc.1",
55
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.2-rc.1",
56
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1",
57
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
58
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1",
59
- "@deepseek-ai/dsh-tools": "^0.1.2-rc.1",
53
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.1",
54
+ "@deepseek-ai/dsh-agent": "^0.1.5-alpha.1",
55
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.1",
56
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-alpha.1",
57
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.1",
58
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-alpha.1",
59
+ "@deepseek-ai/dsh-compaction": "^0.1.5-alpha.1",
60
+ "@deepseek-ai/dsh-tools": "^0.1.5-alpha.1",
61
+ "@deepseek-ai/dsh-client-ui-chat": "^0.1.5-alpha.1",
60
62
  "@deepseek-ai/cordis": "^4.0.2",
61
- "@deepseek-ai/dsh-client-store": "^0.1.2-rc.1",
62
- "@deepseek-ai/dsh-client-ui-chat": "^0.1.2-rc.1",
63
- "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
64
- "@deepseek-ai/dsh-api-session-controller": "^0.1.2-rc.1",
65
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-rc.1",
66
- "@deepseek-ai/dsh-client-ui-session": "^0.1.2-rc.1",
67
- "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
68
- "@deepseek-ai/dsh-attachment": "^0.1.2-rc.1",
69
- "@deepseek-ai/dsh-compaction": "^0.1.2-rc.1"
63
+ "@deepseek-ai/dsh-client-store": "^0.1.5-alpha.1",
64
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.5-alpha.1",
65
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.1",
66
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.1",
67
+ "@deepseek-ai/dsh-llm": "^0.1.5-alpha.1",
68
+ "@deepseek-ai/dsh-attachment": "^0.1.5-alpha.1",
69
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.1"
70
70
  },
71
71
  "files": [
72
72
  "lib/index.js",