@meiyukichan/dsh-sandore 0.1.0-rc.6

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 (295) hide show
  1. package/LICENSE +201 -0
  2. package/cordis.patch.yml +39 -0
  3. package/dsh.plugin.json +16 -0
  4. package/lib/client/CommandEditor.d.ts +19 -0
  5. package/lib/client/DagEditor.d.ts +22 -0
  6. package/lib/client/DefinitionDetail.d.ts +13 -0
  7. package/lib/client/DefinitionList.d.ts +19 -0
  8. package/lib/client/DiscussionInput.d.ts +63 -0
  9. package/lib/client/DragHandle.d.ts +16 -0
  10. package/lib/client/EditorPane.d.ts +13 -0
  11. package/lib/client/FlowEditor.d.ts +31 -0
  12. package/lib/client/InstanceList.d.ts +24 -0
  13. package/lib/client/NewInstanceDialog.d.ts +19 -0
  14. package/lib/client/PreviewPanel.d.ts +13 -0
  15. package/lib/client/RunView.d.ts +79 -0
  16. package/lib/client/StepChatPanel.d.ts +34 -0
  17. package/lib/client/TreeNav.d.ts +27 -0
  18. package/lib/client/UnifiedTimeline.d.ts +11 -0
  19. package/lib/client/WorkflowButton.d.ts +11 -0
  20. package/lib/client/WorkflowList.d.ts +18 -0
  21. package/lib/client/WorkflowOverlay.d.ts +6 -0
  22. package/lib/client/chat/AssistantMarkdown.d.ts +22 -0
  23. package/lib/client/chat/CompactionItem.d.ts +24 -0
  24. package/lib/client/chat/ContextBody.d.ts +136 -0
  25. package/lib/client/chat/ContextInjectionRow.d.ts +25 -0
  26. package/lib/client/chat/GenericCommandCard.d.ts +10 -0
  27. package/lib/client/chat/MessageIconActions.d.ts +41 -0
  28. package/lib/client/chat/MessageItem.d.ts +65 -0
  29. package/lib/client/chat/ReasoningRow.d.ts +13 -0
  30. package/lib/client/chat/TurnTailNodeView.d.ts +29 -0
  31. package/lib/client/chat/UserMessageRow.d.ts +18 -0
  32. package/lib/client/chat/attachment/ImageLightbox.d.ts +26 -0
  33. package/lib/client/chat/attachment/MessageImage.d.ts +56 -0
  34. package/lib/client/chat/attachment/image-attachment.d.ts +41 -0
  35. package/lib/client/chat/attachment/image-labels.d.ts +21 -0
  36. package/lib/client/chat/message-chrome.d.ts +51 -0
  37. package/lib/client/chat/reference/ReferenceIcon.d.ts +20 -0
  38. package/lib/client/chat/turn-assistant.d.ts +12 -0
  39. package/lib/client/chat/turn-metrics.d.ts +42 -0
  40. package/lib/client/chat/use-calendar-day.d.ts +5 -0
  41. package/lib/client/chat/use-throttled-visual-update.d.ts +7 -0
  42. package/lib/client/chat-flow.d.ts +110 -0
  43. package/lib/client/composer-selects.d.ts +17 -0
  44. package/lib/client/dag-editor-model.d.ts +141 -0
  45. package/lib/client/describe-error.d.ts +9 -0
  46. package/lib/client/index.d.ts +6 -0
  47. package/lib/client/locales.d.ts +777 -0
  48. package/lib/client/model-faces.d.ts +156 -0
  49. package/lib/client/open-discussion.d.ts +24 -0
  50. package/lib/client/panel-model.d.ts +34 -0
  51. package/lib/client/remote.d.ts +34 -0
  52. package/lib/client/run-view-model.d.ts +304 -0
  53. package/lib/client/sandore-save.d.ts +41 -0
  54. package/lib/client/sandore-sse.d.ts +15 -0
  55. package/lib/client/step-chat-retry.d.ts +44 -0
  56. package/lib/client/tool/GenericToolCard.d.ts +28 -0
  57. package/lib/client/tool/ToolRow.d.ts +95 -0
  58. package/lib/client/tool/diff-card-model.d.ts +63 -0
  59. package/lib/client/tool/read-card-model.d.ts +67 -0
  60. package/lib/client/tool/search-card-model.d.ts +94 -0
  61. package/lib/client/tool/terminal-card-model.d.ts +86 -0
  62. package/lib/client/tool/tool-call-model.d.ts +74 -0
  63. package/lib/client/tool/web-card-model.d.ts +44 -0
  64. package/lib/client/use-focus-refresh.d.ts +1 -0
  65. package/lib/client/workflow-face.d.ts +68 -0
  66. package/lib/client/workflow-store.d.ts +80 -0
  67. package/lib/client/workspace-path.d.ts +15 -0
  68. package/lib/client.js +38389 -0
  69. package/lib/index.js +8854 -0
  70. package/lib/microflow/branch-evaluator.d.ts +290 -0
  71. package/lib/microflow/concurrency-limiter.d.ts +39 -0
  72. package/lib/microflow/errors.d.ts +39 -0
  73. package/lib/microflow/file-utils.d.ts +210 -0
  74. package/lib/microflow/finalize-manager.d.ts +414 -0
  75. package/lib/microflow/fork-session.d.ts +151 -0
  76. package/lib/microflow/index.d.ts +17 -0
  77. package/lib/microflow/internal.d.ts +56 -0
  78. package/lib/microflow/loop-manager.d.ts +127 -0
  79. package/lib/microflow/rules-resolver.d.ts +28 -0
  80. package/lib/microflow/stage-executor.d.ts +362 -0
  81. package/lib/microflow/step-navigator.d.ts +108 -0
  82. package/lib/microflow/step-runner.d.ts +190 -0
  83. package/lib/microflow/subagents.d.ts +380 -0
  84. package/lib/tool/index.d.ts +65 -0
  85. package/lib/typert.host.d.ts +16 -0
  86. package/lib/typert.host.js +25 -0
  87. package/lib/typert.remote-client.d.ts +114 -0
  88. package/lib/typert.remote-client.js +399 -0
  89. package/lib/workflow/ask-tool.d.ts +78 -0
  90. package/lib/workflow/dag-navigator.d.ts +136 -0
  91. package/lib/workflow/domain.d.ts +304 -0
  92. package/lib/workflow/engine-surface.d.ts +70 -0
  93. package/lib/workflow/errors.d.ts +13 -0
  94. package/lib/workflow/host-agent.d.ts +53 -0
  95. package/lib/workflow/index.d.ts +23 -0
  96. package/lib/workflow/interaction-manager.d.ts +170 -0
  97. package/lib/workflow/lifecycle-manager.d.ts +111 -0
  98. package/lib/workflow/persistence.d.ts +35 -0
  99. package/lib/workflow/plugin.d.ts +27 -0
  100. package/lib/workflow/reactive-state.d.ts +27 -0
  101. package/lib/workflow/recovery-manager.d.ts +158 -0
  102. package/lib/workflow/remote.d.ts +139 -0
  103. package/lib/workflow/schema.d.ts +1917 -0
  104. package/lib/workflow/seam.d.ts +254 -0
  105. package/lib/workflow/shell.d.ts +127 -0
  106. package/lib/workflow/sse.d.ts +16 -0
  107. package/lib/workflow/typert/typert.host.d.ts +16 -0
  108. package/lib/workflow/typert/typert.remote-client.d.ts +114 -0
  109. package/lib/workflow/types.d.ts +187 -0
  110. package/lib/workflow/wire.d.ts +256 -0
  111. package/lib/workflow/workflow-engine.d.ts +373 -0
  112. package/lib/workflow/yaml-error.d.ts +22 -0
  113. package/lib/workflow/yaml-parser.d.ts +23 -0
  114. package/package.json +140 -0
  115. package/packages/client/CommandEditor.tsx +73 -0
  116. package/packages/client/DagEditor.module.css +711 -0
  117. package/packages/client/DagEditor.tsx +1203 -0
  118. package/packages/client/DefinitionDetail.module.css +110 -0
  119. package/packages/client/DefinitionDetail.tsx +126 -0
  120. package/packages/client/DefinitionList.tsx +173 -0
  121. package/packages/client/DiscussionInput.tsx +763 -0
  122. package/packages/client/DragHandle.tsx +92 -0
  123. package/packages/client/EditorPane.tsx +70 -0
  124. package/packages/client/FlowEditor.tsx +227 -0
  125. package/packages/client/InstanceList.tsx +250 -0
  126. package/packages/client/NewInstanceDialog.module.css +244 -0
  127. package/packages/client/NewInstanceDialog.tsx +478 -0
  128. package/packages/client/PreviewPanel.tsx +55 -0
  129. package/packages/client/RunView.module.css +246 -0
  130. package/packages/client/RunView.tsx +314 -0
  131. package/packages/client/StepChatPanel.tsx +486 -0
  132. package/packages/client/TreeNav.tsx +294 -0
  133. package/packages/client/UnifiedTimeline.tsx +484 -0
  134. package/packages/client/WorkflowButton.module.css +72 -0
  135. package/packages/client/WorkflowButton.tsx +50 -0
  136. package/packages/client/WorkflowList.tsx +158 -0
  137. package/packages/client/WorkflowOverlay.module.css +390 -0
  138. package/packages/client/WorkflowOverlay.tsx +136 -0
  139. package/packages/client/WorkflowPanel.module.css +247 -0
  140. package/packages/client/chat/AssistantMarkdown.module.css +38 -0
  141. package/packages/client/chat/AssistantMarkdown.tsx +120 -0
  142. package/packages/client/chat/ChatView.module.css +198 -0
  143. package/packages/client/chat/CompactionItem.tsx +85 -0
  144. package/packages/client/chat/ContextBody.module.css +162 -0
  145. package/packages/client/chat/ContextBody.tsx +598 -0
  146. package/packages/client/chat/ContextInjectionRow.module.css +63 -0
  147. package/packages/client/chat/ContextInjectionRow.tsx +78 -0
  148. package/packages/client/chat/GenericCommandCard.module.css +87 -0
  149. package/packages/client/chat/GenericCommandCard.tsx +76 -0
  150. package/packages/client/chat/MessageIconActions.module.css +84 -0
  151. package/packages/client/chat/MessageIconActions.tsx +147 -0
  152. package/packages/client/chat/MessageItem.module.css +293 -0
  153. package/packages/client/chat/MessageItem.tsx +384 -0
  154. package/packages/client/chat/ReasoningRow.module.css +82 -0
  155. package/packages/client/chat/ReasoningRow.tsx +70 -0
  156. package/packages/client/chat/TurnTailNodeView.module.css +10 -0
  157. package/packages/client/chat/TurnTailNodeView.tsx +63 -0
  158. package/packages/client/chat/UserMessageRow.module.css +75 -0
  159. package/packages/client/chat/UserMessageRow.tsx +92 -0
  160. package/packages/client/chat/accessibility.module.css +9 -0
  161. package/packages/client/chat/attachment/ImageLightbox.module.css +45 -0
  162. package/packages/client/chat/attachment/ImageLightbox.tsx +70 -0
  163. package/packages/client/chat/attachment/MessageImage.module.css +70 -0
  164. package/packages/client/chat/attachment/MessageImage.tsx +136 -0
  165. package/packages/client/chat/attachment/image-attachment.ts +98 -0
  166. package/packages/client/chat/attachment/image-labels.ts +34 -0
  167. package/packages/client/chat/message-chrome.ts +96 -0
  168. package/packages/client/chat/reference/ReferenceIcon.tsx +40 -0
  169. package/packages/client/chat/turn-assistant.ts +15 -0
  170. package/packages/client/chat/turn-metrics.ts +102 -0
  171. package/packages/client/chat/use-calendar-day.ts +27 -0
  172. package/packages/client/chat/use-throttled-visual-update.ts +44 -0
  173. package/packages/client/chat-flow.spec.ts +357 -0
  174. package/packages/client/chat-flow.ts +278 -0
  175. package/packages/client/composer-selects.module.css +376 -0
  176. package/packages/client/composer-selects.tsx +531 -0
  177. package/packages/client/dag-editor-model.spec.ts +173 -0
  178. package/packages/client/dag-editor-model.ts +359 -0
  179. package/packages/client/describe-error.spec.ts +18 -0
  180. package/packages/client/describe-error.ts +12 -0
  181. package/packages/client/index.ts +144 -0
  182. package/packages/client/input.module.css +854 -0
  183. package/packages/client/is-conditional-check.spec.ts +34 -0
  184. package/packages/client/locales.ts +789 -0
  185. package/packages/client/model-faces.ts +225 -0
  186. package/packages/client/open-discussion.spec.ts +39 -0
  187. package/packages/client/open-discussion.ts +32 -0
  188. package/packages/client/panel-model.spec.ts +152 -0
  189. package/packages/client/panel-model.ts +72 -0
  190. package/packages/client/preview.module.css +95 -0
  191. package/packages/client/react-dom.d.ts +11 -0
  192. package/packages/client/reactflow-theme.css +91 -0
  193. package/packages/client/remote.spec.ts +41 -0
  194. package/packages/client/remote.ts +46 -0
  195. package/packages/client/run-view-model.spec.ts +641 -0
  196. package/packages/client/run-view-model.ts +636 -0
  197. package/packages/client/sandore-save.spec.ts +146 -0
  198. package/packages/client/sandore-save.ts +97 -0
  199. package/packages/client/sandore-sse.ts +59 -0
  200. package/packages/client/step-chat-retry.spec.ts +70 -0
  201. package/packages/client/step-chat-retry.ts +65 -0
  202. package/packages/client/timeline.module.css +503 -0
  203. package/packages/client/tool/GenericToolCard.tsx +99 -0
  204. package/packages/client/tool/ToolRow.module.css +308 -0
  205. package/packages/client/tool/ToolRow.tsx +328 -0
  206. package/packages/client/tool/diff-card-model.ts +103 -0
  207. package/packages/client/tool/read-card-model.ts +86 -0
  208. package/packages/client/tool/search-card-model.ts +166 -0
  209. package/packages/client/tool/terminal-card-model.ts +225 -0
  210. package/packages/client/tool/tool-call-model.ts +267 -0
  211. package/packages/client/tool/web-card-model.ts +80 -0
  212. package/packages/client/tree-nav.module.css +282 -0
  213. package/packages/client/use-focus-refresh.ts +23 -0
  214. package/packages/client/workflow-face.ts +72 -0
  215. package/packages/client/workflow-store.spec.ts +49 -0
  216. package/packages/client/workflow-store.ts +161 -0
  217. package/packages/client/workspace-path.ts +27 -0
  218. package/packages/css-modules.d.ts +6 -0
  219. package/packages/microflow/branch-evaluator.spec.ts +745 -0
  220. package/packages/microflow/branch-evaluator.ts +1037 -0
  221. package/packages/microflow/concurrency-limiter.ts +70 -0
  222. package/packages/microflow/errors.ts +64 -0
  223. package/packages/microflow/file-utils.spec.ts +604 -0
  224. package/packages/microflow/file-utils.ts +521 -0
  225. package/packages/microflow/finalize-manager.spec.ts +1977 -0
  226. package/packages/microflow/finalize-manager.ts +1464 -0
  227. package/packages/microflow/fork-session.spec.ts +184 -0
  228. package/packages/microflow/fork-session.ts +316 -0
  229. package/packages/microflow/index.ts +17 -0
  230. package/packages/microflow/internal.ts +166 -0
  231. package/packages/microflow/loop-manager.spec.ts +294 -0
  232. package/packages/microflow/loop-manager.ts +255 -0
  233. package/packages/microflow/rules-resolver.spec.ts +147 -0
  234. package/packages/microflow/rules-resolver.ts +58 -0
  235. package/packages/microflow/stage-executor.spec.ts +1577 -0
  236. package/packages/microflow/stage-executor.ts +1478 -0
  237. package/packages/microflow/step-navigator.spec.ts +301 -0
  238. package/packages/microflow/step-navigator.ts +300 -0
  239. package/packages/microflow/step-runner.spec.ts +1132 -0
  240. package/packages/microflow/step-runner.ts +752 -0
  241. package/packages/microflow/subagents.spec.ts +215 -0
  242. package/packages/microflow/subagents.ts +422 -0
  243. package/packages/tests/fixtures/test-workflow.yaml +128 -0
  244. package/packages/tests/mocks/agents.ts +206 -0
  245. package/packages/tests/mocks/llm.ts +46 -0
  246. package/packages/tests/mocks/session-title.ts +31 -0
  247. package/packages/tests/mocks/sessions.ts +131 -0
  248. package/packages/tests/mocks/shell.ts +60 -0
  249. package/packages/tests/mocks/storage-domain.ts +101 -0
  250. package/packages/tests/mocks/subagents.ts +197 -0
  251. package/packages/tests/mocks/subprocess.ts +59 -0
  252. package/packages/tests/mocks/user-questions.ts +33 -0
  253. package/packages/tests/mocks/workspace.ts +52 -0
  254. package/packages/tool/index.spec.ts +287 -0
  255. package/packages/tool/index.ts +348 -0
  256. package/packages/workflow/ask-tool.ts +173 -0
  257. package/packages/workflow/dag-navigator.spec.ts +236 -0
  258. package/packages/workflow/dag-navigator.ts +425 -0
  259. package/packages/workflow/domain.ts +26 -0
  260. package/packages/workflow/engine-surface.ts +71 -0
  261. package/packages/workflow/errors.ts +13 -0
  262. package/packages/workflow/host-agent.spec.ts +92 -0
  263. package/packages/workflow/host-agent.ts +62 -0
  264. package/packages/workflow/index.ts +25 -0
  265. package/packages/workflow/interaction-manager.spec.ts +390 -0
  266. package/packages/workflow/interaction-manager.ts +397 -0
  267. package/packages/workflow/lifecycle-manager.spec.ts +683 -0
  268. package/packages/workflow/lifecycle-manager.ts +288 -0
  269. package/packages/workflow/persistence.spec.ts +109 -0
  270. package/packages/workflow/persistence.ts +64 -0
  271. package/packages/workflow/plugin.ts +39 -0
  272. package/packages/workflow/reactive-state.spec.ts +69 -0
  273. package/packages/workflow/reactive-state.ts +52 -0
  274. package/packages/workflow/recovery-manager.spec.ts +606 -0
  275. package/packages/workflow/recovery-manager.ts +471 -0
  276. package/packages/workflow/remote.spec.ts +1045 -0
  277. package/packages/workflow/remote.ts +479 -0
  278. package/packages/workflow/schema.spec.ts +303 -0
  279. package/packages/workflow/schema.ts +562 -0
  280. package/packages/workflow/seam.ts +293 -0
  281. package/packages/workflow/shell.ts +142 -0
  282. package/packages/workflow/sse.ts +110 -0
  283. package/packages/workflow/typert/typert.host.d.ts +16 -0
  284. package/packages/workflow/typert/typert.host.js +25 -0
  285. package/packages/workflow/typert/typert.remote-client.d.ts +114 -0
  286. package/packages/workflow/typert/typert.remote-client.js +399 -0
  287. package/packages/workflow/types.ts +258 -0
  288. package/packages/workflow/wire.ts +267 -0
  289. package/packages/workflow/workflow-engine-e2e.spec.ts +262 -0
  290. package/packages/workflow/workflow-engine-real-e2e.spec.ts +922 -0
  291. package/packages/workflow/workflow-engine.spec.ts +4389 -0
  292. package/packages/workflow/workflow-engine.ts +2133 -0
  293. package/packages/workflow/yaml-error.ts +29 -0
  294. package/packages/workflow/yaml-parser.spec.ts +757 -0
  295. package/packages/workflow/yaml-parser.ts +287 -0
