@xgspz/cli-ui 0.0.0-stage → 0.6.14

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 (169) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/README.md +512 -2
  3. package/dist/cli-ui-embed-D-3DvyTN.js +50279 -0
  4. package/dist/cli-ui-index-C34qFb4u.js +11209 -0
  5. package/dist/cli-ui-jszip.min-BObcE2nn.js +26 -0
  6. package/dist/cli-ui-jszip.min-DojbZPiE.js +2324 -0
  7. package/dist/cli-ui-xlsx-JVLtx0qR.js +93 -0
  8. package/dist/cli-ui.css +32 -0
  9. package/dist/cli-ui.js +17 -0
  10. package/package.json +60 -4
  11. package/src/board/BrowserPane.tsx +969 -0
  12. package/src/board/ChangesPane.tsx +185 -0
  13. package/src/board/CliBoard.tsx +484 -0
  14. package/src/board/FileViewer.tsx +721 -0
  15. package/src/board/FilesPane.tsx +262 -0
  16. package/src/board/HtmlReadingPreview.tsx +52 -0
  17. package/src/board/MarkdownPreview.tsx +134 -0
  18. package/src/board/PanelControls.tsx +40 -0
  19. package/src/board/blocks.tsx +97 -0
  20. package/src/board/context.ts +65 -0
  21. package/src/board/diffLines.ts +46 -0
  22. package/src/board/fileKind.ts +250 -0
  23. package/src/board/fileOps.ts +85 -0
  24. package/src/board/fileScope.ts +32 -0
  25. package/src/board/fileSync.ts +351 -0
  26. package/src/board/fileSyncCache.ts +250 -0
  27. package/src/board/fileSyncProcessor.ts +72 -0
  28. package/src/board/fileSyncQueue.ts +56 -0
  29. package/src/board/fileSyncTransport.ts +85 -0
  30. package/src/board/fileWatch.ts +211 -0
  31. package/src/board/folderDownload.ts +211 -0
  32. package/src/board/frameQuote.ts +52 -0
  33. package/src/board/htmlReading.ts +148 -0
  34. package/src/board/panels.ts +33 -0
  35. package/src/board/textLoad.ts +76 -0
  36. package/src/board/useBoardUpload.tsx +51 -0
  37. package/src/board/useBoardVisibility.ts +35 -0
  38. package/src/board/workspace.ts +200 -0
  39. package/src/board/zip.ts +85 -0
  40. package/src/chat/CliChat.tsx +932 -0
  41. package/src/chat/Composer.tsx +239 -0
  42. package/src/chat/GroupChat.tsx +113 -0
  43. package/src/chat/RunControls.tsx +196 -0
  44. package/src/chat/configuration.ts +15 -0
  45. package/src/chat/draft.ts +49 -0
  46. package/src/chat/export.ts +34 -0
  47. package/src/chat/queueEdit.ts +27 -0
  48. package/src/chat/useExecutionConfig.ts +93 -0
  49. package/src/components.tsx +13 -0
  50. package/src/core/blocks.ts +205 -0
  51. package/src/core/callbacks.ts +8 -0
  52. package/src/core/client.ts +822 -0
  53. package/src/core/config.ts +252 -0
  54. package/src/core/conversation.ts +577 -0
  55. package/src/core/documentUpload.ts +196 -0
  56. package/src/core/execution.ts +24 -0
  57. package/src/core/id.ts +15 -0
  58. package/src/core/identity.ts +21 -0
  59. package/src/core/provider.tsx +141 -0
  60. package/src/core/sha256.ts +21 -0
  61. package/src/core/sse.ts +51 -0
  62. package/src/core/store.ts +1103 -0
  63. package/src/core/subagents.ts +78 -0
  64. package/src/core/textDecode.ts +24 -0
  65. package/src/core/types.ts +505 -0
  66. package/src/core/xlsx.ts +142 -0
  67. package/src/elements.tsx +376 -0
  68. package/src/embed.ts +56 -0
  69. package/src/index.ts +56 -0
  70. package/src/layout/CliSplit.tsx +53 -0
  71. package/src/layout/split.ts +251 -0
  72. package/src/msglist/CliMsgList.tsx +422 -0
  73. package/src/operator/branchFlow.ts +35 -0
  74. package/src/operator/index.ts +9 -0
  75. package/src/operator/liveStream.ts +1 -0
  76. package/src/operator/node/AgentConnect.tsx +619 -0
  77. package/src/operator/node/AppearancePanel.tsx +35 -0
  78. package/src/operator/node/BranchHistory.tsx +67 -0
  79. package/src/operator/node/BranchNavigator.tsx +36 -0
  80. package/src/operator/node/BrowserPane.tsx +128 -0
  81. package/src/operator/node/BrowserPopup.tsx +22 -0
  82. package/src/operator/node/Chat.tsx +538 -0
  83. package/src/operator/node/CommandPalette.tsx +213 -0
  84. package/src/operator/node/Composer.tsx +316 -0
  85. package/src/operator/node/InsightsView.tsx +831 -0
  86. package/src/operator/node/Layout.tsx +192 -0
  87. package/src/operator/node/Markdown.tsx +61 -0
  88. package/src/operator/node/Modal.tsx +179 -0
  89. package/src/operator/node/NeedsYouMenu.tsx +54 -0
  90. package/src/operator/node/OnboardingWizard.tsx +248 -0
  91. package/src/operator/node/Orchestrator.tsx +644 -0
  92. package/src/operator/node/ProjectLanding.tsx +86 -0
  93. package/src/operator/node/ProjectsColumn.tsx +137 -0
  94. package/src/operator/node/ProviderAuth.tsx +559 -0
  95. package/src/operator/node/RunControls.tsx +2 -0
  96. package/src/operator/node/RunSwitch.tsx +113 -0
  97. package/src/operator/node/Services.tsx +245 -0
  98. package/src/operator/node/SessionRail.tsx +299 -0
  99. package/src/operator/node/SessionView.tsx +292 -0
  100. package/src/operator/node/SettingsView.tsx +690 -0
  101. package/src/operator/node/TaskBoard.tsx +352 -0
  102. package/src/operator/node/TaskChanges.tsx +526 -0
  103. package/src/operator/node/TaskHero.tsx +50 -0
  104. package/src/operator/node/TasksColumn.tsx +200 -0
  105. package/src/operator/node/Terminal.tsx +143 -0
  106. package/src/operator/node/Transcript.tsx +485 -0
  107. package/src/operator/node/ViewFile.tsx +79 -0
  108. package/src/operator/node/Welcome.tsx +101 -0
  109. package/src/operator/node/WorktreePrune.tsx +333 -0
  110. package/src/operator/node/agents.ts +58 -0
  111. package/src/operator/node/api.ts +57 -0
  112. package/src/operator/node/boardBackend.ts +63 -0
  113. package/src/operator/node/boardEvents.ts +40 -0
  114. package/src/operator/node/boardInteraction.ts +15 -0
  115. package/src/operator/node/boardState.ts +74 -0
  116. package/src/operator/node/boardWorkspace.ts +15 -0
  117. package/src/operator/node/boundedPoll.ts +47 -0
  118. package/src/operator/node/branchFlow.ts +1 -0
  119. package/src/operator/node/composerDraft.ts +78 -0
  120. package/src/operator/node/fileLinks.ts +81 -0
  121. package/src/operator/node/format.ts +292 -0
  122. package/src/operator/node/github.tsx +232 -0
  123. package/src/operator/node/icons.tsx +88 -0
  124. package/src/operator/node/liveStream.ts +323 -0
  125. package/src/operator/node/modals.tsx +766 -0
  126. package/src/operator/node/navHistory.ts +70 -0
  127. package/src/operator/node/persist.ts +26 -0
  128. package/src/operator/node/protocol/agents/optIn.ts +9 -0
  129. package/src/operator/node/protocol/agents/types.ts +394 -0
  130. package/src/operator/node/protocol/approvalFailure.ts +77 -0
  131. package/src/operator/node/protocol/authFailure.ts +66 -0
  132. package/src/operator/node/protocol/customModels.ts +23 -0
  133. package/src/operator/node/protocol/events.ts +13 -0
  134. package/src/operator/node/protocol/features.ts +70 -0
  135. package/src/operator/node/protocol/internalUsage.ts +1 -0
  136. package/src/operator/node/protocol/promptLimits.ts +52 -0
  137. package/src/operator/node/protocol/storageRetention.ts +13 -0
  138. package/src/operator/node/protocol/types.ts +335 -0
  139. package/src/operator/node/protocol/usageLimit.ts +72 -0
  140. package/src/operator/node/shared.tsx +89 -0
  141. package/src/operator/node/transcriptModel.ts +129 -0
  142. package/src/operator/node/types.ts +305 -0
  143. package/src/operator/node/useGlobalEvents.ts +97 -0
  144. package/src/operator/node/useOrchestrator.ts +697 -0
  145. package/src/operator/node/usePrefs.ts +114 -0
  146. package/src/operator/node/useRecaps.ts +102 -0
  147. package/src/operator/node/useTaskStream.ts +286 -0
  148. package/src/operator.base.css +1949 -0
  149. package/src/operator.css +1949 -0
  150. package/src/styles.css +579 -0
  151. package/src/ui/ErrorBoundary.tsx +38 -0
  152. package/src/ui/Markdown.tsx +60 -0
  153. package/src/ui/Popover.tsx +95 -0
  154. package/src/ui/QueuedMessages.tsx +110 -0
  155. package/src/ui/ReferenceCard.tsx +28 -0
  156. package/src/ui/SubagentChat.tsx +233 -0
  157. package/src/ui/TextBlock.tsx +51 -0
  158. package/src/ui/Transcript.tsx +435 -0
  159. package/src/ui/UploadProgress.tsx +46 -0
  160. package/src/ui/WorkspaceDrawer.tsx +64 -0
  161. package/src/ui/WorkspaceTabs.tsx +60 -0
  162. package/src/ui/fileLinks.ts +52 -0
  163. package/src/ui/format.ts +103 -0
  164. package/src/ui/icons.tsx +92 -0
  165. package/src/ui/reference.ts +40 -0
  166. package/src/ui/shared.tsx +38 -0
  167. package/src/ui/transcriptModel.ts +151 -0
  168. package/src/ui/useChatScrollbar.ts +29 -0
  169. package/src/ui/useComposerAutosize.ts +54 -0
