Merge latest master into docs/i18n-batch-core

This commit is contained in:
Tianyi Cui
2026-07-24 17:13:25 +08:00
59 changed files with 2638 additions and 290 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-23-trajectory-step-cell.md: 414c3aac856fb5e60f0e4cf42f8e7b410cdf3413
2026-07-23-trajectory-step-cell.zh.md: aa76b422f165ebf6918b3781fdfe38797a34ba51

View File

@@ -0,0 +1,35 @@
# Agent Note: Trajectory step cell and turn list chrome
Status: implemented
English | [中文](2026-07-23-trajectory-step-cell.zh.md)
## Problem
The trajectory tab needs a reusable step row and turn-list chrome that can show expanded assistant blocks, own-duration times, Message token columns, and in-flight work. Without folding session event times into conversation nodes and expanding blocks into cells, the UI cannot match the product chrome.
## Decision
[`@deepseek-ai/dsh-client-ui-trajectory`](../../../../packages/client/ui-trajectory/README.md) owns the presentational trajectory list chrome:
- [`TrajectoryCell`](../../../../packages/client/ui-trajectory/src/client/TrajectoryCell.tsx) — 38px step row with kinds User / Message / Tool (no Think, Call, or Result rows). Reasoning blocks are skipped (no block-level clock). Each `tool-call` + paired `tool-result` folds into one Tool row (`name ·` truncated args) whose Time is `result.time callTime` when both are known. Message rows carry Input/Output/Think token columns from `assistant.usage`. Own-duration Time uses `+Ns` / `+N.1s`, or `—` when absent. Selected state draws a 2px inset `--dsw-alias-brand-primary-new-colorprimary-new-color` ring (`selected` prop) and is not wired to chat selection.
- [`TrajectoryTurn`](../../../../packages/client/ui-trajectory/src/client/TrajectoryTurn.tsx) / header / group header — sticky Turn bar paints full-bleed `ghost-active-fill`; title/columns and the Message/Step body sit in a centered `max-width: 880px` lane. Cell trailing columns share the Turn header geometry (`320 = 4×71 + 3×12`); cells use pad 20/8.
- [`deriveTrajectoryLayout`](../../../../packages/client/ui-trajectory/src/client/layout.ts) expands assistant `blocks[]` into cells, pairs tool-calls with `tool-result` by `callId` into Tool, folds `partial` and `runningCalls` (deduped), hangs usage on Message only (including the empty fallback when there is no text block), and builds group descriptions as wall-span + tool histogram (`1.5s bash×6`). `user/message` has no wire turn, so each User row is enclosed in the next assistant/steering turn, else the in-flight `partial` turn, else `lastAssistantTurn + 1` (or `1`). Context nodes emit no cell but still advance the Message duration cursor.
[`ConversationNode`](../../../../packages/client/runtime/src/client/sessions/conversation.ts) carries `time` from `SessionEvent.time`; `ToolResultNode.callTime` and `RunningToolCall.time` come from the paired `tool/call`. Duration rules: User `+0s`; Message = assistant.time previous surface time (including skipped context); Tool = result.time callTime when both known; in-flight Tool = `—`. Group header duration is earliest→latest absolute time in the group (wall span; Tool contributes start and start+duration).
## Alternatives considered
**Keep a Think cell for reasoning blocks.** Rejected: a single `assistant/message.time` cannot yield Think own-duration without chunk-level clocks; omit the row rather than show `—`.
**Keep separate Call and Result rows.** Rejected: Result had no own duration to show; one Tool row carries the call→result interval.
**Cumulative elapsed from session/turn start.** Rejected; the Time column is each row's own duration.
**Hang usage on the first expanded row.** Rejected; usage attaches to Message only.
**Show in-flight tool durations via Date.now().** Deferred; in-flight Time stays `—`.
## Consequences
The Trajectory tab can render expanded finalized and in-flight rows with own-duration times once fold emits `time`. Behavior-shaped coverage lives in `packages/client/ui-trajectory/tests/{cell,layout,views}.spec.tsx`. Chat selection deep-links and finer block-level clocks remain deferred.

View File