@@ -0,0 +1,636 @@
1
+ /**
2
+ * RunView 的可测纯逻辑(无 React 依赖)。
3
+ *
4
+ * 从 `dsh-sandore/schema`(浏览器安全子路径)导入
5
+ * `WorkflowTaskState` 等 wire 类型,并以其派生 `DagRunStatus`/`StepStatus`/
6
+ * `ExecutedStep`/`StageInfo`(避免从包根 `types.ts` 导入带出 `dsh-llm` 依赖)。
7
+ * 此处提供:7 态 → StateDotState 穷尽映射、运行中/终态判定(驱动 5s 轮询)、
8
+ * pending 交互分类、retry message 校验与 `RetryMode` 词汇。
9
+ * @module @deepseek-ai/dsh-client-ui-sandore/src/client/run-view-model
10
+ */
11
+ import type { StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
12
+ import type {
13
+ PendingPermissionEntry, PendingQuestionEntry, WorkflowTaskState,
14
+ } from 'dsh-sandore/schema'
15
+ import type { RevertBoundaryOption } from 'dsh-sandore/types'
16
+
17
+ /** 实例生命周期状态(从 `WorkflowTaskState` 派生,避免 dsh-llm 依赖)。 */
18
+ export type DagRunStatus = WorkflowTaskState['status']
19
+
20
+ /** 单个 Step/Stage 执行状态(从 `WorkflowTaskState` 派生)。 */
21
+ export type StepStatus = WorkflowTaskState['executedSteps'][number]['status']
22
+
23
+ /** 已执行 Step 的快照(从 `WorkflowTaskState` 派生)。 */
24
+ export type ExecutedStep = WorkflowTaskState['executedSteps'][number]
25
+
26
+ /** Stage 状态快照(从 `WorkflowTaskState` 派生)。 */
27
+ export type StageInfo = WorkflowTaskState['stageInfos'][number]
28
+
29
+ /** 重试模式(对齐 `retryStep` 入参 `mode` 的 zod 枚举)。 */
30
+ export type RetryMode = 'continue' | 'revert' | 'fork'
31
+
32
+ /** revert 回退点候选(对齐 `listRevertBoundaries` 返回的 `RevertBoundaryOption`)。 */
33
+ export type { RevertBoundaryOption }
34
+
35
+ /** replyQuestion 单个问题的回答(对齐 Host 的 `AskUserQuestionAnswerItem`)。 */
36
+ export interface AnswerItem {
37
+ readonly id: string
38
+ readonly selected: readonly string[]
39
+ readonly custom?: string
40
+ }
41
+
42
+ /** 三种重试模式(供重试表单下拉/选项渲染)。 */
43
+ export const RETRY_MODES: readonly RetryMode[] = ['continue', 'revert', 'fork']
44
+
45
+ /**
46
+ * StepStatus 7 态 → StateDotState 4 态穷尽映射:
47
+ * `completed→done`、`failed→error`、`running→ongoing`,其余(等待用户注意的
48
+ * blocked/ready/interacting/paused)折入 `warning`。
49
+ * @param status - Step 执行状态。
50
+ * @returns 四态语义点。
51
+ */
52
+ export function stepDotState(status: StepStatus): StateDotState {
53
+ switch (status) {
54
+ case 'completed':
55
+ return 'done'
56
+ case 'failed':
57
+ return 'error'
58
+ case 'running':
59
+ return 'ongoing'
60
+ case 'blocked':
61
+ case 'ready':
62
+ case 'interacting':
63
+ case 'paused':
64
+ return 'warning'
65
+ }
66
+ }
67
+
68
+ /**
69
+ * DagRunStatus 7 态 → StateDotState 4 态穷尽映射:
70
+ * `completed→done`、`failed→error`、`running→ongoing`,其余(pending/interacting/
71
+ * aborted/paused)折入 `warning`。
72
+ * @param status - 实例生命周期状态。
73
+ * @returns 四态语义点。
74
+ */
75
+ export function runDotState(status: DagRunStatus): StateDotState {
76
+ switch (status) {
77
+ case 'completed':
78
+ return 'done'
79
+ case 'failed':
80
+ return 'error'
81
+ case 'running':
82
+ return 'ongoing'
83
+ case 'pending':
84
+ case 'interacting':
85
+ case 'aborted':
86
+ case 'paused':
87
+ return 'warning'
88
+ }
89
+ }
90
+
91
+ /**
92
+ * 实例是否处于「运行中」状态(`running`/`interacting`),决定是否启动 5s 轮询。
93
+ * @param status - 实例生命周期状态。
94
+ * @returns 运行中(需轮询)为 true。
95
+ */
96
+ export function isRunningStatus(status: DagRunStatus): boolean {
97
+ return status === 'running' || status === 'interacting'
98
+ }
99
+
100
+ /**
101
+ * 实例是否已进入终态(`completed`/`failed`/`aborted`),终态停止轮询。
102
+ * @param status - 实例生命周期状态。
103
+ * @returns 终态为 true。
104
+ */
105
+ export function isTerminalStatus(status: DagRunStatus): boolean {
106
+ return status === 'completed' || status === 'failed' || status === 'aborted'
107
+ }
108
+
109
+ /** pending 交互分类结果。 */
110
+ export interface PendingInteractionPartition {
111
+ /** 待答问题条目。 */
112
+ readonly questions: readonly PendingQuestionEntry[]
113
+ /** 待批权限条目。 */
114
+ readonly permissions: readonly PendingPermissionEntry[]
115
+ }
116
+
117
+ /**
118
+ * 按 `kind` 把 `pendingInteractions` 分为问答与权限两组。
119
+ *
120
+ * 探索修正(2026-08):仅取 `status === 'pending'` 的条目——后端已只持久化 pending,
121
+ * 此处过滤是防御旧快照(含 replied/rejected 全量历史时避免讨论页复弹接管面板)。
122
+ * @param interactions - 实例的 pending 交互列表。
123
+ * @returns 问答与权限两个分组。
124
+ */
125
+ export function partitionInteractions(
126
+ interactions: readonly (PendingQuestionEntry | PendingPermissionEntry)[],
127
+ ): PendingInteractionPartition {
128
+ const questions: PendingQuestionEntry[] = []
129
+ const permissions: PendingPermissionEntry[] = []
130
+ for (const interaction of interactions) {
131
+ if (interaction.status !== 'pending') continue
132
+ if (interaction.kind === 'question') {
133
+ questions.push(interaction)
134
+ } else {
135
+ permissions.push(interaction)
136
+ }
137
+ }
138
+ return { questions, permissions }
139
+ }
140
+
141
+ /**
142
+ * 判断字符串是否为空或仅含空白(用于 retry message 等必填文本校验)。
143
+ * @param value - 待校验文本。
144
+ * @returns 空白为 true。
145
+ */
146
+ export function isBlank(value: string): boolean {
147
+ return value.trim() === ''
148
+ }
149
+
150
+ /**
151
+ * Step 对外交互标识:定义 `stepId` 优先,缺省回退唯一执行 `id`。
152
+ * 与引擎侧 `findStep`(先 `stepId` 后 `id` 反查)保持一致,供
153
+ * `pauseStep`/`resumeStep`/`retryStep` 入参使用。
154
+ * @param step - 含 `id` 与可选 `stepId` 的 Step 快照。
155
+ * @returns 可反查该 Step 的稳定标识。
156
+ */
157
+ export function stepIdOf(step: { readonly id: string; readonly stepId?: string | undefined }): string {
158
+ return step.stepId ?? step.id
159
+ }
160
+
161
+ /**
162
+ * 判定 Step 是否为可续接的子代理 Step(agent/skill/mcp/prompt),非 shell 合成 session。
163
+ *
164
+ * 对齐引擎侧 `recovery-manager.ts` 的 `isSubagentStep`:优先 `sessionMeta.command`
165
+ * (运行时 session 元数据),缺省回退 `command.type`(定义态命令类型)。shell
166
+ * 合成 session 无真实子代理,引擎对暂停/重试明确抛 `INVALID_OPERATION`,故 UI 不应
167
+ * 对其渲染 pause/resume/retry 控件;prompt 为真实子代理会话(决策 D3),与
168
+ * agent/skill/mcp 同语义。
169
+ * @param step - 已执行 Step 快照。
170
+ * @returns 子代理 Step(可暂停/恢复/重试)为 true。
171
+ */
172
+ export function isSubagentStep(step: ExecutedStep): boolean {
173
+ const command = step.sessionMeta?.command ?? step.command?.type
174
+ return command === 'agent' || command === 'skill' || command === 'mcp' || command === 'prompt'
175
+ }
176
+
177
+ // ── 树形导航 / 统一时间线派生(纯函数,无 React 依赖) ─────────────────────
178
+
179
+ /** 树形导航节点类型。 */
180
+ export type RunTreeNodeType = 'workflow' | 'stage' | 'step' | 'result' | 'artifact'
181
+
182
+ /** 树形导航节点。 */
183
+ export interface RunTreeNode {
184
+ readonly id: string
185
+ readonly name: string
186
+ readonly type: RunTreeNodeType
187
+ /** 节点状态(workflow 为实例生命周期态;stage/step 为 Step 态;叶子无)。 */
188
+ readonly status?: StepStatus | DagRunStatus | undefined
189
+ /** 归属 Step 的稳定标识(stepIdOf;供预览/操作反查)。 */
190
+ readonly stepId?: string | undefined
191
+ /** 子代理会话 id(sessionMeta.childId,供「跳子会话」)。 */
192
+ readonly childId?: string | undefined
193
+ /** 副信息(artifact 的 outputToId / 文件路径)。 */
194
+ readonly meta?: string | undefined
195
+ /** 预览正文(result 的输出文本 / artifact 的文件路径)。 */
196
+ readonly text?: string | undefined
197
+ readonly children?: readonly RunTreeNode[] | undefined
198
+ }
199
+
200
+ /** agent 内嵌消息框的展示行(who + 正文 + 类型)。 */
201
+ export interface ChatLine {
202
+ /** 行归属(`{stepName} · agent` / `{stepName} · tool`)。 */
203
+ readonly who: string
204
+ /** 行正文(pre-wrap)。 */
205
+ readonly text: string
206
+ /** assistant 正文行 / tool mono 行。 */
207
+ readonly kind: 'assistant' | 'tool'
208
+ }
209
+
210
+ /** 统一时间线的 Step 卡片项。 */
211
+ export interface TimelineStepItem {
212
+ readonly kind: 'step'
213
+ readonly stepId: string
214
+ readonly name: string
215
+ readonly status: StepStatus
216
+ /** 循环迭代标签(如 `#1.2`)。 */
217
+ readonly loopLabel?: string | undefined
218
+ /** 段开始时间(ISO,createdAt 优先回退 updatedAt/startedAt;旧实例可能缺)。 */
219
+ readonly time?: string | undefined
220
+ /** completed 态输出文本(text 块拼接)。 */
221
+ readonly outputText?: string | undefined
222
+ /** failed 态错误详情。 */
223
+ readonly error?: string | undefined
224
+ /** 收尾阶段(finalizeMeta.currentPhase:assess/ask/finalize/gate/completed;供状态按钮徽标)。 */
225
+ readonly finalizePhase?: string | undefined
226
+ /** 收尾前一阶段(finalizeMeta.previousPhase;供 UI 展示阶段流转痕迹)。 */
227
+ readonly finalizePreviousPhase?: string | undefined
228
+ /** 收尾错误详情(finalizeMeta.errorDetail,如门禁失败原因;failed 态附加展示)。 */
229
+ readonly finalizeErrorDetail?: string | undefined
230
+ /** Phase B 收尾评估降级原因(finalizeMeta.assessResult.errorDetail;非 undefined 表示压缩降级,completed 态回退显示 output)。 */
231
+ readonly finalizeAssessErrorDetail?: string | undefined
232
+ /** Phase B 收尾评估 fork 会话 id(finalizeMeta.assessResult.forkSessionId,供「收尾中」徽标点击打开)。 */
233
+ readonly finalizeAssessSessionId?: string | undefined
234
+ /** Phase C 门禁 fork 会话 id(finalizeMeta.gateResult.gateSessionId,供「门禁中」徽标点击打开)。 */
235
+ readonly finalizeGateSessionId?: string | undefined
236
+ /** Phase B 收尾评估结果(finalizeMeta.assessResult;completed 态展示 compressedOutput)。 */
237
+ readonly finalizeAssessResult?: { readonly assessmentRaw: string; readonly compressedOutput?: string; readonly status?: 'running' | 'done' } | undefined
238
+ /** Phase C 门禁结果(finalizeMeta.gateResult)。 */
239
+ readonly finalizeGateResult?: { readonly gatesPassed: boolean; readonly status?: 'running' | 'done' | 'failed' } | undefined
240
+ /** Phase A ask 判定结果(finalizeMeta.askResult)。 */
241
+ readonly finalizeAskResult?: { readonly hasAsk: boolean; readonly askQuestion?: unknown; readonly status?: 'running' | 'done' } | undefined
242
+ /** Phase D 路由判断结果(finalizeMeta.routingResult)。 */
243
+ readonly finalizeRoutingResult?: { readonly branchMeta?: unknown; readonly loopMeta?: unknown; readonly status?: 'running' | 'done' } | undefined
244
+ /** Phase D 路由判断 fork 会话 id(finalizeMeta.routingResult.routingSessionId)。 */
245
+ readonly finalizeRoutingSessionId?: string | undefined
246
+ // Round 11:各阶段独立 status 字段
247
+ readonly askStatus?: 'running' | 'done'
248
+ readonly assessStatus?: 'running' | 'done'
249
+ readonly gateStatus?: 'running' | 'done' | 'failed'
250
+ readonly routingStatus?: 'running' | 'done'
251
+ /** 子代理会话 id(跳「查看详情」)。 */
252
+ readonly childId?: string | undefined
253
+ /** 命令类型(step.sessionMeta.command:'shell'/'prompt'/'agent'/'skill'/'mcp';shell completed 直接显示命令输出)。 */
254
+ readonly commandType?: string | undefined
255
+ /** 是否合成会话(shell 无真实子代理,标题不可点击跳子代理页)。 */
256
+ readonly synthetic?: boolean | undefined
257
+ /** sessionMessages 展示行(内嵌消息框 / 失败·暂停最近 N 条)。 */
258
+ readonly messages?: readonly ChatLine[] | undefined
259
+ readonly visitCount: number
260
+ /** 执行段序号(同 stepId 第几次执行段;0-based,缺省 0 = 首次执行)。 */
261
+ readonly attemptIndex?: number | undefined
262
+ /** 暂停定格时间(paused 段展示暂停提示时用)。 */
263
+ readonly pausedAt?: string | undefined
264
+ }
265
+
266
+ /** 统一时间线的用户气泡项。 */
267
+ export interface TimelineUserItem {
268
+ readonly kind: 'user'
269
+ readonly content: string
270
+ /** 消息时间(ISO,createdAt)。 */
271
+ readonly time: string
272
+ /** 消息种类(input/resume/retry/answer;旧实例可能缺,回退 input 语义)。 */
273
+ readonly messageKind?: string | undefined
274
+ /** 目标:'broadcast' 或 Step 定义 id(定点);input 时为 ''。 */
275
+ readonly target?: string | undefined
276
+ }
277
+
278
+ /** 统一时间线条目(Step 卡片 / 用户气泡,按时间顺序)。 */
279
+ export type TimelineItem = TimelineStepItem | TimelineUserItem
280
+
281
+ /** 相对时间展示结果(由组件用字典文案渲染)。 */
282
+ export type RelativeTime =
283
+ | { readonly kind: 'justNow' }
284
+ | { readonly kind: 'minutes'; readonly count: number }
285
+ | { readonly kind: 'hours'; readonly count: number }
286
+ | { readonly kind: 'days'; readonly count: number }
287
+ | { readonly kind: 'date'; readonly text: string }
288
+
289
+ /**
290
+ * 计算相对时间(ISO → 刚刚 / N 分钟前 / N 小时前 / N 天前;跨 7 天回退短日期)。
291
+ * @param iso - 目标时间(ISO 串),undefined 返回 undefined。
292
+ * @param nowIso - 基准「现在」(ISO 串)。
293
+ * @returns 相对时间结果或 undefined。
294
+ */
295
+ export function relativeTimeOf(iso: string | undefined, nowIso: string): RelativeTime | undefined {
296
+ if (iso === undefined) return undefined
297
+ const then = Date.parse(iso)
298
+ const now = Date.parse(nowIso)
299
+ if (Number.isNaN(then) || Number.isNaN(now)) return undefined
300
+ const diffMs = now - then
301
+ if (diffMs < 60_000) return { kind: 'justNow' }
302
+ const minutes = Math.floor(diffMs / 60_000)
303
+ if (minutes < 60) return { kind: 'minutes', count: minutes }
304
+ const hours = Math.floor(minutes / 60)
305
+ if (hours < 24) return { kind: 'hours', count: hours }
306
+ const days = Math.floor(hours / 24)
307
+ if (days < 7) return { kind: 'days', count: days }
308
+ const date = new Date(then)
309
+ const pad = (value: number): string => String(value).padStart(2, '0')
310
+ return {
311
+ kind: 'date',
312
+ text: `${date.getFullYear()}/${pad(date.getMonth() + 1)}/${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`,
313
+ }
314
+ }
315
+
316
+ /** 把任意载荷值缩略为单行文本(供 tool 消息行展示)。 */
317
+ function summarizeValue(value: unknown): string | undefined {
318
+ if (value === undefined || value === null) return undefined
319
+ if (typeof value === 'string') return value === '' ? undefined : value
320
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value)
321
+ try {
322
+ const text = JSON.stringify(value)
323
+ if (text === undefined) return undefined
324
+ return text.length > 160 ? `${text.slice(0, 160)}…` : text
325
+ } catch {
326
+ return String(value)
327
+ }
328
+ }
329
+
330
+ /**
331
+ * 把 step 的 sessionMessages 派生为展示行(agent 内嵌消息框 / 失败列表用)。
332
+ * tool 消息 → `┌ tool\n└ 输出摘要`(mono 行);text/reasoning → 正文行。
333
+ * @param step - 已执行 Step 快照。
334
+ * @returns 展示行列表;无消息返回 undefined。
335
+ */
336
+ export function chatLinesOf(step: ExecutedStep): ChatLine[] | undefined {
337
+ const messages = step.sessionMessages
338
+ if (messages === undefined || messages.length === 0) return undefined
339
+ const lines: ChatLine[] = []
340
+ for (const message of messages) {
341
+ if (message.type === 'tool') {
342
+ const head = `┌ ${message.tool ?? 'tool'}`
343
+ const output = summarizeValue(message.output)
344
+ lines.push({
345
+ who: `${step.name} · tool`,
346
+ text: output === undefined ? head : `${head}\n└ ${output}`,
347
+ kind: 'tool',
348
+ })
349
+ } else {
350
+ const text = message.text
351
+ if (text === undefined || text === '') continue
352
+ lines.push({ who: `${step.name} · agent`, text, kind: 'assistant' })
353
+ }
354
+ }
355
+ return lines.length === 0 ? undefined : lines
356
+ }
357
+
358
+ /**
359
+ * 把 `output: ContentBlock[]` 的 text 块拼接为纯文本(无 text 块返回 undefined)。
360
+ * @param output - Step 执行输出块(引擎在 step-completed 事件/快照中内联)。
361
+ * @returns 拼接文本或 undefined。
362
+ */
363
+ export function outputTextOf(output: readonly { readonly type: string; readonly text?: string }[] | undefined): string | undefined {
364
+ if (output === undefined || output.length === 0) return undefined
365
+ const parts: string[] = []
366
+ for (const block of output) {
367
+ if (block.type === 'text' && block.text !== undefined) parts.push(block.text)
368
+ }
369
+ return parts.length === 0 ? undefined : parts.join('\n')
370
+ }
371
+
372
+ /** 循环迭代路径 → 展示标签(`#1.2`),非循环返回 undefined。 */
373
+ export function loopLabelOf(loopIteration: readonly number[] | undefined): string | undefined {
374
+ return loopIteration === undefined || loopIteration.length === 0
375
+ ? undefined
376
+ : `#${loopIteration.join('.')}`
377
+ }
378
+
379
+ /** 取文件路径的 basename(兼容 / 与 \ 分隔;无分隔符时原样返回)。 */
380
+ export function fileBaseName(file: string): string {
381
+ const parts = file.split(/[\\/]/)
382
+ return parts[parts.length - 1] ?? file
383
+ }
384
+
385
+ /**
386
+ * 从 `WorkflowTaskState` 构建树形导航数据:workflow → stage → step → result/artifact 叶子。
387
+ * result 叶子来自 step 输出文本;artifact 叶子来自 `stageContracts` 产物契约的 files。
388
+ *
389
+ * Round 26:**完全按 `state.executedSteps` 渲染**——
390
+ * ① `executedSteps` 里**每条执行段**都展示(含 paused/failed/同 stepId 多次 visit 的
391
+ * 每一段,不做 attemptIndex 聚合);
392
+ * ② step 按自身 `stageId` 归组到 stage 下;
393
+ * ③ **只展示「有 step 执行记录」的 stage**(空 stage / 尚未执行的 stage 不展示);
394
+ * ④ stage 与 step 均按**创建顺序**(executedSteps 数组序 = 引擎追加序)排列。
395
+ * ⑤ **返回结果按执行段挂载**(每段只挂自己这段的 output 文本);**产物按 step 级
396
+ * 归并、只挂到该 stepId 的「最后一段」**(loop 多次 visit 产出同一 outputTo 文件,
397
+ * 挂一次展示最新产物状态,避免每段重复)。
398
+ * 无 `stageId` 的异常段归入合成 stage「未分组」兜底(保证所有 step 可见)。
399
+ * @param state - 实例状态快照。
400
+ * @returns 树形导航根节点列表(单个 workflow 根)。
401
+ */
402
+ export function buildRunTree(state: WorkflowTaskState): RunTreeNode[] {
403
+ // 返回结果:按「执行段 id(step.id)」挂载——每段只显示自己这段的 output。
404
+ const resultByExecId = new Map<string, RunTreeNode[]>()
405
+ for (const step of state.executedSteps) {
406
+ const output = outputTextOf(step.output)
407
+ if (output === undefined) continue
408
+ const current = resultByExecId.get(step.id) ?? []
409
+ resultByExecId.set(step.id, [...current, {
410
+ id: `${step.id}:result`,
411
+ name: '',
412
+ type: 'result',
413
+ stepId: stepIdOf(step),
414
+ text: output,
415
+ }])
416
+ }
417
+ // 产物(Round 26):优先**段快照** `executedStep.artifacts`(step 完成收尾时引擎
418
+ // collectStepArtifacts 收集写入——运行中即可展示,真正按执行段归属);
419
+ // 旧数据/历史实例无该字段时**回退 stage 契约**(stageContracts 的 artifacts 无执行段
420
+ // id,按定义 stepId 归并、只挂到该 stepId 的「最后一段」)。
421
+ const artifactByExecId = new Map<string, RunTreeNode[]>()
422
+ const artifactByStepId = new Map<string, RunTreeNode[]>()
423
+ for (const step of state.executedSteps) {
424
+ for (const file of step.artifacts ?? []) {
425
+ const current = artifactByExecId.get(step.id) ?? []
426
+ artifactByExecId.set(step.id, [...current, {
427
+ id: `${step.id}:file:${file}`,
428
+ // 导航只展示文件名(basename);完整路径保留在 text(预览用)。
429
+ name: fileBaseName(file),
430
+ type: 'artifact',
431
+ stepId: stepIdOf(step),
432
+ text: file,
433
+ }])
434
+ }
435
+ }
436
+ for (const contract of Object.values(state.stageContracts)) {
437
+ for (const artifact of contract.artifacts) {
438
+ for (const file of artifact.files) {
439
+ const current = artifactByStepId.get(artifact.stepId) ?? []
440
+ artifactByStepId.set(artifact.stepId, [...current, {
441
+ id: `${artifact.stepId}:file:${file}`,
442
+ name: fileBaseName(file),
443
+ type: 'artifact',
444
+ stepId: artifact.stepId,
445
+ meta: artifact.outputToId,
446
+ text: file,
447
+ }])
448
+ }
449
+ }
450
+ }
451
+ // 每个 stepId 的「最后一段」执行 id(数组顺序 = 创建顺序 → 最后一个为最新段)。
452
+ const lastSegmentByStepId = new Map<string, string>()
453
+ for (const step of state.executedSteps) {
454
+ lastSegmentByStepId.set(stepIdOf(step), step.id)
455
+ }
456
+
457
+ // Round 26:stage/step 全部从 executedSteps 派生(创建顺序 = 数组顺序);
458
+ // stage 元信息(name/status)从 stageInfos 查表,查不到回退 stageId。
459
+ const stageMeta = new Map(state.stageInfos.map(stage => [stage.id, stage]))
460
+ const stageOrder: string[] = []
461
+ const stepsByStage = new Map<string, RunTreeNode[]>()
462
+ for (const step of state.executedSteps) {
463
+ const stageId = step.stageId ?? UNASSIGNED_STAGE_ID
464
+ if (!stageOrder.includes(stageId)) stageOrder.push(stageId)
465
+ const key = stepIdOf(step)
466
+ const children = [
467
+ ...(resultByExecId.get(step.id) ?? []),
468
+ // 段快照产物(按段)优先;无段快照(旧数据)时回退 stage 契约产物(挂最后一段)。
469
+ ...(artifactByExecId.get(step.id) ?? []),
470
+ ...(artifactByExecId.has(step.id) ? [] : (
471
+ lastSegmentByStepId.get(key) === step.id ? (artifactByStepId.get(key) ?? []) : []
472
+ )),
473
+ ]
474
+ const node: RunTreeNode = {
475
+ id: step.id,
476
+ name: step.name,
477
+ type: 'step',
478
+ status: step.status,
479
+ stepId: key,
480
+ childId: step.sessionMeta?.childId,
481
+ children: children.length > 0 ? children : undefined,
482
+ }
483
+ const list = stepsByStage.get(stageId) ?? []
484
+ list.push(node)
485
+ stepsByStage.set(stageId, list)
486
+ }
487
+ const stageNodes = stageOrder.map(stageId => {
488
+ const meta = stageMeta.get(stageId)
489
+ return {
490
+ id: stageId,
491
+ name: meta?.name ?? stageId,
492
+ type: 'stage' as const,
493
+ status: meta?.status,
494
+ children: stepsByStage.get(stageId),
495
+ }
496
+ })
497
+
498
+ return [{
499
+ id: state.workflowId,
500
+ name: state.workflowName ?? state.workflowId,
501
+ type: 'workflow' as const,
502
+ status: state.status,
503
+ children: stageNodes,
504
+ }]
505
+ }
506
+
507
+ /** Round 26:无 stageId 的异常执行段归入的合成 stage id(名称「未分组」)。 */
508
+ export const UNASSIGNED_STAGE_ID = '_unassigned'
509
+
510
+ /**
511
+ * 段模型下按「最后活跃段」聚合 Step 列表(DiscussionInput @Step 定点恢复用)。
512
+ *
513
+ * 同 stepId 多次执行段(暂停续跑/重试/ask 续问)只保留 attemptIndex 最大的段,
514
+ * 避免 @Step 选择器重复展示同一 Step 的多个执行段。
515
+ * @param steps - 实例的 executedSteps(可能含同 stepId 多段)。
516
+ * @returns 按 stepId 去重后的 Step 快照列表(取最后活跃段;非子代理 Step 原样保留)。
517
+ */
518
+ export function lastActiveSegmentsOf(steps: readonly ExecutedStep[]): ExecutedStep[] {
519
+ const byStepId = new Map<string, ExecutedStep>()
520
+ for (const step of steps) {
521
+ const key = stepIdOf(step)
522
+ const current = byStepId.get(key)
523
+ if (current === undefined || (step.attemptIndex ?? 0) >= (current.attemptIndex ?? 0)) {
524
+ byStepId.set(key, step)
525
+ }
526
+ }
527
+ return [...byStepId.values()]
528
+ }
529
+
530
+ /**
531
+ * 从 `WorkflowTaskState` 构建统一时间线(段模型 + 用户消息统一排序)。
532
+ *
533
+ * 三路来源严格按 `createdAt` 排序合并:
534
+ * 1. `userMessages`(input/resume/retry/message,含原始需求——初始 input 消息
535
+ * 由引擎 makeInitialState 写入,前端不再单独拼 `state.originalInput` 气泡;
536
+ * Round 26:**过滤 kind:'answer'**——Phase A ask 的回答是给子代理的内部闭环
537
+ * 消息,不注入 discussion 时间轴;历史实例已落盘的 answer 记录同样隐藏)
538
+ * 2. executedSteps 各**执行段**(`createdAt` 为段开始时间;同 stepId 多次执行段
539
+ * 各自成卡,`attemptIndex` 标注第几次)
540
+ * 3. running 卡无实时消息流(引擎不写 sessionMessages、无 step-message 事件),
541
+ * body 按状态展示输出文本 / 错误 / 运行中占位。
542
+ * @param state - 实例状态快照。
543
+ * @returns 时间线条目列表(按 createdAt 升序)。
544
+ */
545
+ export function buildTimeline(state: WorkflowTaskState): TimelineItem[] {
546
+ const items: TimelineItem[] = []
547
+
548
+ for (const message of state.userMessages ?? []) {
549
+ // Round 26:回答不进讨论页(Phase A ask 回答已 followup 注回子代理)。
550
+ if (message.kind === 'answer') continue
551
+ items.push({
552
+ kind: 'user',
553
+ content: message.content,
554
+ time: message.createdAt,
555
+ messageKind: message.kind,
556
+ target: message.target,
557
+ })
558
+ }
559
+
560
+ for (const step of state.executedSteps) {
561
+ items.push({
562
+ kind: 'step',
563
+ stepId: stepIdOf(step),
564
+ name: step.name,
565
+ status: step.status,
566
+ loopLabel: loopLabelOf(step.loopIteration),
567
+ time: step.createdAt ?? step.updatedAt ?? step.startedAt,
568
+ outputText: outputTextOf(step.output),
569
+ error: step.error,
570
+ finalizePhase: step.finalizeMeta?.currentPhase,
571
+ finalizePreviousPhase: step.finalizeMeta?.previousPhase,
572
+ finalizeErrorDetail: step.finalizeMeta?.errorDetail ?? step.error,
573
+ finalizeAssessErrorDetail: step.finalizeMeta?.assessResult?.errorDetail,
574
+ finalizeAssessSessionId: step.finalizeMeta?.assessResult?.forkSessionId,
575
+ finalizeGateSessionId: step.finalizeMeta?.gateResult?.gateSessionId,
576
+ finalizeAssessResult: step.finalizeMeta?.assessResult !== undefined
577
+ ? {
578
+ assessmentRaw: step.finalizeMeta.assessResult.assessmentRaw,
579
+ ...(step.finalizeMeta.assessResult.compressedOutput !== undefined
580
+ ? { compressedOutput: step.finalizeMeta.assessResult.compressedOutput }
581
+ : {}),
582
+ ...(step.finalizeMeta.assessResult.status !== undefined
583
+ ? { status: step.finalizeMeta.assessResult.status }
584
+ : {}),
585
+ }
586
+ : undefined,
587
+ finalizeGateResult: step.finalizeMeta?.gateResult !== undefined
588
+ ? {
589
+ gatesPassed: step.finalizeMeta.gateResult.gatesPassed,
590
+ ...(step.finalizeMeta.gateResult.status !== undefined
591
+ ? { status: step.finalizeMeta.gateResult.status }
592
+ : {}),
593
+ }
594
+ : undefined,
595
+ finalizeAskResult: step.finalizeMeta?.askResult !== undefined
596
+ ? {
597
+ hasAsk: step.finalizeMeta.askResult.hasAsk,
598
+ ...(step.finalizeMeta.askResult.askQuestion !== undefined ? { askQuestion: step.finalizeMeta.askResult.askQuestion } : {}),
599
+ ...(step.finalizeMeta.askResult.status !== undefined
600
+ ? { status: step.finalizeMeta.askResult.status }
601
+ : {}),
602
+ }
603
+ : undefined,
604
+ finalizeRoutingResult: step.finalizeMeta?.routingResult !== undefined
605
+ ? {
606
+ ...(step.finalizeMeta.routingResult.branchMeta !== undefined ? { branchMeta: step.finalizeMeta.routingResult.branchMeta } : {}),
607
+ ...(step.finalizeMeta.routingResult.loopMeta !== undefined ? { loopMeta: step.finalizeMeta.routingResult.loopMeta } : {}),
608
+ ...(step.finalizeMeta.routingResult.status !== undefined
609
+ ? { status: step.finalizeMeta.routingResult.status }
610
+ : {}),
611
+ }
612
+ : undefined,
613
+ finalizeRoutingSessionId: step.finalizeMeta?.routingResult?.routingSessionId,
614
+ // Round 11:各阶段独立 status 字段(顶层,供 PhaseBadge 直接读取)
615
+ ...(step.finalizeMeta?.askResult?.status !== undefined ? { askStatus: step.finalizeMeta.askResult.status } : {}),
616
+ ...(step.finalizeMeta?.assessResult?.status !== undefined ? { assessStatus: step.finalizeMeta.assessResult.status } : {}),
617
+ ...(step.finalizeMeta?.gateResult?.status !== undefined ? { gateStatus: step.finalizeMeta.gateResult.status } : {}),
618
+ ...(step.finalizeMeta?.routingResult?.status !== undefined ? { routingStatus: step.finalizeMeta.routingResult.status } : {}),
619
+ childId: step.sessionMeta?.childId,
620
+ commandType: step.sessionMeta?.command,
621
+ synthetic: step.sessionMeta?.synthetic,
622
+ messages: chatLinesOf(step),
623
+ visitCount: step.visitCount,
624
+ attemptIndex: step.attemptIndex,
625
+ pausedAt: step.pausedAt,
626
+ })
627
+ }
628
+
629
+ // 统一时间轴:严格按 createdAt 排序(旧实例缺 createdAt 的段排最后)。
630
+ items.sort((a, b) => {
631
+ const ta = a.time === undefined ? '' : a.time
632
+ const tb = b.time === undefined ? '' : b.time
633
+ return ta < tb ? -1 : ta > tb ? 1 : 0
634
+ })
635
+ return items
636
+ }