@codexview/react 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -7
- package/dist/index.js.map +1 -1
- package/docs/changelog.md +11 -0
- package/package.json +2 -2
- package/LICENSE +0 -21
- package/docs/superpowers/plans/2026-05-15-claude-code-adapter-implementation.md +0 -2005
- package/docs/superpowers/plans/2026-05-15-codexview-implementation.md +0 -3903
- package/docs/superpowers/specs/2026-05-15-claude-code-adapter-design.md +0 -402
- package/docs/superpowers/specs/2026-05-15-codexview-design.md +0 -661
|
@@ -1,661 +0,0 @@
|
|
|
1
|
-
# CodexView 设计文档
|
|
2
|
-
|
|
3
|
-
- **Created**: 2026-05-15
|
|
4
|
-
- **Status**: Approved
|
|
5
|
-
- **Owner**: Jay (maxazure)
|
|
6
|
-
- **Repo**: `/Volumes/MaxSSD1/MigratedHome/maxazure/projects/CodexView`
|
|
7
|
-
- **Primary consumer**: `~/Projects/agentweb`
|
|
8
|
-
|
|
9
|
-
## 1. 目标与非目标
|
|
10
|
-
|
|
11
|
-
### 1.1 目标
|
|
12
|
-
|
|
13
|
-
构建一个独立的 React 组件库 `codexview`,把 OpenAI Codex CLI 的对话事件流渲染成聊天式 transcript,具备:
|
|
14
|
-
|
|
15
|
-
- 多种 item 类型的 UI(文本消息、reasoning、function call、shell exec、MCP 调用、web search、patch apply、未知类型兑底)
|
|
16
|
-
- 会话级状态显示(正在工作 / 已完成 / 已停止 / 失败)+ 实时动画
|
|
17
|
-
- 可被 agentweb 一行代码引入;同时支持其他类似宿主复用
|
|
18
|
-
- 主题化通过 CSS variables 实现,与宿主样式无冲突
|
|
19
|
-
- 内部有完善的 reducer + fixture 测试,保证健壮
|
|
20
|
-
|
|
21
|
-
### 1.2 非目标
|
|
22
|
-
|
|
23
|
-
- **不**解析 Codex 原生 rollout JSONL 文件(`~/.codex/sessions/.../rollout-*.jsonl`)。Agentweb 后端 [`backend/src/codex/eventMap.ts`](file:///Users/maxazure/Projects/agentweb/backend/src/codex/eventMap.ts) 已经把它标准化成 `ChatStreamEvent`,本组件以这个标准化事件为输入。
|
|
24
|
-
- **不**实现 SSE 接收 / 网络层 / 状态持久化 —— 这是宿主的职责。
|
|
25
|
-
- **不**实现 composer(输入框)、会话列表、文件预览。仅渲染聊天主体(transcript)。
|
|
26
|
-
- **不**实现虚拟列表 v1。暴露 `maxItems` prop 让宿主裁剪历史。
|
|
27
|
-
- **不**实现可访问性以外的 i18n / RTL / 主题切换器(v1)。
|
|
28
|
-
|
|
29
|
-
## 2. 仓库与技术栈
|
|
30
|
-
|
|
31
|
-
### 2.1 目录结构
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
CodexView/
|
|
35
|
-
├── package.json
|
|
36
|
-
├── tsconfig.json # 严格模式 + noUncheckedIndexAccess
|
|
37
|
-
├── tsup.config.ts # 构建配置
|
|
38
|
-
├── vitest.config.ts # 测试配置
|
|
39
|
-
├── README.md
|
|
40
|
-
├── docs/
|
|
41
|
-
│ ├── api.md # 权威 API 参考(手写)
|
|
42
|
-
│ ├── events.md # ChatStreamEvent 输入契约
|
|
43
|
-
│ ├── styling.md # CSS variables 全清单
|
|
44
|
-
│ ├── integration-agentweb.md # agentweb 集成手册
|
|
45
|
-
│ ├── changelog.md
|
|
46
|
-
│ └── superpowers/specs/
|
|
47
|
-
│ └── 2026-05-15-codexview-design.md # 本文件
|
|
48
|
-
├── src/
|
|
49
|
-
│ ├── index.ts # 主入口:re-export 公共 API
|
|
50
|
-
│ ├── types/
|
|
51
|
-
│ │ ├── events.ts # ChatStreamEvent / NormalizedItem 类型
|
|
52
|
-
│ │ ├── model.ts # TranscriptModel / TurnView / ItemView
|
|
53
|
-
│ │ └── theme.ts # CSS variables 字典作为 TS 类型
|
|
54
|
-
│ ├── reducer/
|
|
55
|
-
│ │ ├── transcript.ts # events[] → TranscriptModel
|
|
56
|
-
│ │ ├── transcript.test.ts # 表驱动单测
|
|
57
|
-
│ │ ├── property.test.ts # 性质测试:增量 vs 全量等价
|
|
58
|
-
│ │ └── status.ts # inferStatus(model)
|
|
59
|
-
│ ├── hooks/
|
|
60
|
-
│ │ ├── useCodexTranscript.ts # 主 hook
|
|
61
|
-
│ │ ├── useCodexTranscript.test.ts
|
|
62
|
-
│ │ ├── useSmoothStream.ts # 平滑打字机效果
|
|
63
|
-
│ │ └── useSmoothStream.test.ts
|
|
64
|
-
│ ├── components/
|
|
65
|
-
│ │ ├── CodexTranscript.tsx # 主组件
|
|
66
|
-
│ │ ├── CodexTranscript.module.css
|
|
67
|
-
│ │ ├── StatusBar.tsx + .module.css
|
|
68
|
-
│ │ ├── TurnContainer.tsx + .module.css
|
|
69
|
-
│ │ ├── MessageBubble.tsx + .module.css
|
|
70
|
-
│ │ ├── ReasoningBlock.tsx + .module.css
|
|
71
|
-
│ │ ├── ToolCallBlock.tsx + .module.css
|
|
72
|
-
│ │ ├── ExecBlock.tsx + .module.css
|
|
73
|
-
│ │ ├── SearchBlock.tsx + .module.css
|
|
74
|
-
│ │ ├── PatchBlock.tsx + .module.css
|
|
75
|
-
│ │ ├── RawEventBlock.tsx + .module.css
|
|
76
|
-
│ │ ├── ItemErrorBoundary.tsx
|
|
77
|
-
│ │ └── icons.ts # lucide-react 别名
|
|
78
|
-
│ └── styles/
|
|
79
|
-
│ ├── reset.module.css # 局部 reset,仅作用 .codexview-root
|
|
80
|
-
│ └── tokens.css # CSS variables 默认值(亮色)
|
|
81
|
-
├── fixtures/
|
|
82
|
-
│ ├── short-chat.jsonl
|
|
83
|
-
│ ├── tool-heavy.jsonl
|
|
84
|
-
│ ├── mcp-flow.jsonl
|
|
85
|
-
│ ├── failed-turn.jsonl
|
|
86
|
-
│ ├── aborted-turn.jsonl
|
|
87
|
-
│ ├── unknown-types.jsonl
|
|
88
|
-
│ └── README.md # 说明 fixture 来源与匿名化规则
|
|
89
|
-
└── dev/ # 本地 demo(vite SPA)
|
|
90
|
-
├── index.html
|
|
91
|
-
├── main.tsx
|
|
92
|
-
├── App.tsx # 选择 fixture + 渲染组件
|
|
93
|
-
└── vite.config.ts
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### 2.2 技术栈
|
|
97
|
-
|
|
98
|
-
| 项 | 选择 | 理由 |
|
|
99
|
-
|---|---|---|
|
|
100
|
-
| UI 框架 | React 18.3 | 与 agentweb 一致 |
|
|
101
|
-
| 语言 | TypeScript 5.5 strict + `noUncheckedIndexAccess` | 严格类型保证 reducer 不变式 |
|
|
102
|
-
| 打包 | tsup(基于 esbuild) | 零配置出 ESM + d.ts,CSS 单独发出 |
|
|
103
|
-
| 测试 | vitest + @testing-library/react + jsdom | 与 agentweb 一致,复用心智 |
|
|
104
|
-
| 图标 | lucide-react(peerDependency) | 行业事实标准;agentweb 加上后体积小、tree-shake |
|
|
105
|
-
| 样式 | CSS Modules + CSS variables | 与 agentweb tokens.css 体系对接,零运行时 |
|
|
106
|
-
| 状态 | useReducer / useMemo(无外部状态库) | 组件库不应强制宿主用 Jotai |
|
|
107
|
-
| Dev 环境 | vite + 本地 fixture | 不依赖 agentweb 跑起来 |
|
|
108
|
-
|
|
109
|
-
### 2.3 包结构与发布
|
|
110
|
-
|
|
111
|
-
```jsonc
|
|
112
|
-
{
|
|
113
|
-
"name": "codexview",
|
|
114
|
-
"version": "0.1.0",
|
|
115
|
-
"type": "module",
|
|
116
|
-
"exports": {
|
|
117
|
-
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
|
|
118
|
-
"./styles.css": "./dist/styles.css"
|
|
119
|
-
},
|
|
120
|
-
"files": ["dist/", "README.md", "docs/"],
|
|
121
|
-
"sideEffects": ["**/*.css"],
|
|
122
|
-
"peerDependencies": {
|
|
123
|
-
"react": "^18.3.0",
|
|
124
|
-
"react-dom": "^18.3.0",
|
|
125
|
-
"lucide-react": "^0.400.0"
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
集成阶段:
|
|
131
|
-
- **Phase A(开发期)**:agentweb 用 `pnpm add file:../CodexView` 本地 link
|
|
132
|
-
- **Phase B(接口稳定后)**:发布到 npm 或 GitHub Packages,agentweb 改为版本依赖
|
|
133
|
-
|
|
134
|
-
## 3. 公共 API
|
|
135
|
-
|
|
136
|
-
### 3.1 主组件
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
export interface CodexTranscriptProps {
|
|
140
|
-
/** 受控的事件序列。append-only 与全量替换都受支持。 */
|
|
141
|
-
events: ChatStreamEvent[];
|
|
142
|
-
|
|
143
|
-
/** 显式覆盖 inferStatus(model) 的结果。SSE 异常断开时由宿主传 'stopped'。 */
|
|
144
|
-
status?: TranscriptStatus;
|
|
145
|
-
|
|
146
|
-
/** 显式错误信息(与 status='failed'/'stopped' 配合)。会显示在 StatusBar。 */
|
|
147
|
-
error?: { message: string; details?: string };
|
|
148
|
-
|
|
149
|
-
/** 透传到 .codexview-root 容器的额外 className。 */
|
|
150
|
-
className?: string;
|
|
151
|
-
|
|
152
|
-
/** 限制渲染最近 N 条 ItemView;超过时顶部显示 "省略 X 条" hint。默认不裁剪。 */
|
|
153
|
-
maxItems?: number;
|
|
154
|
-
|
|
155
|
-
/** events 为空时显示的内容。默认是一行灰字。 */
|
|
156
|
-
emptyState?: ReactNode;
|
|
157
|
-
|
|
158
|
-
/** 任意 ItemView 被点击时回调。语义由宿主决定(v1 自带空实现)。 */
|
|
159
|
-
onItemClick?: (itemId: string) => void;
|
|
160
|
-
|
|
161
|
-
/** 局部替换某种 item 块的渲染。未提供则用内置组件。 */
|
|
162
|
-
components?: Partial<{
|
|
163
|
-
StatusBar: ComponentType<StatusBarProps>;
|
|
164
|
-
MessageBubble: ComponentType<MessageBubbleProps>;
|
|
165
|
-
ReasoningBlock: ComponentType<ReasoningBlockProps>;
|
|
166
|
-
ToolCallBlock: ComponentType<ToolCallBlockProps>;
|
|
167
|
-
ExecBlock: ComponentType<ExecBlockProps>;
|
|
168
|
-
SearchBlock: ComponentType<SearchBlockProps>;
|
|
169
|
-
PatchBlock: ComponentType<PatchBlockProps>;
|
|
170
|
-
RawEventBlock: ComponentType<RawEventBlockProps>;
|
|
171
|
-
}>;
|
|
172
|
-
|
|
173
|
-
/** 关闭 useSmoothStream 平滑打字机(默认开启)。低端设备或测试可关。 */
|
|
174
|
-
disableSmoothStream?: boolean;
|
|
175
|
-
|
|
176
|
-
/** reducer 内部异常上报。返回的 model 是异常前的最新可用状态。 */
|
|
177
|
-
onInternalError?: (err: unknown, event?: ChatStreamEvent) => void;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export const CodexTranscript: FC<CodexTranscriptProps>;
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### 3.2 子组件(escape hatch)
|
|
184
|
-
|
|
185
|
-
每个都是独立 export,参数最小化。详细 props 写在 [docs/api.md](../../api.md)。
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
export const StatusBar: FC<StatusBarProps>;
|
|
189
|
-
export const TurnContainer: FC<{ turn: TurnView; children: ReactNode }>;
|
|
190
|
-
export const MessageBubble: FC<MessageBubbleProps>;
|
|
191
|
-
export const ReasoningBlock: FC<ReasoningBlockProps>;
|
|
192
|
-
export const ToolCallBlock: FC<ToolCallBlockProps>;
|
|
193
|
-
export const ExecBlock: FC<ExecBlockProps>;
|
|
194
|
-
export const SearchBlock: FC<SearchBlockProps>;
|
|
195
|
-
export const PatchBlock: FC<PatchBlockProps>;
|
|
196
|
-
export const RawEventBlock: FC<RawEventBlockProps>;
|
|
197
|
-
export const ItemErrorBoundary: FC<{ fallback?: ReactNode; children: ReactNode }>;
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### 3.3 Hook 与纯函数
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
/**
|
|
204
|
-
* 主 hook:把 events + 可选 status 转换为 (model, derivedStatus)。
|
|
205
|
-
* 内部使用 useMemo 与增量 reduce;events 是 append-only 时性能最佳。
|
|
206
|
-
*/
|
|
207
|
-
export function useCodexTranscript(
|
|
208
|
-
events: ChatStreamEvent[],
|
|
209
|
-
options?: { status?: TranscriptStatus; onInternalError?: (e: unknown) => void },
|
|
210
|
-
): { model: TranscriptModel; status: TranscriptStatus };
|
|
211
|
-
|
|
212
|
-
/** 平滑打字机效果。返回当前应该显示的字符串。 */
|
|
213
|
-
export function useSmoothStream(
|
|
214
|
-
fullText: string,
|
|
215
|
-
options?: { enabled?: boolean; charsPerFrame?: number; minDelayMs?: number },
|
|
216
|
-
): string;
|
|
217
|
-
|
|
218
|
-
/** events[] → TranscriptModel 的纯函数 reducer(暴露用于 SSR / 测试 / 自定义 hook)。 */
|
|
219
|
-
export function reduceTranscript(prev: TranscriptModel, event: ChatStreamEvent): TranscriptModel;
|
|
220
|
-
|
|
221
|
-
/** 从 model 推断会话状态。等价于 useCodexTranscript 内部规则。 */
|
|
222
|
-
export function inferStatus(model: TranscriptModel): TranscriptStatus;
|
|
223
|
-
|
|
224
|
-
/** 空 model(reducer 初始状态)。 */
|
|
225
|
-
export const EMPTY_MODEL: TranscriptModel;
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### 3.4 对外类型
|
|
229
|
-
|
|
230
|
-
```ts
|
|
231
|
-
export type ChatStreamEvent; // 输入事件联合类型,详见 §4.1
|
|
232
|
-
export type TranscriptModel;
|
|
233
|
-
export type TurnView;
|
|
234
|
-
export type ItemView;
|
|
235
|
-
export type ItemKind;
|
|
236
|
-
export type ItemStatus;
|
|
237
|
-
export type TranscriptStatus;
|
|
238
|
-
export type TokenUsage;
|
|
239
|
-
export type SearchResult;
|
|
240
|
-
export type PatchFile;
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## 4. 数据模型与 reducer
|
|
244
|
-
|
|
245
|
-
### 4.1 输入契约:`ChatStreamEvent`
|
|
246
|
-
|
|
247
|
-
CodexView 接受的事件类型来自 agentweb [`backend/src/codex/eventMap.ts`](file:///Users/maxazure/Projects/agentweb/backend/src/codex/eventMap.ts) 中的 `NormalizedEvent`。本组件**不**直接耦合 agentweb 类型,而是在 `src/types/events.ts` 中重新声明一个等价的 `ChatStreamEvent` 类型,作为契约边界。
|
|
248
|
-
|
|
249
|
-
```ts
|
|
250
|
-
export type ChatStreamEvent =
|
|
251
|
-
// 会话/轮次生命周期
|
|
252
|
-
| { type: 'thread_started'; threadId: string; at: number }
|
|
253
|
-
| { type: 'turn_started'; turnId: string; at: number }
|
|
254
|
-
| { type: 'turn_completed'; turnId: string; at: number; usage?: TokenUsage }
|
|
255
|
-
| { type: 'turn_failed'; turnId: string; at: number; error: { message: string; code?: string } }
|
|
256
|
-
| { type: 'turn_aborted'; turnId: string; at: number; reason?: string }
|
|
257
|
-
|
|
258
|
-
// 消息与思考
|
|
259
|
-
| { type: 'user_message'; turnId: string; itemId: string; text: string; at: number }
|
|
260
|
-
| { type: 'agent_message'; turnId: string; itemId: string; text: string; partial: boolean; at: number }
|
|
261
|
-
| { type: 'reasoning'; turnId: string; itemId: string; text: string; partial: boolean; at: number }
|
|
262
|
-
|
|
263
|
-
// 工具调用(成对到达)
|
|
264
|
-
| { type: 'function_call'; turnId: string; callId: string; name: string; args: unknown; at: number }
|
|
265
|
-
| { type: 'function_call_output'; turnId: string; callId: string; output?: unknown; error?: string; at: number }
|
|
266
|
-
|
|
267
|
-
// Shell 执行
|
|
268
|
-
| { type: 'exec_command_begin'; turnId: string; callId: string; command: string; at: number }
|
|
269
|
-
| { type: 'exec_command_end'; turnId: string; callId: string; exit: number; stdout: string; stderr: string; durationMs: number; at: number }
|
|
270
|
-
|
|
271
|
-
// MCP / 自定义工具(与 function_call 同结构,type 区分)
|
|
272
|
-
| { type: 'mcp_tool_call'; turnId: string; callId: string; server: string; name: string; args: unknown; at: number }
|
|
273
|
-
| { type: 'mcp_tool_call_output'; turnId: string; callId: string; output?: unknown; error?: string; at: number }
|
|
274
|
-
|
|
275
|
-
// Web search
|
|
276
|
-
| { type: 'web_search_call'; turnId: string; callId: string; query: string; at: number }
|
|
277
|
-
| { type: 'web_search_end'; turnId: string; callId: string; results: SearchResult[]; at: number }
|
|
278
|
-
|
|
279
|
-
// Patch apply
|
|
280
|
-
| { type: 'patch_apply_end'; turnId: string; callId: string; files: PatchFile[]; ok: boolean; at: number }
|
|
281
|
-
|
|
282
|
-
// 未知 / 兑底
|
|
283
|
-
| { type: 'raw'; turnId?: string; itemId?: string; payload: unknown; at: number };
|
|
284
|
-
|
|
285
|
-
export interface TokenUsage {
|
|
286
|
-
inputTokens: number;
|
|
287
|
-
outputTokens: number;
|
|
288
|
-
cachedInputTokens?: number;
|
|
289
|
-
reasoningOutputTokens?: number;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
export interface SearchResult { title: string; url: string; snippet?: string }
|
|
293
|
-
|
|
294
|
-
export interface PatchFile {
|
|
295
|
-
path: string;
|
|
296
|
-
status: 'added' | 'modified' | 'deleted';
|
|
297
|
-
diff?: string; // 统一 git diff 文本
|
|
298
|
-
}
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
不在上面列出的、agentweb 未来可能新增的事件类型,本组件以"无法识别"处理:reducer 落入 `kind: 'raw'` ItemView,UI 用 `RawEventBlock` 兑底。
|
|
302
|
-
|
|
303
|
-
### 4.2 视图模型
|
|
304
|
-
|
|
305
|
-
```ts
|
|
306
|
-
export type ItemStatus = 'pending' | 'running' | 'completed' | 'failed' | 'stopped';
|
|
307
|
-
export type ItemKind = 'user_message' | 'reasoning' | 'assistant_text'
|
|
308
|
-
| 'tool_call' | 'exec' | 'search' | 'patch' | 'raw';
|
|
309
|
-
|
|
310
|
-
export interface ItemViewBase {
|
|
311
|
-
id: string; // 稳定 ID(来自 itemId 或 callId 或合成)
|
|
312
|
-
kind: ItemKind;
|
|
313
|
-
status: ItemStatus;
|
|
314
|
-
startedAt: number;
|
|
315
|
-
updatedAt: number;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export type ItemView =
|
|
319
|
-
| ItemViewBase & { kind: 'user_message'; text: string }
|
|
320
|
-
| ItemViewBase & { kind: 'reasoning'; text: string }
|
|
321
|
-
| ItemViewBase & { kind: 'assistant_text'; text: string }
|
|
322
|
-
| ItemViewBase & { kind: 'tool_call'; name: string; args: unknown; result?: unknown; error?: string }
|
|
323
|
-
| ItemViewBase & { kind: 'exec'; command: string; exit?: number; stdout?: string; stderr?: string; durationMs?: number }
|
|
324
|
-
| ItemViewBase & { kind: 'search'; query: string; results?: SearchResult[] }
|
|
325
|
-
| ItemViewBase & { kind: 'patch'; files: PatchFile[]; ok?: boolean }
|
|
326
|
-
| ItemViewBase & { kind: 'raw'; payload: unknown };
|
|
327
|
-
|
|
328
|
-
export interface TurnView {
|
|
329
|
-
turnId: string;
|
|
330
|
-
startedAt: number;
|
|
331
|
-
completedAt?: number;
|
|
332
|
-
status: 'running' | 'completed' | 'failed' | 'aborted';
|
|
333
|
-
items: ItemView[]; // 按时间顺序
|
|
334
|
-
usage?: TokenUsage;
|
|
335
|
-
error?: { message: string; code?: string };
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
export interface TranscriptModel {
|
|
339
|
-
threadId?: string;
|
|
340
|
-
turns: TurnView[]; // 按时间顺序
|
|
341
|
-
lastEventAt: number;
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### 4.3 reducer 的合并语义
|
|
346
|
-
|
|
347
|
-
| 输入事件 | reducer 行为 |
|
|
348
|
-
|---|---|
|
|
349
|
-
| `thread_started` | 设置 `model.threadId` |
|
|
350
|
-
| `turn_started` | 追加新 `TurnView { status: 'running' }` |
|
|
351
|
-
| `turn_completed` | 把对应 turn 标记为 `completed`、写入 `usage`,把所有 item 中状态为 `running`/`pending` 的全部翻转为 `completed` |
|
|
352
|
-
| `turn_failed` | turn 标 `failed`、写 `error`,未完成 item 翻转为 `failed` |
|
|
353
|
-
| `turn_aborted` | turn 标 `aborted`,未完成 item 翻转为 `stopped` |
|
|
354
|
-
| `user_message` | 追加 `kind: 'user_message'` ItemView,`status: 'completed'` |
|
|
355
|
-
| `agent_message` | 在事件指定的 `turnId` 内找同 `itemId` 的 `assistant_text` ItemView:存在则更新 `text`,根据 `partial` 决定 `status`(true → running,false → completed);不存在则新建 |
|
|
356
|
-
| `reasoning` | 同 `agent_message`,但 `kind: 'reasoning'`。**不**与 `agent_message` 合并 |
|
|
357
|
-
| `function_call` / `mcp_tool_call` | 在事件指定的 `turnId` 内追加 `kind: 'tool_call'` ItemView,`status: 'pending'` |
|
|
358
|
-
| `function_call_output` / `mcp_tool_call_output` | 在事件指定的 `turnId` 内找同 `callId` 的 `tool_call`:写入 `result` / `error`,状态翻转 `completed` 或 `failed` |
|
|
359
|
-
| `exec_command_begin` | 在事件指定的 `turnId` 内追加 `kind: 'exec'` ItemView,`status: 'running'` |
|
|
360
|
-
| `exec_command_end` | 在事件指定的 `turnId` 内找同 `callId` 的 `exec`:写入 `exit/stdout/stderr/durationMs`,状态 `exit === 0 ? completed : failed` |
|
|
361
|
-
| `web_search_call` | 在事件指定的 `turnId` 内追加 `kind: 'search'`,`status: 'pending'` |
|
|
362
|
-
| `web_search_end` | 在事件指定的 `turnId` 内找同 `callId` 的 `search`:写入 `results`,状态 `completed` |
|
|
363
|
-
| `patch_apply_end` | 追加 `kind: 'patch'` ItemView,`status: ok ? completed : failed` |
|
|
364
|
-
| `raw` 或未知 type | 追加 `kind: 'raw'` ItemView,`status: 'completed'` |
|
|
365
|
-
|
|
366
|
-
**不变式**(必须在 property test 中验证):
|
|
367
|
-
|
|
368
|
-
1. 任何 `events: ChatStreamEvent[]`,`events.reduce(reduceTranscript, EMPTY_MODEL)` **不抛错**
|
|
369
|
-
2. 对同一 events 序列,**全量 reduce 一次** 与 **逐个增量 reduce N 次**,最终 model 严格相等(深比较)
|
|
370
|
-
3. reducer 是纯函数:相同输入相同输出,不读不写外部状态
|
|
371
|
-
4. reducer 不持有 events 数组的引用;外部 mutate events 不影响已计算 model
|
|
372
|
-
5. 收到未知事件类型时不丢失任何字段(payload 完整保留在 `kind: 'raw'` ItemView)
|
|
373
|
-
|
|
374
|
-
### 4.4 性能与增量
|
|
375
|
-
|
|
376
|
-
`useCodexTranscript` 内部缓存上次的 `(eventsRef, model)`:
|
|
377
|
-
- 若新 events 是旧 events 的前缀加新元素(`events.slice(0, prev.length) === prev`),只 reduce 新增部分
|
|
378
|
-
- 否则全量重 reduce
|
|
379
|
-
- 这覆盖了 agentweb 的两种使用模式(增量 SSE + 历史一次性加载),无需配置
|
|
380
|
-
|
|
381
|
-
## 5. 状态机
|
|
382
|
-
|
|
383
|
-
### 5.1 Item 级(5 态)
|
|
384
|
-
|
|
385
|
-
```
|
|
386
|
-
pending ─────────┐
|
|
387
|
-
│ │
|
|
388
|
-
(开始执行) │
|
|
389
|
-
▼ │ (turn_aborted)
|
|
390
|
-
running ──────────┤
|
|
391
|
-
/ | \ │
|
|
392
|
-
(output 来了) | (失败) (turn_aborted)
|
|
393
|
-
▼ ▼ │
|
|
394
|
-
completed failed stopped
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
适用范围:`tool_call` / `exec` / `search`。
|
|
398
|
-
|
|
399
|
-
文本类 (`assistant_text` / `reasoning`):仅 `running` (partial=true) 与 `completed` (partial=false) 两态;`turn_failed` / `turn_aborted` 时仍然映射到 `failed` / `stopped`。
|
|
400
|
-
|
|
401
|
-
`user_message` / `patch`:始终 `completed` 或 `failed`(一次性事件)。
|
|
402
|
-
|
|
403
|
-
### 5.2 Turn 级(4 态)
|
|
404
|
-
|
|
405
|
-
```
|
|
406
|
-
running ─── turn_completed ──▶ completed
|
|
407
|
-
│
|
|
408
|
-
├─── turn_failed ─────────▶ failed
|
|
409
|
-
│
|
|
410
|
-
└─── turn_aborted ────────▶ aborted
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
### 5.3 会话级(5 态,由 inferStatus 推断)
|
|
414
|
-
|
|
415
|
-
```
|
|
416
|
-
type TranscriptStatus = 'idle' | 'working' | 'completed' | 'stopped' | 'failed';
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
推断规则(按顺序,第一条命中即返回):
|
|
420
|
-
|
|
421
|
-
1. `model.turns.length === 0` → `idle`
|
|
422
|
-
2. 最后一个 turn 的 `status === 'running'` → `working`
|
|
423
|
-
3. 最后一个 turn 的 `status === 'failed'` → `failed`
|
|
424
|
-
4. 最后一个 turn 的 `status === 'aborted'` → `stopped`
|
|
425
|
-
5. 最后一个 turn 的 `status === 'completed'` → `completed`
|
|
426
|
-
|
|
427
|
-
外部覆盖:`props.status` 一旦传入,**直接采用**,跳过推断(典型场景:SSE 网络断开,宿主主动判定 `stopped`)。
|
|
428
|
-
|
|
429
|
-
## 6. 渲染规则
|
|
430
|
-
|
|
431
|
-
### 6.1 整体布局
|
|
432
|
-
|
|
433
|
-
```
|
|
434
|
-
.codexview-root (CSS reset + 字体)
|
|
435
|
-
└── <StatusBar /> sticky top, 仅 status !== 'idle' 时显示
|
|
436
|
-
└── <ol> (semantic, role="log", aria-live="polite")
|
|
437
|
-
└── <li><TurnContainer turn={turn}>
|
|
438
|
-
├── <MessageBubble /> (user_message,贴右、无时间轴)
|
|
439
|
-
├── ...assistant items 共享左侧时间轴竖线...
|
|
440
|
-
│ ├── <ReasoningBlock />
|
|
441
|
-
│ ├── <ToolCallBlock />
|
|
442
|
-
│ ├── <ExecBlock />
|
|
443
|
-
│ ├── <SearchBlock />
|
|
444
|
-
│ ├── <PatchBlock />
|
|
445
|
-
│ ├── <MessageBubble /> (assistant_text)
|
|
446
|
-
│ └── <RawEventBlock />
|
|
447
|
-
└── ...
|
|
448
|
-
</TurnContainer></li>
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
`TurnContainer` 的 assistant 区段用 `border-left: 2px solid var(--cv-axis-color)` + `padding-left: 16px` 形成时间轴。每个 item 用 `::before` 渲染 8px 圆点。
|
|
452
|
-
|
|
453
|
-
### 6.2 各 ItemKind 的渲染惯例
|
|
454
|
-
|
|
455
|
-
| 类型 | 默认显示 | 折叠规则 | 关键视觉 |
|
|
456
|
-
|---|---|---|---|
|
|
457
|
-
| `user_message` | 完整气泡,贴右 | 不折叠 | `var(--cv-bg-user-bubble)` |
|
|
458
|
-
| `assistant_text` | 完整气泡,贴左 | 不折叠 | running 时末尾 `▋` blink;启用 useSmoothStream |
|
|
459
|
-
| `reasoning` | `<details>` 折叠,header "💭 思考中..." 或 "💭 思考 (X.Xs)" | 默认折叠 | 灰色斜体;展开后用 useSmoothStream 渲染 |
|
|
460
|
-
| `tool_call` | header 一行:`<icon> getName(name) status` | 参数默认展开(紧凑键值对列表,单行 max 60 字符截断+悬停 tooltip 全文);result 默认折叠(触发条件:序列化后字符串 > 500 字符 **或** > 4 行 **或** 非标量 JSON 结构 > 3 层深度) | 状态色:pending 中性 / running 蓝 / completed 绿 / failed 红 |
|
|
461
|
-
| `exec` | header 一行:`$ command` + `(exit X, Yms)` | command 展开;stdout/stderr 默认折叠(同 tool_call 阈值) | 等宽字体、暗色背景;stderr 红字 |
|
|
462
|
-
| `search` | header 一行:`🔍 query` | query 展开;结果列表默认显示前 3,余 N 折叠 | 链接蓝 |
|
|
463
|
-
| `patch` | header 一行:`📝 N files (added/modified/deleted)` | 文件名列表展开;每个 diff 默认折叠 | git-diff 配色:绿/红行 |
|
|
464
|
-
| `raw` | header 一行:`Unknown event: <type>` | 默认折叠的 `<details>`,展开为 `<pre>` JSON | 暗色 chip 提示 |
|
|
465
|
-
|
|
466
|
-
### 6.3 工具命名与图标
|
|
467
|
-
|
|
468
|
-
`src/components/icons.ts` 用 lucide-react 别名导出:
|
|
469
|
-
|
|
470
|
-
```ts
|
|
471
|
-
import { Wrench, Terminal, FileEdit, Search, Globe, AlertCircle, Sparkles, MessageSquare } from 'lucide-react';
|
|
472
|
-
export const ICONS = { tool: Wrench, exec: Terminal, patch: FileEdit, search: Search, web: Globe, error: AlertCircle, reasoning: Sparkles, message: MessageSquare };
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
`src/components/ToolCallBlock.tsx` 内部一个 `getToolPhrase(name, args)` 函数,按已知工具名生成可读 label(参考 Proma `tool-phrase.ts`)。未知工具回退到 `name`。
|
|
476
|
-
|
|
477
|
-
### 6.4 状态动画(无 spinner)
|
|
478
|
-
|
|
479
|
-
| 层级 | 动画 | CSS 实现 |
|
|
480
|
-
|---|---|---|
|
|
481
|
-
| Turn 级 StatusBar | `working` 时 6×6 脉冲圆点 | `@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.4} }` |
|
|
482
|
-
| Item 时间轴节点 | item.status === 'running' 时 8px 圆环旋转 | `@keyframes ring { to { transform: rotate(360deg) } }` |
|
|
483
|
-
| 长 exec 的输出区 | exec.status === 'running' 时,在 header 下方显示一条 4px 高度全宽 shimmer 条(linear-gradient 滑动),exec_command_end 到达后立即移除 | `@keyframes shimmer { from{background-pos:-200%} to{background-pos:200%} }`,配合 `linear-gradient(90deg, transparent, var(--cv-shimmer-color), transparent)` 与 200% background-size |
|
|
484
|
-
| assistant_text 文字末端 | running 时追加 `▋` 字符 + 1s blink | `@keyframes blink { 50% { opacity: 0 } } ::after { content: '▋' }` |
|
|
485
|
-
|
|
486
|
-
所有动画都是纯 CSS。`prefers-reduced-motion: reduce` 时全部退化为静态。
|
|
487
|
-
|
|
488
|
-
### 6.5 useSmoothStream
|
|
489
|
-
|
|
490
|
-
参考 [Proma packages/ui/src/hooks/useSmoothStream.ts:58-191](file:///Users/maxazure/Projects/Proma/packages/ui/src/hooks/useSmoothStream.ts) 的策略,简化后实现:
|
|
491
|
-
|
|
492
|
-
- 用 `Intl.Segmenter` 按 grapheme 切分(兼容 emoji 与 CJK)
|
|
493
|
-
- `requestAnimationFrame` 驱动;每帧追加 `Math.max(1, Math.ceil(remaining / 8))` 个 grapheme
|
|
494
|
-
- `partial: false` 后,每帧除以 4 加速排空
|
|
495
|
-
- `disableSmoothStream: true` 或 `prefers-reduced-motion` 时直接返回 `fullText`
|
|
496
|
-
|
|
497
|
-
## 7. 错误处理与降级
|
|
498
|
-
|
|
499
|
-
| 错误类别 | 来源 | 降级 |
|
|
500
|
-
|---|---|---|
|
|
501
|
-
| 未知 event.type / payload.type | Codex 升级、agentweb 新事件 | reducer 落 `kind: 'raw'`,UI 用 `RawEventBlock`(折叠 JSON) |
|
|
502
|
-
| 必填字段缺失(function_call 没 callId) | 上游 bug | reducer 用合成 ID `synth-${index}`,加 warning chip |
|
|
503
|
-
| reducer 内部异常 | 我们的 bug | try/catch;保留上一个有效 model;调用 `onInternalError(err, event)`;继续处理后续 events |
|
|
504
|
-
| 单个 item 渲染崩溃 | 渲染 bug 或恶意 payload | `<ItemErrorBoundary>` 包裹每个 item;崩溃后回退到 `<RawEventBlock payload={item} />` |
|
|
505
|
-
| SSE 断开 | 网络 | 宿主传 `status='stopped'` + `error={message}`;StatusBar 显示失败状态 |
|
|
506
|
-
| 空 events | 首次加载 | 渲染 `emptyState` prop(默认灰字"暂无对话") |
|
|
507
|
-
|
|
508
|
-
## 8. 测试策略
|
|
509
|
-
|
|
510
|
-
### 8.1 测试矩阵
|
|
511
|
-
|
|
512
|
-
| 测试类型 | 路径 | 覆盖目标 | 验收 |
|
|
513
|
-
|---|---|---|---|
|
|
514
|
-
| reducer 表驱动单测 | `src/reducer/transcript.test.ts` | 每种事件类型至少一条 case | 行覆盖 100% |
|
|
515
|
-
| reducer 性质测试 | `src/reducer/property.test.ts` | 增量 vs 全量等价、reducer 纯净 | 5 个 fixture 全通过 |
|
|
516
|
-
| inferStatus 单测 | `src/reducer/status.ts` 同名 .test.ts | 5 态推断 | 行覆盖 100% |
|
|
517
|
-
| 各 Block RTL | `src/components/*.test.tsx` | 折叠/展开、状态颜色、a11y role | 每个组件至少 3 case |
|
|
518
|
-
| useSmoothStream | `src/hooks/useSmoothStream.test.ts` | RAF 行为、reduced-motion | fake timers |
|
|
519
|
-
| 回放集成测试 | `src/integration/replay.test.tsx` | 6 个 fixture 完整渲染 | 不抛错 + 关键 DOM 元素存在 |
|
|
520
|
-
|
|
521
|
-
### 8.2 fixtures
|
|
522
|
-
|
|
523
|
-
存放于 `fixtures/`,每个 `.jsonl` 一行一个 `ChatStreamEvent`。来源:从 `~/.codex/sessions/.../rollout-*.jsonl` 抽样后用脚本匿名化(替换 cwd / 用户名 / API key / URL 域名)。
|
|
524
|
-
|
|
525
|
-
| Fixture | 内容 | 用途 |
|
|
526
|
-
|---|---|---|
|
|
527
|
-
| `short-chat.jsonl` | 一个 turn,user_message + agent_message 各一条 | 最小路径 |
|
|
528
|
-
| `tool-heavy.jsonl` | 多 function_call + exec 混合 | 验证合并语义、状态翻转 |
|
|
529
|
-
| `mcp-flow.jsonl` | MCP 工具调用 + web_search | 验证 MCP 与 search |
|
|
530
|
-
| `failed-turn.jsonl` | turn_failed | 错误状态 |
|
|
531
|
-
| `aborted-turn.jsonl` | turn_aborted(用户中止) | 停止状态 |
|
|
532
|
-
| `unknown-types.jsonl` | 故意混入 `type: 'foobar'` 与未知 payload | 兑底 |
|
|
533
|
-
|
|
534
|
-
`fixtures/README.md` 说明匿名化规则与添加新 fixture 的流程。
|
|
535
|
-
|
|
536
|
-
### 8.3 a11y
|
|
537
|
-
|
|
538
|
-
- `<CodexTranscript>` 容器 `role="log"` `aria-live="polite"` `aria-relevant="additions text"`
|
|
539
|
-
- `StatusBar` `role="status"`
|
|
540
|
-
- 折叠 header `<button aria-expanded={open}>`
|
|
541
|
-
- 错误状态 `aria-invalid="true"`
|
|
542
|
-
- 测试中用 `@testing-library/react` 的 `getByRole` 而非 `getByTestId`
|
|
543
|
-
|
|
544
|
-
### 8.4 不做的事
|
|
545
|
-
|
|
546
|
-
- 不引入 visual regression(Chromatic / Percy)
|
|
547
|
-
- 不做 SSR 测试
|
|
548
|
-
- 不做跨浏览器(v1 假定现代 Chromium / Safari)
|
|
549
|
-
|
|
550
|
-
## 9. 文档契约
|
|
551
|
-
|
|
552
|
-
### 9.1 必交付清单
|
|
553
|
-
|
|
554
|
-
| 文件 | 作用 | 写完标准 |
|
|
555
|
-
|---|---|---|
|
|
556
|
-
| `README.md` | 5 分钟 quick start + cheatsheet | 含一个完整可运行示例(< 30 行) |
|
|
557
|
-
| `docs/api.md` | 权威 API 参考 | 每个 export 含签名 / 每个 prop 默认值 / 最小示例 / 副作用说明 |
|
|
558
|
-
| `docs/events.md` | ChatStreamEvent 输入契约 | 列出所有事件类型 + 字段 + reducer 行为 |
|
|
559
|
-
| `docs/styling.md` | CSS variables 全清单 | 含每个变量的默认值、推荐用途、覆盖示例 |
|
|
560
|
-
| `docs/integration-agentweb.md` | agentweb 集成手册 | 4 步走 + 回滚步骤 |
|
|
561
|
-
| `docs/changelog.md` | 每版变化 | 0.1.0 起按 keepachangelog 格式 |
|
|
562
|
-
|
|
563
|
-
### 9.2 强制规则
|
|
564
|
-
|
|
565
|
-
- 任何 `export` 没有 JSDoc 注释 → CI 不通过(用 ESLint `require-jsdoc` 规则在 v0.2 引入;v0.1 人工 review 把关)
|
|
566
|
-
- 任何新 export 没在 `docs/api.md` 出现 → 视为未完成
|
|
567
|
-
- README 必须有"开箱 60 秒上手"段落
|
|
568
|
-
|
|
569
|
-
## 10. agentweb 集成手册
|
|
570
|
-
|
|
571
|
-
### 10.1 替换步骤
|
|
572
|
-
|
|
573
|
-
1. 保留 agentweb 现有的 `streamingAtomFamily(sessionId)` 与 SSE 接收([`agentweb/frontend/src/codex/atoms/streaming.ts`](file:///Users/maxazure/Projects/agentweb/frontend/src/codex/atoms/streaming.ts))
|
|
574
|
-
2. 在 [`ChatThread.tsx`](file:///Users/maxazure/Projects/agentweb/frontend/src/codex/components/ChatThread.tsx) 内替换内部渲染:
|
|
575
|
-
|
|
576
|
-
```tsx
|
|
577
|
-
import { CodexTranscript } from 'codexview';
|
|
578
|
-
import 'codexview/styles.css';
|
|
579
|
-
|
|
580
|
-
const events = useAtomValue(streamingAtomFamily(sessionId));
|
|
581
|
-
return <CodexTranscript events={events.list} className="aw-codex-transcript" />;
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
3. 在 agentweb 全局 CSS 桥接 tokens:
|
|
585
|
-
|
|
586
|
-
```css
|
|
587
|
-
.aw-codex-transcript {
|
|
588
|
-
--cv-bg-user-bubble: var(--aw-bg-bubble-user);
|
|
589
|
-
--cv-bg-assistant-bubble: var(--aw-bg-bubble-bot);
|
|
590
|
-
--cv-text: var(--aw-text-primary);
|
|
591
|
-
--cv-axis-color: var(--aw-border-subtle);
|
|
592
|
-
--cv-radius: 12px;
|
|
593
|
-
/* ... 完整清单见 docs/styling.md */
|
|
594
|
-
}
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
4. 删除 agentweb 内的 `MessageBubble.tsx` / `StreamingBubble.tsx` / `ToolUseBlock.tsx`。同 PR 完成。**例外**:agentweb 现有的 approval 气泡逻辑(人类介入审批)**不**在 v1 codexview 范围内(见 §11),需要从 `StreamingBubble.tsx` 中拆出保留为独立的 `<ApprovalBubble>`,由 agentweb 自己继续维护,与 `<CodexTranscript>` 并排渲染。
|
|
598
|
-
|
|
599
|
-
### 10.2 契约边界
|
|
600
|
-
|
|
601
|
-
- agentweb 后端 [`backend/src/codex/eventMap.ts`](file:///Users/maxazure/Projects/agentweb/backend/src/codex/eventMap.ts) 的 `ChatStreamEvent` 类型应该和 codexview 的 `ChatStreamEvent` **结构等价**。
|
|
602
|
-
- 集成测试:在 agentweb 项目内加一个类型 assert(`type _check = AssertEqual<AgentwebChatStreamEvent, CodexChatStreamEvent>`),任一边新增字段都会编译失败提醒。
|
|
603
|
-
|
|
604
|
-
### 10.3 回滚
|
|
605
|
-
|
|
606
|
-
- v0.1.0 发现重大问题:git revert 替换 commit,恢复原 `MessageBubble`/`StreamingBubble`/`ToolUseBlock`
|
|
607
|
-
- 后端 `ChatStreamEvent` 与 SSE 端点不需要变化,回滚是纯前端
|
|
608
|
-
|
|
609
|
-
## 11. 不做的事(v1 显式范围外)
|
|
610
|
-
|
|
611
|
-
- 虚拟列表(`react-virtuoso` 等) —— v2 评估
|
|
612
|
-
- SSR / Next.js App Router 兼容(agentweb 是 Vite SPA)
|
|
613
|
-
- 国际化(i18n) —— UI 文案默认中文,extract 留 v2
|
|
614
|
-
- 暗色 / 亮色主题切换器 —— 通过 CSS variables 由宿主切换
|
|
615
|
-
- 自动生成 API 文档(TypeDoc) —— 手写权威 + JSDoc 已足够
|
|
616
|
-
- 文件预览(图片、视频、二进制) —— v2
|
|
617
|
-
- Composer / 输入框 —— 不在职责范围
|
|
618
|
-
- approval 气泡(人类介入审批) —— v2 评估
|
|
619
|
-
- 富 Markdown 渲染(表格、数学公式、Mermaid) —— assistant_text 当前用纯文本 + code-fence,富 markdown 看 v0.2 需求
|
|
620
|
-
- 链接预览 / OG 卡片 —— v2
|
|
621
|
-
|
|
622
|
-
## 12. 开放问题与决策日志
|
|
623
|
-
|
|
624
|
-
| # | 问题 | 决策 | 决策时间 |
|
|
625
|
-
|---|---|---|---|
|
|
626
|
-
| 1 | 数据源(原始 rollout vs ChatStreamEvent vs 兼) | ChatStreamEvent | 2026-05-15 |
|
|
627
|
-
| 2 | 仓库形态(独立包 vs monorepo 内 vs 暂定) | 独立 npm 包 | 2026-05-15 |
|
|
628
|
-
| 3 | API 形态(全包 vs hook + 积木 vs 双轨) | 全包 + 暴露子组件 | 2026-05-15 |
|
|
629
|
-
| 4 | 状态控制权与粒度 | 会话级从 events 推断 + 可被 prop 覆盖 | 2026-05-15 |
|
|
630
|
-
| 5 | 事件覆盖范围 | 核心 8 类 + RawEventBlock 兑底 | 2026-05-15 |
|
|
631
|
-
| 6 | 样式策略 | CSS Modules + CSS variables | 2026-05-15 |
|
|
632
|
-
| 7 | 文档形式 | 手写 docs/api.md + JSDoc,不用 TypeDoc | 2026-05-15 |
|
|
633
|
-
| 8 | API 待定项(components / maxItems / onItemClick) | 全部保留 | 2026-05-15 |
|
|
634
|
-
| 9 | 图标系统 | lucide-react peerDependency | 2026-05-15 |
|
|
635
|
-
| 10 | useSmoothStream 是否在 v1 | v1 包含,默认启用 | 2026-05-15 |
|
|
636
|
-
| 11 | 加载模式 | 全量 + 增量都支持 | 2026-05-15 |
|
|
637
|
-
| 12 | reasoning 是否合并进 assistant_text | 不合并;独立块;默认折叠 | 2026-05-15(research 修订) |
|
|
638
|
-
| 13 | 同 turn 多 item 渲染 | 分块渲染,左侧时间轴竖线串联 | 2026-05-15(research 修订) |
|
|
639
|
-
| 14 | item 级状态机 | 5 态:pending/running/completed/failed/stopped | 2026-05-15(research 修订) |
|
|
640
|
-
| 15 | 工具调用折叠默认 | 参数展开,结果折叠(>500 字符 / >4 行) | 2026-05-15(research 修订) |
|
|
641
|
-
|
|
642
|
-
## 13. 验收标准(v0.1.0 发布门槛)
|
|
643
|
-
|
|
644
|
-
- [ ] `pnpm build` 成功,`dist/` 含 `index.js` / `index.d.ts` / `styles.css`
|
|
645
|
-
- [ ] `pnpm test` 100% 通过
|
|
646
|
-
- [ ] reducer 行覆盖 = 100%;性质测试通过
|
|
647
|
-
- [ ] 6 个 fixture 在 dev/ SPA 中目视渲染正确
|
|
648
|
-
- [ ] `docs/api.md` 列出全部 export,每个含签名 + 示例
|
|
649
|
-
- [ ] `docs/integration-agentweb.md` 步骤可被新工程师独立完成
|
|
650
|
-
- [ ] 在 agentweb 项目内 `pnpm add file:../CodexView`,替换 ChatThread 渲染,agentweb 旧聊天功能无回归
|
|
651
|
-
- [ ] README 60 秒上手段落经新读者验证
|
|
652
|
-
|
|
653
|
-
## 14. 参考实现来源
|
|
654
|
-
|
|
655
|
-
- Proma:流式平滑、工具短语+图标+颜色三位一体、Jotai reducer 模式([Proma packages/ui/src/hooks/useSmoothStream.ts](file:///Users/maxazure/Projects/Proma/packages/ui/src/hooks/useSmoothStream.ts)、[Proma apps/electron/src/renderer/components/chat/ChatToolBlock.tsx](file:///Users/maxazure/Projects/Proma/apps/electron/src/renderer/components/chat/ChatToolBlock.tsx))
|
|
656
|
-
- Vercel AI SDK UI:parts[] 模型、useChat status 状态机、tool 4 态
|
|
657
|
-
- assistant-ui:makeAssistantToolUI 注册机制、ToolGroup / ToolFallback
|
|
658
|
-
- LangGraph agent-chat-ui:「输入展开 / 输出折叠」不对称默认、JSON 截断
|
|
659
|
-
- Aider / Continue.dev:read/write 工具二分配色、git-diff 视觉复用
|
|
660
|
-
- Claude Code:reasoning 独立块默认折叠的行业惯例
|
|
661
|
-
- AgentWeb 现有实现([backend/src/codex/eventMap.ts](file:///Users/maxazure/Projects/agentweb/backend/src/codex/eventMap.ts)、[backend/src/codex/codexChatRunner.ts](file:///Users/maxazure/Projects/agentweb/backend/src/codex/codexChatRunner.ts)、[frontend/src/codex/components/StreamingBubble.tsx](file:///Users/maxazure/Projects/agentweb/frontend/src/codex/components/StreamingBubble.tsx))
|