@@ -0,0 +1,78 @@
1
+ /** Shared wire format for native CLI subagents. These are not Center tasks. */
2
+ export interface SubagentInfo {
3
+ id: string;
4
+ parentId?: string;
5
+ title: string;
6
+ task?: string;
7
+ status: "running" | "completed" | "failed" | "cancelled" | "unknown";
8
+ detailAvailability: "full" | "partial" | "unavailable";
9
+ detailReason?: string;
10
+ updatedAt?: number;
11
+ }
12
+
13
+ export interface SubagentMessage {
14
+ subagentId: string;
15
+ role: "user" | "assistant" | "tool" | "system";
16
+ content: string;
17
+ tool?: { id: string; title: string; detail?: string; result?: string; isError?: boolean };
18
+ eventId: string;
19
+ updatedAt?: number;
20
+ }
21
+
22
+ export interface SubagentFields {
23
+ subagent?: SubagentInfo;
24
+ subagentMessage?: SubagentMessage;
25
+ }
26
+
27
+ type Row = { id: string; role: string; content: string; generation: number; ts?: number; created_at?: number };
28
+ export interface SubagentTranscript { subagent: SubagentInfo; messages: Row[]; refreshable?: boolean; older?: number }
29
+ export type LoadSubagent = (id: string, generation: number, before?: string) => Promise<SubagentTranscript>;
30
+
31
+ const cache = new WeakMap<Row, SubagentFields>();
32
+ export function subagentFields(row: Row): SubagentFields {
33
+ const known = cache.get(row);
34
+ if (known) return known;
35
+ const fields: SubagentFields = {};
36
+ if (row.role === "tool") {
37
+ try {
38
+ const value = JSON.parse(row.content);
39
+ const info = value?.subagent;
40
+ if (info && typeof info.id === "string" && typeof info.title === "string") fields.subagent = {
41
+ id: info.id, title: info.title,
42
+ parentId: typeof info.parentId === "string" ? info.parentId : undefined,
43
+ task: typeof info.task === "string" ? info.task : undefined,
44
+ status: ["running", "completed", "failed", "cancelled", "unknown"].includes(info.status) ? info.status : "unknown",
45
+ detailAvailability: ["full", "partial", "unavailable"].includes(info.detailAvailability) ? info.detailAvailability : "partial",
46
+ detailReason: typeof info.detailReason === "string" ? info.detailReason : undefined,
47
+ updatedAt: typeof info.updatedAt === "number" && Number.isFinite(info.updatedAt) ? info.updatedAt : undefined,
48
+ };
49
+ if (value?.subagentMessage && typeof value.subagentMessage.subagentId === "string" && typeof value.subagentMessage.content === "string"
50
+ && ["user", "assistant", "tool", "system"].includes(value.subagentMessage.role)) fields.subagentMessage = value.subagentMessage;
51
+ } catch { /* Ordinary or malformed tool output is handled by the existing renderer. */ }
52
+ }
53
+ cache.set(row, fields);
54
+ return fields;
55
+ }
56
+
57
+ /** Keep child activity out of the parent timeline, but retain an entry if its card is outside the loaded tail. */
58
+ export function parentMessages<T extends Row>(rows: T[]): T[] {
59
+ const cardKeys = new Set(rows.flatMap(row => {
60
+ const info = subagentFields(row).subagent;
61
+ return info ? [`${row.generation}:${info.id}`] : [];
62
+ }));
63
+ const emitted = new Set<string>();
64
+ return rows.flatMap(row => {
65
+ const { subagent, subagentMessage } = subagentFields(row);
66
+ if (subagent?.parentId && cardKeys.has(`${row.generation}:${subagent.parentId}`)) return [];
67
+ if (!subagentMessage) return [row];
68
+ const key = `${row.generation}:${subagentMessage.subagentId}`;
69
+ if (cardKeys.has(key) || emitted.has(key)) return [];
70
+ emitted.add(key);
71
+ const info: SubagentInfo = { id: subagentMessage.subagentId, title: "子代理", status: "unknown", detailAvailability: "partial", detailReason: "正在读取子代理详情" };
72
+ return [{ ...row, id: `subagent-entry:${key}`, role: "tool", content: JSON.stringify({ title: info.title, subagent: info }) }];
73
+ });
74
+ }
75
+
76
+ export function childMessages(rows: Row[], id: string, generation: number) {
77
+ return rows.filter(row => row.generation === generation && (subagentFields(row).subagentMessage?.subagentId === id || subagentFields(row).subagent?.parentId === id));
78
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 工作区文本文件的解码:智能体写的多是 UTF-8,但业务导出的 CSV / TXT 常常是 GBK(GB18030),
3
+ * 按 UTF-8 硬解会满屏乱码。顺序:BOM(UTF-8 / UTF-16)→ 严格 UTF-8 → GB18030。
4
+ *
5
+ * 只读了文件的一段(开头 / 末尾)时:开头那段末尾可能切在一个字中间——按流式解码,把不完整的尾巴留下不报错;
6
+ * 末尾那段开头可能切在一个 UTF-8 字中间——跳过开头最多 3 个续字节。
7
+ */
8
+ export function decodeText(bytes: Uint8Array, opts: { fromMiddle?: boolean } = {}): { text: string; encoding: string } {
9
+ if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) return { text: new TextDecoder("utf-8").decode(bytes.subarray(3), { stream: true }), encoding: "utf-8" };
10
+ if (bytes[0] === 0xff && bytes[1] === 0xfe) return { text: new TextDecoder("utf-16le").decode(bytes.subarray(2), { stream: true }), encoding: "utf-16le" };
11
+ if (bytes[0] === 0xfe && bytes[1] === 0xff) return { text: new TextDecoder("utf-16be").decode(bytes.subarray(2), { stream: true }), encoding: "utf-16be" };
12
+ let start = 0;
13
+ if (opts.fromMiddle) while (start < 3 && start < bytes.length && (bytes[start] & 0xc0) === 0x80) start++;
14
+ const body = bytes.subarray(start);
15
+ try {
16
+ return { text: new TextDecoder("utf-8", { fatal: true }).decode(body, { stream: true }), encoding: "utf-8" };
17
+ } catch {
18
+ try {
19
+ return { text: new TextDecoder("gb18030").decode(bytes, { stream: true }), encoding: "gb18030" };
20
+ } catch {
21
+ return { text: new TextDecoder("utf-8").decode(body, { stream: true }), encoding: "utf-8" };
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,505 @@
1
+ import type { SubagentInfo, SubagentMessage } from "./subagents";
2
+ /**
3
+ * 两套数据形状:
4
+ * · center 服务(cli-task-center)对话任务接口的返回(snake_case,原样保留,便于对照 API 文档);
5
+ * · Operator 节点转录的消息与事件(center 的实时流原样转发,界面按 Operator 的规则渲染)。
6
+ */
7
+
8
+ // ── center:对话任务 ────────────────────────────────────────────────
9
+
10
+ export type JobStatus = "queued" | "bound" | "idle" | "running" | "paused" | "succeeded" | "failed" | "cancelled" | string;
11
+ export type TurnStatus = "queued" | "dispatching" | "running" | "succeeded" | "failed" | "cancelled" | "merged" | string;
12
+
13
+ export interface CenterTurn {
14
+ turn_id: string;
15
+ job_id: string;
16
+ task_id: string;
17
+ seq: number;
18
+ business_id: string | null;
19
+ status: TurnStatus;
20
+ input: string;
21
+ /** 合并了后面排队的消息时,实际发给 Agent 的全文;否则为 null(= input)。 */
22
+ sent_input: string | null;
23
+ merged_into_turn_id: string | null;
24
+ reply: string | null;
25
+ /** true = 这一轮以 Agent 提问结束:reply 是问题,下一条消息就是回答。 */
26
+ asked: boolean;
27
+ last_error: string | null;
28
+ attempt: number;
29
+ agent: string | null;
30
+ resolved_model: string | null;
31
+ total_tokens: number;
32
+ input_tokens: number;
33
+ output_tokens: number;
34
+ cache_read_tokens: number;
35
+ cache_creation_tokens: number;
36
+ /** true = 节点上的会话重建了,这一轮起之前的对话没带过去(工作区文件还在)。 */
37
+ context_reset: boolean;
38
+ /** 这一轮的改动是否已合并回项目基线。 */
39
+ merged?: boolean;
40
+ merge_error?: string | null;
41
+ /** 保存本任务分支与合并项目主分支分别记录;旧版中枢不返回。 */
42
+ saved?: boolean;
43
+ saved_at?: string | null;
44
+ save_error?: string | null;
45
+ created_at: string;
46
+ started_at: string | null;
47
+ ended_at: string | null;
48
+ queue_position?: number | null;
49
+ }
50
+
51
+ /** Node-observed human wait state. Missing/null means unavailable, not confirmed idle. */
52
+ export interface AwaitingInputState {
53
+ awaiting_input?: boolean | null;
54
+ awaiting_input_observed_at?: string | null;
55
+ }
56
+
57
+ export interface CenterTask extends AwaitingInputState {
58
+ id: string;
59
+ job_id: string;
60
+ seq?: number;
61
+ title: string;
62
+ status: string;
63
+ agent: string | null;
64
+ resolved_model: string | null;
65
+ total_tokens: number;
66
+ input_tokens: number;
67
+ output_tokens: number;
68
+ cache_read_tokens: number;
69
+ cache_creation_tokens: number;
70
+ browser?: boolean;
71
+ last_error: string | null;
72
+ /** 接续出来的对话:指回旧对话任务里对应的那个对话。 */
73
+ continue_from_task_id?: string;
74
+ }
75
+
76
+ /** 对话任务里的一个对话(一个子任务、一个 Agent 会话)。conversation.conversations 的一项。 */
77
+ export interface ConversationInfo extends AwaitingInputState {
78
+ task_id: string;
79
+ seq: number;
80
+ title: string;
81
+ status: string;
82
+ /** 关掉了(单独关闭这一个,或整个对话任务结束)。 */
83
+ closed: boolean;
84
+ turns: number;
85
+ queued_turns: number;
86
+ running_turn: boolean;
87
+ last_turn: CenterTurn | null;
88
+ remote_task_id: string | null;
89
+ continue_from_task_id: string | null;
90
+ created_at: string | null;
91
+ }
92
+
93
+ export interface ConversationSummary extends AwaitingInputState {
94
+ /** 里面的每个对话(按开的先后)。旧版 center 没有这个字段:当作只有一个对话。 */
95
+ conversations?: ConversationInfo[];
96
+ turns: number;
97
+ queued_turns: number;
98
+ running_turn: boolean;
99
+ last_turn: CenterTurn | null;
100
+ last_activity_at: string | null;
101
+ expires_at: string | null;
102
+ closed_reason: string | null;
103
+ continued_from_job_id: string | null;
104
+ continued_by_job_id: string | null;
105
+ }
106
+
107
+ /** GET /api/jobs/:id */
108
+ export interface CenterJob {
109
+ id: string;
110
+ title: string;
111
+ status: JobStatus;
112
+ mode?: "conversation" | string;
113
+ conversation?: ConversationSummary;
114
+ tasks: CenterTask[];
115
+ }
116
+
117
+ /** POST /api/jobs/:id/messages、POST /api/jobs/:id/conversations */
118
+ export interface SendResult {
119
+ job_id: string;
120
+ /** 实际接收这条消息的对话(对话任务过期接续后是新对话任务里对应的那个)。 */
121
+ task_id?: string;
122
+ status: JobStatus;
123
+ duplicate: boolean;
124
+ /** true = 原对话已结束,这条消息开了一个新对话(job_id 是新的)。 */
125
+ new_conversation: boolean;
126
+ previous_job_id: string | null;
127
+ queue_position: number | null;
128
+ turn: CenterTurn;
129
+ }
130
+
131
+ /** 每次浏览器操作前的随机等待范围;合法范围由任务中心与节点共同校验。 */
132
+ export interface BrowserPacing {
133
+ min_seconds: number;
134
+ max_seconds: number;
135
+ }
136
+
137
+ /** 新建对话时可带的参数(POST /api/jobs,mode=conversation)。 */
138
+ export interface NewConversationOptions {
139
+ /** 首轮执行配置。由服务端校验、持久化,并用于后续派发和重试。 */
140
+ executionConfig?: import("./execution").ExecutionOptions;
141
+ title?: string;
142
+ /** 业务自己的 ID(tasks[0].business_id):第一轮的回调里原样带回,用来和业务数据对应。 */
143
+ businessId?: string;
144
+ workspace?: { name?: string; context?: string; branch?: string };
145
+ callback?: { url: string; code: string };
146
+ agent?: string;
147
+ timeoutSeconds?: number;
148
+ browser?: boolean;
149
+ /** Ego 操作手册的 YAML 正文(不是文件名或 URL);必须显式 browser:true。 */
150
+ browserPlaybook?: string;
151
+ /** 转发至节点执行器和浏览器脚本;必须显式 browser:true。 */
152
+ browserPacing?: BrowserPacing;
153
+ }
154
+
155
+ // ── Operator:节点转录 ─────────────────────────────────────────────
156
+
157
+ export type MsgRole = "user" | "assistant" | "tool" | "system" | "session_break";
158
+
159
+ export interface Message {
160
+ id: string;
161
+ task_id?: string;
162
+ generation: number;
163
+ role: MsgRole;
164
+ content: string;
165
+ created_at: number;
166
+ }
167
+
168
+ export interface PendingMessage {
169
+ id: string;
170
+ generation: number;
171
+ content: string;
172
+ created_at: number;
173
+ }
174
+
175
+ export interface Msg {
176
+ id: string;
177
+ /** queued:中枢里还在排队、没送到节点的消息(界面上是可撤回的「排队中」气泡)。 */
178
+ role: MsgRole | "queued";
179
+ content: string;
180
+ generation: number;
181
+ toolId?: string;
182
+ ts?: number;
183
+ /** queued 气泡对应的中枢轮次,撤回时用。 */
184
+ turnId?: string;
185
+ /** queued 气泡:中枢给出的等待原因(节点连不上、没有可用节点……),正常排队时为空。 */
186
+ note?: string;
187
+ }
188
+
189
+ export interface AskOption { label: string; description?: string }
190
+ export interface AskQuestion { question: string; header: string; multiSelect?: boolean; options: AskOption[] }
191
+ export type AskAnswers = string[][];
192
+
193
+ export interface InputField { label: string; secret?: boolean; options?: string[]; placeholder?: string }
194
+ export interface InputRequest {
195
+ id: string;
196
+ message: string;
197
+ fields: InputField[];
198
+ status: "pending" | "answered" | "cancelled";
199
+ values?: string[];
200
+ }
201
+
202
+ export type DiffLine = { sign: "+" | "-" | " "; text: string };
203
+
204
+ export type ToolPeek =
205
+ | { kind: "count"; text: string }
206
+ | { kind: "diff"; added: number; removed: number; label?: string; lines: DiffLine[]; truncated?: number }
207
+ | { kind: "lines"; label?: string; lines: string[]; truncated?: number }
208
+ | { kind: "todos"; items: { text: string; status: string }[] };
209
+
210
+ export interface ToolData {
211
+ subagent?: SubagentInfo;
212
+ subagentMessage?: SubagentMessage;
213
+ title: string;
214
+ detail?: string;
215
+ result?: string;
216
+ isError?: boolean;
217
+ peek?: ToolPeek;
218
+ diff?: DiffLine[];
219
+ ask?: { id: string; questions: AskQuestion[]; answers?: AskAnswers };
220
+ input?: InputRequest;
221
+ }
222
+
223
+ export interface TurnUsage {
224
+ cost_usd: number;
225
+ input_tokens: number;
226
+ output_tokens: number;
227
+ cache_read_tokens: number;
228
+ cache_creation_tokens: number;
229
+ }
230
+
231
+ /** 节点回合事件(center 流里 `{type:"node", event}` 的 event)。 */
232
+ export type NodeEvent =
233
+ | { type: "user"; content: string; msgId: string; generation: number; ts?: number }
234
+ | { type: "queued"; msgId: string; content: string; generation: number; ts?: number }
235
+ | { type: "dequeued"; msgId: string }
236
+ | { type: "model"; model: string }
237
+ | { type: "assistant"; content: string; msgId?: string; generation?: number; ts?: number }
238
+ | { type: "tool"; id: string; title: string; detail: string; peek?: ToolPeek; diff?: DiffLine[]; subagent?: SubagentInfo; subagentMessage?: SubagentMessage; msgId?: string; generation?: number; ts?: number }
239
+ | { type: "tool_result"; id: string; content: string; isError: boolean; peek?: ToolPeek; subagent?: SubagentInfo; subagentMessage?: SubagentMessage; msgId?: string; generation?: number; ts?: number }
240
+ | { type: "ask"; id: string; questions: AskQuestion[]; msgId?: string; generation?: number }
241
+ | { type: "ask_answered"; id: string; answers: AskAnswers; msgId?: string; awaiting_input?: 0 | 1 }
242
+ | { type: "input"; input: InputRequest; msgId?: string; generation?: number; awaiting_input?: 0 | 1 }
243
+ | { type: "usage"; usage: TurnUsage }
244
+ | { type: "notice"; content: string; msgId?: string; generation?: number }
245
+ | { type: "error"; content: string; msgId?: string; generation?: number }
246
+ | { type: "turn_end" }
247
+ | { type: string; [key: string]: unknown };
248
+
249
+ /** center 实时流 chunk.events 里的一项。 */
250
+ export type StreamItem =
251
+ /** remote_task_id:一次性任务的流里才有(大任务里可能同时有几个节点对话)。 */
252
+ /** older:快照只有最新一段时,更早还有几条(往上翻时经 history 分页取)。旧版中枢没有 = 全量。 */
253
+ | { type: "transcript"; remote_task_id?: string; messages: Message[] | null; pending: PendingMessage[] | null; running: boolean | null; older?: number }
254
+ | { type: "node"; remote_task_id?: string; event: NodeEvent }
255
+ | { type: "turn"; turn: CenterTurn }
256
+ | { type: "job"; job: { id: string; status: JobStatus; closed_reason?: string | null; node_id: string | null } }
257
+ /** 正在看的这个对话的状态变化(关闭了:closed=true);一次性任务的流里是子任务(BatchStreamTask)。 */
258
+ | { type: "task"; task: StreamTask | BatchStreamTask };
259
+
260
+ /** 实时流看的那个对话(GET /api/jobs/:id/stream?task=)。 */
261
+ export interface StreamTask extends AwaitingInputState { id: string; seq: number; title: string; status: string; closed: boolean; remote_task_id: string | null; continue_from_task_id: string | null }
262
+
263
+ export interface StreamSnapshot {
264
+ job: { id: string; status: JobStatus; closed_reason: string | null; node_id: string | null; mode: string; title: string; conversation: ConversationSummary };
265
+ /** 旧版 center 没有。 */
266
+ task?: StreamTask;
267
+ turns: CenterTurn[];
268
+ }
269
+
270
+ /**
271
+ * 一次性任务(含续派)的子任务,实时流里的样子。cli-chat 只读查看这类任务时用。
272
+ * prompt / opener_prompt:指定了 ?task= 时看的那个子任务才有(opener_prompt = 开出它所在节点对话的那个子任务的原文)。
273
+ */
274
+ export interface BatchStreamTask extends AwaitingInputState {
275
+ id: string;
276
+ seq: number;
277
+ task_key: string | null;
278
+ title: string;
279
+ status: string;
280
+ round: number;
281
+ attempt: number;
282
+ remote_task_id: string | null;
283
+ continue_from_task_id: string | null;
284
+ last_error: string | null;
285
+ prompt?: string;
286
+ opener_prompt?: string;
287
+ /**
288
+ * 对话式子任务(续派时 mode=conversation):指定 ?task= 看它时,它和续派接着它聊的子任务算一个对话——
289
+ * 这条是其中最新的;conversation_remote_task_id 是它们共用的节点对话,queued 是还没派发的消息。旧版中枢没有。
290
+ */
291
+ conversation?: boolean;
292
+ conversation_remote_task_id?: string | null;
293
+ queued?: Array<{ id: string; prompt: string; created_at?: string }>;
294
+ }
295
+
296
+ /** 一次性任务(含续派,mode 不是 conversation)的实时流首个事件:没有轮次,只有子任务。 */
297
+ export interface BatchStreamSnapshot {
298
+ job: { id: string; status: JobStatus; node_id: string | null; round: number; terminal_revision: number; mode: string; title: string };
299
+ tasks: BatchStreamTask[];
300
+ /** 指定了 ?task= 时:看的那个子任务。 */
301
+ task?: BatchStreamTask;
302
+ }
303
+
304
+ export const isBatchSnapshot = (snapshot: StreamSnapshot | BatchStreamSnapshot): snapshot is BatchStreamSnapshot =>
305
+ !Array.isArray((snapshot as StreamSnapshot).turns) && Array.isArray((snapshot as BatchStreamSnapshot).tasks);
306
+
307
+ export interface StreamEnd {
308
+ /** 对话任务的结束原因;只是这个对话被关闭时为 conversation_closed。 */
309
+ reason: string;
310
+ task_id?: string;
311
+ continued_by_job_id?: string | null;
312
+ /** 接续这个对话的新对话(新对话任务里的子任务);还没有为 null。 */
313
+ continued_by_task_id?: string | null;
314
+ }
315
+
316
+ // ── center 回调体(与中枢 conversation.ts turnCallbackPayload / job-callback.ts 的字段一一对应) ──
317
+ //
318
+ // 组件在前端发出的 conversation_turn / conversation_closed 事件,载荷与中枢投递给 callback.url 的请求体同名同义,
319
+ // 业务方可以用同一套代码处理「后端回调」和「前端事件」。前端拿不到的字段(event_id、callback_seq、cumulative、
320
+ // carried)不出现;以后端回调为准落库,前端事件只用来刷新界面。
321
+
322
+ /** 每轮一次:type = "conversation_turn"。 */
323
+ export interface ConversationTurnCallback {
324
+ type: "conversation_turn";
325
+ /** 投递顺序号(= seq)。同一对话里按它从小到大处理;结束事件是最后一轮 + 1。 */
326
+ callback_seq: number;
327
+ job_id: string;
328
+ task_id: string;
329
+ turn_id: string;
330
+ seq: number;
331
+ business_id: string | null;
332
+ /** succeeded / failed / cancelled(被合并的消息不单独回调,见 messages)。 */
333
+ status: TurnStatus;
334
+ occurred_at: string;
335
+ /** 实际发给 Agent 的正文(合并了排队消息时是拼起来的全文)。 */
336
+ input: string;
337
+ /** 这一轮包含的全部消息,按发送顺序(排队的消息会合并进来)。 */
338
+ messages: Array<{ turn_id: string; seq: number; business_id: string | null; input: string }>;
339
+ reply: string | null;
340
+ /** true = 这一轮以 Agent 提问结束(reply 是问题),status 仍是 succeeded;下一条消息就是回答。 */
341
+ asked: boolean;
342
+ /** 失败 / 取消原因(中枢原文)。 */
343
+ message: string | null;
344
+ continued_from_job_id: string | null;
345
+ context_reset: boolean;
346
+ merged: boolean;
347
+ merge_error: string | null;
348
+ saved?: boolean;
349
+ saved_at?: string | null;
350
+ save_error?: string | null;
351
+ agent: string | null;
352
+ resolved_model: string | null;
353
+ total_tokens: number;
354
+ input_tokens: number;
355
+ output_tokens: number;
356
+ cache_read_tokens: number;
357
+ cache_creation_tokens: number;
358
+ started_at: string | null;
359
+ ended_at: string | null;
360
+ }
361
+
362
+ /** 对话结束一次:type = "conversation_closed"。 */
363
+ export interface ConversationClosedCallback {
364
+ type: "conversation_closed";
365
+ mode: "conversation";
366
+ /** 投递顺序号:最后一轮的 seq + 1,永远排在这个对话所有 conversation_turn 之后。 */
367
+ callback_seq: number;
368
+ job_id: string;
369
+ /** 只关掉了对话任务里的这一个对话时才有(前端事件;center 只在整个对话任务结束时回调)。 */
370
+ task_id?: string;
371
+ /** succeeded(关闭 / 过期 / 换线)/ cancelled / failed。 */
372
+ status: JobStatus;
373
+ /** closed / cancelled / expired / node_reassigned / node_lost。 */
374
+ closed_reason: string | null;
375
+ /** 一共几轮(= 最大的 seq,被合并的消息也算)。 */
376
+ turns: number;
377
+ /** 接续出来的新对话(过期、换线、节点丢失后再发消息时才有)。 */
378
+ continued_by_job_id: string | null;
379
+ occurred_at: string;
380
+ }
381
+
382
+ export type ConversationCallback = ConversationTurnCallback | ConversationClosedCallback;
383
+
384
+ /** 一轮结束时 onTurnEnd 收到的载荷(字段与 center 的 conversation_turn 回调体一致)。 */
385
+ export type TurnEndPayload = ConversationTurnCallback;
386
+ /** 对话结束时 onConversationEnd 收到的载荷(字段与 center 的 conversation_closed 回调体一致)。 */
387
+ export type ConversationEndPayload = ConversationClosedCallback;
388
+
389
+ // ── 右侧看板(cli-board):center 的 /api/jobs/:id/board、files、diff、browser ──
390
+
391
+ export interface BoardInfo {
392
+ job_id: string;
393
+ /** false = 还没派发到执行节点(文件和浏览器都还没有)。 */
394
+ dispatched: boolean;
395
+ detail?: string;
396
+ task_id?: string;
397
+ remote_task_id?: string;
398
+ node?: { id: string; health: string };
399
+ files: boolean;
400
+ /** 节点开了浏览器功能。null = 这次没探测出来(节点慢 / 暂时连不上),照常显示浏览器页签。 */
401
+ browser: boolean | null;
402
+ }
403
+
404
+ export interface FileEntry {
405
+ name: string;
406
+ /** 相对工作目录的路径,用 / 分隔。 */
407
+ path: string;
408
+ type: "dir" | "file";
409
+ size: number;
410
+ mtime: number;
411
+ /** Opaque metadata fingerprint for reconnect reconciliation, not an immutable content version. */
412
+ changeToken?: string;
413
+ }
414
+
415
+ export interface DirListing { type: "dir"; path: string; entries: FileEntry[]; truncated: boolean; /** 目录里的条目总数(新版节点给)。 */ total?: number; nextCursor?: string; revision?: string }
416
+ export interface FileInfo { type: "file"; path: string; name: string; size: number; mtime: number; contentType: string; inline: boolean }
417
+
418
+ export interface DiffFile {
419
+ path: string; status: string; additions: number; deletions: number; binary: boolean; patch: string; truncated?: boolean;
420
+ /** 改动太多,这个文件的 diff 没有带上(节点对整个 diff 有总量上限);文件本身照常可以打开。 */
421
+ omitted?: boolean;
422
+ }
423
+ /** Operator 的 /api/tasks/:id/diff(经 center 原样转发)。 */
424
+ export interface DiffResult {
425
+ isolated?: boolean;
426
+ reason?: string;
427
+ branch?: string;
428
+ baseLabel?: string;
429
+ files?: DiffFile[];
430
+ isDirty?: boolean;
431
+ ahead?: number;
432
+ alreadyMerged?: boolean;
433
+ /** 因为总量上限没带 diff 的文件数。 */
434
+ patchesOmitted?: number;
435
+ error?: string;
436
+ /** 有的节点只给一整段 unified diff。 */
437
+ diff?: string;
438
+ patch?: string;
439
+ }
440
+
441
+ /** Operator 的任务浏览器状态(/api/tasks/:id/browser)。 */
442
+ export interface BrowserStatus {
443
+ feature: boolean;
444
+ state: "none" | "opening" | "running" | "closing";
445
+ origin: "ai" | "manual" | "check" | null;
446
+ control: "ai" | "human";
447
+ handoff: { instruction: string; by: "ai" | "user" | "browser" | "kept" } | null;
448
+ capability: "config" | "manual" | "ai" | null;
449
+ viewers: number;
450
+ spaceName: string;
451
+ instance: { running: boolean; spaces: number };
452
+ shownLocally: boolean;
453
+ error: string | null;
454
+ }
455
+
456
+ /** 浏览器画面流里的一条消息(节点原样转发)。 */
457
+ export interface BrowserTab { id: string; title: string; url: string }
458
+ /** 新节点用页面身份隔离输入;旧节点可以省略这两个字段。 */
459
+ export interface BrowserTarget { targetId?: string; targetEpoch?: number }
460
+ export type BrowserMessage = BrowserTarget & (
461
+ | { type: "frame"; data: string; metadata?: { deviceWidth: number; deviceHeight: number } }
462
+ | { type: "page"; url?: string; title?: string }
463
+ /** 只包含当前任务内的网页;只有明确声明 canSelect 才显示可切换标签。 */
464
+ | { type: "tabs"; tabs: BrowserTab[]; activeId: string | null; canSelect?: boolean }
465
+ | { type: "error"; message?: string }
466
+ /** 按了复制 / 剪切:选中的文字(只发给按键的人),写进本机剪贴板。 */
467
+ | { type: "clipboard"; text: string }
468
+ /** 网页弹出了 alert / confirm / prompt(画面里看不到,单独显示)。 */
469
+ | { type: "dialog"; dialogType: "alert" | "confirm" | "prompt" | "beforeunload"; message: string; defaultPrompt?: string }
470
+ | { type: "dialog_closed" });
471
+
472
+ /** 浏览器实时连接(GET /browser/live)的画面连接状况。 */
473
+ export type BrowserFramesState = { state: "idle" | "connecting" | "live" | "retrying"; detail?: string };
474
+
475
+
476
+ export interface WorkspaceFileChange {
477
+ kind: "created" | "modified" | "deleted";
478
+ path: string;
479
+ file?: FileEntry;
480
+ previous?: FileEntry;
481
+ }
482
+ export type WorkspaceFileBatch =
483
+ | { type: "snapshot"; files: FileEntry[]; revision?: string; complete: boolean }
484
+ | { type: "changes"; changes: WorkspaceFileChange[]; revision?: string; complete: boolean }
485
+ | { type: "resync"; reason: string; complete: false; revision?: string };
486
+ export interface WorkspaceChangeEvent {
487
+ type: "files_changed";
488
+ /** 对话键;节点适配下是 task.id。 */
489
+ jobId: string;
490
+ mode: "events" | "poll";
491
+ coverage: "workspace" | "listed-files";
492
+ complete: boolean;
493
+ changes: WorkspaceFileChange[];
494
+ revision?: string;
495
+ resync?: boolean;
496
+ }
497
+ export interface WorkspaceWatchStatus {
498
+ type: "files_watch";
499
+ jobId: string;
500
+ state: "connecting" | "ready" | "reconnecting" | "error";
501
+ mode: "events" | "poll";
502
+ coverage: "workspace" | "listed-files";
503
+ complete: boolean;
504
+ error?: string;
505
+ }