@@ -0,0 +1,35 @@
# Agent Note: Trajectory 步骤单元格与轮次列表 chrome
Status: implemented
[English](2026-07-23-trajectory-step-cell.md) | 中文
## Problem
trajectory 标签页需要可复用的步骤行与轮次列表 chrome以展示展开后的 assistant 块、自身耗时、Message token 列以及进行中的工作。若不将会话事件时间折叠进会话节点并将块展开为单元格UI 就无法对齐产品 chrome。
## Decision
[`@deepseek-ai/dsh-client-ui-trajectory`](../../../../packages/client/ui-trajectory/README.md) 拥有展示型 trajectory 列表 chrome
- [`TrajectoryCell`](../../../../packages/client/ui-trajectory/src/client/TrajectoryCell.tsx) — 高 38px 的步骤行,类型为 User / Message / Tool无 Think、Call、Result 行。reasoning 块跳过(无块级时钟)。每对 `tool-call` + `tool-result` 折成一行 Tool`name ·` 加截断参数Time 在两端皆知时为 `result.time callTime`。Message 行携带来自 `assistant.usage` 的 Input/Output/Think token 列。自身耗时 Time 使用 `+Ns` / `+N.1s`,缺失时为 `—`。选中态绘制 2px 内嵌的 `--dsw-alias-brand-primary-new-colorprimary-new-color` 环(`selected` prop且未接线到 chat 选中。
- [`TrajectoryTurn`](../../../../packages/client/ui-trajectory/src/client/TrajectoryTurn.tsx) / header / group header — 粘性 Turn 条背景通栏铺 `ghost-active-fill`;标题/列标与 Message/Step 主体落在居中的 `max-width: 880px` 内容道。单元格右侧列与 Turn 标头共用几何(`320 = 4×71 + 3×12`cell pad 20/8。
- [`deriveTrajectoryLayout`](../../../../packages/client/ui-trajectory/src/client/layout.ts) 将 assistant `blocks[]` 展开为单元格,按 `callId` 将 tool-call 与 tool-result 配对为 Tool折叠 `partial``runningCalls`(去重),仅将用量挂在 Message 上(含无 text 块时的空回退行),并以墙钟跨度 + 工具直方图构建分组描述(`1.5s bash×6`)。`user/message` 无线上 turn故每条 User 行归入下一 assistant/steering 的 turn否则归入进行中的 `partial` turn否则为 `lastAssistantTurn + 1`(或 `1`。context 节点不产出单元格,但仍推进 Message 耗时游标。
[`ConversationNode`](../../../../packages/client/runtime/src/client/sessions/conversation.ts) 携带来自 `SessionEvent.time``time``ToolResultNode.callTime``RunningToolCall.time` 来自配对的 `tool/call`。耗时规则User 为 `+0s`Message = assistant.time 上一表面时间(含跳过的 contextTool = 在两者皆知时 result.time callTime进行中 Tool = `—`。分组标头耗时为组内最早→最晚绝对时间墙钟跨度Tool 贡献起点与起点+自身耗时)。
## Alternatives considered
**为 reasoning 块保留 Think 单元格。** 否决:单条 `assistant/message.time` 无法给出 Think 自身耗时(除非上 chunk 级时钟);与其显示 `—`,不如省略该行。
**保留分开的 Call 与 Result 行。** 否决Result 没有可展示的自身耗时;一行 Tool 承载 call→result 区间。
**自会话/轮次起点累计耗时。** 否决Time 列是每行自身的耗时。
**将用量挂在展开后的第一行。** 否决;用量仅附着于 Message。
**用 Date.now() 显示进行中工具的耗时。** 延后;进行中的 Time 保持为 `—`
## Consequences
一旦 fold 发出 `time`Trajectory 标签页即可渲染带自身耗时的已定稿与进行中展开行。行为导向的覆盖位于 `packages/client/ui-trajectory/tests/{cell,layout,views}.spec.tsx`。chat 选中深链与更细的块级时钟仍延后。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-24-new-session-clears-to-empty-state.md: 1605f44a05d0f59b61fe95cb5b03a0f9f5c3d4ab
2026-07-24-new-session-clears-to-empty-state.zh.md: 1f78d99babc33d30ee1300bfa6bf048a78e7132e

View File

@@ -0,0 +1,23 @@
# Agent Note: New Session clears onto the empty-state launch
Status: implemented
English | [中文](2026-07-24-new-session-clears-to-empty-state.zh.md)
## Problem
Sidebar "New Session" created and opened a blank session immediately, so the center column showed `ConversationRoot` with an empty transcript and the resident composer. The Figma NEW SESSION screen (`EmptyState` + shared `InputBar` hero) only rendered when `sessions.current` was already undefined, so the launch page was unreachable from the primary creation control.
## Decision
`SessionsService.clear()` wipes the persisted selection and `list.current`. Top-level sidebar creation entries (`onCreate()` with no cwd — New Session and New Workspace) call `clear()` so `AppFrame` renders `conversation.empty`. The empty state's first send still runs `conversation.startSession` (create → open → send) and reuses the same `InputBar` component as the resident composer (`variant="hero"`). Per-project "+" (`onCreate(cwd)`) keeps create-then-open until the empty-state picker can accept a seeded cwd.
## Alternatives considered
**Keep create-then-open for New Session and add a second empty chrome inside ConversationRoot when the transcript is empty.** Rejected: that duplicates the launch InputBar and breaks the empty→content ruling that one InputBar moves position rather than swapping components.
**Route New Session through a dedicated route or slot outside selection.** Rejected for this pass: `conversation.empty` already owns the launch UI; clearing `current` is the existing empty branch.
## Consequences
New Session no longer mints a host session until the first send. Reloading after clear stays on the empty state. Project-scoped "+" still creates immediately. `EmptyState` stacks the Figma hero (Input_Bottom 75:8208) as fish + title, a Menu-backed workspace chip above the card, then shared `InputBar` (`variant="hero"`, max-width 800, r20 card matching the composer — not a taller r24 hero), with a soft ellipse glow (figma 313:14109) centered behind the picker + card and width-locked to the card (`1051/776` asset ratio) so it scales with it. The chip uses the soft interactive hover fill + 12px radius from 75:8208 and opens MenuDropdown (figma 122:9481; `--dsw-specific-menu` + `--dsw-shadow-lv3`): basename rows with folder icons and a trailing check, then a separator and "New Workspace" whose submenu (figma 419:16920) offers "Use a existing folder" and "Create new". Use a existing folder opens the path Dialog (figma 451:18655 copy — "Enter an existing folder path" / Open Folder) over a full-viewport mask (`--dsw-alias-bg-mask-1` + `--dsw-mask-blur`) and sets the chip cwd. Create new opens the same Dialog chrome to name a folder under `host.describe().cwd`; success runs `sessions.createWorkspace` → host `session.create` (mkdir recursive) → `sessions.open`, so a default session lands in the new workspace. `InputBar` paints the bottom chrome (attach / Plan / Read-only / model) with local native `<select>` state only — host plan, access, and model seams remain unwired.

View File

@@ -0,0 +1,23 @@
# Agent Note: New Session clears onto the empty-state launch
Status: implemented
[English](2026-07-24-new-session-clears-to-empty-state.md) | 中文
## Problem
侧栏「New Session」会立即创建并打开空白会话因此中间栏显示带空 transcript文本记录与常驻 composer 的 `ConversationRoot`。Figma 的 NEW SESSION 屏(`EmptyState` + 共用的 `InputBar` hero仅在 `sessions.current` 已为 undefined 时渲染,因而主创建控件无法到达启动页。
## Decision
`SessionsService.clear()` 清除持久化选中项与 `list.current`。顶层侧栏创建入口(无 cwd 的 `onCreate()`——New Session 与 New Workspace调用 `clear()`,使 `AppFrame` 渲染 `conversation.empty`。空态的首次发送仍走 `conversation.startSession`create → open → send并复用与常驻 composer 相同的 `InputBar` 组件(`variant="hero"`)。按项目的「+」(`onCreate(cwd)`)继续 create-then-open直到空态选择器能接受预填的 cwd。
## Alternatives considered
**为 New Session 保留 create-then-open并在 transcript 为空时于 ConversationRoot 内再加一套空态 chrome。** 否决:这会重复启动页的 InputBar并破坏 empty→content 的约定——同一 InputBar 应移动位置,而非互换组件。
**将 New Session 路由到选中状态之外的专用 route 或 slot。** 本轮否决:`conversation.empty` 已拥有启动 UI清除 `current` 即是既有的空态分支。
## Consequences
New Session 在首次发送前不再创建 host 会话。clear 后重新加载仍停留在空态。项目范围的「+」仍立即创建。`EmptyState` 按 Figma 堆叠英雄区Input_Bottom 75:8208鱼标 + 标题、卡片上方由 Menu 驱动的工作区 chip再接共用的 `InputBar``variant="hero"`max-width 800与 composer 一致的 r20 卡片——而非更高的 r24 英雄区选择器与卡片背后居中铺一层柔光椭圆figma 313:14109宽度按卡片锁定为 `1051/776` asset 比例随卡片缩放。Chip 采用 75:8208 的柔和交互 hover 填充与 12px 圆角,并打开 MenuDropdownfigma 122:9481`--dsw-specific-menu` + `--dsw-shadow-lv3`):带文件夹图标与尾随勾选的 basename 行,分隔线后是 "New Workspace"其子菜单figma 419:16920提供 "Use a existing folder" 与 "Create new"。Use a existing folder 打开路径 Dialogfigma 451:18655 copy — "Enter an existing folder path" / Open Folder置于全视口遮罩`--dsw-alias-bg-mask-1` + `--dsw-mask-blur`)之上,并设置 chip 的 cwd。Create new 打开同一套 Dialog chrome`host.describe().cwd` 下命名文件夹;成功则走 `sessions.createWorkspace` → host `session.create`mkdir recursive`sessions.open`,在新 workspace 中默认落下一会话。`InputBar` 绘制底栏 chromeattach / Plan / Read-only / model仅用本地原生 `<select>` 状态——host 侧的 plan、access、model 接缝仍未接线。