{renderSlot(seat, {
- entries,
+ entries: seat === 'conversation.session.header.views.actions' ? [] : entries,
onOpen: (id) => { setOpened(`${seat}:${id}`) },
onPinnedChange: (_id, next) => { setPinned(next) },
onDismiss: () => {},
@@ -148,8 +152,7 @@ describe('More menu registrations', () => {
const sidebar = () => within(b.view.getByRole('menu', { name: 'sidebar.panellist.actions' }))
expect(sidebar().queryByRole('menuitem', { name: 'Browse other plugins' })).toBeNull()
expect(sidebar().getByRole('menuitem', { name: 'Create your plugin' })).toBeTruthy()
- // The Creator row takes the separator the absent discovery row would carry.
- expect(sidebar().getAllByRole('separator')).toHaveLength(1)
+ expect(sidebar().queryByRole('separator')).toBeNull()
const injected = b.runtime.slots.entries('sidebar.panellist.actions')[0]!.inject!() as { browsePlugins: () => void }
expect(() => { injected.browsePlugins() }).not.toThrow()
@@ -170,15 +173,10 @@ describe('More menu registrations', () => {
const b = await bench()
for (const seat of SEATS) {
expect(b.runtime.slots.entries(seat).map(entry => entry.options.id)).toEqual(['entry-customization'])
- expect(within(b.view.getByRole('menu', { name: seat })).getByRole('menuitem', { name: 'Notes' })).toBeTruthy()
+ expect(within(b.view.getByRole('menu', { name: seat })).getAllByRole('menuitem').map(row => row.textContent))
+ .toEqual(['Browse other plugins', 'Create your plugin'])
}
const sidebar = within(b.view.getByRole('menu', { name: 'sidebar.panellist.actions' }))
- fireEvent.click(sidebar.getByRole('menuitem', { name: 'Unpin from left sidebar: Notes' }))
- expect(within(b.view.getByRole('menu', { name: 'sidebar.right.tab.guide.actions' }))
- .getByRole('menuitem', { name: 'Pin to right sidebar: Notes' })).toBeTruthy()
- fireEvent.click(within(b.view.getByRole('menu', { name: 'conversation.session.header.views.actions' }))
- .getByRole('menuitem', { name: 'Notes' }))
- expect(b.view.getByTestId('opened').textContent).toBe('conversation.session.header.views.actions:notes')
fireEvent.click(sidebar.getByRole('menuitem', { name: 'Browse other plugins' }))
expect(b.openList).toHaveBeenCalledExactlyOnceWith()
await b.plugin.dispose()
@@ -220,15 +218,6 @@ describe('More menu registrations', () => {
expect(b.track).toHaveBeenCalledTimes(2)
})
- it('reports neither entry opening nor pin changes', async () => {
- const b = await bench()
- const menu = within(b.view.getByRole('menu', { name: 'sidebar.panellist.actions' }))
- fireEvent.click(menu.getByRole('menuitem', { name: 'Unpin from left sidebar: Notes' }))
- fireEvent.click(menu.getByRole('menuitem', { name: 'Notes' }))
- expect(b.view.getByTestId('opened').textContent).toBe('sidebar.panellist.actions:notes')
- expect(b.track).not.toHaveBeenCalled()
- })
-
it('keeps both footer actions working without the analytics service', async () => {
const b = await bench('en', { analytics: false })
const menu = within(b.view.getByRole('menu', { name: 'sidebar.right.tab.guide.actions' }))
diff --git a/packages/client/ui-entry-customization/tests/pin-styles.client.spec.ts b/packages/client/ui-entry-customization/tests/pin-styles.client.spec.ts
deleted file mode 100644
index c30d3ee705..0000000000
--- a/packages/client/ui-entry-customization/tests/pin-styles.client.spec.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/** Pin controls use Session-action ink without changing the navigation control's dimensions. */
-import { readFileSync } from 'node:fs'
-import { describe, expect, it } from 'vitest'
-
-const css = readFileSync(new URL('../src/client/EntryMenu.module.css', import.meta.url), 'utf8')
-
-describe('entry pin styling', () => {
- it('uses tertiary ink for both pin states and primary ink on pointer hover', () => {
- expect(css).toMatch(/\.pinButton\s*\{[^}]*color:\s*var\(--dsw-alias-label-tertiary\)/)
- expect(css).toMatch(/\.pinButton:hover\s*\{[^}]*color:\s*var\(--dsw-alias-label-primary\)/)
- expect(css).not.toMatch(/\[data-pinned=['"]true['"]\]\s+\.pinButton\s*\{[^}]*color:/)
- })
-
- it('centers the pin without creating a transformed tooltip container', () => {
- const pinAnchor = css.match(/\.pinAnchor\s*\{([^}]*)\}/)?.[1]
- expect(pinAnchor).toBeDefined()
- expect(pinAnchor).toContain('align-items: center')
- expect(pinAnchor).not.toContain('transform:')
- })
-})
diff --git a/packages/client/ui-primitives/README.i18n.yaml b/packages/client/ui-primitives/README.i18n.yaml
index 7795512361..2705858c0c 100644
--- a/packages/client/ui-primitives/README.i18n.yaml
+++ b/packages/client/ui-primitives/README.i18n.yaml
@@ -18,8 +18,8 @@
en: 10be7f75a1576575
zh: abf2905d89b0da48
/deepseek-ai-dsh-client-ui-primitives/use-this-package/component-catalog:
- en: 75e1681dcfc61a14
- zh: 3eda3143730f83e1
+ en: 3c79351ff2210ccb
+ zh: 4cbe07ded89bbaa1
/deepseek-ai-dsh-client-ui-primitives/use-this-package/controls-and-icons:
en: dbd7f0e74c45a762
zh: 7efbfbf5500cc835
diff --git a/packages/client/ui-primitives/README.md b/packages/client/ui-primitives/README.md
index 637ff7f55c..b1ee87950b 100644
--- a/packages/client/ui-primitives/README.md
+++ b/packages/client/ui-primitives/README.md
@@ -49,7 +49,7 @@ Check this table before writing a control in a feature package. A plugin cannot
| `Input` | Single-line text entry for search boxes and inline forms. Its ref targets the native input for focus and is cleared on unmount. |
| `InlineEditor` | Autofocused multi-line inline editor for short drafts such as queued messages and goals. It grows with its text and its width up to six lines, then scrolls; Enter saves, Shift+Enter breaks the line, Enter during IME composition does nothing, and Escape cancels. The caller supplies the localized `label` and rebinds the scrollbar on an elevated surface. |
| `ZoomControls`, `DOCUMENT_ZOOM` | Floating document zoom controls with localized labels, fixed presets, and visibility/activity callbacks. An omitted `fit` hides the fitting row. The toolbar carries `data-document-zoom-controls`, plus `data-document-zoom-visible` while visible. An owner `className` overrides `bottom` and `max-width` regardless of stylesheet order; `inset-inline-start` and `transform` center the toolbar and move it on enter and exit, `opacity` fades it, and all three stay with the primitive. Renderers share the published scale limits. |
-| `Menu`, `MenuItemButton` | Dropdown of `items` data rows, separators, and group labels, with nested submenus; `children` adds component rows, each a `MenuItemButton` (`separatorBefore` starts a new group), in the same list. Every row shares the styling, the keyboard walk, and the focus return; closing stays the owner's state change for both kinds. While open, ↑/↓ (with Home and End) walk the list, Tab settles the focused row, and Escape or Shift+Tab close back to the anchor; selecting a row also returns the keyboard to the anchor unless the owner moved it itself. Only a keyboard on the anchor or inside the list is intercepted, and `autoFocus` decides solely whether opening focuses the first row. |
+| `Menu`, `MenuItemButton` | Dropdown of `items` data rows, separators, and group labels, with nested submenus; `children` adds component rows, each a `MenuItemButton` (`separatorBefore` starts a new group), in the same list. `MenuItemButton.current` exposes the current page or view through `aria-current` without changing selection or appearance. Every row shares the styling, the keyboard walk, and the focus return; closing stays the owner's state change for both kinds. While open, ↑/↓ (with Home and End) walk the list, Tab settles the focused row, and Escape or Shift+Tab close back to the anchor; selecting a row also returns the keyboard to the anchor unless the owner moved it itself. Only a keyboard on the anchor or inside the list is intercepted, and `autoFocus` decides solely whether opening focuses the first row. |
| `MenuGroup`, `observeStickyMenuGroups` | Localized, accessible groups for custom menus and listboxes, with shared sticky-heading styling and native intersection/size observation; the caller owns the observer's lifetime. |
| `Pill` | Selectable capsule button for view switchers and filters; takes `active` and `onClick`. |
| `SegmentedTabs` | Controlled equal-width tabs with a sliding indicator and Left/Right, Home, and End navigation. The caller supplies labels, tab/panel ids, and panel content. Default `size="md"` is 42px tall with a 4px inset; `size="sm"` is 34px tall with a 2px inset. |
diff --git a/packages/client/ui-primitives/README.zh.md b/packages/client/ui-primitives/README.zh.md
index e10743c8f9..141eb12665 100644
--- a/packages/client/ui-primitives/README.zh.md
+++ b/packages/client/ui-primitives/README.zh.md
@@ -49,7 +49,7 @@ kind: "package-library"
| `Input` | 单行文本输入,用于搜索框与行内表单。ref 指向原生输入框,供焦点控制使用,并在卸载时清空。 |
| `InlineEditor` | 自动聚焦的多行行内编辑器,用于排队消息、goal 等短草稿。它随文本和宽度增高,最多六行,之后滚动;Enter 保存,Shift+Enter 换行,输入法组合中的 Enter 不生效,Escape 取消。调用方提供本地化 `label`,并在 elevated 表面上重绑滚动条。 |
| `ZoomControls`, `DOCUMENT_ZOOM` | 悬浮文档缩放控件,接收本地化标签、固定预设,以及可见性和交互状态回调。省略 `fit` 时不显示适配项。工具栏带 `data-document-zoom-controls`,可见时另带 `data-document-zoom-visible`。owner 的 `className` 覆盖 `bottom` 与 `max-width`,不受样式表加载顺序影响;`inset-inline-start` 和 `transform` 负责居中与进出场位移,`opacity` 负责淡入淡出,三者都由原语保留。渲染器共用导出的缩放范围。 |
-| `Menu`, `MenuItemButton` | 由 `items` 数据行、分隔线与分组标题构成的下拉菜单,支持嵌套子菜单;`children` 在同一列表中加入组件行,每行一个 `MenuItemButton`(`separatorBefore` 开启新分组)。所有行共享样式、键盘走位与焦点归还;两类行的关闭都是 owner 状态的改变。打开期间 `↑`/`↓`(以及 Home、End)在列表中走位,Tab 选定聚焦行,Escape 或 Shift+Tab 关闭并把焦点还给锚点;选定一行同样把键盘还给锚点——除非拥有者自己移动了焦点。只拦截位于锚点或列表内的键盘,`autoFocus` 仅决定打开时是否聚焦首行。 |
+| `Menu`, `MenuItemButton` | 由 `items` 数据行、分隔线与分组标题构成的下拉菜单,支持嵌套子菜单;`children` 在同一列表中加入组件行,每行一个 `MenuItemButton`(`separatorBefore` 开启新分组)。`MenuItemButton.current` 通过 `aria-current` 标识当前页面或视图,不改变选中状态或外观。所有行共享样式、键盘走位与焦点归还;两类行的关闭都是 owner 状态的改变。打开期间 `↑`/`↓`(以及 Home、End)在列表中走位,Tab 选定聚焦行,Escape 或 Shift+Tab 关闭并把焦点还给锚点;选定一行同样把键盘还给锚点——除非拥有者自己移动了焦点。只拦截位于锚点或列表内的键盘,`autoFocus` 仅决定打开时是否聚焦首行。 |
| `MenuGroup`、`observeStickyMenuGroups` | 为自定义菜单与列表框提供本地化、可访问的分组,共用吸顶标题样式与原生交叉/尺寸观察;调用方负责观察器的生命周期。 |
| `Pill` | 可选中的胶囊按钮,用于视图切换与筛选器;接受 `active` 与 `onClick`。 |
| `SegmentedTabs` | 受控的等宽分段标签,支持滑动指示条及左/右方向键、Home、End 导航。调用方提供文案、标签与面板 id,以及面板内容。默认 `size="md"` 总高 42px、内边距 4px;`size="sm"` 总高 34px、内边距 2px。 |
diff --git a/packages/client/ui-primitives/src/Menu.tsx b/packages/client/ui-primitives/src/Menu.tsx
index 0f1225fc42..87e76287c7 100644
--- a/packages/client/ui-primitives/src/Menu.tsx
+++ b/packages/client/ui-primitives/src/Menu.tsx
@@ -51,6 +51,8 @@ export interface MenuItemButtonProps {
shortcut?: MenuItem['shortcut']
/** Leading icon (figma .Menu_cell gap 8). */
icon?: ReactNode
+ /** Whether this row identifies the currently displayed page or view. */
+ current?: boolean
/** Whether the row cannot be activated. */
disabled?: boolean
/** Destructive row: error-colored text/icon and danger hover fill. */
@@ -75,6 +77,7 @@ export interface MenuItemButtonProps {
* @param props.children - visible row label.
* @param props.shortcut - effective key labels and accessible combination.
* @param props.icon - optional leading icon.
+ * @param props.current - whether the row identifies the currently displayed page or view.
* @param props.disabled - whether the row cannot be activated.
* @param props.danger - whether to use the destructive row colors.
* @param props.separatorBefore - whether this row starts a new group (hairline above it).
@@ -82,7 +85,7 @@ export interface MenuItemButtonProps {
* @returns one menu-item row.
*/
export function MenuItemButton({
- children, shortcut, icon, disabled = false, danger = false, separatorBefore = false, onSelect,
+ children, shortcut, icon, current, disabled = false, danger = false, separatorBefore = false, onSelect,
}: MenuItemButtonProps) {
return (
@@ -93,6 +96,7 @@ export function MenuItemButton({
className={clsx(css.item, danger && css.danger)}
disabled={disabled}
aria-keyshortcuts={shortcut?.aria}
+ aria-current={current === true ? 'page' : undefined}
onClick={onSelect}
>
{icon !== undefined &&
{icon} }
diff --git a/packages/client/ui-primitives/tests/atoms.client.spec.tsx b/packages/client/ui-primitives/tests/atoms.client.spec.tsx
index 3f56fda81c..8b198e4365 100644
--- a/packages/client/ui-primitives/tests/atoms.client.spec.tsx
+++ b/packages/client/ui-primitives/tests/atoms.client.spec.tsx
@@ -157,6 +157,21 @@ describe('Input', () => {
})
})
+describe('MenuItemButton', () => {
+ it('exposes the current page without preventing activation and clears it when selection moves', () => {
+ const onSelect = vi.fn()
+ const { rerender } = render(
Notes )
+ const row = screen.getByRole('menuitem', { name: 'Notes' })
+ expect(row.getAttribute('aria-current')).toBe('page')
+ fireEvent.click(row)
+ expect(onSelect).toHaveBeenCalledOnce()
+ rerender(
Notes )
+ expect(row.hasAttribute('aria-current')).toBe(false)
+ rerender(
Notes )
+ expect(row.hasAttribute('aria-current')).toBe(false)
+ })
+})
+
describe('Menu', () => {
const items = [
{ id: 'a', label: 'Alpha' },
diff --git a/packages/client/ui-sidebar-right/README.i18n.yaml b/packages/client/ui-sidebar-right/README.i18n.yaml
index c781146562..cedfe4f4e4 100644
--- a/packages/client/ui-sidebar-right/README.i18n.yaml
+++ b/packages/client/ui-sidebar-right/README.i18n.yaml
@@ -36,8 +36,8 @@
en: 717b86e05b44ebdd
zh: 0f4f694d0ea1d7f8
/deepseek-ai-dsh-client-ui-sidebar-right/the-guide:
- en: c67935eddef999fe
- zh: 0648664ded92ee82
+ en: 5fcda0a3a4453b52
+ zh: d7818c63bf3371f2
/deepseek-ai-dsh-client-ui-sidebar-right/copy:
en: 4315a1c03ca5fef3
zh: c09a09f1d9b99107
diff --git a/packages/client/ui-sidebar-right/README.md b/packages/client/ui-sidebar-right/README.md
index b5839bd05d..01fbf0a30e 100644
--- a/packages/client/ui-sidebar-right/README.md
+++ b/packages/client/ui-sidebar-right/README.md
@@ -114,7 +114,7 @@ Close and refresh commands resolve the focused pane and tab occurrence. Page bod
Each `guide` entry has an `id` unique within its provider. The guide renders `sidebar.right.tab.guide.entry` by the active definition id, forwarding the entry id, resolved title and optional description, plus the framework `useTabInfo` hook. A provider can register a custom card under its definition id; otherwise the standard icon/title button renders. The sidebar owns width and placement, while custom components own their markup and interaction. Guide cards use the shared R20 radius, including hover fills and provider-owned split buttons. Entry identity remains stable across sorting and unrelated registrations. Unboxed shortcut text aligns at the right. The terminal shell menu sits beside its entry title.
-While the root-scoped `sidebar.right.tab.guide.actions` list has registrations, a **More tools** card follows the guide cards. Its menu end-aligns to the card, is at least as wide as the card, and grows with its rows up to 480px or the viewport width minus 24px. The menu rows are that list's registrations. Each receives `entries` (cards in registry order with `id`, `label`, and `pinned`; kind `files` is never listed), `onOpen(id)`, which opens the card's tab type in the guide's place, `onPinnedChange(id, pinned)`, and `onDismiss()`, which closes the menu. The guide keeps one root list of unpinned card ids, each `kind:entry id`, in browser `localStorage` under `dsh.slot.manual-order.sidebar.right.tab.guide.list.v1`, stored as `{ "unpinned": string[] }`; the key reserves the name of a future guide list slot. The kind identifies a card instead of the provider id, so a pin choice survives an extension taking over that kind with the same entry id. Cards of kind `files` always show. Unpinning hides only the card; it closes no tab and changes neither tab-type registration nor the default page. Description density follows the visible card count. Ids of cards that are not registered stay stored; stored content other than a string array shows every card.
+While the root-scoped `sidebar.right.tab.guide.actions` list has registrations, a **More tools** card follows the guide cards. Its menu end-aligns to the card, is at least as wide as the card, and grows with its rows up to 480px or the viewport width minus 24px. The menu rows are that list's registrations. Each receives `entries` (cards in registry order with `id`, `label`, and `pinned`; kind `files` is never listed), `onOpen(id)`, which opens the card's tab type in the guide's place, `onPinnedChange(id, pinned)`, and `onDismiss()`, which closes the menu. The guide keeps an in-memory list of unpinned card ids, each `kind:entry id`. Plugin reload resets pin choices; legacy localStorage values are ignored. The default More menu only offers plugin browsing and creation; pin callbacks remain available to other registrations. The kind identifies a card instead of the provider id, so a pin choice survives an extension taking over that kind with the same entry id. Cards of kind `files` always show. Unpinning hides only the card; it closes no tab and changes neither tab-type registration nor the default page. Description density follows the visible card count. Unregistered card ids remain in memory until plugin reload.
A tab type can declare `multiple: true` for independent instances such as terminals. Each open receives a distinct content address, so placement and docking preserve those instances; ordinary pages retain per-pane uniqueness.
diff --git a/packages/client/ui-sidebar-right/README.zh.md b/packages/client/ui-sidebar-right/README.zh.md
index cfb16e3e53..96f12af1a6 100644
--- a/packages/client/ui-sidebar-right/README.zh.md
+++ b/packages/client/ui-sidebar-right/README.zh.md
@@ -114,7 +114,7 @@ Tab 域按(Session,Tab id)保留品牌化 occurrence id、导航、中止
每个 `guide` 入口具有 provider 内唯一的 `id`。开始页按当前生效的定义 id 渲染 `sidebar.right.tab.guide.entry`,传入入口 id、解析后的标题、可选描述,以及框架提供的 `useTabInfo` hook。provider 可按自己的定义 id 注册自定义卡片;未注册时使用默认图标与标题按钮。侧栏负责宽度和排列,自定义组件负责内部结构和交互。引导卡片统一使用 R20,包含悬停底色与提供方自有的分体按钮。排序或其他注册变化不会改变入口身份。无背景的快捷键文字右对齐。终端 Shell 菜单位于入口标题旁。
-root 作用域的 `sidebar.right.tab.guide.actions` list 有注册项时,引导卡片之后显示**更多工具**卡片。它的菜单与卡片末端对齐,不窄于卡片,并随菜单行增宽,最大为 480px 与视口宽度减 24px 中的较小值。菜单行为该 list 的注册项。每个注册项接收 `entries`(按注册表顺序的卡片,含 `id`、`label` 与 `pinned`;不含 kind 为 `files` 的卡片)、在引导页位置打开卡片标签类型的 `onOpen(id)`、`onPinnedChange(id, pinned)`,以及关闭菜单的 `onDismiss()`。引导页在浏览器 `localStorage` 的 `dsh.slot.manual-order.sidebar.right.tab.guide.list.v1` 下保存一份 root 级的已收起卡片 id 列表,每个 id 为 `kind:入口 id`,格式为 `{ "unpinned": string[] }`;该键名为以后的引导入口 list slot 预留。卡片由 kind 而不是提供方 id 标识,因此扩展接管某个 kind 且保持入口 id 时,Pin 选择仍然有效。kind 为 `files` 的卡片始终显示。收起只隐藏卡片,不关闭标签,也不改变标签类型注册或默认页。描述的显示密度按可见卡片数计算。未注册卡片的 id 保留在列表中;保存内容不是字符串数组时,所有卡片都显示。
+root 作用域的 `sidebar.right.tab.guide.actions` list 有注册项时,引导卡片之后显示**更多工具**卡片。它的菜单与卡片末端对齐,不窄于卡片,并随菜单行增宽,最大为 480px 与视口宽度减 24px 中的较小值。菜单行为该 list 的注册项。每个注册项接收 `entries`(按注册表顺序的卡片,含 `id`、`label` 与 `pinned`;不含 kind 为 `files` 的卡片)、在引导页位置打开卡片标签类型的 `onOpen(id)`、`onPinnedChange(id, pinned)`,以及关闭菜单的 `onDismiss()`。引导页在内存中保存已收起卡片 id 列表,每个 id 为 `kind:入口 id`。插件重新加载时 Pin 选择重置,不读取旧 localStorage 值。默认更多菜单只提供插件浏览和创建;其他注册项仍可使用 Pin 回调。卡片由 kind 而不是提供方 id 标识,因此扩展接管某个 kind 且保持入口 id 时,Pin 选择仍然有效。kind 为 `files` 的卡片始终显示。收起只隐藏卡片,不关闭标签,也不改变标签类型注册或默认页。描述的显示密度按可见卡片数计算。未注册卡片的 id 保留在内存中,直到插件重新加载。
终端等需要独立实例的 tab 类型可声明 `multiple: true`。每次打开都会获得独立内容地址,因此移动和停靠会保留这些实例;普通页面仍在每个格内去重。
diff --git a/packages/client/ui-sidebar-right/src/client/tabs/guide/GuideBody.tsx b/packages/client/ui-sidebar-right/src/client/tabs/guide/GuideBody.tsx
index 4d40f86e01..febe9f1954 100644
--- a/packages/client/ui-sidebar-right/src/client/tabs/guide/GuideBody.tsx
+++ b/packages/client/ui-sidebar-right/src/client/tabs/guide/GuideBody.tsx
@@ -19,8 +19,7 @@
* open.
*/
import { useState } from 'react'
-import clsx from 'clsx'
-import { IconAppsAddOutlineRegular, Menu, ShortcutKeys } from '@deepseek-ai/dsh-client-ui-primitives'
+import { Menu, ShortcutKeys } from '@deepseek-ai/dsh-client-ui-primitives'
import type { ShortcutCatalogEntry } from '@deepseek-ai/dsh-client-shortcuts/client'
import type { ReactNode } from 'react'
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store'
@@ -30,6 +29,7 @@ import type {
import type { SidebarRightGuideActionsOwnerProps } from '../../contract/slots.ts'
import type { SidebarRightGuideBox } from '../../tab-registry.ts'
import { CompassGlyph, CubeGlyph } from './GuideTitle.tsx'
+import { GuideMoreIcon } from './GuideMoreIcon.tsx'
import { FIXED_GUIDE_KIND, guideEntryId } from './guide-pins.ts'
import css from './GuideBody.module.css'
@@ -39,7 +39,7 @@ export interface GuideInjected {
readonly hooks: {
readonly shortcuts: ObservableSnapshot
readonly guideEntries: ObservableSnapshot
- /** Unpinned `kind:id` card ids, persisted across reloads. */
+ /** Unpinned `kind:id` card ids held in memory until plugin reload. */
readonly entryPins: ObservableSnapshot
/** Whether `sidebar.right.tab.guide.actions` has registrations. */
readonly entryActions: ObservableSnapshot
@@ -106,7 +106,7 @@ function GuideMoreMenu({ label, entries, onOpen, onPinnedChange, renderSlot }: G
anchor={(
{ setOpen(value => !value) }}>
-
+
{label}
)}>
diff --git a/packages/client/ui-sidebar-right/src/client/tabs/guide/GuideMoreIcon.tsx b/packages/client/ui-sidebar-right/src/client/tabs/guide/GuideMoreIcon.tsx
new file mode 100644
index 0000000000..61f7913aa3
--- /dev/null
+++ b/packages/client/ui-sidebar-right/src/client/tabs/guide/GuideMoreIcon.tsx
@@ -0,0 +1,15 @@
+/** Designer-supplied artwork used only by the right-sidebar More tools card. */
+
+/**
+ * Render the More tools glyph at its authored size and ink.
+ * @returns Decorative 22px More tools artwork.
+ */
+export function GuideMoreIcon() {
+ return
+
+
+
+
+
+}
diff --git a/packages/client/ui-sidebar-right/src/client/tabs/guide/guide-pins.ts b/packages/client/ui-sidebar-right/src/client/tabs/guide/guide-pins.ts
index 3614a6778b..f43ee7503d 100644
--- a/packages/client/ui-sidebar-right/src/client/tabs/guide/guide-pins.ts
+++ b/packages/client/ui-sidebar-right/src/client/tabs/guide/guide-pins.ts
@@ -1,12 +1,10 @@
-/** Persisted pin choices for the guide's tool cards. */
+/** In-memory pin choices for the guide's tool cards. */
import { createSnapshotStore, type ObservableSnapshot } from '@deepseek-ai/dsh-client-store'
import type { SidebarRightGuideBox } from '../../tab-registry.ts'
/** Tab kind whose card always shows and never enters the customization list. */
export const FIXED_GUIDE_KIND = 'files'
-/** Root-level key reserved for the future guide list slot's per-list preferences. */
-const STORAGE_KEY = 'dsh.slot.manual-order.sidebar.right.tab.guide.list.v1'
const NO_CARDS: readonly string[] = []
/**
@@ -23,30 +21,23 @@ export interface GuidePins {
/** Unpinned `kind:id` card ids; ids of cards that are not registered stay listed. */
readonly unpinned: ObservableSnapshot
/**
- * Show or hide one guide card; cards of the fixed kind are ignored.
+ * Pin or unpin one guide card; cards of the fixed kind are ignored.
* @param id - `kind:id` card id.
- * @param pinned - Whether the card shows on the guide.
+ * @param pinned - Whether the card is manually pinned.
*/
readonly setPinned: (id: string, pinned: boolean) => void
}
-/** Stored content is unchecked; anything but a string array leaves every card pinned. */
-function unpinnedCards(value: unknown): readonly string[] {
- if (typeof value !== 'object' || value === null || !('unpinned' in value)) return NO_CARDS
- const { unpinned } = value
- return Array.isArray(unpinned) && unpinned.every((id): id is string => typeof id === 'string') ? unpinned : NO_CARDS
-}
-
/**
- * Create the persisted guide pin list; the snapshot keeps its reference until the list changes.
- * @returns Validated unpinned card ids and the pin mutation.
+ * Create the in-memory guide pin list; the snapshot keeps its reference until the list changes.
+ * @returns Unpinned card ids and the pin mutation.
*/
export function createGuidePins(): GuidePins {
- const store = createSnapshotStore({ unpinned: NO_CARDS }, { persist: { name: STORAGE_KEY } })
+ const store = createSnapshotStore<{ readonly unpinned: readonly string[] }>({ unpinned: NO_CARDS })
return {
- unpinned: { getSnapshot: () => unpinnedCards(store.getSnapshot()), subscribe: listener => store.subscribe(listener) },
+ unpinned: { getSnapshot: () => store.getSnapshot().unpinned, subscribe: listener => store.subscribe(listener) },
setPinned: (id, pinned) => {
- const current = unpinnedCards(store.getSnapshot())
+ const current = store.getSnapshot().unpinned
if (id.startsWith(`${FIXED_GUIDE_KIND}:`) || pinned !== current.includes(id)) return
store.set({ unpinned: pinned ? current.filter(entry => entry !== id) : [...current, id] })
},
diff --git a/packages/client/ui-sidebar-right/tests/guide-body.client.spec.tsx b/packages/client/ui-sidebar-right/tests/guide-body.client.spec.tsx
index 34429ce6db..82b8d386e0 100644
--- a/packages/client/ui-sidebar-right/tests/guide-body.client.spec.tsx
+++ b/packages/client/ui-sidebar-right/tests/guide-body.client.spec.tsx
@@ -119,6 +119,12 @@ describe('GuideBody', () => {
const guide = b.view.container.querySelector('[data-sidebar-right-guide]')
expect(guide?.lastElementChild?.contains(more)).toBe(true)
expect(more.getAttribute('aria-haspopup')).toBe('menu')
+ const glyph = more.querySelector('svg')
+ expect(glyph?.getAttribute('width')).toBe('22')
+ expect(glyph?.getAttribute('height')).toBe('22')
+ expect(glyph?.getAttribute('stroke')).toBe('#A6B2C7')
+ expect(glyph?.getAttribute('aria-hidden')).toBe('true')
+ expect(glyph?.querySelectorAll('path')).toHaveLength(4)
fireEvent.click(more)
expect(more.getAttribute('aria-expanded')).toBe('true')
expect(b.actionsOwner().entries).toEqual([
diff --git a/packages/client/ui-sidebar-right/tests/guide-pins.client.spec.ts b/packages/client/ui-sidebar-right/tests/guide-pins.client.spec.ts
index 1bc8994b94..5c775816fd 100644
--- a/packages/client/ui-sidebar-right/tests/guide-pins.client.spec.ts
+++ b/packages/client/ui-sidebar-right/tests/guide-pins.client.spec.ts
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
-/** Guide card pins persist per `kind:id` and ignore every Files card. */
+/** Guide card pins stay in memory per `kind:id` and ignore every Files card. */
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createGuidePins, guideEntryId } from '../src/client/tabs/guide/guide-pins.ts'
@@ -11,15 +11,15 @@ describe('guide card pins', () => {
expect(guideEntryId({ kind: 'terminal', id: 'shell' })).toBe('terminal:shell')
})
- it('persists unpinned card ids and restores them in a new instance', () => {
+ it('keeps unpinned card ids in memory without restoring them in a new instance', () => {
const pins = createGuidePins()
const initial = pins.unpinned.getSnapshot()
expect(initial).toEqual([])
expect(pins.unpinned.getSnapshot()).toBe(initial)
pins.setPinned('terminal:shell', false)
pins.setPinned('browser:page', false)
- expect(JSON.parse(localStorage.getItem(KEY) ?? 'null')).toEqual({ unpinned: ['terminal:shell', 'browser:page'] })
- expect(createGuidePins().unpinned.getSnapshot()).toEqual(['terminal:shell', 'browser:page'])
+ expect(localStorage.getItem(KEY)).toBeNull()
+ expect(createGuidePins().unpinned.getSnapshot()).toEqual([])
pins.setPinned('terminal:shell', true)
expect(pins.unpinned.getSnapshot()).toEqual(['browser:page'])
})
@@ -38,20 +38,14 @@ describe('guide card pins', () => {
})
it('keeps ids of cards that are not registered', () => {
- localStorage.setItem(KEY, JSON.stringify({ unpinned: ['removed:card'] }))
const pins = createGuidePins()
+ pins.setPinned('removed:card', false)
pins.setPinned('terminal:shell', false)
expect(pins.unpinned.getSnapshot()).toEqual(['removed:card', 'terminal:shell'])
})
- it.each([
- ['a non-array list', { unpinned: 'terminal:shell' }],
- ['non-string ids', { unpinned: [false] }],
- ['a missing list', {}],
- ['null', null],
- ['a number', 7],
- ])('reads %s as no unpinned cards with a stable snapshot', (_name, stored) => {
- localStorage.setItem(KEY, JSON.stringify(stored))
+ it('ignores a legacy stored list instead of restoring it', () => {
+ localStorage.setItem(KEY, JSON.stringify({ unpinned: ['terminal:shell'] }))
const pins = createGuidePins()
const snapshot = pins.unpinned.getSnapshot()
expect(snapshot).toEqual([])
diff --git a/packages/client/ui-sidebar/README.i18n.yaml b/packages/client/ui-sidebar/README.i18n.yaml
index 8bf7b111e1..9f24479203 100644
--- a/packages/client/ui-sidebar/README.i18n.yaml
+++ b/packages/client/ui-sidebar/README.i18n.yaml
@@ -18,11 +18,11 @@
en: a970f0547496634f
zh: 36ef9a85b26e19f1
/deepseek-ai-dsh-client-ui-sidebar/use-this-package/brand-and-new-session:
- en: f2c2254325cea5db
- zh: c9ef643b431f8328
+ en: 44db2d448bc2ea6d
+ zh: cbb1143afb524d5b
/deepseek-ai-dsh-client-ui-sidebar/use-this-package/global-panel-entries:
- en: b109498d88367117
- zh: 6d407f265bacdb0d
+ en: e7f316023445511b
+ zh: 6271c8c011533877
/deepseek-ai-dsh-client-ui-sidebar/use-this-package/collapse-behavior:
en: 20e744360b4be2cc
zh: a90be7e8526bbb21
@@ -33,11 +33,11 @@
en: 9e7b31acee759f2b
zh: 3a6beb63b23d26ae
/deepseek-ai-dsh-client-ui-sidebar/use-this-package/click-analytics:
- en: bb3f4a199ed49039
- zh: cc29413fd31fe2e7
+ en: 8855ad013285a27e
+ zh: f19b3e75c38c5b0a
/deepseek-ai-dsh-client-ui-sidebar/understand-the-implementation:
- en: bc51c446d8b8e098
- zh: c0c7bbfb50541d33
+ en: 39b5e3ca8ce8c5c2
+ zh: 1d652b7554772dad
/deepseek-ai-dsh-client-ui-sidebar/understand-the-implementation/slot-discipline:
en: 6eefc5c45abe8a8f
zh: 20b651e48a0ea903
diff --git a/packages/client/ui-sidebar/README.md b/packages/client/ui-sidebar/README.md
index 049673a2ce..5a21b27987 100644
--- a/packages/client/ui-sidebar/README.md
+++ b/packages/client/ui-sidebar/README.md
@@ -31,13 +31,13 @@ The sidebar is the navigation shell: users see the brand, start new sessions, co
### Brand and New Session
-The expanded brand row renders `sidebar.brand.mark` and `sidebar.brand.name` as independent single slots; the collapsed rail renders the same mark slot. On Web and Windows desktop, clicking the expanded brand starts a new session; hovering or focusing it shows no tooltip. Without occupants, the shell uses the fish mark and a localized local-build label. A complete build stacks a code badge below the label as `version[-commit][-dirty]`, using `DSH_CLIENT_VERSION`, the optional 7-character `DSH_CLIENT_COMMIT_HASH`, and `DSH_CLIENT_GIT_DIRTY=true`; missing version metadata omits the badge. Clicking the fallback mark or label of such a build copies that badge text instead of starting a session, also on macOS desktop, and a successful write shows a Version copied toast. New Session targets the explicit Workspace used by a scoped action, otherwise the current Session's Workspace, otherwise the most recently active Workspace; when none exists it clears into the blank New Session page. The expanded New Session button displays the effective binding as trailing grey text on keyboard focus regardless of hover capability, and on hover when the device supports it. While the shortcut is visible, the centered icon and label fade before it when space is narrow; CSS reserves the shortcut’s width without measuring the button. Icon-only controls retain tooltips with platform-formatted keycaps, including the resident macOS header control when the sidebar is hidden. All controls expose `aria-keyshortcuts`.
+The expanded brand row renders `sidebar.brand.mark` and `sidebar.brand.name` as independent single slots; the collapsed rail renders the same mark slot. On Web and Windows desktop, clicking the expanded brand starts a new session; hovering or focusing it shows no tooltip. Without occupants, the shell uses the fish mark and a localized local-build label. A complete build stacks a code badge below the label as `version[-commit][-dirty]`, using `DSH_CLIENT_VERSION`, the optional 7-character `DSH_CLIENT_COMMIT_HASH`, and `DSH_CLIENT_GIT_DIRTY=true`; missing version metadata omits the badge. Clicking the fallback mark or label of such a build copies that badge text instead of starting a session, also on macOS desktop, and a successful write shows a Version copied toast. New Session targets the explicit Workspace used by a scoped action, otherwise the current Session's Workspace, otherwise the most recently active Workspace; when none exists it clears into the blank New Session page. The expanded New Session button displays the effective binding as trailing grey text on keyboard focus regardless of hover capability, and on hover when the device supports it. While the shortcut is visible, the centered icon and label fade before it when space is narrow; CSS reserves the shortcut’s width without measuring the button. Icon-only controls retain tooltips with platform-formatted keycaps, including the resident macOS header control when the sidebar is hidden. All controls expose `aria-keyshortcuts`. While the global access snapshot excludes `session/create`, the brand row stays inert and neither the New Session button nor the window-chrome New Session control renders; the Host still decides every create request.
### Global panel entries
Plugins add an icon component to the root-scoped `sidebar.panellist` list with an `id`, optional `order`, and a string or locale-aware `label`. The same id addresses the component registered in the layout's root-scoped `main` keyed slot; selecting a missing main entry throws without changing the current selection. The label supplies plain visible text, the accessible name, and the collapsed tooltip. Expanded navigation rows have a 2px gap. Each row reads its own selected state through `usePanelInfo`; moving DOM focus to search or a directory picker does not change the displayed panel or its selected row. With no panel registrations, neither the list nor spacing for it is rendered. The shipped composition registers no example panel.
-While the root-scoped `sidebar.panellist.actions` list has registrations, a **More** button styled as a panel row follows the rows inside the navigation landmark; the collapsed rail shows its label as a tooltip. It opens a menu whose rows are that list's registrations. Each receives `entries` (customizable panels in registration order with `id`, `label`, and `pinned`; Plugins is never listed), `onOpen(id)`, which selects the panel, `onPinnedChange(id, pinned)`, and `onDismiss()`, which closes the menu. The sidebar keeps one root list of unpinned panel ids in browser `localStorage` under `dsh.slot.manual-order.sidebar.panellist.v1`, stored as `{ "unpinned": string[] }` and shared by every Session; the rows omit unpinned panels except the selected one, which keeps its row and `aria-current` until another panel is selected, and Plugins always shows. Unpinning hides only the row; the main panel, its registration, and the current selection stay unchanged. Ids of panels that are not registered stay stored, so a panel that registers again stays unpinned; stored content other than a string array shows every row.
+While the root-scoped `sidebar.panellist.actions` list has registrations, a **More** button styled as a panel row follows the rows inside the navigation landmark; the collapsed rail shows its label as a tooltip. It opens a menu whose rows are that list's registrations. Each receives `entries` (customizable panels in registration order with `id`, `label`, `pinned`, and `active`; Plugins is never listed), `onOpen(id)`, which selects the panel, `onPinnedChange(id, pinned)`, and `onDismiss()`, which closes the menu. The sidebar keeps one in-memory list of unpinned panel ids shared by every Session; Plugins always shows. The default More menu only offers plugin browsing and creation, without panel rows or pin controls. Pin callbacks remain available to other registrations, but choices reset when the plugin reloads and legacy localStorage values are ignored. Unpinning hides only the row; the main panel, its registration, and the current selection stay unchanged. Unregistered panel ids remain in the in-memory list until the plugin reloads.
### Collapse behavior
@@ -64,7 +64,7 @@ On Desktop, the official navigation controls report `sidebar_menu_click` with `m
- The expanded brand and the New Session button report `menu_name=new_session` with `position=sidebar`. While the Windows caption hosts the collapsed New Session button, that button reports `position=titlebar`; the macOS New Session control beside the traffic lights also reports `position=titlebar`.
- The **Plugins** and **Automation tasks** rows report `menu_name=plugin` and `menu_name=cron` with `position=sidebar`. Other panel rows report nothing.
- Each **More** button click reports `menu_name=more` with `position=sidebar`, whether it opens or closes the menu.
-- A More menu row that opens Plugins or Automation tasks through `onOpen(id)` reports `menu_name=plugin` or `menu_name=cron` with `position=sidebar_more`. The report precedes the registration check, so a request for a panel that has unregistered still counts although nothing is selected.
+- A third-party More menu row that calls `onOpen(id)` for Plugins or Automation tasks reports `menu_name=plugin` or `menu_name=cron` with `position=sidebar_more`. The shipped More menu only browses and creates plugins, so it never calls `onOpen`. The report precedes the registration check, so a request for a panel that has unregistered still counts although nothing is selected.
-----
@@ -74,7 +74,7 @@ On Desktop, the official navigation controls report `sidebar_menu_click` with `m
Implementation internals — click to expand
-The shell is pure composition: `SidebarRootComponentProps` composes the layout owner share, the global `useSessions` and `useWorkspaces` hooks, the declared brand, the `sidebar.workspaces` and `sidebar.settings` child slots, and injected navigation callbacks. Panel entries and their optional titles use the same composition path. Panel metadata is derived from list registrations and locale changes; selection belongs to the layout store. The injected `trackEntryClick(attributes)` forwards to `ctx.get('productAnalytics')?.track('sidebar_menu_click', attributes)`, and `selectPanel` only selects, so each control supplies its own entry and position.
+The shell is pure composition: `SidebarRootComponentProps` composes the layout owner share, the global `useSessions` and `useWorkspaces` hooks, the declared brand, the `sidebar.workspaces` and `sidebar.settings` child slots, and injected navigation callbacks. The sidebar owns `sidebar.panellist` and renders each panel's registered icon directly. Panel metadata is derived from list registrations and locale changes; selection belongs to the layout store. The injected `trackEntryClick(attributes)` forwards to `ctx.get('productAnalytics')?.track('sidebar_menu_click', attributes)`, and `selectPanel` only selects, so each control supplies its own entry and position.
### Slot discipline
diff --git a/packages/client/ui-sidebar/README.zh.md b/packages/client/ui-sidebar/README.zh.md
index c9acb9aa78..08eb1f1648 100644
--- a/packages/client/ui-sidebar/README.zh.md
+++ b/packages/client/ui-sidebar/README.zh.md
@@ -31,13 +31,13 @@ dsh Web 客户端的侧边栏让用户识别当前构建、启动新会话、将
### 品牌与 New Session
-展开的品牌行把 `sidebar.brand.mark` 与 `sidebar.brand.name` 渲染为两个独立的 single slot;收起轨道则渲染同一个 mark slot。在 Web 和 Windows 桌面端,点击展开态的品牌会新建会话,悬停或聚焦时不显示 tooltip。没有占位者时,外壳使用鱼形标记和本地化的本地构建标签。完整构建会在标签下方显示代码徽标;该徽标使用 `DSH_CLIENT_VERSION`、可选的 7 位 `DSH_CLIENT_COMMIT_HASH` 与 `DSH_CLIENT_GIT_DIRTY=true` 组装成 `version[-commit][-dirty]`;缺少版本元数据时不显示徽标。此类构建中点击回退的标记或标签会复制该徽标文本而非新建会话,macOS 桌面端同样适用;写入成功后弹出“版本号复制成功”toast。New Session 优先使用作用域操作明确指定的 Workspace,否则使用当前 Session 所属 Workspace,再否则使用最近活跃 Workspace;一个 Workspace 都没有时则清空选择,进入空白 New Session 页面。 展开态的新建会话按钮在键盘聚焦时于右侧以灰色文字显示当前有效绑定,不受设备悬停能力限制;支持悬停的设备也会在悬停时显示。快捷键可见且空间较窄时,居中的图标与文字在快捷键之前渐隐;CSS 为快捷键保留宽度,无需测量按钮。纯图标控件保留使用平台键位样式的 tooltip,侧栏隐藏时常驻的 macOS 头部控件也保持一致。所有控件均提供 `aria-keyshortcuts`。
+展开的品牌行把 `sidebar.brand.mark` 与 `sidebar.brand.name` 渲染为两个独立的 single slot;收起轨道则渲染同一个 mark slot。在 Web 和 Windows 桌面端,点击展开态的品牌会新建会话,悬停或聚焦时不显示 tooltip。没有占位者时,外壳使用鱼形标记和本地化的本地构建标签。完整构建会在标签下方显示代码徽标;该徽标使用 `DSH_CLIENT_VERSION`、可选的 7 位 `DSH_CLIENT_COMMIT_HASH` 与 `DSH_CLIENT_GIT_DIRTY=true` 组装成 `version[-commit][-dirty]`;缺少版本元数据时不显示徽标。此类构建中点击回退的标记或标签会复制该徽标文本而非新建会话,macOS 桌面端同样适用;写入成功后弹出“版本号复制成功”toast。New Session 优先使用作用域操作明确指定的 Workspace,否则使用当前 Session 所属 Workspace,再否则使用最近活跃 Workspace;一个 Workspace 都没有时则清空选择,进入空白 New Session 页面。 展开态的新建会话按钮在键盘聚焦时于右侧以灰色文字显示当前有效绑定,不受设备悬停能力限制;支持悬停的设备也会在悬停时显示。快捷键可见且空间较窄时,居中的图标与文字在快捷键之前渐隐;CSS 为快捷键保留宽度,无需测量按钮。纯图标控件保留使用平台键位样式的 tooltip,侧栏隐藏时常驻的 macOS 头部控件也保持一致。所有控件均提供 `aria-keyshortcuts`。全局访问快照不含 `session/create` 时,品牌行不可点击,New Session 按钮和窗口标题栏中的 New Session 控件都不渲染;每个新建请求仍由 Host 判定。
### 全局面板入口
插件在 root 作用域的 `sidebar.panellist` list 中注册图标组件,提供 `id`、可选 `order`,以及字符串或 locale-aware 的 `label`。同一个 id 寻址布局中 root 作用域 `main` keyed slot 的组件;选择不存在的主面板条目会抛错,并保留当前选中态。标签提供普通可见文字、无障碍名称和折叠提示。展开态导航行的间距为 2px。每一行通过 `usePanelInfo` 读取自己的选中态;DOM 焦点移到搜索框或目录选择器时,显示的面板及其列表项选中态不变。没有面板注册项时,列表及其间距均不渲染。产品随附的组合不注册示例面板。
-root 作用域的 `sidebar.panellist.actions` list 有注册项时,导航区域的面板行之后显示面板行样式的**更多**按钮;收起的窄栏以提示框显示其名称。它打开的菜单以该 list 的注册项为菜单行。每个注册项接收 `entries`(按注册顺序的可自定义面板,含 `id`、`label` 与 `pinned`;不含插件管理)、选中面板的 `onOpen(id)`、`onPinnedChange(id, pinned)`,以及关闭菜单的 `onDismiss()`。侧边栏在浏览器 `localStorage` 的 `dsh.slot.manual-order.sidebar.panellist.v1` 下保存一份 root 级的已收起面板 id 列表,格式为 `{ "unpinned": string[] }`,所有会话共用;面板行不显示已收起的面板,但当前选中的面板保留其行和 `aria-current`,直到选中其他面板;插件管理入口始终显示。收起只隐藏导航行,主面板、其注册和当前选中态不变。未注册面板的 id 保留在列表中,因此重新注册的面板保持收起;保存内容不是字符串数组时,所有行都显示。
+root 作用域的 `sidebar.panellist.actions` list 有注册项时,导航区域的面板行之后显示面板行样式的**更多**按钮;收起的窄栏以提示框显示其名称。它打开的菜单以该 list 的注册项为菜单行。每个注册项接收 `entries`(按注册顺序的可自定义面板,含 `id`、`label`、`pinned` 与 `active`;不含插件管理)、选中面板的 `onOpen(id)`、`onPinnedChange(id, pinned)`,以及关闭菜单的 `onDismiss()`。侧边栏在内存中保存所有会话共用的已收起面板 id 列表;插件管理入口始终显示。默认更多菜单只提供插件浏览和创建,不列出面板或 Pin 控件。其他注册项仍可调用 Pin 回调,但插件重新加载时选择重置,不读取旧 localStorage 值。收起只隐藏导航行,主面板、其注册和当前选中态不变。未注册面板的 id 保留在内存列表中,直到插件重新加载。
### 折叠行为
@@ -64,7 +64,7 @@ root 作用域的 `sidebar.panellist.actions` list 有注册项时,导航区
- 展开态品牌与新建会话按钮上报 `menu_name=new_session`、`position=sidebar`。Windows 顶栏承载收起态新建会话按钮时,该按钮上报 `position=titlebar`;macOS 红绿灯旁的新建会话控件同样上报 `position=titlebar`。
- **插件**与**自动化任务**面板行分别上报 `menu_name=plugin` 与 `menu_name=cron`,`position=sidebar`。其他面板行不上报。
- 每次点击**更多**按钮都上报 `menu_name=more`、`position=sidebar`,无论打开还是关闭菜单。
-- 更多菜单中的行通过 `onOpen(id)` 打开插件或自动化任务时,上报 `menu_name=plugin` 或 `menu_name=cron`,`position=sidebar_more`。上报先于注册检查,因此所请求的面板已注销时仍会计数,但不会选中任何面板。
+- 第三方注册的更多菜单行通过 `onOpen(id)` 打开插件或自动化任务时,上报 `menu_name=plugin` 或 `menu_name=cron`,`position=sidebar_more`。随产品提供的更多菜单只浏览和创建插件,不会调用 `onOpen`。上报先于注册检查,因此所请求的面板已注销时仍会计数,但不会选中任何面板。
-----
@@ -74,7 +74,7 @@ root 作用域的 `sidebar.panellist.actions` list 有注册项时,导航区
实现细节——点击展开
-外壳是纯组合:`SidebarRootComponentProps` 组合布局 owner share、全局 `useSessions` 与 `useWorkspaces` 钩子、已声明的品牌、`sidebar.workspaces` 与 `sidebar.settings` 子 slot,以及注入的导航回调。面板入口及其可选标题使用相同的组合方式。面板元数据由列表注册和 locale 变化派生;选中态属于布局存储。注入的 `trackEntryClick(attributes)` 转发到 `ctx.get('productAnalytics')?.track('sidebar_menu_click', attributes)`;`selectPanel` 只负责选中,因此由各控件自行提供入口与位置。
+外壳是纯组合:`SidebarRootComponentProps` 组合布局 owner share、全局 `useSessions` 与 `useWorkspaces` 钩子、已声明的品牌、`sidebar.workspaces` 与 `sidebar.settings` 子 slot,以及注入的导航回调。侧边栏声明 `sidebar.panellist`,并直接渲染各面板注册的图标。面板元数据由列表注册和 locale 变化派生;选中态属于布局存储。注入的 `trackEntryClick(attributes)` 转发到 `ctx.get('productAnalytics')?.track('sidebar_menu_click', attributes)`;`selectPanel` 只负责选中,因此由各控件自行提供入口与位置。
### slot 纪律
diff --git a/packages/client/ui-sidebar/src/client/HeaderLeadingControls.tsx b/packages/client/ui-sidebar/src/client/HeaderLeadingControls.tsx
index 3bb05369cb..618bdd26e7 100644
--- a/packages/client/ui-sidebar/src/client/HeaderLeadingControls.tsx
+++ b/packages/client/ui-sidebar/src/client/HeaderLeadingControls.tsx
@@ -20,10 +20,14 @@ export type HeaderLeadingControlsProps =
* controls off screen; this occupant puts them back beside the traffic
* lights. The frame mounts the seat only in that state and owns its
* placement, so the occupant renders unconditionally.
- * @param props - Injected sidebar actions plus the sidebar locale seat.
- * @returns the two window-chrome controls.
+ * @param props - Injected sidebar actions, the global access Hook, and the sidebar locale seat.
+ * @returns the sidebar toggle, plus the New Session control while the access snapshot allows `session/create`.
*/
-export function HeaderLeadingControls({ toggleSidebar, startSession, trackEntryClick, useShortcuts, t }: HeaderLeadingControlsProps) {
+export function HeaderLeadingControls({
+ toggleSidebar, startSession, trackEntryClick, useRemoteAccess, useShortcuts, t,
+}: HeaderLeadingControlsProps) {
+ // A page whose access snapshot excludes starting a Session keeps only the sidebar toggle here.
+ const canStartSession = useRemoteAccess('session/create')
const shortcut = useShortcuts(rows => rows.find(row => row.id === 'sidebar.left.toggle'))
const newShortcut = useShortcuts(rows => rows.find(row => row.id === 'session.new'))
return (
@@ -39,20 +43,22 @@ export function HeaderLeadingControls({ toggleSidebar, startSession, trackEntryC
-
- {
- trackEntryClick({ menu_name: 'new_session', position: 'titlebar' })
- startSession()
- }}
- >
-
-
-
+ {canStartSession && (
+
+ {
+ trackEntryClick({ menu_name: 'new_session', position: 'titlebar' })
+ startSession()
+ }}
+ >
+
+
+
+ )}
)
}
diff --git a/packages/client/ui-sidebar/src/client/SidebarRoot.tsx b/packages/client/ui-sidebar/src/client/SidebarRoot.tsx
index cf8bcc1167..c4083d223d 100644
--- a/packages/client/ui-sidebar/src/client/SidebarRoot.tsx
+++ b/packages/client/ui-sidebar/src/client/SidebarRoot.tsx
@@ -135,6 +135,7 @@ export function SidebarRoot({
selectPanel,
setPinned,
trackEntryClick,
+ useRemoteAccess,
usePanels,
useEntryPins,
useEntryActions,
@@ -149,6 +150,8 @@ export function SidebarRoot({
const hasEntryActions = useEntryActions(registered => registered)
const shortcut = useShortcuts(rows => rows.find(row => row.id === 'sidebar.left.toggle'))
const newShortcut = useShortcuts(rows => rows.find(row => row.id === 'session.new'))
+ // A page whose access snapshot excludes starting a Session offers no New Session affordance.
+ const canStartSession = useRemoteAccess('session/create')
const toggleLabel = collapsed ? t('toggle.open') : t('toggle.collapse')
// Wide content stays mounted while the collapse animates (fading via
// .collapsed .wide), unmounts at settle, and remounts right away on expand.
@@ -278,7 +281,8 @@ export function SidebarRoot({
traffic lights and keeps the toggle at the sidebar's top-right. */}
{darwinDesktop &&
{toggle}
}
- {/* Expanded, the brand doubles as a New Session shortcut — except on
+ {/* Expanded, the brand doubles as a New Session shortcut while the
+ access snapshot allows `session/create` — except on
macOS, where it stays part of the logo row's window-drag surface
(a button would subtract itself through the global no-drag rule);
the collapsed rail's logo is the expand toggle below instead.
@@ -307,7 +311,7 @@ export function SidebarRoot({
)
- return darwinDesktop
+ return darwinDesktop || !canStartSession
?
{identity}
: (
{/* The label fades before the hover/focus shortcut, including on translucent backgrounds. */}
-
-
-
- {wide
- ?
- : }
- {wide && {t('session.new')} }
-
- {wide && newShortcut !== undefined && newShortcut.keys.length > 0 &&
-
- }
-
-
+ {canStartSession && (
+
+
+
+ {wide
+ ?
+ : }
+ {wide && {t('session.new')} }
+
+ {wide && newShortcut !== undefined && newShortcut.keys.length > 0 &&
+
+ }
+
+
+ )}
{panels.length > 0 && (
- {panels.filter(({ id }) => id === FIXED_PANEL_ID || id === activePanelId || !unpinned.includes(id)).map(({ id, label }) => (
+ {panels.filter(({ id }) => id === FIXED_PANEL_ID || !unpinned.includes(id)).map(({ id, label }) => (
id !== FIXED_PANEL_ID)
- .map(({ id, label }) => ({ id, label, pinned: !unpinned.includes(id) }))}
+ .map(({ id, label }) => ({ id, label, pinned: !unpinned.includes(id), active: id === activePanelId }))}
onOpen={(id) => {
const entry = officialEntry(id)
if (entry !== undefined) trackEntryClick({ menu_name: entry, position: 'sidebar_more' })
diff --git a/packages/client/ui-sidebar/src/client/contract/slots.ts b/packages/client/ui-sidebar/src/client/contract/slots.ts
index 645f3daeda..9e018b8be6 100644
--- a/packages/client/ui-sidebar/src/client/contract/slots.ts
+++ b/packages/client/ui-sidebar/src/client/contract/slots.ts
@@ -95,6 +95,8 @@ export interface SidebarPanelActionEntry {
readonly label: string
/** False while the launcher is unpinned from the sidebar. */
readonly pinned: boolean
+ /** Whether this panel is selected, independently of its pin state. */
+ readonly active?: boolean
}
/** Owner share of the panel list's More menu rows. */
@@ -169,7 +171,7 @@ export type SidebarRootInjected = {
hooks: {
panels: ObservableSnapshot
shortcuts: ObservableSnapshot
- /** Ids of unpinned panel launchers, persisted across reloads. */
+ /** Ids of unpinned panel launchers held in memory until plugin reload. */
entryPins: ObservableSnapshot
/** Whether `sidebar.panellist.actions` has registrations. */
entryActions: ObservableSnapshot
diff --git a/packages/client/ui-sidebar/src/client/panel-pins.ts b/packages/client/ui-sidebar/src/client/panel-pins.ts
index a5adfdbbff..72d0157ff2 100644
--- a/packages/client/ui-sidebar/src/client/panel-pins.ts
+++ b/packages/client/ui-sidebar/src/client/panel-pins.ts
@@ -1,11 +1,9 @@
-/** Persisted pin choices for the global panel launchers. */
+/** In-memory pin choices for the global panel launchers. */
import { createSnapshotStore, type ObservableSnapshot } from '@deepseek-ai/dsh-client-store'
/** Panel whose launcher always shows and never enters the customization list. */
export const FIXED_PANEL_ID = 'plugins'
-/** Root-level key shared with the slot layer's future per-list preferences. */
-const STORAGE_KEY = 'dsh.slot.manual-order.sidebar.panellist.v1'
const NO_PANELS: readonly string[] = []
/** Unpinned panel ids and their only mutation. */
@@ -13,30 +11,23 @@ export interface PanelPins {
/** Unpinned panel ids; ids of panels that are not registered stay listed. */
readonly unpinned: ObservableSnapshot
/**
- * Show or hide one panel launcher; the fixed panel is ignored.
+ * Pin or unpin one panel launcher; the fixed panel is ignored.
* @param id - Panel id.
- * @param pinned - Whether the launcher shows in the sidebar.
+ * @param pinned - Whether the launcher is manually pinned.
*/
readonly setPinned: (id: string, pinned: boolean) => void
}
-/** Stored content is unchecked; anything but a string array leaves every panel pinned. */
-function unpinnedPanels(value: unknown): readonly string[] {
- if (typeof value !== 'object' || value === null || !('unpinned' in value)) return NO_PANELS
- const { unpinned } = value
- return Array.isArray(unpinned) && unpinned.every((id): id is string => typeof id === 'string') ? unpinned : NO_PANELS
-}
-
/**
- * Create the persisted panel pin list; the snapshot keeps its reference until the list changes.
- * @returns Validated unpinned panel ids and the pin mutation.
+ * Create the in-memory panel pin list; the snapshot keeps its reference until the list changes.
+ * @returns Unpinned panel ids and the pin mutation.
*/
export function createPanelPins(): PanelPins {
- const store = createSnapshotStore({ unpinned: NO_PANELS }, { persist: { name: STORAGE_KEY } })
+ const store = createSnapshotStore<{ readonly unpinned: readonly string[] }>({ unpinned: NO_PANELS })
return {
- unpinned: { getSnapshot: () => unpinnedPanels(store.getSnapshot()), subscribe: listener => store.subscribe(listener) },
+ unpinned: { getSnapshot: () => store.getSnapshot().unpinned, subscribe: listener => store.subscribe(listener) },
setPinned: (id, pinned) => {
- const current = unpinnedPanels(store.getSnapshot())
+ const current = store.getSnapshot().unpinned
if (id === FIXED_PANEL_ID || pinned !== current.includes(id)) return
store.set({ unpinned: pinned ? current.filter(entry => entry !== id) : [...current, id] })
},
diff --git a/packages/client/ui-sidebar/tests/apply.client.spec.tsx b/packages/client/ui-sidebar/tests/apply.client.spec.tsx
index c843149edd..7f9b2884d5 100644
--- a/packages/client/ui-sidebar/tests/apply.client.spec.tsx
+++ b/packages/client/ui-sidebar/tests/apply.client.spec.tsx
@@ -125,13 +125,23 @@ describe('ui-sidebar apply', () => {
const fiber = b.ctx.plugin({ inject: [...inject], apply })
await fiber.await()
expect(b.slots.entries('sidebar')).toHaveLength(0)
- const disposeRoot = b.slots.register({
+ expect(b.slots.spec('sidebar.panellist')).toBeUndefined()
+ const declareSidebar = () => b.slots.register({
name: 'root',
children: { sidebar: { kind: 'single', scope: 'root' } },
}, SidebarFrame)
+ const disposeRoot = declareSidebar()
expect(b.slots.entries('sidebar')).toHaveLength(1)
+ expect(b.slots.spec('sidebar.panellist')).toEqual({ kind: 'list', scope: 'root' })
disposeRoot()
+ expect(b.slots.entries('sidebar')).toEqual([])
+ expect(b.slots.spec('sidebar.panellist')).toBeUndefined()
+ const disposeReplacement = declareSidebar()
+ expect(b.slots.entries('sidebar')).toHaveLength(1)
+ expect(b.slots.spec('sidebar.panellist')).toEqual({ kind: 'list', scope: 'root' })
await fiber.dispose()
+ expect(b.slots.spec('sidebar.panellist')).toBeUndefined()
+ disposeReplacement()
})
it('projects plugin-owned panel entries and removes them with their owner', async () => {
diff --git a/packages/client/ui-sidebar/tests/panel-list.client.spec.tsx b/packages/client/ui-sidebar/tests/panel-list.client.spec.tsx
index f4bda913fa..68e15ed3f5 100644
--- a/packages/client/ui-sidebar/tests/panel-list.client.spec.tsx
+++ b/packages/client/ui-sidebar/tests/panel-list.client.spec.tsx
@@ -91,7 +91,8 @@ function ProbeActions({ entries, onOpen, onPinnedChange, onDismiss }: PropsRunti
<>
{entries.map(entry => (
- { onOpen(entry.id) }}>Open {entry.label}
+ { onOpen(entry.id) }}>Open {entry.label}
{ onPinnedChange(entry.id, !entry.pinned) }}>
{entry.pinned ? 'Unpin' : 'Pin'} {entry.label}
@@ -323,21 +324,28 @@ describe('sidebar global panels', () => {
expect(view.getByRole('heading', { name: 'Alpha content' })).toBeTruthy()
act(() => { locale.setLocale('zh') })
expect(view.queryByRole('navigation')).toBeNull()
+ await runtime.mount({ inject: [...inject], apply })
+ const restored = await view.findByRole('button', { name: '甲面板' })
+ expect(restored.getAttribute('aria-current')).toBe('page')
+ const icon = within(restored).getByTestId(`${ALPHA}-icon`)
+ expect(icon.getAttribute('data-active')).toBe('true')
+ expect(icon.querySelector('svg')?.getAttribute('width')).toBe('16')
+ expect(runtime.slots.entries('sidebar.panellist').map(entry => entry.options.id)).toEqual([BETA, ALPHA])
})
})
describe('sidebar launcher pins', () => {
const PINS_KEY = 'dsh.slot.manual-order.sidebar.panellist.v1'
- it('hides stored unpinned panels, keeps Plugins, and keeps the list while panels are registered', async () => {
+ it('ignores stored unpinned panels and shows all registered launchers', async () => {
localStorage.setItem(PINS_KEY, JSON.stringify({ unpinned: [ALPHA, BETA, PLUGINS, 'removed-plugin'] }))
const { runtime, locale, view } = await bench()
await mountPanels(runtime, locale)
const navigation = await view.findByRole('navigation', { name: 'Global panels' })
- expect(within(navigation).queryAllByRole('button')).toEqual([])
+ expect(within(navigation).getAllByRole('button').map(row => row.textContent)).toEqual(['Beta panel', 'Alpha panel'])
await mountPanel(runtime, { id: PLUGINS, heading: 'Plugins content', label: 'Plugins', order: 0 })
await waitFor(() => {
- expect(within(navigation).getAllByRole('button').map(row => row.textContent)).toEqual(['Plugins'])
+ expect(within(navigation).getAllByRole('button').map(row => row.textContent)).toEqual(['Plugins', 'Beta panel', 'Alpha panel'])
})
expect(runtime.slots.entries('main').map(entry => entry.options.key)).toEqual(['conversation', ALPHA, BETA, PLUGINS])
})
@@ -377,16 +385,25 @@ describe('sidebar launcher pins', () => {
.toEqual(['Open Beta panel', 'Unpin Beta panel', 'Open Alpha panel', 'Unpin Alpha panel', 'Dismiss'])
fireEvent.click(menu.getByRole('menuitem', { name: 'Unpin Alpha panel' }))
- expect(JSON.parse(localStorage.getItem(PINS_KEY) ?? 'null')).toEqual({ unpinned: [ALPHA] })
+ expect(localStorage.getItem(PINS_KEY)).toBeNull()
await waitFor(() => { expect(within(navigation).queryByRole('button', { name: 'Alpha panel' })).toBeNull() })
expect(menu.getByRole('menuitem', { name: 'Pin Alpha panel' })).toBeTruthy()
fireEvent.click(menu.getByRole('menuitem', { name: 'Open Alpha panel' }))
expect(layout.selectPanel).toHaveBeenLastCalledWith(ALPHA)
expect(view.getByRole('heading', { name: 'Alpha content' })).toBeTruthy()
- // The open panel keeps its unpinned row as the current page until another panel is selected.
+ await waitFor(() => { expect(within(navigation).queryByRole('button', { name: 'Alpha panel' })).toBeNull() })
+ fireEvent.click(menu.getByRole('menuitem', { name: 'Pin Alpha panel' }))
const alphaRow = await within(navigation).findByRole('button', { name: 'Alpha panel' })
expect(alphaRow.getAttribute('aria-current')).toBe('page')
+ fireEvent.click(menu.getByRole('menuitem', { name: 'Unpin Alpha panel' }))
+ await waitFor(() => { expect(within(navigation).queryByRole('button', { name: 'Alpha panel' })).toBeNull() })
+ expect(view.getByRole('heading', { name: 'Alpha content' })).toBeTruthy()
+ expect(menu.getByRole('menuitem', { name: 'Open Alpha panel' }).getAttribute('aria-current')).toBe('page')
act(() => { layout.selectPanel(BETA) })
+ await waitFor(() => {
+ expect(menu.getByRole('menuitem', { name: 'Open Alpha panel' }).hasAttribute('aria-current')).toBe(false)
+ expect(menu.getByRole('menuitem', { name: 'Open Beta panel' }).getAttribute('aria-current')).toBe('page')
+ })
await waitFor(() => { expect(within(navigation).queryByRole('button', { name: 'Alpha panel' })).toBeNull() })
fireEvent.click(menu.getByRole('menuitem', { name: 'Pin Alpha panel' }))
diff --git a/packages/client/ui-sidebar/tests/panel-pins.client.spec.ts b/packages/client/ui-sidebar/tests/panel-pins.client.spec.ts
index a870fefed0..245a2a4b34 100644
--- a/packages/client/ui-sidebar/tests/panel-pins.client.spec.ts
+++ b/packages/client/ui-sidebar/tests/panel-pins.client.spec.ts
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
-/** Sidebar launcher pins persist per panel id and ignore the fixed Plugins panel. */
+/** Sidebar launcher pins stay in memory per panel id and ignore the fixed Plugins panel. */
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createPanelPins } from '../src/client/panel-pins.ts'
@@ -7,7 +7,7 @@ const KEY = 'dsh.slot.manual-order.sidebar.panellist.v1'
afterEach(() => { localStorage.clear(); vi.restoreAllMocks() })
describe('sidebar panel pins', () => {
- it('persists unpinned panel ids and restores them in a new instance', () => {
+ it('keeps unpinned panel ids in memory without restoring them in a new instance', () => {
const pins = createPanelPins()
const listener = vi.fn()
pins.unpinned.subscribe(listener)
@@ -17,12 +17,12 @@ describe('sidebar panel pins', () => {
pins.setPinned('notes', false)
pins.setPinned('tasks', false)
expect(pins.unpinned.getSnapshot()).toEqual(['notes', 'tasks'])
- expect(JSON.parse(localStorage.getItem(KEY) ?? 'null')).toEqual({ unpinned: ['notes', 'tasks'] })
+ expect(localStorage.getItem(KEY)).toBeNull()
expect(listener).toHaveBeenCalledTimes(2)
- expect(createPanelPins().unpinned.getSnapshot()).toEqual(['notes', 'tasks'])
+ expect(createPanelPins().unpinned.getSnapshot()).toEqual([])
pins.setPinned('notes', true)
expect(pins.unpinned.getSnapshot()).toEqual(['tasks'])
- expect(JSON.parse(localStorage.getItem(KEY) ?? 'null')).toEqual({ unpinned: ['tasks'] })
+ expect(localStorage.getItem(KEY)).toBeNull()
})
it('ignores Plugins and repeated choices without republishing', () => {
@@ -39,20 +39,14 @@ describe('sidebar panel pins', () => {
})
it('keeps ids of panels that are not registered', () => {
- localStorage.setItem(KEY, JSON.stringify({ unpinned: ['removed-plugin'] }))
const pins = createPanelPins()
+ pins.setPinned('removed-plugin', false)
pins.setPinned('notes', false)
expect(pins.unpinned.getSnapshot()).toEqual(['removed-plugin', 'notes'])
})
- it.each([
- ['a non-array list', { unpinned: 'notes' }],
- ['non-string ids', { unpinned: ['notes', 1] }],
- ['a missing list', { order: ['notes'] }],
- ['null', null],
- ['a string', 'notes'],
- ])('reads %s as no unpinned panels with a stable snapshot', (_name, stored) => {
- localStorage.setItem(KEY, JSON.stringify(stored))
+ it('ignores a legacy stored list instead of restoring it', () => {
+ localStorage.setItem(KEY, JSON.stringify({ unpinned: ['notes'] }))
const pins = createPanelPins()
const snapshot = pins.unpinned.getSnapshot()
expect(snapshot).toEqual([])
diff --git a/packages/client/ui-sidebar/tests/sidebar-root.client.spec.tsx b/packages/client/ui-sidebar/tests/sidebar-root.client.spec.tsx
index 8b3b77265b..4dedd711db 100644
--- a/packages/client/ui-sidebar/tests/sidebar-root.client.spec.tsx
+++ b/packages/client/ui-sidebar/tests/sidebar-root.client.spec.tsx
@@ -157,6 +157,37 @@ describe('SidebarRoot shell', () => {
expect(b.trackEntryClick).toHaveBeenCalledExactlyOnceWith({ menu_name: 'new_session', position: 'sidebar' })
})
+ it('offers no New Session control while the access snapshot excludes session/create', () => {
+ const toggleSidebar = vi.fn()
+ const startSession = vi.fn()
+ const useRemoteAccess = vi.fn((action: string) => action !== 'session/create')
+ render( {}} usePanels={selector => selector([])} useEntryPins={selector => selector([])}
+ useEntryActions={selector => selector(false)} setPinned={vi.fn()}
+ useShortcuts={selector => selector([])}
+ useResource={useResource} useWorkspaces={neverHook}
+ {...allowAllAccess}
+ useRemoteAccess={useRemoteAccess}
+ startSession={startSession} toggleSidebar={toggleSidebar} trackEntryClick={vi.fn()} t={t}
+ renderSlot={((key: string) => {
+ if (key === 'sidebar.brand.mark') return M
+ if (key === 'sidebar.brand.name') return Brand
+ return null
+ }) as SidebarRootComponentProps['renderSlot']}
+ />)
+
+ // Neither the brand row nor the dedicated button starts a Session; the brand still renders.
+ expect(useRemoteAccess).toHaveBeenCalledWith('session/create')
+ expect(screen.queryByRole('button', { name: 'New session' })).toBeNull()
+ expect(screen.getByTestId('brand-mark')).toBeTruthy()
+ expect(screen.getByTestId('brand-name')).toBeTruthy()
+ fireEvent.click(screen.getByRole('button', { name: 'Collapse sidebar' }))
+ expect(toggleSidebar).toHaveBeenCalledOnce()
+ expect(startSession).not.toHaveBeenCalled()
+ })
+
it('renders generic brand fallbacks when no package fills the slots', () => {
vi.stubEnv('DSH_CLIENT_COMMIT_HASH', '0123456')
vi.stubEnv('DSH_CLIENT_GIT_DIRTY', 'true')
@@ -359,8 +390,8 @@ it('wires the shell.leading controls to the shared sidebar actions', () => {
const toggleSidebar = vi.fn()
const startSession = vi.fn()
const trackEntryClick = vi.fn()
- // This occupant only consumes its actions and locale, not Session hooks.
- const props = { toggleSidebar, startSession, trackEntryClick, t, useShortcuts: select => select([{ id: 'sidebar.left.toggle' as ShortcutCommandId, label: 'Toggle sidebar', aliases: [], binding: null, modified: false, conflicts: [], issue: null, keys: ['⌘', 'B'], aria: 'Meta+B' }]) } as HeaderLeadingControlsProps
+ // This occupant only consumes its actions, the global access Hook, and locale, not Session hooks.
+ const props = { toggleSidebar, startSession, trackEntryClick, t, useRemoteAccess: allowAllAccess.useRemoteAccess, useShortcuts: select => select([{ id: 'sidebar.left.toggle' as ShortcutCommandId, label: 'Toggle sidebar', aliases: [], binding: null, modified: false, conflicts: [], issue: null, keys: ['⌘', 'B'], aria: 'Meta+B' }]) } as HeaderLeadingControlsProps
render( )
fireEvent.click(screen.getByRole('button', { name: en['toggle.open'] }))
expect(toggleSidebar).toHaveBeenCalledOnce()
@@ -370,6 +401,12 @@ it('wires the shell.leading controls to the shared sidebar actions', () => {
// The window-chrome New Session control reports a titlebar click before starting the Session.
expect(trackEntryClick).toHaveBeenCalledExactlyOnceWith({ menu_name: 'new_session', position: 'titlebar' })
expect(trackEntryClick.mock.invocationCallOrder[0]).toBeLessThan(startSession.mock.invocationCallOrder[0]!)
+ cleanup()
+
+ // A page whose access snapshot excludes starting a Session keeps only the sidebar toggle.
+ render( action !== 'session/create'} />)
+ expect(screen.getByRole('button', { name: en['toggle.open'] })).toBeTruthy()
+ expect(screen.queryByRole('button', { name: en['session.new.label'] })).toBeNull()
})
describe('Windows caption New Session analytics', () => {
diff --git a/packages/client/ui-workspace/README.i18n.yaml b/packages/client/ui-workspace/README.i18n.yaml
index 3d826e05b2..2db12fdf09 100644
--- a/packages/client/ui-workspace/README.i18n.yaml
+++ b/packages/client/ui-workspace/README.i18n.yaml
@@ -33,8 +33,8 @@
en: 9fb88260380bf093
zh: 3bace2b96ecd4a82
/deepseek-ai-dsh-client-ui-workspace/use-this-package/active-schedule-markers:
- en: ac15c05cb045f748
- zh: 6330172c7231b2b8
+ en: 2168316f604893a5
+ zh: fe11347009339a9a
/deepseek-ai-dsh-client-ui-workspace/understand-the-implementation:
en: 96a37e74555a36b7
zh: d03941523509a5cf
diff --git a/packages/client/ui-workspace/README.md b/packages/client/ui-workspace/README.md
index 754428e4e8..5048b50c93 100644
--- a/packages/client/ui-workspace/README.md
+++ b/packages/client/ui-workspace/README.md
@@ -73,7 +73,9 @@ Grouped and flat Session rows show a clock mark when the Session has active sche
-----
-`ctx.uiWorkspace.openSession(target)` synchronously replaces the owned `mainView` reference and returns the main area to Conversation without waiting for `reference.ready`, so history loading renders inside the selected Session view. The target may be a known Session id or a durable direct-parent subagent address; an explicit address does not require a preloaded parent catalog. `openWorkspace(id, beforeOpen?, options?)` opens a Workspace blank only if no later navigation has superseded the request; New Session opens the same blank as Workspace reopening, so it returns to that blank's unsent draft. `forkSession(id)` creates the child without navigating or superseding a pending navigation. The optional synchronous preparation callback runs after the target is retained and only for a current Workspace request, so superseded requests do not move composer drafts. Navigation or owner disposal suppresses the late UI commit, not the underlying Session creation. Startup restoration retains its main reference without changing the selected panel or cancelling a later navigation. Archiving the main Session releases its reference and clears the main selection. Selection failure leaves a global panel visible. Session rows read `usePanelInfo` to suppress their selected appearance while a global panel is active; search and directory-picker focus alone do not leave that panel.
+`ctx.uiWorkspace.openSession(target)` synchronously replaces the owned `mainView` reference and returns the main area to Conversation without waiting for `reference.ready`, so history loading renders inside the selected Session view. The target may be a known Session id or a durable direct-parent subagent address; an explicit address does not require a preloaded parent catalog. `openWorkspace(id, beforeOpen?, options?)` opens a Workspace blank only if no later navigation has superseded the request; New Session opens the same blank as Workspace reopening, so it returns to that blank's unsent draft. `forkSession(id, onCreated?, options?)` creates the child without navigating or superseding a pending navigation; `allowMigration` carries the caller's migration permission. The optional synchronous preparation callback runs after the target is retained and only for a current Workspace request, so superseded requests do not move composer drafts. Navigation or owner disposal suppresses the late UI commit, not the underlying Session creation.
+
+Session and Workspace rows derive their affordances from the browser root's access Hooks: the global `useRemoteAccess('session/create')` gates every New Session entry, `useWorkspaceAccess` gates a Workspace's create, rename, delete, and reorder verbs, and `useSessionAccess` gates a Session row's rename, fork, drag, pin, archive, and unarchive verbs plus the search result's restore control. A refused verb is not rendered, while every Host request remains authoritative. Startup restoration retains its main reference without changing the selected panel or cancelling a later navigation. Archiving the main Session releases its reference and clears the main selection. Selection failure leaves a global panel visible. Session rows read `usePanelInfo` to suppress their selected appearance while a global panel is active; search and directory-picker focus alone do not leave that panel.
Navigation and startup restoration obtain the selected Session's projections from its `follow`; neither issues a separate projection refresh for that Session or its parent, except that restoring a saved blank first reloads that Session's projections, because listed values are cached hints that can lag its log after an unclean Host exit.
diff --git a/packages/client/ui-workspace/README.zh.md b/packages/client/ui-workspace/README.zh.md
index 34da2eb9b8..e28bcfda6e 100644
--- a/packages/client/ui-workspace/README.zh.md
+++ b/packages/client/ui-workspace/README.zh.md
@@ -73,7 +73,9 @@ Session 行渲染运行时的实时 `pendingInteraction` 分类:审批显示**
-----
-`ctx.uiWorkspace.openSession(target)` 会同步替换其拥有的 `mainView` reference,并让主区域返回 Conversation,而不等待 `reference.ready`,因此历史加载会显示在已经选中的 Session 视图内。目标可以是已知 Session id,也可以是持久的直接父子 subagent 地址;显式地址不要求预先加载 parent catalog。`openWorkspace(id, beforeOpen?, options?)` 仅在请求未被后续导航替代时打开工作区空白 Session;新会话与重新打开工作区打开同一个空白会话,因此会回到该空白会话中未发送的草稿。`forkSession(id)` 创建子会话,不导航,也不替代尚未完成的导航。可选的同步准备回调在目标被 retain 后执行,并且仅对仍有效的 Workspace 请求执行,因此过期请求不会搬移 composer 草稿。后续导航或 owner 释放会阻止晚到的 UI 提交,但不取消底层 Session 创建。启动恢复会 retain 主 reference,不改变已选面板,也不取消后续导航。归档主 Session 会释放其 reference 并清除主选择。选择失败时保留当前全局面板。Session 行读取 `usePanelInfo`,在全局面板活跃时不显示 Session 选中样式;仅把焦点移到搜索框或目录选择器不会离开该面板。
+`ctx.uiWorkspace.openSession(target)` 会同步替换其拥有的 `mainView` reference,并让主区域返回 Conversation,而不等待 `reference.ready`,因此历史加载会显示在已经选中的 Session 视图内。目标可以是已知 Session id,也可以是持久的直接父子 subagent 地址;显式地址不要求预先加载 parent catalog。`openWorkspace(id, beforeOpen?, options?)` 仅在请求未被后续导航替代时打开工作区空白 Session;新会话与重新打开工作区打开同一个空白会话,因此会回到该空白会话中未发送的草稿。`forkSession(id, onCreated?, options?)` 创建子会话,不导航,也不替代尚未完成的导航;`allowMigration` 携带调用方的迁移许可。可选的同步准备回调在目标被 retain 后执行,并且仅对仍有效的 Workspace 请求执行,因此过期请求不会搬移 composer 草稿。后续导航或 owner 释放会阻止晚到的 UI 提交,但不取消底层 Session 创建。
+
+Session 行与 Workspace 行的可用操作来自浏览器根的访问 Hook:全局 `useRemoteAccess('session/create')` 门控所有新建会话入口,`useWorkspaceAccess` 门控 Workspace 的新建、重命名、删除与排序动作,`useSessionAccess` 门控 Session 行的重命名、分叉、拖拽、置顶、归档与取消归档动作,以及搜索结果的恢复控件。被拒绝的动作不渲染,但每个发往 Host 的请求仍以其授权为准。启动恢复会 retain 主 reference,不改变已选面板,也不取消后续导航。归档主 Session 会释放其 reference 并清除主选择。选择失败时保留当前全局面板。Session 行读取 `usePanelInfo`,在全局面板活跃时不显示 Session 选中样式;仅把焦点移到搜索框或目录选择器不会离开该面板。
导航和启动恢复通过所选 Session 的 `follow` 获取投影,不会另行刷新该 Session 或其父会话的投影;例外是恢复已保存的空白会话时先重新加载该 Session 的投影,因为列表中的值是缓存提示,Host 非正常退出后可能落后于其日志。
diff --git a/packages/client/ui-workspace/src/client/navigation.ts b/packages/client/ui-workspace/src/client/navigation.ts
index 6b4221ae19..42265f1af6 100644
--- a/packages/client/ui-workspace/src/client/navigation.ts
+++ b/packages/client/ui-workspace/src/client/navigation.ts
@@ -71,9 +71,10 @@ export interface UiWorkspace {
options?: Pick[0], 'allowMigration'>,
): Promise
/**
- * Resolve the Session for a new conversation in a Workspace: navigation names the first
- * reusable blank member, skipping blanks that list a preset other than a requested one, and
- * the Host reuses it under the requested preset or its default, or creates another Session.
+ * Resolve the Session for a new conversation in a Workspace: navigation names the first reusable
+ * blank member — listed in the Workspace, not archived, and listing no preset or the requested one —
+ * and the Host reuses it under the requested preset or its default, creating another Session when it
+ * reuses none. Concurrent calls for the same Workspace and preset share one attempt.
* @param workspaceId - target Workspace.
* @param options - choices for the created or reused Session.
* @returns a Session already addressable through the Session Controller.
diff --git a/packages/client/ui-workspace/src/client/rows/Rows.tsx b/packages/client/ui-workspace/src/client/rows/Rows.tsx
index 291c3f7ff4..d34f1b4a59 100644
--- a/packages/client/ui-workspace/src/client/rows/Rows.tsx
+++ b/packages/client/ui-workspace/src/client/rows/Rows.tsx
@@ -23,6 +23,7 @@ import {
import type { ShortcutCatalogEntry } from '@deepseek-ai/dsh-client-shortcuts/client'
import type { StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
import { abbreviateHomePath } from '@deepseek-ai/dsh-util-workspace-path'
+import type { WorkspaceId } from '@deepseek-ai/dsh-api-workspace-controller/client'
import type { MenuOpenState, WorkspaceBrowserProps } from '../contract/slots.ts'
import type { GroupNode, SearchResultNode, SessionNode } from '../tree.ts'
import css from './Rows.module.css'
@@ -203,21 +204,8 @@ function rowHalf(e: { clientY: number; currentTarget: HTMLElement }): 'before' |
return e.clientY < rect.top + rect.height / 2 ? 'before' : 'after'
}
-/**
- * Project (workspace) header row: folder + title;
- * hover reveals the chevron and create button, and dwelling on a real
- * Workspace shows its hover card (the ungrouped bucket has none).
- * `containsCurrent` arrives on the node (derivation fact, no renderer scan).
- * @param props.group - derived group node.
- * @param props.containsCurrentDescendant - highlight an ancestor even when its subtree is collapsed.
- * @param props.onToggle - expand/collapse the group.
- * @param props.onCreate - start a frontend Session inside this Workspace.
- * @param props.drag - optional workspace-row drag wiring.
- * @param props.home - host account home for POSIX hover-path abbreviation.
- * @param props.t - the browser root's locale seat.
- * @returns the row element.
- */
-export function ProjectRowItem({ group, containsCurrentDescendant = false, onToggle, onCreate, actions, drag, home, newShortcut, t }: {
+/** Owner props of one Workspace header row; the access Hooks are the browser root's own standard seats. */
+type ProjectRowItemProps = {
group: GroupNode
newShortcut?: ShortcutCatalogEntry | undefined
containsCurrentDescendant?: boolean
@@ -230,7 +218,62 @@ export function ProjectRowItem({ group, containsCurrentDescendant = false, onTog
/** Host account home; POSIX home-rooted hover paths display as `~`. */
home?: string | undefined
t: RowTranslate
-}) {
+} & Pick
+
+/** Which of one header row's actions the caller's access snapshot allows. */
+interface ProjectRowAccess {
+ readonly create: boolean
+ readonly rename: boolean
+ readonly delete: boolean
+ readonly reorder: boolean
+}
+
+/**
+ * Project (workspace) header row: folder + title;
+ * hover reveals the chevron and create button, and dwelling on a real
+ * Workspace shows its hover card (the ungrouped bucket has none).
+ * `containsCurrent` arrives on the node (derivation fact, no renderer scan).
+ * The create button, each Workspace menu row, and the drag handle are absent
+ * while the access snapshot excludes their endpoint: a real Workspace asks
+ * its own resource, and the ungrouped bucket asks the global resource.
+ * @param props.group - derived group node.
+ * @param props.containsCurrentDescendant - highlight an ancestor even when its subtree is collapsed.
+ * @param props.onToggle - expand/collapse the group.
+ * @param props.onCreate - start a frontend Session inside this Workspace.
+ * @param props.drag - optional workspace-row drag wiring.
+ * @param props.home - host account home for POSIX hover-path abbreviation.
+ * @param props.t - the browser root's locale seat.
+ * @returns the row element.
+ */
+export function ProjectRowItem(props: ProjectRowItemProps) {
+ const workspaceId = props.group.workspaceId
+ return workspaceId === undefined
+ ?
+ :
+}
+
+/** The ungrouped bucket's header: no Workspace resource, so starting a Session there asks the global resource. */
+function UngroupedProjectRow(props: ProjectRowItemProps) {
+ const create = props.useRemoteAccess('session/create')
+ return
+}
+
+/** A real Workspace's header: every action asks that Workspace's resource. */
+function WorkspaceProjectRow({ workspaceId, ...props }: ProjectRowItemProps & { workspaceId: WorkspaceId }) {
+ const { useWorkspaceAccess } = props
+ const access: ProjectRowAccess = {
+ create: useWorkspaceAccess(workspaceId, 'session/create'),
+ rename: useWorkspaceAccess(workspaceId, 'workspace/rename'),
+ delete: useWorkspaceAccess(workspaceId, 'workspace/delete'),
+ reorder: useWorkspaceAccess(workspaceId, 'workspace/insertBefore'),
+ }
+ return
+}
+
+function ProjectRow({
+ group, containsCurrentDescendant = false, onToggle, onCreate, actions, drag: offeredDrag, home, newShortcut, t, access,
+}: ProjectRowItemProps & { access: ProjectRowAccess }) {
+ const drag = access.reorder ? offeredDrag : undefined
const row = group
// The ungrouped bucket has no workspace title: its label is dictionary copy.
const label = row.workspaceId === undefined ? t('group.ungrouped') : row.label
@@ -238,8 +281,8 @@ export function ProjectRowItem({ group, containsCurrentDescendant = false, onTog
const [menuOpen, setMenuOpen] = useState(false)
const dragPreview = useRowDragPreview(css.dragPreview as string)
const workspaceMenuItems = [
- { id: 'rename', label: t('rename'), icon: },
- { id: 'delete', label: t('delete.workspace'), icon: , danger: true },
+ ...access.rename ? [{ id: 'rename', label: t('rename'), icon: }] : [],
+ ...access.delete ? [{ id: 'delete', label: t('delete.workspace'), icon: , danger: true }] : [],
]
const ownRow = (
{label}
- {actions !== undefined && (
+ {actions !== undefined && workspaceMenuItems.length > 0 && (
{ setMenuOpen(false) }}
@@ -297,17 +340,19 @@ export function ProjectRowItem({ group, containsCurrentDescendant = false, onTog
)}
/>
)}
-
- { e.stopPropagation(); onCreate() }}
- >
-
-
-
+ {access.create && (
+
+ { e.stopPropagation(); onCreate() }}
+ >
+
+
+
+ )}
)
@@ -472,17 +517,22 @@ function SessionHoverContent({ node, cwd, now, renderSlot, t }: {
* @param props.currentId - selected session id.
* @param props.onOpen - open the selected session.
* @param props.onUnarchive - unarchive an archived result row.
+ * @param props.useSessionAccess - the browser root's Session access Hook; the restore control
+ * appears only while the snapshot allows `workspace/unarchiveSession` for that Session.
* @param props.t - Workspace-browser translation seat.
* @returns the result row.
*/
-export function SearchResultItem({ result, currentId, onOpen, onUnarchive, t }: {
+export function SearchResultItem({
+ result, currentId, onOpen, onUnarchive, useSessionAccess, t,
+}: {
result: SearchResultNode
currentId: string | undefined
onOpen: (id: SearchResultNode['id']) => void
onUnarchive: (id: SearchResultNode['id']) => void
t: RowTranslate
-}) {
+} & Pick) {
const selected = result.id === currentId
+ const canUnarchive = useSessionAccess(result.id, 'workspace/unarchiveSession')
const statuses = sessionStatuses(result, t)
const primaryStatus = statuses[0]
return (
@@ -503,7 +553,7 @@ export function SearchResultItem({ result, currentId, onOpen, onUnarchive, t }:
)}
{result.title || t('session.untitled')}
- {result.archived && (
+ {result.archived && canUnarchive && (
& PropsRenderSlots<
| 'sidebar.workspaces.session.menu.item'
| 'sidebar.workspaces.session.row.action'
| 'sidebar.session.row.trailing'
@@ -584,7 +637,11 @@ export function SessionNodeItem({
// Archived rows hold their in-place grayed slot, so manual reorder cannot
// move them. Pinned rows drag within the pinned block: the browser gates
// their drop targets to fellow pinned rows.
- const draggable = drag !== undefined && !row.blank && !row.archived
+ // Renaming from the title and starting a drag ask the row's own access snapshot; a row that
+ // cannot start a drag still accepts drops from rows that can.
+ const renamable = useSessionAccess(node.id, 'session/rename')
+ const reorderable = useSessionAccess(node.id, 'workspace/insertSessionBefore')
+ const draggable = drag !== undefined && reorderable && !row.blank && !row.archived
const [menuOpen, setMenuOpen] = useState(false)
const dragPreview = useRowDragPreview(css.dragPreview as string)
// The menu's open state, bound into the row entries' `useMenuOpenState` hook.
@@ -670,7 +727,7 @@ export function SessionNodeItem({
// One shared function and options: repeated presses add one listener.
window.addEventListener('blur', disarmRename, { once: true })
}}
- onDoubleClick={row.blank
+ onDoubleClick={row.blank || !renamable
? undefined
: (e) => {
e.stopPropagation()
diff --git a/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx b/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx
index fd708fbebe..5b869f0598 100644
--- a/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx
+++ b/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx
@@ -253,6 +253,7 @@ type SessionTreeProps = Pick<
WorkspaceBrowserProps,
'useSessionStatus' | 'startSession' | 'open'
| 'insertWorkspaceBefore' | 't' | 'usePanelInfo'
+ | 'useRemoteAccess' | 'useSessionAccess' | 'useWorkspaceAccess'
> & PropsRenderSlots<
| 'sidebar.workspaces.session.menu.item'
| 'sidebar.workspaces.session.row.action'
@@ -317,7 +318,7 @@ function EmptySessions({ rowState, onLeaveArchivedOnly, t }: Pick row.id === 'session.new')}
group={group}
+ useRemoteAccess={useRemoteAccess}
+ useWorkspaceAccess={useWorkspaceAccess}
containsCurrentDescendant={currentAncestors.has(group.key)}
home={home}
t={t}
@@ -634,6 +637,7 @@ function SessionTree({
: undefined}
drag={dragProps}
home={home}
+ useSessionAccess={useSessionAccess}
t={t}
/>
)
@@ -689,12 +693,13 @@ function SessionTree({
/** The flat "In one list" body: every session is one draggable top-level row. */
function FlatList({
- list, sessionIds, rowState, onLeaveArchivedOnly, useSessionStatus, open, onSessionRenameRequest,
+ list, sessionIds, rowState, onLeaveArchivedOnly, useSessionStatus, useSessionAccess, open, onSessionRenameRequest,
usePanelInfo, setSessionOrder, workspaceReady, animationResetKey,
revealSessionId, onSessionRevealed, renderSlot, home, t,
}: Pick<
SessionTreeProps,
| 'useSessionStatus'
+ | 'useSessionAccess'
| 'open'
| 'onSessionRenameRequest'
| 'renderSlot'
@@ -761,6 +766,7 @@ function FlatList({
onReveal={node.id === revealSessionId
? () => { onSessionRevealed(node.id) }
: undefined}
+ useSessionAccess={useSessionAccess}
drag={{
start: () => {
dropCommitted.current = false
@@ -813,8 +819,9 @@ function SearchResults({
remote,
resultLimit,
usePanelInfo,
+ useSessionAccess,
t,
-}: Pick & {
+}: Pick & {
workspaces: readonly WorkspaceView[]
archivedSessionIds: readonly SessionNode['id'][]
/** Search matches follow the archived filter selected for the list. */
@@ -860,6 +867,7 @@ function SearchResults({
currentId={currentId}
onOpen={open}
onUnarchive={onUnarchive}
+ useSessionAccess={useSessionAccess}
t={t}
/>
))}
@@ -904,6 +912,9 @@ export function WorkspaceBrowser({
expandSidebar,
useSessions,
useSessionStatus,
+ useRemoteAccess,
+ useSessionAccess,
+ useWorkspaceAccess,
useWorkspaces,
useStore,
actions,
@@ -1409,6 +1420,7 @@ export function WorkspaceBrowser({
? (
) {
const [, setMenuOpen] = useMenuOpenState()
const shortcut = useShortcuts(rows => rows.find(row => row.id === 'session.archive'))
const archived = useArchived(set => set.has(sessionId))
+ const allowed = useSessionAccess(sessionId, archived ? 'workspace/unarchiveSession' : 'workspace/archiveSession')
+ if (!allowed) return null
return (
) {
const archived = useArchived(set => set.has(sessionId))
+ const allowed = useSessionAccess(sessionId, archived ? 'workspace/unarchiveSession' : 'workspace/archiveSession')
+ if (!allowed) return null
return (
, 'sessionId' | 'usePinned' | 'useArchived'>
+type PinState = Pick, 'sessionId' | 'usePinned' | 'useArchived' | 'useSessionAccess'>
-/** The row's pin and archive membership, one Set lookup each. */
-function usePinState({ sessionId, usePinned, useArchived }: PinState) {
+/** The row's pin and archive membership, one Set lookup each, and whether its access allows the toggle. */
+function usePinState({ sessionId, usePinned, useArchived, useSessionAccess }: PinState) {
+ const pinned = usePinned(set => set.has(sessionId))
return {
- pinned: usePinned(pinned => pinned.has(sessionId)),
+ pinned,
archived: useArchived(archived => archived.has(sessionId)),
+ allowed: useSessionAccess(sessionId, pinned ? 'workspace/unpinSession' : 'workspace/pinSession'),
}
}
/**
- * Menu row (order 100): pin or unpin by the row's current state; absent on archived rows.
- * @param props - owner share, the pin share, and the menu open state.
- * @returns the row, or null for an archived Session.
+ * Menu row (order 100): pin or unpin by the row's current state; absent on archived rows and while
+ * the Session's access snapshot excludes that toggle.
+ * @param props - owner share, the pin share, the access Hook, and the menu open state.
+ * @returns the row, or null for an archived or refused Session.
*/
export function PinSessionMenuItem(props: SessionMenuItemProps) {
const { sessionId, useMenuOpenState, pinSession, unpinSession, t } = props
const [, setMenuOpen] = useMenuOpenState()
- const { pinned, archived } = usePinState(props)
- if (archived) return null
+ const { pinned, archived, allowed } = usePinState(props)
+ if (archived || !allowed) return null
return (
: }
@@ -46,14 +49,15 @@ export function PinSessionMenuItem(props: SessionMenuItemProps) {
const { sessionId, pinSession, unpinSession, t } = props
- const { pinned, archived } = usePinState(props)
- if (archived) return null
+ const { pinned, archived, allowed } = usePinState(props)
+ if (archived || !allowed) return null
return (
) {
const [, setMenuOpen] = useMenuOpenState()
const shortcut = useShortcuts(rows => rows.find(row => row.id === 'session.rename'))
+ const renameAllowed = useSessionAccess(sessionId, 'session/rename')
+ if (!renameAllowed) return null
return (
{ localStorage.clear() })
+
+/** Test-owned sidebar shell role: declares and renders the browsing region. */
+type FrameProps = PropsRenderSlots<'sidebar.workspaces'>
+function SidebarFrame({ renderSlot }: FrameProps) {
+ return <>{renderSlot('sidebar.workspaces', { wide: true, expandSidebar: () => {} })}>
+}
+
+const WORKSPACE = 'w1' as WorkspaceId
+
+/**
+ * The assembled browsing region over one Workspace, answering access from one snapshot owner.
+ * @param access - Snapshot owner the page's access Hooks read.
+ * @returns The runtime.
+ */
+async function bench(access: ClientAccessState, options: { ungroupedSession?: SessionId } = {}) {
+ const runtime = await SlotTestRuntime.create()
+ await Promise.all([...runtime.ctx.registry.get(allowAllAccessPlugin)?.fibers ?? []].map(fiber => fiber.dispose()))
+ await runtime.ctx.plugin({
+ name: 'access-assembly:snapshot-access',
+ inject: ['slots'],
+ apply(ctx) {
+ ctx.provide('remoteAccess', {
+ source: (kind: TypertResourceKind, id: string, action: string) => access.resource(kind, id, action),
+ })
+ ctx.slots.provideRoot({ keyedHooks: { remoteAccess: action => access.global(action) } })
+ },
+ }).await()
+ runtime.ctx.provide('shortcuts', { register: () => () => {}, catalog: createSnapshotStore([]) })
+ runtime.ctx.provide('uiConversation', {})
+ runtime.ctx.provide('layout', { selectPanel: vi.fn(), beginNavigation: () => new AbortController().signal })
+ runtime.releaseWorkspaceSource()
+ runtime.remote.provideNamespaces({ directoryPicker: {} })
+ const locale = new LocaleRuntime(runtime.ctx)
+ runtime.ctx.provide('locale', locale)
+ runtime.slots.installLocale(locale)
+ if (options.ungroupedSession !== undefined) {
+ await runtime.sessions.add({
+ id: options.ungroupedSession,
+ summary: { title: 'Loose', displayTitle: 'Loose', cwd: '/elsewhere' },
+ })
+ }
+ await runtime.workspaces.update((draft) => {
+ draft.items = [{
+ workspaceId: WORKSPACE, title: 'Project', path: '/projects/project',
+ sessionIds: [], createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z',
+ }] as never
+ })
+ await runtime.root.declare(
+ { 'sidebar.workspaces': { kind: 'single', scope: 'root' } } as never,
+ SidebarFrame as never,
+ )
+ await runtime.mount({ inject: [...inject], apply })
+ return runtime
+}
+
+describe('Workspace header actions under the page access snapshot', () => {
+ it('offers New Session, the Workspace menu, and the drag handle only where the snapshot grants them', async () => {
+ const access = new ClientAccessState({
+ type: 'baseline',
+ owner: false,
+ grants: [{ resource: { kind: 'workspace', id: WORKSPACE }, actions: ['workspace/follow'] }],
+ })
+ const runtime = await bench(access)
+ try {
+ runtime.renderRoot()
+ const header = await screen.findByRole('treeitem')
+ expect(screen.queryByRole('button', { name: '在“Project”中新建会话' })).toBeNull()
+ expect(screen.queryByRole('button', { name: '工作区“Project”的操作' })).toBeNull()
+ expect(header.getAttribute('draggable')).toBe('false')
+
+ // The Workspace's own grant moves its actions; the global answer is never consulted for them.
+ act(() => {
+ access.update({
+ type: 'upsert',
+ grant: {
+ resource: { kind: 'workspace', id: WORKSPACE },
+ actions: ['session/create', 'workspace/follow', 'workspace/insertBefore', 'workspace/rename'],
+ },
+ })
+ })
+ expect(screen.getByRole('button', { name: '在“Project”中新建会话' })).toBeTruthy()
+ expect(screen.getByRole('button', { name: '工作区“Project”的操作' })).toBeTruthy()
+ expect(screen.getByRole('treeitem').getAttribute('draggable')).toBe('true')
+
+ // The owner's snapshot answers every action.
+ act(() => { access.replace({ type: 'baseline', owner: true }) })
+ expect(screen.getByRole('button', { name: '在“Project”中新建会话' })).toBeTruthy()
+ } finally {
+ await runtime.dispose()
+ }
+ })
+
+ it('asks the global resource before the Ungrouped header offers New Session', async () => {
+ const access = new ClientAccessState({ type: 'baseline', owner: false, grants: [] })
+ const runtime = await bench(access, { ungroupedSession: 'loose' as SessionId })
+ try {
+ runtime.renderRoot()
+ await screen.findByText('未分组')
+ expect(screen.queryByRole('button', { name: '在“未分组”中新建会话' })).toBeNull()
+ act(() => {
+ access.update({ type: 'upsert', grant: { resource: { kind: 'global' }, actions: ['session/create'] } })
+ })
+ expect(screen.getByRole('button', { name: '在“未分组”中新建会话' })).toBeTruthy()
+ } finally {
+ await runtime.dispose()
+ }
+ })
+})
diff --git a/packages/client/ui-workspace/tests/rows.client.spec.tsx b/packages/client/ui-workspace/tests/rows.client.spec.tsx
index a77c230456..073a26ae41 100644
--- a/packages/client/ui-workspace/tests/rows.client.spec.tsx
+++ b/packages/client/ui-workspace/tests/rows.client.spec.tsx
@@ -4,7 +4,7 @@ import { act, cleanup, createEvent, fireEvent, render, screen } from '@testing-l
import type { ComponentProps } from 'react'
import type { WorkspaceId } from '@deepseek-ai/dsh-api-workspace-controller/client'
import type { SessionId } from '@deepseek-ai/dsh-session/types'
-import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
+import { allowAllAccess, makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
import { en as commonEn } from '@deepseek-ai/dsh-client-locale/src/locales/en.ts'
import { HoverCardGroup, MenuItemButton, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'
import type { PropsRenderSlots } from '@deepseek-ai/dsh-client-ui-slots'
@@ -12,7 +12,7 @@ import type { MenuOpenState, SessionRowOwnerProps } from '../src/client/contract
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
import type { RowDragProps } from '../src/client/rows/Rows.tsx'
import {
- ProjectRowItem, SearchResultItem, SessionNodeItem as SessionNodeItemComponent,
+ ProjectRowItem as ProjectRowItemComponent, SearchResultItem, SessionNodeItem as SessionNodeItemComponent,
} from '../src/client/rows/Rows.tsx'
import type { GroupNode, SearchResultNode, SessionNode } from '../src/client/tree.ts'
import { en, zh } from '../src/client/locales.ts'
@@ -34,6 +34,7 @@ type RowSlotName =
| 'sidebar.session.row.hover'
type RowRenderSlot = PropsRenderSlots['renderSlot']
type SessionNodeItemProps = ComponentProps
+type ProjectRowItemProps = ComponentProps
const renderNoRowEntries: RowRenderSlot = () => null
@@ -48,10 +49,24 @@ function realDoubleClick(el: Element): void {
fireEvent.doubleClick(el)
}
-function SessionNodeItem({ renderSlot = renderNoRowEntries, onRenameRequest = () => {}, ...props }: Omit<
- SessionNodeItemProps, 'renderSlot' | 'onRenameRequest'
-> & Partial>) {
- return
+function SessionNodeItem({
+ renderSlot = renderNoRowEntries, onRenameRequest = () => {}, useSessionAccess = allowAllAccess.useSessionAccess, ...props
+}: Omit<
+ SessionNodeItemProps, 'renderSlot' | 'onRenameRequest' | 'useSessionAccess'
+> & Partial>) {
+ return (
+
+ )
+}
+
+// Direct header specs hold every access answer unless the case refuses one.
+function ProjectRowItem({
+ useRemoteAccess = allowAllAccess.useRemoteAccess, useWorkspaceAccess = allowAllAccess.useWorkspaceAccess, ...props
+}: Omit
+ & Partial>) {
+ return
}
/** Half detection reads the row rect; jsdom rects are all-zero by default. */
@@ -273,7 +288,8 @@ describe('workspace browser rows', () => {
archived: false,
snippet: 'matching message excerpt',
}
- render( )
+ render( )
const row = screen.getByRole('treeitem')
expect(row.getAttribute('aria-selected')).toBe('true')
expect(screen.getByText('Workspace context')).toBeTruthy()
@@ -285,6 +301,24 @@ describe('workspace browser rows', () => {
expect(onOpen).toHaveBeenCalledWith(result.id)
})
+ it('withholds the search-result restore control while the Session access excludes unarchiving', () => {
+ const result: SearchResultNode = {
+ id: sid('archived-result'), title: 'Archived', workspace: 'Project',
+ running: false, runningSubagentCount: 0, completed: false, archived: true,
+ }
+ const denied = vi.fn(() => false)
+ const view = render( )
+ expect(denied).toHaveBeenCalledWith(result.id, 'workspace/unarchiveSession')
+ expect(view.queryByRole('button', { name: zh['menu.unarchiveSession'] })).toBeNull()
+ cleanup()
+ // The allowed answer renders the same verb.
+ const allowed = vi.fn(() => true)
+ const restored = render( )
+ expect(restored.getByRole('button', { name: zh['menu.unarchiveSession'] })).toBeTruthy()
+ })
+
it.each([
['approval', '等待审批'],
['plan-review', '计划待审'],
@@ -294,7 +328,8 @@ describe('workspace browser rows', () => {
id: sid(pendingInteraction), title: 'Needs input', workspace: 'Project',
pendingInteraction, running: true, runningSubagentCount: 0, completed: false, archived: false,
}
- render( )
+ render( )
const row = screen.getByRole('treeitem')
expect(row.querySelector('[data-state="warning"]')).toBeTruthy()
expect(row.querySelector('[data-state="ongoing"]')).toBeNull()
@@ -582,7 +617,8 @@ describe('workspace browser rows', () => {
id: sid('result'), title: 'Done', workspace: 'Workspace', running: false,
runningSubagentCount: 0, completed: true, archived: false,
}}
- currentId={undefined} onOpen={vi.fn()} onUnarchive={vi.fn()} t={t}
+ currentId={undefined} onOpen={vi.fn()} onUnarchive={vi.fn()}
+ useSessionAccess={allowAllAccess.useSessionAccess} t={t}
/>)
expect(screen.getByRole('treeitem').querySelector('[data-state="done"]')).not.toBeNull()
})
@@ -617,6 +653,82 @@ describe('workspace browser rows', () => {
expect(screen.queryByRole('menu')).toBeNull()
})
+ it('workspace row offers only the actions its Workspace access snapshot allows', () => {
+ const group: GroupNode = {
+ key: 'project', workspaceId: wid('project'), cwd: '/projects/project', createdAt: 0, label: 'Project',
+ sessionCount: 0, expanded: false, containsCurrent: false, sessions: [],
+ }
+ const allowed = new Set(['workspace/rename'])
+ const useWorkspaceAccess = vi.fn((_workspaceId: WorkspaceId, endpoint: string) => allowed.has(endpoint))
+ const drag = { start: vi.fn(), end: vi.fn() }
+ const props = {
+ group, onToggle: vi.fn(), onCreate: vi.fn(), actions: { rename: vi.fn(), delete: vi.fn() }, drag, t,
+ }
+ const view = render( )
+ expect(useWorkspaceAccess).toHaveBeenCalledWith(wid('project'), 'session/create')
+ // No New Session button, no drag handle, and a menu holding only the allowed rename row.
+ expect(screen.queryByRole('button', { name: '在“Project”中新建会话' })).toBeNull()
+ expect(screen.getByRole('treeitem').getAttribute('draggable')).toBe('false')
+ fireEvent.click(screen.getByRole('button', { name: '工作区“Project”的操作' }))
+ expect(screen.getByRole('menuitem', { name: '重命名' })).toBeTruthy()
+ expect(screen.queryByRole('menuitem', { name: '删除工作区' })).toBeNull()
+ fireEvent.keyDown(document, { key: 'Escape' })
+
+ // Without rename either, the header carries no menu at all.
+ allowed.clear()
+ view.rerender( )
+ expect(screen.queryByRole('button', { name: '工作区“Project”的操作' })).toBeNull()
+
+ // Every answer allowed restores the button, the full menu, and the drag handle.
+ for (const endpoint of ['session/create', 'workspace/rename', 'workspace/delete', 'workspace/insertBefore']) {
+ allowed.add(endpoint)
+ }
+ view.rerender( )
+ expect(screen.getByRole('treeitem').getAttribute('draggable')).toBe('true')
+ fireEvent.click(screen.getByRole('button', { name: '在“Project”中新建会话' }))
+ expect(props.onCreate).toHaveBeenCalledOnce()
+ })
+
+ it('ungrouped bucket asks the global resource whether it may start a Session', () => {
+ const group: GroupNode = {
+ key: 'ungrouped', workspaceId: undefined, cwd: undefined, createdAt: undefined, label: '',
+ sessionCount: 0, expanded: false, containsCurrent: false, sessions: [],
+ }
+ const useRemoteAccess = vi.fn((action: string) => action !== 'session/create')
+ const useWorkspaceAccess = vi.fn(() => true)
+ render( )
+ expect(useRemoteAccess).toHaveBeenCalledWith('session/create')
+ expect(useWorkspaceAccess).not.toHaveBeenCalled()
+ expect(screen.queryByRole('button', { name: /新建会话/ })).toBeNull()
+ })
+
+ it('session row drags and renames from its title only while its access snapshot allows each', () => {
+ const node: SessionNode = {
+ id: sid('s1'), title: 'One', blank: false, running: false,
+ runningSubagentCount: 0, completed: false, updatedAt: 0, pinned: false, archived: false,
+ }
+ const drag: RowDragProps = {
+ active: false, marker: null, start: vi.fn(), end: vi.fn(), hover: vi.fn(), drop: vi.fn(),
+ }
+ const onRenameRequest = vi.fn()
+ const useSessionAccess = vi.fn((_sessionId: SessionId, _endpoint: string) => false)
+ const view = render( )
+ expect(useSessionAccess).toHaveBeenCalledWith(sid('s1'), 'session/rename')
+ expect(useSessionAccess).toHaveBeenCalledWith(sid('s1'), 'workspace/insertSessionBefore')
+ expect(screen.getByRole('treeitem').getAttribute('draggable')).toBe('false')
+ fireEvent.doubleClick(screen.getByText('One'))
+ expect(onRenameRequest).not.toHaveBeenCalled()
+
+ view.rerender( true} t={t} />)
+ expect(screen.getByRole('treeitem').getAttribute('draggable')).toBe('true')
+ // A browser rename arms on the first press and fires on the double-click; the helper states both.
+ realDoubleClick(screen.getByText('One'))
+ expect(onRenameRequest).toHaveBeenCalledWith(sid('s1'), 'One')
+ })
+
it('workspace hover card shows its details and copies the full directory path', async () => {
vi.useFakeTimers()
const writeText = vi.fn(async () => {})
@@ -1288,7 +1400,8 @@ describe('workspace browser rows', () => {
id: sid('result'), title: 'Old result', workspace: 'Workspace context',
running: false, runningSubagentCount: 0, completed: false, archived: true,
}
- render( )
+ render( )
const row = screen.getByRole('treeitem')
expect(row.className).toMatch(/archived/)
// No leading marker: the grayed row alone carries the archived look.
@@ -1459,7 +1572,7 @@ it.each([['未命名', t], ['Untitled', tEn]])('labels unnamed search results as
running: false, runningSubagentCount: 0, completed: false, archived: false }
const onOpen = vi.fn()
render( )
+ onUnarchive={vi.fn()} useSessionAccess={allowAllAccess.useSessionAccess} t={translate} />)
fireEvent.click(screen.getByText(label))
expect(onOpen).toHaveBeenCalledWith(result.id)
})
diff --git a/packages/client/ui-workspace/tests/session-actions.client.spec.tsx b/packages/client/ui-workspace/tests/session-actions.client.spec.tsx
index 1b7533e4dc..6fc2f0ad4b 100644
--- a/packages/client/ui-workspace/tests/session-actions.client.spec.tsx
+++ b/packages/client/ui-workspace/tests/session-actions.client.spec.tsx
@@ -171,6 +171,22 @@ describe('pin action', () => {
expect(pin.pinSession).toHaveBeenCalledOnce()
})
+ it('offers neither toggle while the access snapshot excludes it for this Session', () => {
+ const { state } = openMenu()
+ const useSessionAccess = vi.fn((_id: SessionId, endpoint: string) => endpoint !== 'workspace/pinSession')
+ const menu = render( )
+ expect(menu.container.innerHTML).toBe('')
+ expect(useSessionAccess).toHaveBeenCalledWith(sid('one'), 'workspace/pinSession')
+ menu.unmount()
+ const button = render( )
+ expect(button.container.innerHTML).toBe('')
+ button.unmount()
+ // A pinned row asks for its unpin endpoint instead.
+ const pinned = render( )
+ expect(useSessionAccess).toHaveBeenLastCalledWith(sid('one'), 'workspace/unpinSession')
+ expect(pinned.getByRole('button', { name: '取消置顶' })).toBeTruthy()
+ })
+
it('offers nothing on an archived Session', () => {
const { state } = openMenu()
const pin = pinShare(['one'], ['one'])
@@ -215,6 +231,22 @@ describe('archive action', () => {
expect(callOrder(setMenuOpen)).toBeLessThan(callOrder(archive.unarchiveSession))
})
+ it('offers neither the archive row nor its button while the access snapshot excludes archiving', () => {
+ const { state } = openMenu()
+ const useSessionAccess = vi.fn((_id: SessionId, endpoint: string) => endpoint !== 'workspace/archiveSession')
+ const menu = render( )
+ expect(menu.container.innerHTML).toBe('')
+ expect(useSessionAccess).toHaveBeenCalledWith(sid('one'), 'workspace/archiveSession')
+ menu.unmount()
+ const button = render( )
+ expect(button.container.innerHTML).toBe('')
+ button.unmount()
+ // An archived row asks for its restore endpoint instead.
+ const archived = render( )
+ expect(useSessionAccess).toHaveBeenLastCalledWith(sid('one'), 'workspace/unarchiveSession')
+ expect(archived.getByRole('button')).toBeTruthy()
+ })
+
it('row button archives and restores', () => {
const archive = archiveShare()
const view = render( )
@@ -248,6 +280,16 @@ describe('fork and rename rows', () => {
expect(useSessionAccess).toHaveBeenCalledWith(sid('one'), 'session/fork')
})
+ it('omits the rename row when the access snapshot excludes session/rename for this Session', () => {
+ const { state } = openMenu()
+ const useSessionAccess = vi.fn(() => false)
+ const view = render(
+ ,
+ )
+ expect(view.container.innerHTML).toBe('')
+ expect(useSessionAccess).toHaveBeenCalledWith(sid('one'), 'session/rename')
+ })
+
it('rename closes the menu, then asks for the dialog with the row title', () => {
const { state, setMenuOpen } = openMenu()
const requestSessionRename = vi.fn()
diff --git a/packages/client/ui-workspace/tests/tree.client.spec.ts b/packages/client/ui-workspace/tests/tree.client.spec.ts
index 4c9cf139fe..6ae374bd75 100644
--- a/packages/client/ui-workspace/tests/tree.client.spec.ts
+++ b/packages/client/ui-workspace/tests/tree.client.spec.ts
@@ -1,10 +1,13 @@
import { describe, expect, it } from 'vitest'
-import type { SessionListState, SessionSummary } from '@deepseek-ai/dsh-api-session-controller/client'
+import type {
+ SessionListState, SessionProjectionMap, SessionSummary,
+} from '@deepseek-ai/dsh-api-session-controller/client'
import type { WorkspaceId, WorkspaceView } from '@deepseek-ai/dsh-api-workspace-controller/client'
import type {
SessionPendingInteraction, SessionStatus, SessionStatusSnapshot,
} from '@deepseek-ai/dsh-client-ui-session/client'
import type { SessionId } from '@deepseek-ai/dsh-session/types'
+import type { JsonValue } from '@deepseek-ai/dsh-util-values'
import type { SessionProjectionSnapshot } from '@deepseek-ai/dsh-api-session-controller/client'
import {
type ArchivedFilter,
@@ -39,6 +42,38 @@ const withMain = (state: SessionListState, id: SessionId): SessionListState => (
[id]: { ...state.byId[id]!, retainedBy: { ...state.byId[id]!.retainedBy, mainView: 1 } },
},
})
+/**
+ * Projection values one shipped Host publishes for a Session. The `forkLineage` key belongs to the
+ * Liveshare package, which this program does not name, so the values are built through the open key
+ * space the wire allows.
+ */
+const projectionValues = (values: Record): Readonly> => values
+/**
+ * One list where the named Session carries the branch facts the Host publishes for this reader.
+ * @param items - list rows.
+ * @param branchId - Session the facts belong to.
+ * @param lineage - the branch fields that case exercises; `origin` is always a fork.
+ * @returns the list with that Session's `forkLineage` projection value.
+ */
+const branchedList = (
+ items: readonly SessionSummary[],
+ branchId: SessionId,
+ lineage: { readonly sourceSessionId: SessionId; readonly workspaceReadOnly?: boolean },
+): SessionListState => {
+ const base = list(...items)
+ return {
+ ...base,
+ byId: {
+ ...base.byId,
+ [branchId]: {
+ ...base.byId[branchId]!,
+ projectionValues: projectionValues({
+ forkLineage: { origin: 'fork', authoredByViewer: true, workspaceReadOnly: true, ...lineage },
+ }),
+ },
+ },
+ }
+}
const workspace = (id: string, sessionIds: string[], title = id): WorkspaceView => ({
workspaceId: wid(id), path: `/projects/${id}`, title,
sessionIds: sessionIds.map(sid), createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z',
@@ -544,6 +579,17 @@ describe('deriveFlat', () => {
expect(visibleSessionIds(partial, noArchive, 'default')).toEqual([sid('present')])
})
+ it('leaves plugin-owned lineage interpretation to row slots in both list modes', () => {
+ const source = summary('shared', 1)
+ const child = summary('my-branch', 2)
+ const branched = branchedList([source, child], child.id, { sourceSessionId: source.id })
+ const rows = deriveFlat(branched, [source.id, child.id], noRows, noAttention)
+ expect(rows.every(row => !('fork' in row))).toBe(true)
+ const groups = deriveGroups(branched, [workspace('first', ['shared', 'my-branch'])],
+ noRows, noAttention, view(['first']))
+ expect(groups[0]!.sessions.every(row => !('fork' in row))).toBe(true)
+ })
+
it('shows only the current blank session and excludes blanks from search', () => {
const currentBlank = { ...summary('current-blank', 9), blank: true, retainedBy: { mainView: 1 } }
const staleBlank = { ...summary('stale-blank', 8), blank: true }
@@ -623,6 +669,26 @@ describe('deriveSearchResults archive filtering', () => {
})
})
+describe('deriveSearchResults branch marks', () => {
+ it('carries the same branch facts a matching row shows in the list', () => {
+ const source = summary('shared', 1)
+ source.title = source.displayTitle = 'Needle source'
+ const branch = summary('my-branch', 2)
+ branch.title = branch.displayTitle = 'Needle branch'
+ const result = deriveSearchResults(
+ branchedList([source, branch], branch.id, { sourceSessionId: source.id }),
+ [workspace('first', ['shared', 'my-branch'])],
+ 'needle',
+ noArchive,
+ 'default',
+ noAttention,
+ { items: [], hasMore: false },
+ 10,
+ )
+ expect(result.items.every(item => !('fork' in item))).toBe(true)
+ })
+})
+
describe('deriveSearchResults', () => {
it('merges local title/Workspace matches before ranked content hits and enriches duplicates', () => {
const titleHit = summary('title-hit', 30, '/projects/a')
diff --git a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts
index 8dca745d3f..db9a59e0ed 100644
--- a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts
+++ b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts
@@ -355,7 +355,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
{
signature: 'connectWorkspace(workspaceId: WorkspaceId, options?: Pick): Promise',
- description: 'Resolve the Session for a new conversation in a Workspace: navigation names the first reusable blank member, skipping blanks that list a preset other than a requested one, and the Host reuses it under the requested preset or its default, or creates another Session.',
+ description: 'Resolve the Session for a new conversation in a Workspace: navigation names the first reusable blank member — listed in the Workspace, not archived, and listing no preset or the requested one — and the Host reuses it under the requested preset or its default, creating another Session when it reuses none. Concurrent calls for the same Workspace and preset share one attempt.',
parameters: [{ name: 'workspaceId', description: 'target Workspace.' }, { name: 'options', description: 'choices for the created or reused Session.' }],
returns: 'a Session already addressable through the Session Controller.',
},
diff --git a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
index 9f04498de3..dd4be0eabf 100644
--- a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
+++ b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
@@ -1912,7 +1912,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
},
],
ownerProps: [
- '/** Owner share of the View tab row\'s More menu rows. */\nexport interface ConversationViewActionsOwnerProps {\n /** Available Views in registration order; Chat is never listed. */\n readonly entries: readonly ConversationViewActionEntry[]\n /**\n * Select a View in this Session without changing its pin state.\n * @param id - Listed View id.\n */\n readonly onOpen: (id: string) => void\n /**\n * Show (true) or unpin (false) a View tab in every Session.\n * @param id - Listed View id.\n * @param pinned - Whether the tab shows in the header.\n */\n readonly onPinnedChange: (id: string, pinned: boolean) => void\n /** Close the More menu. */\n readonly onDismiss: () => void\n}',
+ '/** Owner share of the View tab row\'s More menu rows. */\nexport interface ConversationViewActionsOwnerProps {\n /** Views outside the header in registration order. */\n readonly entries: readonly ConversationViewActionEntry[]\n /**\n * Select a View in this Session without changing its pin state.\n * @param id - Listed View id.\n */\n readonly onOpen: (id: string) => void\n /**\n * Pin or unpin a View in every Session.\n * @param id - Listed View id.\n * @param pinned - Whether the View is manually pinned; the width budget can still fold it into More.\n */\n readonly onPinnedChange: (id: string, pinned: boolean) => void\n /** Close the More menu. */\n readonly onDismiss: () => void\n}',
],
ownerPropsReferences: [
'ConversationViewActionEntry',
diff --git a/packages/interaction/permission-presets/tests/projection.spec.ts b/packages/interaction/permission-presets/tests/projection.spec.ts
index 6b82374298..d1d98d7c66 100644
--- a/packages/interaction/permission-presets/tests/projection.spec.ts
+++ b/packages/interaction/permission-presets/tests/projection.spec.ts
@@ -169,7 +169,7 @@ describe('/permission command', () => {
expect(inject.mock.calls[0]?.[0]).toMatchObject({
content: [{
type: 'text',
- text: 'The approval policy changed from "ask" to "never" (changed by the user).',
+ text: 'The user changed the approval policy from "ask the user for approval" to "never ask the user for approval".',
}],
})
const run = session.snapshotEvents().find(event => event.type === 'command/run')
diff --git a/packages/interaction/user-approval/README.i18n.yaml b/packages/interaction/user-approval/README.i18n.yaml
index 88bac2611d..d648bb92f3 100644
--- a/packages/interaction/user-approval/README.i18n.yaml
+++ b/packages/interaction/user-approval/README.i18n.yaml
@@ -21,14 +21,14 @@
en: 3618ddc3386e65f0
zh: d6c0ac91aae122bb
/deepseek-ai-dsh-user-approval/use-this-package/setting-the-policy:
- en: d4ef1901ac78b431
- zh: 822b97f8bed1b2c9
+ en: 79f3334a395b9368
+ zh: 0c4026b505ee254a
/deepseek-ai-dsh-user-approval/use-this-package/requesting-a-decision:
en: dc465de82799bab0
zh: 51144cc77bb15689
/deepseek-ai-dsh-user-approval/use-this-package/what-the-model-and-user-see:
- en: 016bf3cd1891d3d7
- zh: 33e7e5ffaf95f5e7
+ en: 56e57ef3cac09a2a
+ zh: 8f1faadcbba500e3
/deepseek-ai-dsh-user-approval/understand-the-implementation:
en: d07be7ba08baf010
zh: 665e68e9188e5d60
@@ -54,14 +54,17 @@
en: d23980f003b42fbb
zh: 532f0174de5d87b5
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/what-the-model-sees:
- en: a38775858ccd61f3
- zh: c19c76827be7aabf
+ en: eb41f1a4972c07f5
+ zh: 4264f408eb951951
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/what-the-model-sees/ask-policy-contribution:
en: b9f1262c97ec7568
zh: 8a6613695f896f70
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/what-the-model-sees/never-policy-contribution:
en: f13f09fc2466513b
zh: 4a755a682551c481
+/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/what-the-model-sees/switch-notice:
+ en: e87e1e28ae7d7243
+ zh: c6daddb5a131565a
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/token-effect:
en: e96807422bdce44d
zh: 9fdcfafcc96bd004
diff --git a/packages/interaction/user-approval/README.md b/packages/interaction/user-approval/README.md
index d94986a978..1ac2fce1d8 100644
--- a/packages/interaction/user-approval/README.md
+++ b/packages/interaction/user-approval/README.md
@@ -46,7 +46,7 @@ The effective policy is the one set for the session, falling back to the configu
|---|---|---|
| `policy` | `ask` | Default for sessions without an `approval/policy` override |
-The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-user-approval) is the exhaustive source for every accepted field and its JSDoc. `setPolicy(agent, policy)` switches a live agent and queues a "changed by the user" message for its next model step; `setApprovalPolicy(session, policy)` is the direct durable write path used by session initialization.
+The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-user-approval) is the exhaustive source for every accepted field and its JSDoc. `setPolicy(agent, policy)` switches a live agent and queues the switch notice described under Model Experience for its next model step; `setApprovalPolicy(session, policy)` is the direct durable write path used by session initialization.
### Requesting a decision
@@ -54,7 +54,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
### What the model and user see
-The model sees only the asking consumer's eventual tool outcome — allowed, rejected, cancelled, or unavailable — plus the current policy in the runtime-context snapshot; the audit events and the human permission UI are not model context. A `never` switch is announced to the model by a sourced user message, and both policies contribute their complete current meaning to the snapshot.
+The model sees only the asking consumer's eventual tool outcome — allowed, rejected, cancelled, or unavailable — plus the current policy in the runtime-context snapshot; the audit events and the human permission UI are not model context. Each policy switch is announced to the model by a sourced user message, and both policies contribute their complete current meaning to the snapshot.
-----
@@ -112,12 +112,12 @@ Read these pages when the package-level contract is not enough. They move from t
#### What the model sees
-The first request and each effective policy change append a full runtime-context snapshot after retained history. Under `ask`, the approval contribution states that configured answerers may be consulted and absence fails closed. Under `never`, it states the deterministic rejection and non-escalation consequence. Unchanged requests retain the earlier snapshot without adding another message.
+The first request and each effective policy change append a full runtime-context snapshot after retained history. Under `ask`, the approval contribution states that configured answerers may be consulted and absence fails closed. Under `never`, it states the deterministic rejection and non-escalation consequence. Unchanged requests retain the earlier snapshot without adding another message. When the user switches a live Session's policy, the next model step also receives a switch notice.
##### Ask-policy contribution
```markdown
-Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.
+Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.
```
##### Never-policy contribution
@@ -126,6 +126,12 @@ Approval policy: ask. Operations that require approval may ask through the confi
Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).
```
+##### Switch notice
+
+```markdown
+The user changed the approval policy from "ask the user for approval" to "never ask the user for approval".
+```
+
#### Token effect
One concise context message on the first request and on an effective change; unchanged requests add no duplicate policy tokens.
diff --git a/packages/interaction/user-approval/README.zh.md b/packages/interaction/user-approval/README.zh.md
index 1d523081d2..1dabd4bfe9 100644
--- a/packages/interaction/user-approval/README.zh.md
+++ b/packages/interaction/user-approval/README.zh.md
@@ -46,7 +46,7 @@ kind: "package-reference"
|---|---|---|
| `policy` | `ask` | 没有 `approval/policy` 覆盖的会话的默认策略 |
-生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-user-approval)是每个受支持字段及其 JSDoc 的穷尽式真源。`setPolicy(agent, policy)` 切换运行中的 agent 的策略,并为它的下一个模型步骤排队一条「由用户更改」消息;`setApprovalPolicy(session, policy)` 是会话初始化使用的直接持久写入路径。
+生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-user-approval)是每个受支持字段及其 JSDoc 的穷尽式真源。`setPolicy(agent, policy)` 切换运行中的 agent 的策略,并为它的下一个模型步骤排队一条模型体验一节所述的切换通知;`setApprovalPolicy(session, policy)` 是会话初始化使用的直接持久写入路径。
### 请求决定
@@ -54,7 +54,7 @@ kind: "package-reference"
### 模型与用户看到什么
-模型只会看到发起请求的消费方最终给出的工具结果——允许、拒绝、取消或不可用——以及运行时上下文快照中的当前策略;审计事件与面向人类的权限 UI 不属于模型上下文。`never` 切换会以一条带来源的用户消息告知模型,两种策略都会把各自的完整当前含义贡献给快照。
+模型只会看到发起请求的消费方最终给出的工具结果——允许、拒绝、取消或不可用——以及运行时上下文快照中的当前策略;审计事件与面向人类的权限 UI 不属于模型上下文。每次策略切换都会以一条带来源的用户消息告知模型,两种策略都会把各自的完整当前含义贡献给快照。
-----
@@ -112,12 +112,12 @@ kind: "package-reference"
#### 模型看到的内容
-首次请求与有效策略每次变化时,都会在保留的历史后追加一份完整运行时上下文快照。在 `ask` 下,审批上下文内容会说明系统可以咨询已配置的应答者,缺少可用应答者时则以拒绝方式关闭。在 `never` 下,它会说明确定性的拒绝与非升权后果。未变化的请求会保留先前快照,不增加另一条消息。
+首次请求与有效策略每次变化时,都会在保留的历史后追加一份完整运行时上下文快照。在 `ask` 下,审批上下文内容会说明系统可以咨询已配置的应答者,缺少可用应答者时则以拒绝方式关闭。在 `never` 下,它会说明确定性的拒绝与非升权后果。未变化的请求会保留先前快照,不增加另一条消息。用户切换运行中 Session 的策略时,下一次模型调用还会收到一条切换通知。
##### Ask 策略贡献
```markdown
-Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.
+Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.
```
##### Never 策略贡献
@@ -126,6 +126,12 @@ Approval policy: ask. Operations that require approval may ask through the confi
Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).
```
+##### 切换通知
+
+```markdown
+The user changed the approval policy from "ask the user for approval" to "never ask the user for approval".
+```
+
#### Token 影响
首次请求和策略实际变化时增加一条简洁的上下文消息;未变化的请求不增加重复的策略 token。
diff --git a/packages/interaction/user-approval/src/index.ts b/packages/interaction/user-approval/src/index.ts
index 978f6a7092..df48aca514 100644
--- a/packages/interaction/user-approval/src/index.ts
+++ b/packages/interaction/user-approval/src/index.ts
@@ -73,7 +73,12 @@ export const APPROVAL_POLICIES: readonly ApprovalPolicy[] = ['ask', 'never']
/** Model-facing statement for the deterministic `'never'` policy. */
const NEVER_SENTENCE = 'Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).'
/** Model-facing statement for an interactive policy that may still fail closed. */
-const ASK_SENTENCE = 'Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.'
+const ASK_SENTENCE = 'Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.'
+/** Model-facing policy names in switch notices; a bare `never` reads as "approval is never granted". */
+const POLICY_NOTICE_NAMES: Readonly> = {
+ ask: 'ask the user for approval',
+ never: 'never ask the user for approval',
+}
interface ApprovalState {
policy: ApprovalPolicy | null
@@ -210,7 +215,7 @@ export class ApprovalService extends Service {
agent.inject(createUserMessage({
content: [{
type: 'text',
- text: `The approval policy changed from "${previous}" to "${policy}" (changed by the user).`,
+ text: `The user changed the approval policy from "${POLICY_NOTICE_NAMES[previous]}" to "${POLICY_NOTICE_NAMES[policy]}".`,
}],
source: { kind: 'user-approval' },
}))
diff --git a/packages/interaction/user-approval/tests/approval.spec.ts b/packages/interaction/user-approval/tests/approval.spec.ts
index dac2e822c7..03886992f3 100644
--- a/packages/interaction/user-approval/tests/approval.spec.ts
+++ b/packages/interaction/user-approval/tests/approval.spec.ts
@@ -353,7 +353,7 @@ describe('ApprovalService.request', () => {
describe('approval policy (the approval/policy fold)', () => {
const NEVER_SENTENCE = 'Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).'
- const ASK_SENTENCE = 'Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.'
+ const ASK_SENTENCE = 'Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.'
/**
* An agent stand-in over a REAL Session — gate and context fold real events;
@@ -466,10 +466,19 @@ describe('approval policy (the approval/policy fold)', () => {
expect(inject.mock.calls[0]?.[0]).toMatchObject({
content: [{
type: 'text',
- text: 'The approval policy changed from "ask" to "never" (changed by the user).',
+ text: 'The user changed the approval policy from "ask the user for approval" to "never ask the user for approval".',
}],
source: { kind: 'user-approval' },
})
+
+ ctx.approval.setPolicy(liveAgent, 'ask')
+
+ expect(inject.mock.calls[1]?.[0]).toMatchObject({
+ content: [{
+ type: 'text',
+ text: 'The user changed the approval policy from "never ask the user for approval" to "ask the user for approval".',
+ }],
+ })
})
it('contributes the complete current ask or never policy as cache-safe context', async () => {
diff --git a/packages/llm/llm-deepseek-account/README.i18n.yaml b/packages/llm/llm-deepseek-account/README.i18n.yaml
index a1482909bd..6bd168a382 100644
--- a/packages/llm/llm-deepseek-account/README.i18n.yaml
+++ b/packages/llm/llm-deepseek-account/README.i18n.yaml
@@ -15,11 +15,11 @@
en: 23386452a66b2fbd
zh: 4cf93c4bf17b3da2
/deepseek-ai-dsh-llm-deepseek-account/use-this-package:
- en: d6fcd541fcd882ac
- zh: c859dff4d6cd36d4
+ en: b92099c600203029
+ zh: 5d8e48261d873793
/deepseek-ai-dsh-llm-deepseek-account/understand-the-implementation:
- en: 8e8d8e4f786e03c3
- zh: 7b32d8cea29d2b1a
+ en: 534b344a98142d91
+ zh: 1291b721c8fead84
/deepseek-ai-dsh-llm-deepseek-account/further-exploration:
en: 30a69b9c66d89580
zh: fcc026a6373c1bee
@@ -36,8 +36,8 @@
en: b0f531baf29daf52
zh: c89837e35981c318
/deepseek-ai-dsh-llm-deepseek-account/model-experience/authenticated-model-requests/kv-cache-effect:
- en: db6aea22ebba25e1
- zh: caf4817596204df4
+ en: 2ecaa7bbbdd638e9
+ zh: bb067520885d877b
/deepseek-ai-dsh-llm-deepseek-account/known-limitations-and-deferred-work:
en: ee8b7ff23c806e18
zh: bd26832c8d455012
diff --git a/packages/llm/llm-deepseek-account/README.md b/packages/llm/llm-deepseek-account/README.md
index 6ceafc3bc9..4711eed96c 100644
--- a/packages/llm/llm-deepseek-account/README.md
+++ b/packages/llm/llm-deepseek-account/README.md
@@ -33,7 +33,7 @@ The first access of a credential waits for the listing up to `modelsTimeoutMs`.
A listed model is offered only when the current DeepSeek Harness version can serve it. A model that declares `client_requirements["deepseek-harness"].min_version` is offered when the current version is at least that version in semantic-version order, including prerelease tags such as `0.2.0-alpha.1` and `0.2.0-rc.1`. A prerelease orders below its release: `min_version: 0.2.1` refuses `0.2.1-alpha.2`, so a listing that admits prereleases declares the earliest one it admits, such as `0.2.1-alpha.0`. A model without that declaration is offered only when the built-in catalog knows its id; other listed models stay hidden. A request for a listed model the current version cannot serve fails before network I/O with `CLIENT_UPGRADE_REQUIRED`, which names the required and current versions, or with `MODEL_UNSUPPORTED`. Model ids the listing does not name pass through unchanged.
-Offered models take the listing's name, `context_window`, input modalities, and Messages system prompt mode over the bundled entry for the same id, which keeps its tool-update mode, image budgets, and description. `max_output_tokens` bounds the request default: a request that names no cap sends the smaller of `maxTokens` and that capability. The listing's effort levels are not read; every model offers `off`, `low`, `high`, and `max`.
+Offered models take the listing's name, `context_window`, input modalities, and Messages system prompt and tool update modes (`api_capabilities.anthropic_messages.system_prompt_update` and `tool_update`) over the bundled entry for the same id, which keeps its image budgets and description. A mode the listing omits or sets to `null` keeps the bundled one; any value other than the modes this build reads (`in-history` for system prompts; `in-history` or `addition-only` for tools) reads as `leading-only`, which removes the bundled mode. `max_output_tokens` bounds the request default: a request that names no cap sends the smaller of `maxTokens` and that capability. The listing's effort levels are not read; every model offers `off`, `low`, `high`, and `max`.
`modelOverrides` adjusts single models without replacing the catalog. Keys are model ids; values take the fields of a [Messages `models` entry](../llm-deepseek/README.md#minimal-configuration), and unset fields inherit. Overrides apply over the listing and the bundled catalog; they cannot add models or make a refused model available. Listed capabilities do not bound an override: an override `maxTokens` above the listed `max_output_tokens` is sent as written, and the service rejects requests beyond its limits. An override that names a model neither the listing nor the built-in catalog describes, or that the listed model rejects, becomes the provider diagnostic on the Models page, which has no editor for this route. An override for a refused model that the built-in catalog does not describe is checked only once the current version can serve that model. The route has no `models` field; a configuration that still sets it fails at load and names `modelOverrides`. Edit `$DSH_HOME/profiles//cordis.patch.yml`; the next request uses the change.
@@ -61,7 +61,7 @@ The settings namespace is the Cordis entry id, or the plugin name without an ent
## Understand the implementation
-Registrations and listeners dispose with the plugin, and disposal aborts an in-flight listing request. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route and uses the transport's `connect` and `admit` hooks for the catalog and the gate. The listing cache lives in memory and is keyed by endpoint and a SHA-256 of the token.
+Registrations and listeners dispose with the plugin, and disposal aborts an in-flight listing request. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route and uses the transport's `connect` and `admit` hooks for the catalog and the gate. The listing request, its cache, and catalog resolution come from the shared transport, which the API-key route also uses; the cache lives in memory and is keyed by endpoint and a SHA-256 of the token.
A failed request the shared transport classifies as `QUOTA` is rewritten to `ACCOUNT_QUOTA` before it leaves this provider, covering an HTTP 402 response and an in-band SSE error alike. The shared transport and the `deepseek-official` route keep `QUOTA`, so the account route's top-up action never appears for an API-key or third-party failure.
@@ -85,7 +85,7 @@ Authentication and the listing add no input tokens. The resolved catalog sets ea
#### KV Cache effect
-Credentials and catalog availability do not enter model input; the protocol transport owns request-prefix serialization. A catalog change to a model's system prompt mode changes how later system prompt updates are serialized.
+Credentials and catalog availability do not enter model input; the protocol transport owns request-prefix serialization. A catalog change to a model's system prompt or tool update mode changes how later system prompt or tool updates are serialized.
## Known Limitations and Deferred Work
diff --git a/packages/llm/llm-deepseek-account/README.zh.md b/packages/llm/llm-deepseek-account/README.zh.md
index 09954ce198..25e6cd4bfd 100644
--- a/packages/llm/llm-deepseek-account/README.zh.md
+++ b/packages/llm/llm-deepseek-account/README.zh.md
@@ -33,7 +33,7 @@ kind: "package-reference"
只有当前版本的 DeepSeek Harness 能用的模型才会出现在目录里。声明了 `client_requirements["deepseek-harness"].min_version` 的模型,在当前版本号按 semver 顺序不低于该版本时提供,预发布号(如 `0.2.0-alpha.1`、`0.2.0-rc.1`)同样参与比较。预发布版本低于同号的正式版本:`min_version: 0.2.1` 会拒绝 `0.2.1-alpha.2`,因此要放行预发布版本的列表应声明它允许的最早一个,如 `0.2.1-alpha.0`。没有这个声明的模型,只有内置目录认识它的 id 时才提供;其余列出的模型保持隐藏。请求一个当前版本不能用的已列出模型时,会在网络请求之前失败:`CLIENT_UPGRADE_REQUIRED` 会写明所需版本和当前版本,否则为 `MODEL_UNSUPPORTED`。列表没有列出的模型 id 照常透传。
-提供的模型以列表中的名称、`context_window`、输入模态和 Messages system prompt 模式覆盖同 id 的内置条目,内置条目保留工具更新模式、图片预算和描述。`max_output_tokens` 限制请求默认值:请求没有指定上限时,发送 `maxTokens` 与该能力上限中较小的一个。列表中的推理档位不被读取;每个模型都提供 `off`、`low`、`high`、`max`。
+提供的模型以列表中的名称、`context_window`、输入模态,以及 Messages 的 system prompt 模式和工具更新模式(`api_capabilities.anthropic_messages.system_prompt_update` 与 `tool_update`)覆盖同 id 的内置条目,内置条目保留图片预算和描述。列表没有给出或设为 `null` 的模式沿用内置值;本版本能读的模式之外的任何值(system prompt 能读 `in-history`,工具能读 `in-history` 或 `addition-only`)都按 `leading-only` 处理,即去掉内置的模式。`max_output_tokens` 限制请求默认值:请求没有指定上限时,发送 `maxTokens` 与该能力上限中较小的一个。列表中的推理档位不被读取;每个模型都提供 `off`、`low`、`high`、`max`。
`modelOverrides` 调整单个模型而不替换整个目录。键是模型 id;值的字段与 [Messages `models` 条目](../llm-deepseek/README.zh.md#minimal-configuration)相同,未设置的字段沿用下层的值。覆盖作用在列表和内置目录之上;不能新增模型,也不能让被拒绝的模型变得可用。列表声明的能力不约束覆盖:覆盖的 `maxTokens` 高于列表的 `max_output_tokens` 时按覆盖值发送,超出服务端限制的请求由服务端拒绝。覆盖指向的模型既不在列表里、内置目录也不认识,或者被列出的模型拒绝时,会成为模型页上该提供方的诊断信息;模型页不为此路由提供编辑器。被拒绝且内置目录不认识的模型,其覆盖要等当前版本能用该模型时才校验。此路由没有 `models` 字段;配置里仍写了它时,加载即失败并提示改用 `modelOverrides`。修改 `$DSH_HOME/profiles//cordis.patch.yml`,下一次请求即生效。
@@ -61,7 +61,7 @@ kind: "package-reference"
## 理解实现
-注册和监听随插件生命周期释放,释放时会中止进行中的列表请求。共享 Host 绑定提供附件访问、请求扩展、匿名标识和重试策略的原子更新;本包只注册自己的路由,并通过传输层的 `connect` 与 `admit` 钩子接入目录和门控。列表缓存只在内存中,以端点和 token 的 SHA-256 为键。
+注册和监听随插件生命周期释放,释放时会中止进行中的列表请求。共享 Host 绑定提供附件访问、请求扩展、匿名标识和重试策略的原子更新;本包只注册自己的路由,并通过传输层的 `connect` 与 `admit` 钩子接入目录和门控。列表请求、缓存和目录解析来自共享传输包,API key 路由也使用它们;缓存只在内存中,以端点和 token 的 SHA-256 为键。
共享传输判定为 `QUOTA` 的失败请求离开本提供方前会被改写为 `ACCOUNT_QUOTA`,HTTP 402 响应与流内 SSE 错误都适用。共享传输与 `deepseek-official` 路由保留 `QUOTA`,因此账号路由的充值操作不会出现在 API Key 或第三方失败上。
@@ -85,7 +85,7 @@ kind: "package-reference"
#### KV Cache effect
-凭据与目录可用性不进入模型输入;协议传输拥有请求前缀的序列化。目录中某个模型的 system prompt 模式变化,会改变之后 system prompt 更新的序列化方式。
+凭据与目录可用性不进入模型输入;协议传输拥有请求前缀的序列化。目录中某个模型的 system prompt 模式或工具更新模式变化,会改变之后 system prompt 更新或工具更新的序列化方式。
## 已知限制与后续工作
diff --git a/packages/llm/llm-deepseek-account/package.json b/packages/llm/llm-deepseek-account/package.json
index 963714641a..d28bf3b4be 100644
--- a/packages/llm/llm-deepseek-account/package.json
+++ b/packages/llm/llm-deepseek-account/package.json
@@ -30,9 +30,7 @@
"license": "MIT",
"dependencies": {
"@deepseek-ai/cosmokit": "workspace:~",
- "@deepseek-ai/dsh-util-values": "workspace:*",
- "@deepseek-ai/schemastery": "workspace:~",
- "semver": "^7.8.5"
+ "@deepseek-ai/schemastery": "workspace:~"
},
"peerDependencies": {
"@deepseek-ai/cordis": "workspace:~",
@@ -40,8 +38,7 @@
"@deepseek-ai/dsh-llm-deepseek": "workspace:*",
"@deepseek-ai/dsh-launch-environment": "workspace:*",
"@deepseek-ai/dsh-deepseek-account": "workspace:*",
- "@deepseek-ai/cordis-plugin-loader": "workspace:~",
- "@deepseek-ai/dsh-timeout": "workspace:*"
+ "@deepseek-ai/cordis-plugin-loader": "workspace:~"
},
"devDependencies": {
"@deepseek-ai/cordis": "workspace:~",
@@ -51,8 +48,7 @@
"@deepseek-ai/dsh-deepseek-account": "workspace:*",
"@deepseek-ai/cordis-plugin-loader": "workspace:~",
"@deepseek-ai/dsh-llm-deepseek-api-key": "workspace:*",
- "@deepseek-ai/dsh-timeout": "workspace:*",
- "@types/semver": "^7.8.0"
+ "@deepseek-ai/dsh-timeout": "workspace:*"
},
"peerDependenciesMeta": {
"@deepseek-ai/dsh-deepseek-account": {
diff --git a/packages/llm/llm-deepseek-account/src/config.ts b/packages/llm/llm-deepseek-account/src/config.ts
index c22e5f566b..acf1d3f07a 100644
--- a/packages/llm/llm-deepseek-account/src/config.ts
+++ b/packages/llm/llm-deepseek-account/src/config.ts
@@ -2,28 +2,17 @@
import type { Volatile } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { isVolatile } from '@deepseek-ai/cosmokit'
-import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
import type { LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
-import { catalogModelFields, deepSeekConfigFields, resolveAdapterOptions } from '@deepseek-ai/dsh-llm-deepseek'
-import type { Config as ProtocolConfig, DeepSeekCatalogModel, Options as ProtocolOptions, ResolvedDeepSeekOptions } from '@deepseek-ai/dsh-llm-deepseek'
-
-/** Default deadline of one model-listing request and of a first-use wait. */
-export const DEFAULT_MODELS_TIMEOUT_MS = 5_000
-/** Default listing age after which the next access refreshes it in the background. */
-export const DEFAULT_MODELS_MAX_AGE_MS = 600_000
-
-/** Field overrides for one model; unset fields and empty lists inherit the underlying entry. */
-export type ModelOverride = Omit
+import {
+ catalogModelFields, deepSeekConfigFields, modelListingConfigFields, modelListingURL, resolveAdapterOptions, resolveListingTiming,
+} from '@deepseek-ai/dsh-llm-deepseek'
+import type {
+ Config as ProtocolConfig, ListingSettings, ModelListingConfig, ModelListingOptions, ModelOverride, Options as ProtocolOptions,
+ ResolvedDeepSeekOptions,
+} from '@deepseek-ai/dsh-llm-deepseek'
/** Account route configuration; authentication comes exclusively from the account service. */
-export interface Config extends Omit {
- /** Deadline of one listing request, which also bounds a first-use wait (default five seconds). */
- modelsTimeoutMs: Volatile
- /**
- * Listing age after which the next access refreshes it in the background;
- * a failed load retries after the same age (default ten minutes).
- */
- modelsMaxAgeMs: Volatile
+export interface Config extends Omit, ModelListingConfig {
/** Field overrides by model id, layered over the account listing and the built-in catalog. */
modelOverrides: Volatile>
}
@@ -38,34 +27,21 @@ const modelOverride: z = z.object({
export const Config = z.object({
...protocolFields,
- modelsTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_MODELS_TIMEOUT_MS).volatile(),
- modelsMaxAgeMs: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MODELS_MAX_AGE_MS).volatile(),
+ ...modelListingConfigFields,
modelOverrides: z.dict(modelOverride).default({}).volatile(),
})
/** Plain account options accepted by {@link resolveAccountOptions}. */
-export type AccountOptions = Omit & {
- modelsTimeoutMs?: number
- modelsMaxAgeMs?: number
+export type AccountOptions = Omit & ModelListingOptions & {
modelOverrides?: Record
}
-/** Model-listing endpoint and cache timing of one configuration generation. */
-export interface CatalogSettings {
- /** Listing endpoint; it always shares the Messages origin. */
- readonly url: string
- /** Deadline of one listing request. */
- readonly timeoutMs: number
- /** Listing age after which the next access refreshes it. */
- readonly maxAgeMs: number
-}
-
/** One validated configuration generation of the account route. */
export interface ResolvedAccountOptions {
/** Messages settings; request resolution replaces `models` with the resolved account catalog. */
readonly protocol: ResolvedDeepSeekOptions
/** Listing endpoint and cache timing. */
- readonly catalog: CatalogSettings
+ readonly catalog: ListingSettings
/** Overrides by model id, holding only the fields each one declares. */
readonly modelOverrides: Readonly>
}
@@ -79,11 +55,6 @@ export function plainAccountOptions(config: Config): AccountOptions {
return Object.fromEntries(Object.entries(config).map(([key, value]) => [key, isVolatile(value) ? value.get() : value]))
}
-/** The listing endpoint: `/models` at the Messages origin, the only origin that receives the account token. */
-function listingEndpoint(baseURL: string): string {
- return new URL('/models', new URL(baseURL).origin).href
-}
-
/** Keep an override's declared fields: parsed configuration carries unset fields as `undefined` and unset arrays as `[]`. */
function resolveOverride(id: string, override: Readonly>): ModelOverride {
if (id.length === 0) throw new Error('llm-deepseek-account: modelOverrides keys must be non-empty model ids')
@@ -115,20 +86,12 @@ export function resolveAccountOptions(options: AccountOptions, environment?: Lau
throw new Error('llm-deepseek-account: models is no longer configurable; the account /models listing supplies the catalog. '
+ 'Remove models from this entry and adjust single models with modelOverrides.')
}
- const {
- modelsTimeoutMs = DEFAULT_MODELS_TIMEOUT_MS, modelsMaxAgeMs = DEFAULT_MODELS_MAX_AGE_MS,
- modelOverrides = {}, ...protocolOptions
- } = options
- if (!Number.isFinite(modelsTimeoutMs) || modelsTimeoutMs <= 0 || modelsTimeoutMs > MAX_TIMER_DELAY_MS) {
- throw new Error(`llm-deepseek-account: modelsTimeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`)
- }
- if (!Number.isSafeInteger(modelsMaxAgeMs) || modelsMaxAgeMs < 0) {
- throw new Error('llm-deepseek-account: modelsMaxAgeMs must be a non-negative safe integer')
- }
+ const { modelsTimeoutMs: _timeout, modelsMaxAgeMs: _maxAge, modelOverrides = {}, ...protocolOptions } = options
+ const timing = resolveListingTiming(options, 'llm-deepseek-account')
const protocol = resolveAdapterOptions(protocolOptions, environment)
return {
protocol,
- catalog: { url: listingEndpoint(protocol.baseURL), timeoutMs: modelsTimeoutMs, maxAgeMs: modelsMaxAgeMs },
+ catalog: { url: modelListingURL(protocol.baseURL), ...timing },
modelOverrides: Object.fromEntries(Object.entries(modelOverrides).map(([id, override]) => [id, resolveOverride(id, override)])),
}
}
diff --git a/packages/llm/llm-deepseek-account/src/index.ts b/packages/llm/llm-deepseek-account/src/index.ts
index 79feb7994c..2ed44f794b 100644
--- a/packages/llm/llm-deepseek-account/src/index.ts
+++ b/packages/llm/llm-deepseek-account/src/index.ts
@@ -4,16 +4,12 @@ import type { Context } from '@deepseek-ai/cordis'
import { ACCOUNT_QUOTA_EXCEEDED_CODE, APP_IDENTITY, LlmError, QUOTA_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm'
import type {} from '@deepseek-ai/dsh-deepseek-account'
import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
-import { assertNever } from '@deepseek-ai/dsh-util-values'
-import { catalogModelInfo, registerDeepSeekProvider, resolveAdapterOptions } from '@deepseek-ai/dsh-llm-deepseek'
-import type { DeepSeekRequestAuth, ResolvedDeepSeekOptions } from '@deepseek-ai/dsh-llm-deepseek'
-import { AccountModelCatalog } from './catalog.ts'
-import type { CatalogView } from './catalog.ts'
+import {
+ catalogModelInfo, ModelListingCache, refuseListedModel, registerDeepSeekProvider, requestModelListing, resolveAdapterOptions,
+ resolveListedCatalog,
+} from '@deepseek-ai/dsh-llm-deepseek'
+import type { DeepSeekRequestAuth, ListedCatalog, ListingView, ModelListing, ResolvedDeepSeekOptions } from '@deepseek-ai/dsh-llm-deepseek'
import { Config, plainAccountOptions, resolveAccountOptions } from './config.ts'
-import { requestModelListing } from './listing.ts'
-import type { ModelListing } from './listing.ts'
-import { resolveAccountCatalog } from './resolve-catalog.ts'
-import type { AccountCatalog, CatalogRefusal } from './resolve-catalog.ts'
export { Config } from './config.ts'
export const name = 'llm-deepseek-account'
@@ -27,23 +23,7 @@ const LOG_TAG = '[llm-deepseek-account]'
/** Connection facts of one account operation: protocol settings plus the catalog resolved with them. */
interface AccountConnection extends ResolvedDeepSeekOptions {
- readonly catalog: AccountCatalog
-}
-
-function refuse(model: string, refusal: CatalogRefusal | undefined): void {
- if (refusal === undefined) return
- switch (refusal.kind) {
- case 'upgrade-required':
- throw new LlmError(
- `DeepSeek model "${model}" requires DeepSeek Harness ${refusal.requiredVersion} or later; current version is ${APP_IDENTITY.version}`,
- 'CLIENT_UPGRADE_REQUIRED',
- )
- case 'unsupported':
- throw new LlmError(`DeepSeek model "${model}" is not supported by DeepSeek Harness; current version is ${APP_IDENTITY.version}`, 'MODEL_UNSUPPORTED')
- /* v8 ignore next 2 -- catalog resolution produces only the closed refusal union */
- default:
- assertNever(refusal, 'account catalog refusal')
- }
+ readonly catalog: ListedCatalog
}
export function apply(ctx: Context, config: Config): void {
@@ -70,9 +50,8 @@ export function apply(ctx: Context, config: Config): void {
},
}
}
- const catalog = new AccountModelCatalog({
- settings: () => settings().catalog,
- token: async url => account()?.resolveToken(url),
+ const catalog = new ModelListingCache({
+ credential: async url => account()?.resolveToken(url),
load: (url, token, signal) => requestModelListing(url, { [TOKEN_HEADER]: token }, signal),
now: () => Date.now(),
// ACP reserves stdout for protocol messages, including during successful catalog loads.
@@ -81,9 +60,9 @@ export function apply(ctx: Context, config: Config): void {
})
let loggedListing: ModelListing | undefined
/** Resolve the catalog for one view, logging the listing's hidden entries once per listing. */
- const connectionOf = (view: CatalogView): AccountConnection => {
+ const connectionOf = (view: ListingView): AccountConnection => {
const resolved = settings()
- const result = resolveAccountCatalog({
+ const result = resolveListedCatalog({
listing: view.listing?.entries,
supported,
overrides: resolved.modelOverrides,
@@ -100,7 +79,7 @@ export function apply(ctx: Context, config: Config): void {
const directory = ctx.llm.registerConfigurableProviders([entry])
let reportedError: string | undefined
/** Publish override diagnostics on the provider directory. */
- const discovered = (view: CatalogView): AccountConnection => {
+ const discovered = (view: ListingView): AccountConnection => {
const connection = connectionOf(view)
const error = connection.catalog.overrideErrors.length === 0 ? undefined : connection.catalog.overrideErrors.join('; ')
if (error !== reportedError) {
@@ -112,12 +91,12 @@ export function apply(ctx: Context, config: Config): void {
registerDeepSeekProvider(ctx, PROVIDER, {
providerName: 'DeepSeek Account',
options: () => connectionOf(catalog.current()),
- connect: async signal => connectionOf(await catalog.view(signal)),
+ connect: async signal => connectionOf(await catalog.view(settings().catalog, signal)),
resolveAuth,
- admit: (connection, model) => { refuse(model, connection.catalog.refusals.get(model)) },
+ admit: (connection, model) => { refuseListedModel(model, connection.catalog.refusals.get(model)) },
discoverModels: async (provider) => {
- const view = await catalog.view()
- return view.signedIn ? discovered(view).catalog.offered.map(model => catalogModelInfo(provider, model)) : []
+ const view = await catalog.view(settings().catalog)
+ return view.authenticated ? discovered(view).catalog.offered.map(model => catalogModelInfo(provider, model)) : []
},
})
ctx.effect(() => () => { catalog.dispose() }, 'llm-deepseek-account: model catalog')
diff --git a/packages/llm/llm-deepseek-account/tests/account-quota.spec.ts b/packages/llm/llm-deepseek-account/tests/account-quota.spec.ts
index f5c4ef8e71..41bd00a4c0 100644
--- a/packages/llm/llm-deepseek-account/tests/account-quota.spec.ts
+++ b/packages/llm/llm-deepseek-account/tests/account-quota.spec.ts
@@ -13,7 +13,7 @@ afterEach(async () => {
vi.unstubAllEnvs()
})
-/** Fail the account listing request fast, so the route serves its bundled catalog and each call gets a fresh Messages response. */
+/** Fail the model listing request fast, so either route serves its bundled catalog and each call gets a fresh Messages response. */
function messagesOnly(response: () => Response) {
return (input: string | URL | Request): Promise => {
const url = new URL(input instanceof Request ? input.url : input)
diff --git a/packages/llm/llm-deepseek-account/tests/provider.spec.ts b/packages/llm/llm-deepseek-account/tests/provider.spec.ts
index d4a75057d2..c3b5fd2f41 100644
--- a/packages/llm/llm-deepseek-account/tests/provider.spec.ts
+++ b/packages/llm/llm-deepseek-account/tests/provider.spec.ts
@@ -105,7 +105,7 @@ it('falls back to the bundled catalog when the listing fails', async () => {
await ctx.plugin(Account, { baseURL: server.baseURL })
expect((await ctx.llm.listModels('deepseek-account')).map(row => row.id)).toEqual(['deepseek-flash', 'deepseek-v4-pro'])
expect(warn).toHaveBeenCalledExactlyOnceWith('[llm-deepseek-account]', expect.stringContaining('using the bundled catalog') as string)
- await expect(ctx.llm.resolveModelInfo('deepseek-account', 'deepseek-flash')).resolves.toMatchObject({ context: { contextWindow: 1_000_000 } })
+ await expect(ctx.llm.resolveModelInfo('deepseek-account', 'deepseek-flash')).resolves.toMatchObject({ context: { contextWindow: 1_048_576 } })
})
it('hides and refuses listed models this build cannot serve before any inference request', async () => {
diff --git a/packages/llm/llm-deepseek-account/tsconfig.json b/packages/llm/llm-deepseek-account/tsconfig.json
index db02fde98c..73803a4a6e 100644
--- a/packages/llm/llm-deepseek-account/tsconfig.json
+++ b/packages/llm/llm-deepseek-account/tsconfig.json
@@ -31,12 +31,6 @@
},
{
"path": "../../../vendor/schemastery"
- },
- {
- "path": "../../util/values"
- },
- {
- "path": "../../util/timeout"
}
]
}
diff --git a/packages/llm/llm-deepseek-api-key/README.i18n.yaml b/packages/llm/llm-deepseek-api-key/README.i18n.yaml
index 61e0fb296b..bb435c0240 100644
--- a/packages/llm/llm-deepseek-api-key/README.i18n.yaml
+++ b/packages/llm/llm-deepseek-api-key/README.i18n.yaml
@@ -3,26 +3,26 @@
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/llm/llm-deepseek-api-key/README.md
/:
- en: 3bc991d01c87c9c6
- zh: ba63ebb46b72bc3e
+ en: d0825db7a1a9d8ee
+ zh: d0825db7a1a9d8ee
/deepseek-ai-dsh-llm-deepseek-api-key:
en: b6871195e386cc64
zh: 742b05a3ca80883c
/deepseek-ai-dsh-llm-deepseek-api-key/summary:
- en: ad5a00f16f233d42
- zh: 743d55599144836b
+ en: f937052cf8ff4812
+ zh: 899d61f5021136d6
/deepseek-ai-dsh-llm-deepseek-api-key/table-of-contents:
en: 23386452a66b2fbd
zh: 4cf93c4bf17b3da2
/deepseek-ai-dsh-llm-deepseek-api-key/use-this-package:
- en: 10333177c9033b9e
- zh: 069faa6a125cb6a9
+ en: 2aad8a660b90561d
+ zh: 3f425b3d1ea023e1
/deepseek-ai-dsh-llm-deepseek-api-key/understand-the-implementation:
- en: 002ac925beb4e75b
- zh: 815d06f116604a09
+ en: d614c1ea3a9eb382
+ zh: 5ca070349bb22d56
/deepseek-ai-dsh-llm-deepseek-api-key/further-exploration:
- en: e8bf99d78aa7035f
- zh: e4a74d2d48b6d271
+ en: 347659458510940c
+ zh: ccfb03e18b356a39
/deepseek-ai-dsh-llm-deepseek-api-key/model-experience:
en: 215e7ba838619b7b
zh: a311da3843709f90
@@ -33,14 +33,14 @@
en: e5624a2c7bb26e90
zh: 44da0286a548f24a
/deepseek-ai-dsh-llm-deepseek-api-key/model-experience/authenticated-model-requests/token-effect:
- en: fecefb75b719c317
- zh: f0c2322f657c5914
+ en: 9dd6430752202069
+ zh: 53068bf7f828156f
/deepseek-ai-dsh-llm-deepseek-api-key/model-experience/authenticated-model-requests/kv-cache-effect:
- en: 2004df60d997da79
- zh: a215a6586b44746b
+ en: 8226f9d956013ad9
+ zh: bace9524b7acc1fa
/deepseek-ai-dsh-llm-deepseek-api-key/known-limitations-and-deferred-work:
- en: d5880c883b1b678f
- zh: bf7a4da24ba75280
+ en: e0a41598c126c187
+ zh: 857ab65c27f41612
/deepseek-ai-dsh-llm-deepseek-api-key/known-limitations-and-deferred-work/dev-note:
en: 0525041e573e96b6
zh: b05ed8d852498e6b
diff --git a/packages/llm/llm-deepseek-api-key/README.md b/packages/llm/llm-deepseek-api-key/README.md
index 55e13399f1..b3e6fd0d86 100644
--- a/packages/llm/llm-deepseek-api-key/README.md
+++ b/packages/llm/llm-deepseek-api-key/README.md
@@ -1,5 +1,5 @@
---
-description: "DeepSeek api-key authentication and model discovery."
+description: "DeepSeek API-key authentication, model discovery, and the official model listing."
kind: "package-reference"
---
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
## Summary
-Register authentication and model discovery for `deepseek-official`. This plugin shares the [Messages transport](../llm-deepseek/README.md) and owns credential resolution and catalog availability.
+Register authentication, model discovery, and the official model listing for `deepseek-official`. This plugin shares the [Messages transport](../llm-deepseek/README.md) and owns credential resolution, catalog availability, and model admission.
Authentication resolution returns the validated API key in `x-api-key` for both Messages and Files requests.
@@ -37,17 +37,30 @@ Authentication resolution returns the validated API key in `x-api-key` for both
The endpoint and credential reference come from one configuration resolution. In-flight requests retain that snapshot; subsequent updates affect subsequent calls. Account login state cannot change this route’s credential.
-`models` is an independently configurable catalog for this provider; defaults and protocol capabilities come from the shared transport. Discovery does not probe inference endpoints. The settings namespace is the Cordis entry id, or the plugin name without an entry. Product profiles retain the official entry id `llm-deepseek` and use `llm-deepseek-account` for the account route.
+`models` is an independently configurable catalog for this provider; defaults and protocol capabilities come from the shared transport. While `models` equals the built-in catalog and the Messages origin is the official API, `https://api.deepseek.com`, the route serves the official `GET https://api.deepseek.com/models` listing instead. Listing requests carry the API key in `x-api-key` and the harness `User-Agent`, and reject redirects; without an API key the route requests nothing. Any other endpoint, including one named by `baseURL` or `$DEEPSEEK_BASE_URL`, serves `models` and requests no listing. Discovery does not probe inference endpoints.
+
+The first access waits for the listing up to `modelsTimeoutMs`. Later accesses use the loaded listing and refresh it in the background once it is older than `modelsMaxAgeMs`; an idle route sends no requests. A failed or empty listing keeps the previous one; before any listing loads, the route serves the built-in catalog of the current version (`deepseek-flash` and `deepseek-v4-pro`). A listing HTTP 401 fails only that load. Each loaded listing, the first failure of a failing streak with the catalog it falls back to, and each listing entry hidden because its declarations are unreadable or unusable are written to stderr with the `[llm-deepseek-api-key]` tag; stdout remains available for protocol messages, and secrets are never logged.
+
+Listed models are offered, merged with this build's entries, and refused before network I/O with `CLIENT_UPGRADE_REQUIRED` or `MODEL_UNSUPPORTED` by the [account route's rules](../llm-deepseek-account/README.md#use-this-package); model ids the listing does not name pass through unchanged.
+
+A `models` list that differs from the built-in catalog replaces the catalog and stops listing requests; the Models page stores an edited catalog there. Removing that list, as **Restore defaults** on the Models page does, returns the route to the listing, and the next access loads the listing again even when the cached one is still fresh. A list identical to the built-in catalog counts as uncustomized.
+
+| Field | Default | Meaning |
+|---|---|---|
+| `modelsTimeoutMs` | `5,000` | Deadline of one listing request and of a first-access wait |
+| `modelsMaxAgeMs` | `600,000` | Listing age after which the next access refreshes it; a failed load retries after the same age |
+
+The settings namespace is the Cordis entry id, or the plugin name without an entry. Product profiles retain the official entry id `llm-deepseek` and use `llm-deepseek-account` for the account route.
## Understand the implementation
-Registrations and listeners dispose with the plugin. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route.
+Registrations and listeners dispose with the plugin, and disposal aborts an in-flight listing request. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route and uses the transport's `connect` and `admit` hooks for the listed catalog and the gate. The listing request, its cache, and catalog resolution come from the shared transport; the cache lives in memory and is keyed by endpoint and a SHA-256 of the key, so another key loads its own listing. A `loader/volatile-update` listener discards the cached listing whenever the route starts or stops following it.
## Further Exploration
-[LLM streaming](../../../docs/subsystems/llm-streaming.md) · [Messages](../llm-deepseek/README.md)
+[LLM streaming](../../../docs/subsystems/llm-streaming.md) · [Messages](../llm-deepseek/README.md) · [Official listing decision](../../../.agents/notes/implemented/architecture/2026-10-22-deepseek-official-model-listing.md)
## Model Experience
@@ -60,17 +73,23 @@ Requests on `deepseek-official` add no model-visible text from this plugin; the
#### Token effect
-Authentication and discovery add no input tokens; the selected model and request content determine actual usage.
+Authentication, discovery, and the listing add no input tokens. The followed listing sets each model's context window and default output cap, which each request's logged context and header record.
#### KV Cache effect
-Credentials and catalog availability do not enter model input; the protocol transport owns request-prefix serialization.
+Credentials and catalog availability do not enter model input; the protocol transport owns request-prefix serialization. A listing change to a model's system prompt or tool update mode changes how later system prompt or tool updates are serialized.
## Known Limitations and Deferred Work
-- The catalog is configuration metadata, not remote validation that a model is accepted; actual calls can still fail.
+- A configured catalog is configuration metadata, not remote validation that a model is accepted; actual calls can still fail.
+- The Models page shows the built-in catalog while the route follows the listing, so a model only the listing offers appears only in model selectors. Editing that catalog on the page stores the rows the page shows, which drops such a model until **Restore defaults**.
+- A customized catalog does not receive models the listing adds later.
+- Customization is judged against the running version's built-in catalog, so a saved `models` list equal to an earlier version's built-in catalog counts as customized once the built-in catalog changes and stops following the listing until **Restore defaults**.
+- The listing cache lives in memory; a start without network serves the built-in catalog until a listing loads.
+- A listing change during a running process reaches the Web model picker at its next catalog refresh; the plugin does not announce it.
+- When the current version can serve no listed model, the route offers no models.
### Dev Note
diff --git a/packages/llm/llm-deepseek-api-key/README.zh.md b/packages/llm/llm-deepseek-api-key/README.zh.md
index 0ddcde76fb..bf761343c7 100644
--- a/packages/llm/llm-deepseek-api-key/README.zh.md
+++ b/packages/llm/llm-deepseek-api-key/README.zh.md
@@ -1,5 +1,5 @@
---
-description: "DeepSeek API key authentication and model discovery."
+description: "DeepSeek API-key authentication, model discovery, and the official model listing."
kind: "package-reference"
---
@@ -9,7 +9,7 @@ kind: "package-reference"
## 概述
-为 `deepseek-official` 注册独立的鉴权和模型发现插件。通过 [llm-deepseek](../llm-deepseek/README.zh.md) 共用 Messages 请求实现;本包拥有凭据解析和目录可用性判断。
+为 `deepseek-official` 注册鉴权、模型发现和官方模型列表。通过 [llm-deepseek](../llm-deepseek/README.zh.md) 共用 Messages 请求实现;本包拥有凭据解析、目录可用性判断和模型准入。
鉴权解析将验证后的 API key 放入 `x-api-key`,供 Messages 和 Files 请求共同使用。
@@ -37,17 +37,30 @@ kind: "package-reference"
端点和凭据引用来自同一次配置解析;进行中的请求保留该快照,后续配置更新仅影响后续请求。账号登录态不影响该路由使用的凭据。
-`models` 是该 provider 独立的可配置目录;默认值和协议能力来自共享传输包。目录判断不探测推理端点。设置命名空间采用 Cordis entry id,没有 entry 时采用插件名。产品保留 official 的 `llm-deepseek` entry id,账号使用 `llm-deepseek-account`。
+`models` 是该 provider 独立的可配置目录;默认值和协议能力来自共享传输包。当 `models` 等于内置目录、且 Messages 的 origin 是官方 API `https://api.deepseek.com` 时,路由改为提供官方 `GET https://api.deepseek.com/models` 列表。列表请求用 `x-api-key` 携带 API key,带上 harness 的 `User-Agent`,并拒绝重定向;没有 API key 时不发任何请求。其他端点,包括由 `baseURL` 或 `$DEEPSEEK_BASE_URL` 指定的端点,提供 `models`,不请求列表。目录判断不探测推理端点。
+
+第一次访问会等待列表,最长 `modelsTimeoutMs`。之后的访问直接使用已加载的列表,列表超过 `modelsMaxAgeMs` 后在后台刷新;路由空闲时不发请求。获取失败或得到空列表时保留上一份;还没有加载到任何列表时,路由提供当前版本内置的目录(`deepseek-flash` 与 `deepseek-v4-pro`)。列表请求返回 HTTP 401 只让这一次加载失败。每次加载成功、连续失败中的第一次失败及其退回的目录、以及每个因声明无法读取或不可用而被隐藏的列表条目,都会以 `[llm-deepseek-api-key]` 标签写到 stderr;stdout 保留给协议消息,且不记录任何密钥。
+
+列出的模型按[账号路由的规则](../llm-deepseek-account/README.zh.md#use-this-package)提供、与本版本的内置条目合并,并在网络请求之前以 `CLIENT_UPGRADE_REQUIRED` 或 `MODEL_UNSUPPORTED` 拒绝;列表没有列出的模型 id 照常透传。
+
+与内置目录不同的 `models` 列表会替换整个目录,并停止请求列表;模型页把编辑后的目录存在这里。删除这个列表(即模型页上的**恢复默认模型**)后,路由重新使用列表,即使缓存的列表尚未过期,下一次访问也会重新加载。与内置目录完全相同的列表视为未自定义。
+
+| 字段 | 默认值 | 含义 |
+|---|---|---|
+| `modelsTimeoutMs` | `5,000` | 单次列表请求以及首次访问等待的上限 |
+| `modelsMaxAgeMs` | `600,000` | 列表超过这个时长后,下一次访问时刷新;获取失败后同样在这个时长后重试 |
+
+设置命名空间采用 Cordis entry id,没有 entry 时采用插件名。产品保留 official 的 `llm-deepseek` entry id,账号使用 `llm-deepseek-account`。
## 理解实现
-注册和监听随插件生命周期释放。共享 Host 绑定提供附件访问、请求扩展、匿名标识和重试策略的原子更新;本包只注册自己的路由。
+注册和监听随插件生命周期释放,释放时会中止进行中的列表请求。共享 Host 绑定提供附件访问、请求扩展、匿名标识和重试策略的原子更新;本包只注册自己的路由,并通过传输层的 `connect` 与 `admit` 钩子接入列表目录和门控。列表请求、缓存和目录解析来自共享传输包;缓存只在内存中,以端点和 API key 的 SHA-256 为键,因此换一个 key 会加载它自己的列表。一个 `loader/volatile-update` 监听器在路由开始或停止使用列表时丢弃缓存的列表。
## 进一步探索
-[LLM streaming](../../../docs/subsystems/llm-streaming.zh.md) · [Messages](../llm-deepseek/README.zh.md)
+[LLM streaming](../../../docs/subsystems/llm-streaming.zh.md) · [Messages](../llm-deepseek/README.zh.md) · [官方列表决策](../../../.agents/notes/implemented/architecture/2026-10-22-deepseek-official-model-listing.zh.md)
## 模型体验
@@ -60,17 +73,23 @@ kind: "package-reference"
#### Token effect
-鉴权和目录过滤不增加输入 token;实际调用的 token 由所选模型与请求内容决定。
+鉴权、目录过滤和列表都不增加输入 token。所使用的列表决定每个模型的上下文窗口和默认输出上限,每次请求记录的上下文和请求头会写明这些值。
#### KV Cache effect
-凭据与目录可用性不进入模型输入;协议传输拥有请求前缀的序列化。
+凭据与目录可用性不进入模型输入;协议传输拥有请求前缀的序列化。列表改变某个模型的 system prompt 模式或工具更新模式时,之后的 system prompt 更新或工具更新的序列化方式随之改变。
## 已知限制与后续工作
-- 目录是配置元数据,不验证远端是否接受模型;实际调用仍可能失败。
+- 配置的目录是配置元数据,不验证远端是否接受模型;实际调用仍可能失败。
+- 路由使用列表时,模型页显示的仍是内置目录,因此只有列表提供的模型只出现在模型选择器中。在模型页编辑目录会保存页面上显示的行,这类模型会消失,直到点**恢复默认模型**。
+- 自定义的目录收不到列表之后新增的模型。
+- 是否自定义按当前版本的内置目录判断:保存的 `models` 列表若等于旧版本的内置目录,内置目录变化后就算作自定义,不再跟随列表,直到点**恢复默认模型**。
+- 列表缓存只在内存中;没有网络的启动会一直提供内置目录,直到加载到列表。
+- 进程运行期间的列表变化在 Web 模型选择器下一次刷新目录时生效;插件不主动通知。
+- 当前版本不能用任何已列出的模型时,路由不提供模型。
### 开发备注
diff --git a/packages/llm/llm-deepseek-api-key/src/config.ts b/packages/llm/llm-deepseek-api-key/src/config.ts
index 4046483cd7..a05b6c2f5a 100644
--- a/packages/llm/llm-deepseek-api-key/src/config.ts
+++ b/packages/llm/llm-deepseek-api-key/src/config.ts
@@ -1,26 +1,40 @@
-/** API-key configuration resolved together with one Messages endpoint generation. */
+/** API-key configuration resolved together with one Messages endpoint generation and its model listing. */
import type { Volatile } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { credentialRef, type CredentialRef } from '@deepseek-ai/dsh-credentials'
import type { LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
-import { deepSeekConfigFields, type Config as ProtocolConfig, plainOptions as protocolOptions, resolveAdapterOptions as resolveProtocolOptions } from '@deepseek-ai/dsh-llm-deepseek'
-import type { Options as ProtocolOptions, DeepSeekConnectionOptions } from '@deepseek-ai/dsh-llm-deepseek'
+import {
+ deepSeekConfigFields, isBuiltInCatalog, modelListingConfigFields, modelListingURL, plainOptions as protocolOptions, PUBLIC_BASE_URL,
+ resolveAdapterOptions as resolveProtocolOptions, resolveListingTiming,
+} from '@deepseek-ai/dsh-llm-deepseek'
+import type {
+ Config as ProtocolConfig, DeepSeekConnectionOptions, ListingSettings, ModelListingConfig, ModelListingOptions, Options as ProtocolOptions,
+} from '@deepseek-ai/dsh-llm-deepseek'
+
+/** Origin of the official API, the only endpoint whose `/models` listing this route follows. */
+const OFFICIAL_ORIGIN = new URL(PUBLIC_BASE_URL).origin
/** Messages configuration with a per-request API-key reference. */
-export interface Config extends ProtocolConfig {
+export interface Config extends ProtocolConfig, ModelListingConfig {
/** Credential reference resolved per request; defaults to DEEPSEEK_API_KEY. */
apiKeyEnv: Volatile
}
export const Config = z.object({
...deepSeekConfigFields,
+ ...modelListingConfigFields,
apiKeyEnv: z.string().role('credential-ref').default('DEEPSEEK_API_KEY').volatile(),
})
/** Plain deployment inputs for the API-key provider. */
-export type Options = ProtocolOptions & { apiKeyEnv?: string }
+export type Options = ProtocolOptions & ModelListingOptions & { apiKeyEnv?: string }
/** Endpoint and credential reference captured from the same configuration generation. */
export interface ResolvedDeepSeekOptions extends DeepSeekConnectionOptions {
/** Credential reference used only for this connection snapshot. */
apiKeyEnv: CredentialRef
+ /**
+ * The official `/models` listing this generation follows; absent when `models`
+ * differs from the built-in catalog or the endpoint is not the official API.
+ */
+ listing?: ListingSettings
}
/** Read one validated provider configuration.
* @param config - live plugin configuration.
@@ -29,11 +43,19 @@ export interface ResolvedDeepSeekOptions extends DeepSeekConnectionOptions {
export function plainOptions(config: Config): Options {
return { ...protocolOptions(config), apiKeyEnv: config.apiKeyEnv.get() }
}
-/** Resolve API-key and protocol settings together.
+/** Resolve API-key, listing, and protocol settings together.
* @param config - raw deployment settings.
* @param environment - application launch environment.
- * @returns validated endpoint facts and the matching credential reference.
+ * @returns validated endpoint facts, the matching credential reference, and the listing the catalog follows.
*/
export function resolveAdapterOptions(config: Options, environment?: LaunchEnvironmentSnapshot): ResolvedDeepSeekOptions {
- return { ...resolveProtocolOptions(config, environment), apiKeyEnv: credentialRef(config.apiKeyEnv ?? 'DEEPSEEK_API_KEY') }
+ const { modelsTimeoutMs: _timeout, modelsMaxAgeMs: _maxAge, apiKeyEnv, ...messages } = config
+ const timing = resolveListingTiming(config, 'llm-deepseek-api-key')
+ const connection = resolveProtocolOptions(messages, environment)
+ const listed = new URL(connection.baseURL).origin === OFFICIAL_ORIGIN && isBuiltInCatalog(connection.models)
+ return {
+ ...connection,
+ apiKeyEnv: credentialRef(apiKeyEnv ?? 'DEEPSEEK_API_KEY'),
+ ...listed ? { listing: { url: modelListingURL(connection.baseURL), ...timing } } : {},
+ }
}
diff --git a/packages/llm/llm-deepseek-api-key/src/index.ts b/packages/llm/llm-deepseek-api-key/src/index.ts
index fdbf6fba94..f7881991e4 100644
--- a/packages/llm/llm-deepseek-api-key/src/index.ts
+++ b/packages/llm/llm-deepseek-api-key/src/index.ts
@@ -1,9 +1,12 @@
-/** API-key authentication and credential-gated discovery for the official DeepSeek route. */
+/** API-key authentication, credential-gated discovery, and the official model listing for the official DeepSeek route. */
import type { Context } from '@deepseek-ai/cordis'
-import { assertUsableApiKey, LlmError } from '@deepseek-ai/dsh-llm'
+import { APP_IDENTITY, assertUsableApiKey, LlmError } from '@deepseek-ai/dsh-llm'
import type {} from '@deepseek-ai/cordis-plugin-loader'
import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
-import { registerDeepSeekProvider, catalogModelInfo } from '@deepseek-ai/dsh-llm-deepseek'
+import {
+ catalogModelInfo, ModelListingCache, refuseListedModel, registerDeepSeekProvider, requestModelListing, resolveListedCatalog,
+} from '@deepseek-ai/dsh-llm-deepseek'
+import type { CatalogRefusal, DeepSeekCatalogModel, ListingView, ModelListing } from '@deepseek-ai/dsh-llm-deepseek'
import { Config, plainOptions, resolveAdapterOptions } from './config.ts'
import type { ResolvedDeepSeekOptions } from './config.ts'
@@ -13,6 +16,16 @@ export const name = 'llm-deepseek-api-key'
export const inject = ['llm']
const PROVIDER = 'deepseek-official'
+/** Console tag of listing diagnostics; product profiles mount no console exporter for `ctx.logger`. */
+const LOG_TAG = '[llm-deepseek-api-key]'
+
+/** Connection facts of one operation: protocol settings plus the catalog resolved with them. */
+interface OfficialConnection extends ResolvedDeepSeekOptions {
+ /** Selectable models: the configured catalog, or the offered part of the followed listing. */
+ readonly offered: readonly DeepSeekCatalogModel[]
+ /** Listed models refused before network I/O, by id. */
+ readonly refusals: ReadonlyMap
+}
export function apply(ctx: Context, config: Config): void {
const options = () => resolveAdapterOptions(plainOptions(config), launchEnvironmentOf(ctx))
@@ -33,20 +46,83 @@ export function apply(ctx: Context, config: Config): void {
'MISSING_CREDENTIAL',
)
}
+ /** The API key, or `undefined` when none is configured; other credential failures propagate. */
+ const presentApiKey = async (connection: ResolvedDeepSeekOptions): Promise => {
+ try { return await resolveApiKey(connection) }
+ catch (error) {
+ if (error instanceof LlmError && error.code === 'MISSING_CREDENTIAL') return undefined
+ throw error
+ }
+ }
+ const listings = new ModelListingCache({
+ credential: async () => presentApiKey(options()),
+ load: (url, apiKey, signal) => requestModelListing(url, { 'x-api-key': apiKey }, signal),
+ now: () => Date.now(),
+ // ACP reserves stdout for protocol messages, including during successful catalog loads.
+ info: (message) => { console.error(LOG_TAG, message) },
+ warn: (message) => { console.warn(LOG_TAG, message) },
+ })
+ let reportedListing: ModelListing | undefined
+ /** Resolve the catalog of one operation, reporting each listing's hidden entries once. */
+ const connectionOf = (connection: ResolvedDeepSeekOptions, view: ListingView | undefined): OfficialConnection => {
+ if (view === undefined) return { ...connection, offered: connection.models, refusals: new Map() }
+ const catalog = resolveListedCatalog({
+ listing: view.listing?.entries,
+ supported: connection.models,
+ overrides: {},
+ routeMaxTokens: connection.maxTokens,
+ clientVersion: APP_IDENTITY.version,
+ })
+ if (view.listing !== reportedListing) {
+ reportedListing = view.listing
+ for (const problem of catalog.problems) console.warn(LOG_TAG, problem)
+ }
+ return { ...connection, models: catalog.resolution, offered: catalog.offered, refusals: catalog.refusals }
+ }
ctx.llm.registerConfigurableProviders([
{ provider: PROVIDER, displayName: 'DeepSeek', settingsNs: ctx.fiber.entry?.options.id ?? name, settingsPath: [] },
])
- registerDeepSeekProvider(ctx, PROVIDER, {
- options, providerName: 'DeepSeek',
+ registerDeepSeekProvider(ctx, PROVIDER, {
+ providerName: 'DeepSeek',
+ options: () => {
+ const connection = options()
+ return connectionOf(connection, connection.listing === undefined ? undefined : listings.current())
+ },
+ connect: async (signal) => {
+ const connection = options()
+ if (connection.listing === undefined) return connectionOf(connection, undefined)
+ let view: ListingView
+ try { view = await listings.view(connection.listing, signal) }
+ catch (error) {
+ if (signal?.aborted === true) throw error
+ // The cache keeps the previous credential's listing when this lookup fails, so gate nothing;
+ // `resolveAuth` reports the failure on the model request, after the turn records the prompt.
+ return connectionOf(connection, undefined)
+ }
+ return connectionOf(connection, view)
+ },
resolveAuth: async connection => ({ headers: { 'x-api-key': await resolveApiKey(connection) } }),
+ admit: (connection, model) => { refuseListedModel(model, connection.refusals.get(model)) },
discoverModels: async (provider) => {
const connection = options()
- try { await resolveApiKey(connection) }
- catch (error) {
- if (error instanceof LlmError && error.code === 'MISSING_CREDENTIAL') return []
- throw error
+ if (connection.listing === undefined) {
+ if (await presentApiKey(connection) === undefined) return []
+ return connection.models.map(model => catalogModelInfo(provider, model))
}
- return connection.models.map(model => catalogModelInfo(provider, model))
+ const view = await listings.view(connection.listing)
+ return view.authenticated ? connectionOf(connection, view).offered.map(model => catalogModelInfo(provider, model)) : []
},
})
+ // Starting or stopping to follow the listing discards it, so restoring the built-in
+ // catalog after a customization reloads the listing on the next access.
+ let following = options().listing !== undefined
+ ctx.on('loader/volatile-update', () => {
+ let next: boolean
+ // The transport's own listener reports a configuration the adapter rejects.
+ try { next = options().listing !== undefined }
+ catch (_rejectedConfiguration) { return }
+ if (next !== following) listings.invalidate()
+ following = next
+ })
+ ctx.effect(() => () => { listings.dispose() }, 'llm-deepseek-api-key: model listing')
}
diff --git a/packages/llm/llm-deepseek-api-key/tests/config.spec.ts b/packages/llm/llm-deepseek-api-key/tests/config.spec.ts
new file mode 100644
index 0000000000..c66bc2957e
--- /dev/null
+++ b/packages/llm/llm-deepseek-api-key/tests/config.spec.ts
@@ -0,0 +1,30 @@
+/** The API-key route follows the official listing only for the built-in catalog on the official endpoint. */
+import { expect, it } from 'vitest'
+import { resolveAdapterOptions as resolveProtocolOptions } from '@deepseek-ai/dsh-llm-deepseek'
+import { Config, plainOptions, resolveAdapterOptions } from '../src/config.ts'
+
+const LISTING = { url: 'https://api.deepseek.com/models', timeoutMs: 5_000, maxAgeMs: 600_000 }
+
+it('follows the official listing while the catalog is the built-in one', () => {
+ expect(resolveAdapterOptions({}).listing).toEqual(LISTING)
+ expect(resolveAdapterOptions(plainOptions(Config({}))).listing).toEqual(LISTING)
+ expect(resolveAdapterOptions({ models: [...resolveProtocolOptions({}).models] }).listing).toEqual(LISTING)
+ expect(resolveAdapterOptions({ baseURL: 'https://api.deepseek.com/anthropic/v1', modelsTimeoutMs: 7, modelsMaxAgeMs: 0 }).listing)
+ .toEqual({ url: 'https://api.deepseek.com/models', timeoutMs: 7, maxAgeMs: 0 })
+})
+
+it.each([
+ [{ models: [{ id: 'custom' }] }],
+ [{ models: [] }],
+ [{ baseURL: 'https://gateway.example/anthropic' }],
+ [{ baseURL: 'http://api.deepseek.com/anthropic' }],
+])('keeps its own catalog for %j', (options) => {
+ expect(resolveAdapterOptions(options)).not.toHaveProperty('listing')
+})
+
+it.each([
+ [{ modelsTimeoutMs: 0 }, /^llm-deepseek-api-key: modelsTimeoutMs must be a positive finite number/],
+ [{ modelsMaxAgeMs: 1.5 }, /^llm-deepseek-api-key: modelsMaxAgeMs must be a non-negative safe integer/],
+])('rejects %j', (options, message) => {
+ expect(() => resolveAdapterOptions({ models: [{ id: 'custom' }], ...options })).toThrow(message)
+})
diff --git a/packages/llm/llm-deepseek-api-key/tests/discovery.spec.ts b/packages/llm/llm-deepseek-api-key/tests/discovery.spec.ts
index 40216ce5f7..27f55f005b 100644
--- a/packages/llm/llm-deepseek-api-key/tests/discovery.spec.ts
+++ b/packages/llm/llm-deepseek-api-key/tests/discovery.spec.ts
@@ -22,10 +22,10 @@ it('advertises configured models with an API key', async () => {
const ctx = new Context()
try {
await ctx.plugin(LlmRuntime)
- await ctx.plugin(ApiKey, {})
- expect(await ctx.llm.listModels('deepseek-official')).toEqual(expect.arrayContaining([
- expect.objectContaining({ provider: 'deepseek-official', id: 'deepseek-flash' }),
- ]))
+ await ctx.plugin(ApiKey, { models: [{ id: 'configured-model' }] })
+ expect(await ctx.llm.listModels('deepseek-official')).toEqual([
+ expect.objectContaining({ provider: 'deepseek-official', id: 'configured-model' }),
+ ])
} finally {
await ctx.fiber.dispose()
vi.unstubAllEnvs()
diff --git a/packages/llm/llm-deepseek-api-key/tests/listing.spec.ts b/packages/llm/llm-deepseek-api-key/tests/listing.spec.ts
new file mode 100644
index 0000000000..9f6dd64a88
--- /dev/null
+++ b/packages/llm/llm-deepseek-api-key/tests/listing.spec.ts
@@ -0,0 +1,161 @@
+/** The API-key route serves the official /models listing while its catalog is the built-in one. */
+import { afterEach, expect, it, vi } from 'vitest'
+import { Context } from '@deepseek-ai/cordis'
+import LlmRuntime from '@deepseek-ai/dsh-llm'
+import { liveConfig } from '../../../settings/settings/tests/live-config.ts'
+import * as ApiKey from '../src/index.ts'
+
+const LISTING_URL = 'https://api.deepseek.com/models'
+const MESSAGES = { output_modalities: ['text'], api_capabilities: { anthropic_messages: {} } }
+const FLASH = {
+ id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash', context_window: 1_048_576, max_output_tokens: 393_216,
+ input_modalities: ['text', 'image'], output_modalities: ['text'],
+ api_capabilities: { anthropic_messages: { system_prompt_update: 'in-history' } },
+}
+const PRO = { id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro', context_window: 1_048_576, input_modalities: ['text'], ...MESSAGES }
+const requires = (version: string) => ({ client_requirements: { 'deepseek-harness': { min_version: version } } })
+const BUILT_IN = ['deepseek-flash', 'deepseek-v4-pro']
+
+const contexts: Context[] = []
+afterEach(async () => {
+ for (const ctx of contexts.splice(0)) await ctx.fiber.dispose()
+ vi.unstubAllEnvs()
+ vi.restoreAllMocks()
+})
+
+/** Answer the official listing URL with `reply`; any other request fails, so nothing reaches the network. */
+function officialListing(reply: (request: Request, index: number) => Response) {
+ const requests: Request[] = []
+ vi.spyOn(globalThis, 'fetch').mockImplementation((input, init) => {
+ const request = new Request(input, init)
+ if (request.url !== LISTING_URL) return Promise.reject(new Error(`unexpected request to ${request.url}`))
+ requests.push(request)
+ return Promise.resolve(reply(request, requests.length - 1))
+ })
+ return requests
+}
+const listing = (...rows: object[]) => () => new Response(JSON.stringify({ object: 'list', data: rows }), { headers: { 'content-type': 'application/json' } })
+
+async function host(key: string | undefined = 'fixture-api-key') {
+ vi.stubEnv('DEEPSEEK_API_KEY', key)
+ const diagnostics = vi.spyOn(console, 'error').mockImplementation(() => {})
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
+ const ctx = new Context()
+ contexts.push(ctx)
+ await ctx.plugin(LlmRuntime)
+ return { ctx, diagnostics, warn }
+}
+const ids = async (ctx: Context) => (await ctx.llm.listModels('deepseek-official')).map(model => model.id)
+
+it('follows the official listing while the catalog is the built-in one', async () => {
+ const requests = officialListing(listing(FLASH, { ...PRO, id: 'deepseek-v5', ...requires('0.0.1') }, PRO))
+ const { ctx, diagnostics } = await host()
+ await ctx.plugin(ApiKey, {})
+ expect(await ids(ctx)).toEqual(['deepseek-flash', 'deepseek-v5', 'deepseek-v4-pro'])
+ await expect(ctx.llm.resolveModelInfo('deepseek-official', 'deepseek-flash')).resolves.toMatchObject({
+ context: { contextWindow: 1_048_576 }, systemPromptUpdate: 'in-history', toolUpdate: 'addition-only', inputModalities: ['text', 'image'],
+ })
+ expect(requests.map(request => request.headers.get('x-api-key'))).toEqual(['fixture-api-key'])
+ expect(diagnostics).toHaveBeenCalledExactlyOnceWith('[llm-deepseek-api-key]', expect.stringContaining('loaded 3 model(s), skipped 0 row(s)') as string)
+})
+
+it('keeps a customized catalog without requesting the listing', async () => {
+ const requests = officialListing(listing(FLASH))
+ const { ctx } = await host()
+ await ctx.plugin(ApiKey, { models: [{ id: 'custom', name: 'Custom' }] })
+ expect(await ids(ctx)).toEqual(['custom'])
+ await expect(ctx.llm.resolveModelInfo('deepseek-official', 'custom')).resolves.toMatchObject({ name: 'Custom' })
+ expect(requests).toEqual([])
+})
+
+it('serves the built-in catalog on another endpoint without requesting a listing', async () => {
+ const requests = officialListing(listing(FLASH))
+ const { ctx } = await host()
+ await ctx.plugin(ApiKey, { baseURL: 'http://127.0.0.1:9/anthropic' })
+ expect(await ids(ctx)).toEqual(BUILT_IN)
+ expect(requests).toEqual([])
+})
+
+it('falls back to the built-in catalog when the listing fails', async () => {
+ officialListing(() => new Response('down', { status: 503 }))
+ const { ctx, warn } = await host()
+ await ctx.plugin(ApiKey, {})
+ expect(await ids(ctx)).toEqual(BUILT_IN)
+ expect(warn).toHaveBeenCalledExactlyOnceWith('[llm-deepseek-api-key]', expect.stringContaining('using the bundled catalog') as string)
+})
+
+it('refuses listed models this build cannot serve before any inference request', async () => {
+ const requests = officialListing(listing(
+ FLASH, { ...PRO, id: 'deepseek-v5' }, { ...PRO, id: 'deepseek-v5-next', ...requires('99.0.0-alpha.1') }, { id: 'deepseek-v4-pro' },
+ ))
+ const { ctx, warn } = await host()
+ await ctx.plugin(ApiKey, {})
+ expect(await ids(ctx)).toEqual(['deepseek-flash'])
+ await expect(ctx.llm.prepareCall({ provider: 'deepseek-official', model: 'deepseek-v4-pro' })).rejects.toMatchObject({ code: 'MODEL_UNSUPPORTED' })
+ await expect(ctx.llm.prepareCall({ provider: 'deepseek-official', model: 'deepseek-v5' })).rejects.toMatchObject({ code: 'MODEL_UNSUPPORTED' })
+ await expect(ctx.llm.prepareCall({ provider: 'deepseek-official', model: 'deepseek-v5-next' })).rejects.toMatchObject({ code: 'CLIENT_UPGRADE_REQUIRED' })
+ await expect(ctx.llm.prepareCall({ provider: 'deepseek-official', model: 'unlisted-pass-through' })).resolves.toBeDefined()
+ expect(warn).toHaveBeenCalledExactlyOnceWith('[llm-deepseek-api-key]', 'model "deepseek-v4-pro" declares no api_capabilities.anthropic_messages')
+ expect(requests).toHaveLength(1)
+})
+
+it("leaves a malformed replacement key to the request instead of refusing by the previous key's listing", async () => {
+ const requests = officialListing(listing(FLASH, PRO, { ...PRO, id: 'deepseek-v5-next', ...requires('99.0.0-alpha.1') }))
+ const { ctx } = await host()
+ vi.stubEnv('MALFORMED_DEEPSEEK_API_KEY', 'invalid\nheader')
+ const live = await liveConfig(ctx, ApiKey, {})
+ await expect(ctx.llm.prepareCall({ provider: 'deepseek-official', model: 'deepseek-v5-next' })).rejects.toMatchObject({ code: 'CLIENT_UPGRADE_REQUIRED' })
+ await live.update({ apiKeyEnv: 'MALFORMED_DEEPSEEK_API_KEY' })
+ await expect(ctx.llm.prepareCall({ provider: 'deepseek-official', model: 'deepseek-v5-next' })).resolves.toBeDefined()
+ expect(requests).toHaveLength(1)
+})
+
+it('lets a caller abandon the first listing wait', async () => {
+ const started = Promise.withResolvers()
+ vi.spyOn(globalThis, 'fetch').mockImplementation((_input, init) => new Promise((_resolve, reject) => {
+ started.resolve(undefined)
+ init?.signal?.addEventListener('abort', () => {
+ reject(new Error('listing request aborted', { cause: init.signal?.reason }))
+ }, { once: true })
+ }))
+ const { ctx } = await host()
+ await ctx.plugin(ApiKey, {})
+ const caller = new AbortController()
+ const pending = ctx.llm.resolveModelInfo('deepseek-official', 'deepseek-flash', caller.signal)
+ await started.promise
+ caller.abort(new Error('caller left'))
+ await expect(pending).rejects.toThrow('caller left')
+})
+
+it.each([[{}], [{ models: [{ id: 'custom' }] }]])('hides the route without a key and requests nothing for %j', async (config) => {
+ const requests = officialListing(listing(FLASH))
+ const { ctx } = await host('')
+ await ctx.plugin(ApiKey, config)
+ expect(await ids(ctx)).toEqual([])
+ expect(requests).toEqual([])
+})
+
+it('reloads the listing once when the catalog returns to the built-in one', async () => {
+ const requests = officialListing((_request, index) => (index === 0 ? listing(FLASH, PRO) : listing(FLASH, PRO, { ...PRO, id: 'deepseek-v5', ...requires('0.0.1') }))())
+ const { ctx } = await host()
+ const live = await liveConfig(ctx, ApiKey, {})
+ expect(await ids(ctx)).toEqual(BUILT_IN)
+ await live.update({ models: [{ id: 'custom' }] })
+ expect(await ids(ctx)).toEqual(['custom'])
+ expect(requests).toHaveLength(1)
+ await live.replace({})
+ expect(await ids(ctx)).toEqual([...BUILT_IN, 'deepseek-v5'])
+ expect(requests).toHaveLength(2)
+})
+
+it('keeps following the listing across an update the adapter rejects', async () => {
+ const requests = officialListing(listing(FLASH, PRO))
+ const { ctx } = await host()
+ const live = await liveConfig(ctx, ApiKey, {})
+ expect(await ids(ctx)).toEqual(BUILT_IN)
+ await live.update({ thinking: 'disabled', reasoningEffort: 'high' })
+ await expect(ctx.llm.listModels('deepseek-official')).rejects.toThrow(/only reasoningEffort "off"/)
+ await live.replace({})
+ expect(await ids(ctx)).toEqual(BUILT_IN)
+ expect(requests).toHaveLength(1)
+})
diff --git a/packages/llm/llm-deepseek/README.i18n.yaml b/packages/llm/llm-deepseek/README.i18n.yaml
index 379690bd8b..8f1b2c46aa 100644
--- a/packages/llm/llm-deepseek/README.i18n.yaml
+++ b/packages/llm/llm-deepseek/README.i18n.yaml
@@ -21,8 +21,8 @@
en: 6850d08bc091ad23
zh: 2906e6cf1dfed6ce
/deepseek-ai-dsh-llm-deepseek/use-this-package/minimal-configuration:
- en: cfc2de7ef728dbf9
- zh: 2ce1a406061a71aa
+ en: 6a15f2e0efec4c37
+ zh: 7fb5f1e4a92b01ad
/deepseek-ai-dsh-llm-deepseek/use-this-package/endpoint-and-wire-format:
en: 522e74b770e9d4ca
zh: a8b261b41677bfe7
@@ -39,8 +39,8 @@
en: f13f24b504dcfb50
zh: 69b0ed9e2adf3394
/deepseek-ai-dsh-llm-deepseek/use-this-package/failures-and-recovery:
- en: d43f055d2382ae71
- zh: af2a7cb4e3476a3f
+ en: 40ab925291edc6ac
+ zh: 62c42ea3521920dc
/deepseek-ai-dsh-llm-deepseek/understand-the-implementation:
en: 1598ac8a8bb9b6bf
zh: fdd089c5ae7722cd
@@ -48,8 +48,8 @@
en: 829203c4c282dc46
zh: e05dada4a1420a95
/deepseek-ai-dsh-llm-deepseek/understand-the-implementation/source-map:
- en: dc2d2409ad8c41cc
- zh: 51fc4ad65dab6378
+ en: 584698e2c7686997
+ zh: 2aed9608d5317d7b
/deepseek-ai-dsh-llm-deepseek/understand-the-implementation/wire-flow:
en: 9477d8af3d762450
zh: 52a2967518509f94
diff --git a/packages/llm/llm-deepseek/README.md b/packages/llm/llm-deepseek/README.md
index 1c61082cc2..aef0563887 100644
--- a/packages/llm/llm-deepseek/README.md
+++ b/packages/llm/llm-deepseek/README.md
@@ -50,7 +50,7 @@ Choose this adapter for DeepSeek's official API or a Messages-compatible gateway
filesApiTimeoutMs: 60000
```
-A request selects the route with `provider: deepseek-official`; the model id passes through to the wire, so new DeepSeek models need no re-registration. Omitted `models` advertises the text- and image-capable `deepseek-flash` alongside the text-only `deepseek-v4-pro`, each with a 1,000,000-token context window. An explicit list replaces those defaults, and core calls still pass unlisted model ids through as text-only routes. GUI selection requires a catalog entry; a saved selection can still submit requests after its catalog entry disappears. Clients, including model discovery tools, can read the advisory entries through `ctx.llm.listModels('deepseek-official')`. Image-capable entries may set `imagePixelBudget` to a positive integer or `low`, and may set `imageMaxBytes`. An entry may declare `systemPromptUpdate: in-history` when its endpoint reads the latest `system` message at any position of `messages` as the complete effective system prompt; the adapter reports the mode on the resolved model and the prepared call, and the agent loop then appends a changed prompt after the cached history instead of rewriting the leading system message ([decision rule](../../core/agent-loop/README.md#understand-the-implementation)). The default `deepseek-flash` entry declares this mode; other models require an explicit `models` declaration, and any value other than `in-history` fails at load with `llm-deepseek: catalog model "" systemPromptUpdate must be "in-history" when present`.
+A request selects the route with `provider: deepseek-official`; the model id passes through to the wire, so new DeepSeek models need no re-registration. Omitted `models` advertises the text- and image-capable `deepseek-flash` alongside the text-only `deepseek-v4-pro`, each with the 1,048,576-token context window the official model listing declares. An explicit list replaces those defaults, and core calls still pass unlisted model ids through as text-only routes. On the official endpoint the API-key route follows the service's model listing while `models` holds those defaults ([listing rules](../llm-deepseek-api-key/README.md#use-this-package)). GUI selection requires a catalog entry; a saved selection can still submit requests after its catalog entry disappears. Clients, including model discovery tools, can read the advisory entries through `ctx.llm.listModels('deepseek-official')`. Image-capable entries may set `imagePixelBudget` to a positive integer or `low`, and may set `imageMaxBytes`. An entry may declare `systemPromptUpdate: in-history` when its endpoint reads the latest `system` message at any position of `messages` as the complete effective system prompt; the adapter reports the mode on the resolved model and the prepared call, and the agent loop then appends a changed prompt after the cached history instead of rewriting the leading system message ([decision rule](../../core/agent-loop/README.md#understand-the-implementation)). The default `deepseek-flash` entry declares this mode; other models require an explicit `models` declaration, and any value other than `in-history` fails at load with `llm-deepseek: catalog model "" systemPromptUpdate must be "in-history" when present`.
| Field | Default | Meaning |
|---|---|---|
@@ -118,7 +118,7 @@ Successful Files responses must contain valid JSON. JSON decoding failures from
Non-2xx responses fail with stable codes: `AUTH` (401/403), `QUOTA`, `RATE_LIMIT`, `CONTEXT_WINDOW_EXCEEDED`, `INVALID_REQUEST`, `SERVER`, and `HTTP_` otherwise; pre-response transport failures throw `TRANSPORT`, caller aborts throw `ABORTED`, and stream-idle expiry throws `TIMEOUT`. A request-extension field collision, or a rejected preparation from a directly supplied `prepareExtensions`, fails with `REQUEST_EXTENSION` and appends the cause chain to the message; a post-2xx acceptance failure is logged and the response still streams. A normalized-image rejection names every plausible attachment and its durable position when the provider does not identify a file id. Stale-file rejection removes every named mapping (or every mapping used by the attempt) in one upload-index update and permits one replacement model request. Protocol violations throw `STREAM_CLOSED` or `MALFORMED_RESPONSE`, and a terminal `stop` with no content blocks becomes `EMPTY_RESPONSE`, which the default retry policy retries. A request on the official route without an API key fails with `MISSING_CREDENTIAL`, and a malformed credential fails with `INVALID_CREDENTIAL` naming the reference to fix — never any part of the key.
-Provider plugins own catalog availability: the API-key route serves its configured `models` while its API key resolves, and the account route serves its account listing while signed in (see the [account provider](../llm-deepseek-account/README.md)). The transport supplies the bundled catalog, the catalog-entry field schemas, and capability resolution. A provider may also supply `connect(signal)`, which resolves the connection for exact-model lookups and prepared calls after provider-owned work such as a catalog load, and `admit(connection, model)`, which refuses a model before network I/O.
+Provider plugins own catalog availability. While its API key resolves, the API-key route serves its configured `models`, or the official model listing while `models` is the built-in catalog on the official endpoint; the account route serves its account listing while signed in (see the [API-key](../llm-deepseek-api-key/README.md) and [account](../llm-deepseek-account/README.md) providers). The transport supplies the bundled catalog, the catalog-entry field schemas, and capability resolution, plus the listing machinery both routes share: `requestModelListing`, the credential-scoped `ModelListingCache`, and `resolveListedCatalog` with its client-version gate and `refuseListedModel`. A provider may also supply `connect(signal)`, which resolves the connection for exact-model lookups and prepared calls after provider-owned work such as a catalog load, and `admit(connection, model)`, which refuses a model before network I/O.
-----
@@ -136,7 +136,7 @@ The plugin is built on one explicit resolve step and one registration fact. `res
### Source map
-[`src/index.ts`](src/index.ts) exports the protocol library; [`src/host.ts`](src/host.ts) binds shared Host services for provider plugins. [`src/adapter.ts`](src/adapter.ts) owns the request lifecycle; [`src/serialize.ts`](src/serialize.ts) and [`src/translate.ts`](src/translate.ts) map model input and streamed output. [`src/file-store.ts`](src/file-store.ts) owns upload reuse and recovery through [`src/files-api.ts`](src/files-api.ts).
+[`src/index.ts`](src/index.ts) exports the protocol library; [`src/host.ts`](src/host.ts) binds shared Host services for provider plugins. [`src/adapter.ts`](src/adapter.ts) owns the request lifecycle; [`src/serialize.ts`](src/serialize.ts) and [`src/translate.ts`](src/translate.ts) map model input and streamed output. [`src/file-store.ts`](src/file-store.ts) owns upload reuse and recovery through [`src/files-api.ts`](src/files-api.ts). [`src/listing.ts`](src/listing.ts), [`src/listing-cache.ts`](src/listing-cache.ts), and [`src/listed-catalog.ts`](src/listed-catalog.ts) own the `/models` request and decoding, the listing cache, and listed-catalog resolution.
### Wire flow
diff --git a/packages/llm/llm-deepseek/README.zh.md b/packages/llm/llm-deepseek/README.zh.md
index 403fca1dba..092e9b804a 100644
--- a/packages/llm/llm-deepseek/README.zh.md
+++ b/packages/llm/llm-deepseek/README.zh.md
@@ -50,7 +50,7 @@ kind: "package-reference"
filesApiTimeoutMs: 60000
```
-请求用 `provider: deepseek-official` 选择路由;模型 id 原样传到协议,因此新增 DeepSeek 模型无需重新注册。省略 `models` 时公布支持文本和图像的 `deepseek-flash`,以及仅支持文本的 `deepseek-v4-pro`,各自的上下文窗口均为 1,000,000 token。显式列表会替换这些默认值,核心调用中未列出的模型 id 仍作为纯文本路由原样通过。GUI 选择要求模型具有目录条目;条目消失后,已保存的选择仍可提交请求。包括模型发现工具在内的客户端可通过 `ctx.llm.listModels('deepseek-official')` 读取这些建议性条目。支持图片的条目可把 `imagePixelBudget` 设置为正整数或 `low`,也可以设置 `imageMaxBytes`。当端点把 `messages` 中任意位置最新的 `system` 消息读作完整的有效系统提示词时,条目可以声明 `systemPromptUpdate: in-history`;适配器会在已解析模型与已准备调用上报告该模式,agent loop(智能体循环)随后把变化后的提示词追加到已缓存历史之后,而不是改写开头的 system 消息([决策规则](../../core/agent-loop/README.zh.md#understand-the-implementation))。默认的 `deepseek-flash` 条目声明该模式;其他模型需通过 `models` 显式声明,`in-history` 以外的任何值都会在加载时以 `llm-deepseek: catalog model "" systemPromptUpdate must be "in-history" when present` 失败。
+请求用 `provider: deepseek-official` 选择路由;模型 id 原样传到协议,因此新增 DeepSeek 模型无需重新注册。省略 `models` 时公布支持文本和图像的 `deepseek-flash`,以及仅支持文本的 `deepseek-v4-pro`,各自的上下文窗口均为官方模型列表声明的 1,048,576 token。显式列表会替换这些默认值,核心调用中未列出的模型 id 仍作为纯文本路由原样通过。在官方端点上,当 `models` 保持这些默认值时,API key 路由改为使用服务端的模型列表([列表规则](../llm-deepseek-api-key/README.zh.md#use-this-package))。GUI 选择要求模型具有目录条目;条目消失后,已保存的选择仍可提交请求。包括模型发现工具在内的客户端可通过 `ctx.llm.listModels('deepseek-official')` 读取这些建议性条目。支持图片的条目可把 `imagePixelBudget` 设置为正整数或 `low`,也可以设置 `imageMaxBytes`。当端点把 `messages` 中任意位置最新的 `system` 消息读作完整的有效系统提示词时,条目可以声明 `systemPromptUpdate: in-history`;适配器会在已解析模型与已准备调用上报告该模式,agent loop(智能体循环)随后把变化后的提示词追加到已缓存历史之后,而不是改写开头的 system 消息([决策规则](../../core/agent-loop/README.zh.md#understand-the-implementation))。默认的 `deepseek-flash` 条目声明该模式;其他模型需通过 `models` 显式声明,`in-history` 以外的任何值都会在加载时以 `llm-deepseek: catalog model "" systemPromptUpdate must be "in-history" when present` 失败。
| 字段 | 默认值 | 含义 |
|---|---|---|
@@ -118,7 +118,7 @@ Files 模式通过 `maxRequestFilesBytes` 与 `maxImagesPerRequest` 限制保留
非 2xx 响应以稳定 code 失败:`AUTH`(401/403)、`QUOTA`、`RATE_LIMIT`、`CONTEXT_WINDOW_EXCEEDED`、`INVALID_REQUEST`、`SERVER` 以及其他情况的 `HTTP_`;响应前传输失败抛出 `TRANSPORT`,调用方中止抛出 `ABORTED`,流空闲超时抛出 `TIMEOUT`。请求扩展字段冲突,或直接提供的 `prepareExtensions` 拒绝准备时,使用 `REQUEST_EXTENSION` 失败,并在消息中附上原因链;2xx 后接受失败只记录告警,响应仍会继续流式返回。当提供方未指出 file id 时,规范化图片拒绝会列出所有可能附件及其持久位置。陈旧文件拒绝会在一次上传索引更新中删除全部点名映射(或该次尝试使用的全部映射),并允许一次替换模型请求。协议违规抛出 `STREAM_CLOSED` 或 `MALFORMED_RESPONSE`;不带内容块的终止 `stop` 变成 `EMPTY_RESPONSE`,默认重试策略会重试它。官方路由缺少 API Key 的请求以 `MISSING_CREDENTIAL` 失败;格式错误的凭据以 `INVALID_CREDENTIAL` 失败,并点名需要修复的引用——绝不包含密钥的任何部分。
-提供方插件负责目录可用性:API key 路由在能解析到 API key 时提供配置中的 `models`,账号路由在登录时提供账号的模型列表(见[账号提供方](../llm-deepseek-account/README.zh.md))。传输层提供内置目录、目录条目字段的 schema 和能力解析。提供方还可以提供 `connect(signal)`:在提供方自己的工作(例如加载目录)完成后,为精确模型查询和预备调用解析连接;以及 `admit(connection, model)`:在网络请求之前拒绝某个模型。
+提供方插件负责目录可用性:API key 路由在能解析到 API key 时提供配置中的 `models`,在官方端点上且 `models` 为内置目录时改为提供官方模型列表;账号路由在登录时提供账号的模型列表(见 [API key](../llm-deepseek-api-key/README.zh.md) 与[账号](../llm-deepseek-account/README.zh.md)提供方)。传输层提供内置目录、目录条目字段的 schema 和能力解析,以及两条路由共用的列表机制:`requestModelListing`、按凭据区分的 `ModelListingCache`,以及带客户端版本门控的 `resolveListedCatalog` 与 `refuseListedModel`。提供方还可以提供 `connect(signal)`:在提供方自己的工作(例如加载目录)完成后,为精确模型查询和预备调用解析连接;以及 `admit(connection, model)`:在网络请求之前拒绝某个模型。
-----
@@ -136,7 +136,7 @@ Files 模式通过 `maxRequestFilesBytes` 与 `maxImagesPerRequest` 限制保留
### 源码导航
-[`src/index.ts`](src/index.ts) 导出协议库;[`src/host.ts`](src/host.ts) 为 provider 插件绑定共享 Host 服务。[`src/adapter.ts`](src/adapter.ts) 管理请求生命周期;[`src/serialize.ts`](src/serialize.ts) 和 [`src/translate.ts`](src/translate.ts) 映射模型输入与流式输出。[`src/file-store.ts`](src/file-store.ts) 通过 [`src/files-api.ts`](src/files-api.ts) 管理上传复用与恢复。
+[`src/index.ts`](src/index.ts) 导出协议库;[`src/host.ts`](src/host.ts) 为 provider 插件绑定共享 Host 服务。[`src/adapter.ts`](src/adapter.ts) 管理请求生命周期;[`src/serialize.ts`](src/serialize.ts) 和 [`src/translate.ts`](src/translate.ts) 映射模型输入与流式输出。[`src/file-store.ts`](src/file-store.ts) 通过 [`src/files-api.ts`](src/files-api.ts) 管理上传复用与恢复。[`src/listing.ts`](src/listing.ts)、[`src/listing-cache.ts`](src/listing-cache.ts) 和 [`src/listed-catalog.ts`](src/listed-catalog.ts) 负责 `/models` 请求与解码、列表缓存和列表目录解析。
### 协议流程
diff --git a/packages/llm/llm-deepseek/package.json b/packages/llm/llm-deepseek/package.json
index e23cf8f784..78cc597e83 100644
--- a/packages/llm/llm-deepseek/package.json
+++ b/packages/llm/llm-deepseek/package.json
@@ -44,7 +44,8 @@
"@deepseek-ai/dsh-brand": "workspace:*",
"@deepseek-ai/dsh-util-values": "workspace:*",
"@deepseek-ai/schemastery": "workspace:~",
- "eventsource-parser": "^3.1.0"
+ "eventsource-parser": "^3.1.0",
+ "semver": "^7.8.5"
},
"devDependencies": {
"@deepseek-ai/dsh-llm-deepseek-api-key": "workspace:*",
@@ -76,7 +77,8 @@
"@deepseek-ai/dsh-tools": "workspace:*",
"@deepseek-ai/dsh-credentials-local": "workspace:*",
"@deepseek-ai/dsh-settings": "workspace:*",
- "@deepseek-ai/dsh-deepseek-account": "workspace:*"
+ "@deepseek-ai/dsh-deepseek-account": "workspace:*",
+ "@types/semver": "^7.8.0"
},
"peerDependenciesMeta": {
"@deepseek-ai/cordis-plugin-loader": {
diff --git a/packages/llm/llm-deepseek/src/config.ts b/packages/llm/llm-deepseek/src/config.ts
index 8f6c06bc59..630ccea010 100644
--- a/packages/llm/llm-deepseek/src/config.ts
+++ b/packages/llm/llm-deepseek/src/config.ts
@@ -3,6 +3,7 @@ import type { Volatile } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { isVolatile } from '@deepseek-ai/cosmokit'
+import { deepEqualJson } from '@deepseek-ai/dsh-util-values'
import { resolveRetryPolicy, RetryPolicySchema } from '@deepseek-ai/dsh-llm'
import type { ModelModality, RetryPolicyConfig } from '@deepseek-ai/dsh-llm'
import type { LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
@@ -120,6 +121,16 @@ const BASE_URL_ENV = 'DEEPSEEK_BASE_URL'
/** Complete protocol settings captured for one request operation. */
export type ResolvedDeepSeekOptions = DeepSeekConnectionOptions
+/**
+ * Whether a resolved catalog is the built-in one, which is what a `models`
+ * setting nobody customized resolves to.
+ * @param models - catalog resolved by {@link resolveAdapterOptions}.
+ * @returns whether `models` equals the resolved built-in catalog, entry by entry and in order.
+ */
+export function isBuiltInCatalog(models: readonly DeepSeekCatalogModel[]): boolean {
+ return deepEqualJson(models, resolveModels(DEFAULT_MODELS))
+}
+
/** Resolve, validate, and detach the advisory model catalog. */
function resolveModels(models: readonly DeepSeekCatalogModel[] | undefined): DeepSeekCatalogModel[] {
const seen = new Set()
diff --git a/packages/llm/llm-deepseek/src/index.ts b/packages/llm/llm-deepseek/src/index.ts
index 3684ef56bd..8e92ffef31 100644
--- a/packages/llm/llm-deepseek/src/index.ts
+++ b/packages/llm/llm-deepseek/src/index.ts
@@ -1,5 +1,5 @@
/** DeepSeek Messages transport, request configuration, and model capabilities. */
-export { catalogModelFields, deepSeekConfigFields, Config, plainOptions, resolveAdapterOptions, PUBLIC_BASE_URL } from './config.ts'
+export { catalogModelFields, deepSeekConfigFields, Config, isBuiltInCatalog, plainOptions, resolveAdapterOptions, PUBLIC_BASE_URL } from './config.ts'
export type { Options, ResolvedDeepSeekOptions } from './config.ts'
export {
DEFAULT_CONTEXT_WINDOW,
@@ -39,3 +39,11 @@ export type { RequestDefaults } from './types.ts'
export { catalogModelInfo } from './model-info.ts'
export { registerDeepSeekProvider } from './host.ts'
+export { LISTING_TIMEOUT, modelListingURL, parseModelListing, requestModelListing } from './listing.ts'
+export type { ClientRequirement, ListedCapabilities, ListedFacts, ListingEntry, ModelListing } from './listing.ts'
+export { DEFAULT_MODELS_MAX_AGE_MS, DEFAULT_MODELS_TIMEOUT_MS, modelListingConfigFields, resolveListingTiming } from './listing-config.ts'
+export type { ListingSettings, ListingTiming, ModelListingConfig, ModelListingOptions } from './listing-config.ts'
+export { ModelListingCache } from './listing-cache.ts'
+export type { ListingCacheDependencies, ListingView } from './listing-cache.ts'
+export { refuseListedModel, resolveListedCatalog } from './listed-catalog.ts'
+export type { CatalogRefusal, ListedCatalog, ListedCatalogInput, ModelOverride } from './listed-catalog.ts'
diff --git a/packages/llm/llm-deepseek-account/src/resolve-catalog.ts b/packages/llm/llm-deepseek/src/listed-catalog.ts
similarity index 71%
rename from packages/llm/llm-deepseek-account/src/resolve-catalog.ts
rename to packages/llm/llm-deepseek/src/listed-catalog.ts
index 70cdb3178c..c7f59133dc 100644
--- a/packages/llm/llm-deepseek-account/src/resolve-catalog.ts
+++ b/packages/llm/llm-deepseek/src/listed-catalog.ts
@@ -1,10 +1,13 @@
-/** The account catalog: version gating, this build's model knowledge, and deployment overrides. */
+/** A listed catalog: version gating, this build's model knowledge, and deployment overrides. */
import semver from 'semver'
+import { APP_IDENTITY, LlmError } from '@deepseek-ai/dsh-llm'
import { assertNever } from '@deepseek-ai/dsh-util-values'
-import { resolveAdapterOptions } from '@deepseek-ai/dsh-llm-deepseek'
-import type { DeepSeekCatalogModel } from '@deepseek-ai/dsh-llm-deepseek'
-import type { ModelOverride } from './config.ts'
+import { resolveAdapterOptions } from './config.ts'
import type { ListedFacts, ListingEntry } from './listing.ts'
+import type { DeepSeekCatalogModel } from './types.ts'
+
+/** Field overrides for one model; unset fields and empty lists inherit the underlying entry. */
+export type ModelOverride = Omit
/** Why this build refuses one listed model before network I/O. */
export type CatalogRefusal =
@@ -12,7 +15,7 @@ export type CatalogRefusal =
| { readonly kind: 'unsupported' }
/** Inputs of one catalog resolution. */
-export interface AccountCatalogInput {
+export interface ListedCatalogInput {
/** Entries of the last successful listing, or `undefined` before one succeeds. */
readonly listing: readonly ListingEntry[] | undefined
/** Validated models this build supports, carrying its client-side knowledge. */
@@ -25,8 +28,8 @@ export interface AccountCatalogInput {
readonly clientVersion: string
}
-/** One resolved account catalog. */
-export interface AccountCatalog {
+/** One resolved listed catalog. */
+export interface ListedCatalog {
/** Selectable models in listing order, or this build's models when no listing succeeded. */
readonly offered: readonly DeepSeekCatalogModel[]
/** Entries request resolution consults: offered models, then supported models the listing does not offer. */
@@ -49,14 +52,20 @@ function layered(model: DeepSeekCatalogModel, layer: ModelOverride): DeepSeekCat
return merged
}
-/** Layer one usable listing entry over this build's entry for the same id. */
+/** Layer one usable listing entry over this build's entry for the same id; a declared `leading-only` mode removes the inherited one. */
function listed(base: DeepSeekCatalogModel, entry: ListingEntry, facts: ListedFacts, routeMaxTokens: number): DeepSeekCatalogModel {
- const { systemPromptUpdate: _replaced, ...leadingOnly } = base
- return layered(facts.systemPromptUpdate === 'leading-only' ? leadingOnly : base, {
+ const { systemPromptUpdate, toolUpdate, ...rest } = base
+ const inherited: DeepSeekCatalogModel = {
+ ...rest,
+ ...systemPromptUpdate === undefined || facts.systemPromptUpdate === 'leading-only' ? {} : { systemPromptUpdate },
+ ...toolUpdate === undefined || facts.toolUpdate === 'leading-only' ? {} : { toolUpdate },
+ }
+ return layered(inherited, {
...entry.name === undefined ? {} : { name: entry.name },
...facts.contextWindow === undefined ? {} : { contextWindow: facts.contextWindow },
...facts.maxOutputTokens === undefined ? {} : { maxTokens: Math.min(routeMaxTokens, facts.maxOutputTokens) },
...facts.systemPromptUpdate === 'in-history' ? { systemPromptUpdate: 'in-history' as const } : {},
+ ...facts.toolUpdate === undefined || facts.toolUpdate === 'leading-only' ? {} : { toolUpdate: facts.toolUpdate },
inputModalities: [...facts.inputModalities],
})
}
@@ -82,13 +91,13 @@ function gateOf(entry: ListingEntry, known: boolean, clientVersion: string, prob
}
/**
- * Resolve the account catalog. A declared minimum client version decides a
+ * Resolve a route's catalog from its listing. A declared minimum client version decides a
* listed model's availability; without one, only models this build supports
* are offered and the other listed models stay hidden.
* @param input - listing, supported models, overrides, route cap, and this build's version.
* @returns offered models, request-resolution entries, refusals, and diagnostics.
*/
-export function resolveAccountCatalog(input: AccountCatalogInput): AccountCatalog {
+export function resolveListedCatalog(input: ListedCatalogInput): ListedCatalog {
const supported = new Map(input.supported.map(model => [model.id, model]))
const problems: string[] = []
const overrideErrors: string[] = []
@@ -127,7 +136,7 @@ export function resolveAccountCatalog(input: AccountCatalogInput): AccountCatalo
const listedIds = new Set(input.listing.map(entry => entry.id))
for (const id of Object.keys(input.overrides)) {
if (!listedIds.has(id) && !supported.has(id)) {
- overrideErrors.push(`modelOverrides names "${id}", which neither the account listing nor the built-in catalog describes`)
+ overrideErrors.push(`modelOverrides names "${id}", which neither the model listing nor the built-in catalog describes`)
}
}
const resolution = resolveAdapterOptions({
@@ -135,3 +144,25 @@ export function resolveAccountCatalog(input: AccountCatalogInput): AccountCatalo
}).models
return { offered: resolution.slice(0, offered.length), resolution, refusals, problems, overrideErrors }
}
+
+/**
+ * Refuse a listed model this build cannot serve, before any network I/O.
+ * @param model - requested wire model id.
+ * @param refusal - the catalog's refusal for `model`, or `undefined` when it is admitted.
+ * @throws LlmError `CLIENT_UPGRADE_REQUIRED` naming the required and current versions, or `MODEL_UNSUPPORTED`.
+ */
+export function refuseListedModel(model: string, refusal: CatalogRefusal | undefined): void {
+ if (refusal === undefined) return
+ switch (refusal.kind) {
+ case 'upgrade-required':
+ throw new LlmError(
+ `DeepSeek model "${model}" requires DeepSeek Harness ${refusal.requiredVersion} or later; current version is ${APP_IDENTITY.version}`,
+ 'CLIENT_UPGRADE_REQUIRED',
+ )
+ case 'unsupported':
+ throw new LlmError(`DeepSeek model "${model}" is not supported by DeepSeek Harness; current version is ${APP_IDENTITY.version}`, 'MODEL_UNSUPPORTED')
+ /* v8 ignore next 2 -- catalog resolution produces only the closed refusal union */
+ default:
+ assertNever(refusal, 'listed catalog refusal')
+ }
+}
diff --git a/packages/llm/llm-deepseek-account/src/catalog.ts b/packages/llm/llm-deepseek/src/listing-cache.ts
similarity index 67%
rename from packages/llm/llm-deepseek-account/src/catalog.ts
rename to packages/llm/llm-deepseek/src/listing-cache.ts
index 9718dbc394..eef8dd48b9 100644
--- a/packages/llm/llm-deepseek-account/src/catalog.ts
+++ b/packages/llm/llm-deepseek/src/listing-cache.ts
@@ -1,18 +1,16 @@
/** Credential-scoped `/models` cache with single-flight loads, background refresh, and last-known-good retention. */
import { createHash } from 'node:crypto'
import { deadline } from '@deepseek-ai/dsh-timeout'
-import type { CatalogSettings } from './config.ts'
+import type { ListingSettings } from './listing-config.ts'
import { LISTING_TIMEOUT } from './listing.ts'
import type { ModelListing } from './listing.ts'
-/** Host-owned inputs of one catalog instance. */
-export interface CatalogDependencies {
- /** Current endpoint settings, re-read on every access. */
- readonly settings: () => CatalogSettings
- /** The account token allowed for `url`, or `undefined` while signed out. */
- readonly token: (url: string) => Promise
- /** Perform one listing request authenticated with `token`; `signal` carries the {@link LISTING_TIMEOUT} request deadline. */
- readonly load: (url: string, token: string, signal: AbortSignal) => Promise
+/** Host-owned inputs of one cache instance. */
+export interface ListingCacheDependencies {
+ /** The credential the route sends to `url`, or `undefined` when it has none. */
+ readonly credential: (url: string) => Promise
+ /** Perform one listing request authenticated with `credential`; `signal` carries the {@link LISTING_TIMEOUT} request deadline. */
+ readonly load: (url: string, credential: string, signal: AbortSignal) => Promise
/** Wall clock used for listing age. */
readonly now: () => number
/** Report a loaded listing. */
@@ -22,9 +20,9 @@ export interface CatalogDependencies {
}
/** What request resolution reads. */
-export interface CatalogView {
- /** Whether an account token is available for the listing endpoint. */
- readonly signedIn: boolean
+export interface ListingView {
+ /** Whether a credential is available for the listing endpoint. */
+ readonly authenticated: boolean
/** Last successful listing for this credential and endpoint; `undefined` until one succeeds. */
readonly listing: ModelListing | undefined
}
@@ -42,8 +40,8 @@ async function abandonable(task: Promise, signal: AbortSignal | undefined)
}
}
-/** One account route's listing cache; each endpoint and credential pair has its own scope. */
-export class AccountModelCatalog {
+/** One route's listing cache; each endpoint and credential pair has its own scope. */
+export class ModelListingCache {
private scope: string | undefined
private listing: ModelListing | undefined
/** Settle time of the latest load in this scope; `undefined` before the first one settles. */
@@ -54,38 +52,43 @@ export class AccountModelCatalog {
private failing = false
private closed = false
- /** @param dependencies - settings, credential, transport, clock, and diagnostic callbacks. */
- constructor(private readonly dependencies: CatalogDependencies) {}
+ /** @param dependencies - credential, transport, clock, and diagnostic callbacks. */
+ constructor(private readonly dependencies: ListingCacheDependencies) {}
/**
* The last published view, without I/O.
- * @returns the signed-in state and listing of the current scope.
+ * @returns the credential state and listing of the current scope.
*/
- current(): CatalogView {
- return { signedIn: this.scope !== undefined, listing: this.listing }
+ current(): ListingView {
+ return { authenticated: this.scope !== undefined, listing: this.listing }
}
/**
* Resolve the view for the current credential. The first access of a scope
* waits for its load, which the request deadline bounds; later accesses
* return at once and refresh a stale listing in the background.
+ * @param settings - endpoint and timing of the caller's configuration generation.
* @param signal - abandons this wait only; the shared load continues.
* @returns the view after any first load of the scope settles.
*/
- async view(signal?: AbortSignal): Promise {
- const settings = this.dependencies.settings()
- const token = await this.dependencies.token(settings.url)
+ async view(settings: ListingSettings, signal?: AbortSignal): Promise {
+ const credential = await this.dependencies.credential(settings.url)
signal?.throwIfAborted()
if (this.closed) return this.current()
- const scope = token === undefined ? undefined : `${settings.url}\n${createHash('sha256').update(token).digest('hex')}`
+ const scope = credential === undefined ? undefined : `${settings.url}\n${createHash('sha256').update(credential).digest('hex')}`
if (scope !== this.scope) this.rescope(scope)
- if (token !== undefined) {
- if (this.settledAt === undefined) await abandonable(this.start(settings, token), signal)
- else if (this.dependencies.now() - this.settledAt >= settings.maxAgeMs) void this.start(settings, token)
+ if (credential !== undefined) {
+ if (this.settledAt === undefined) await abandonable(this.start(settings, credential), signal)
+ else if (this.dependencies.now() - this.settledAt >= settings.maxAgeMs) void this.start(settings, credential)
}
return this.current()
}
+ /** Discard the listing and abort any in-flight load; the next access loads again and waits like a first access. */
+ invalidate(): void {
+ this.rescope(this.scope)
+ }
+
/** Abort in-flight loads; later results and accesses change nothing. */
dispose(): void {
this.closed = true
@@ -103,14 +106,14 @@ export class AccountModelCatalog {
this.generation += 1
}
- private start(settings: CatalogSettings, token: string): Promise {
+ private start(settings: ListingSettings, credential: string): Promise {
if (this.inflight !== undefined) return this.inflight
this.generation += 1
const generation = this.generation
const request = new AbortController()
this.request = request
const limit = deadline(request.signal, settings.timeoutMs, LISTING_TIMEOUT)
- const run = this.dependencies.load(settings.url, token, limit.signal)
+ const run = this.dependencies.load(settings.url, credential, limit.signal)
.then((listing) => {
if (generation !== this.generation) return
this.listing = listing
diff --git a/packages/llm/llm-deepseek/src/listing-config.ts b/packages/llm/llm-deepseek/src/listing-config.ts
new file mode 100644
index 0000000000..5e306b2823
--- /dev/null
+++ b/packages/llm/llm-deepseek/src/listing-config.ts
@@ -0,0 +1,64 @@
+/** Configuration of a route that follows a `/models` listing: cache timing and its validation. */
+import type { Volatile } from '@deepseek-ai/cordis'
+import z from '@deepseek-ai/schemastery'
+import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
+
+/** Default deadline of one model-listing request and of a first-use wait. */
+export const DEFAULT_MODELS_TIMEOUT_MS = 5_000
+/** Default listing age after which the next access refreshes it in the background. */
+export const DEFAULT_MODELS_MAX_AGE_MS = 600_000
+
+/** Listing fields of a route Config. */
+export interface ModelListingConfig {
+ /** Deadline of one listing request, which also bounds a first-use wait (default five seconds). */
+ modelsTimeoutMs: Volatile
+ /**
+ * Listing age after which the next access refreshes it in the background;
+ * a failed load retries after the same age (default ten minutes).
+ */
+ modelsMaxAgeMs: Volatile
+}
+
+/** Schemas of the {@link ModelListingConfig} fields. */
+export const modelListingConfigFields = {
+ modelsTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_MODELS_TIMEOUT_MS).volatile(),
+ modelsMaxAgeMs: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MODELS_MAX_AGE_MS).volatile(),
+}
+
+/** Plain listing options accepted by {@link resolveListingTiming}. */
+export interface ModelListingOptions {
+ modelsTimeoutMs?: number
+ modelsMaxAgeMs?: number
+}
+
+/** Listing cache timing of one configuration generation. */
+export interface ListingTiming {
+ /** Deadline of one listing request. */
+ readonly timeoutMs: number
+ /** Listing age after which the next access refreshes it. */
+ readonly maxAgeMs: number
+}
+
+/** Listing endpoint and cache timing of one configuration generation. */
+export interface ListingSettings extends ListingTiming {
+ /** Listing endpoint; it always shares the Messages origin. */
+ readonly url: string
+}
+
+/**
+ * Validate listing timing. Settings updates can reach it without schema validation, so every bound is judged here.
+ * @param options - plain listing options; absent values take the defaults.
+ * @param owner - plugin name that prefixes each failure message.
+ * @returns the validated timing.
+ * @throws Error when a value is outside its schema bounds.
+ */
+export function resolveListingTiming(options: ModelListingOptions, owner: string): ListingTiming {
+ const { modelsTimeoutMs = DEFAULT_MODELS_TIMEOUT_MS, modelsMaxAgeMs = DEFAULT_MODELS_MAX_AGE_MS } = options
+ if (!Number.isFinite(modelsTimeoutMs) || modelsTimeoutMs <= 0 || modelsTimeoutMs > MAX_TIMER_DELAY_MS) {
+ throw new Error(`${owner}: modelsTimeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`)
+ }
+ if (!Number.isSafeInteger(modelsMaxAgeMs) || modelsMaxAgeMs < 0) {
+ throw new Error(`${owner}: modelsMaxAgeMs must be a non-negative safe integer`)
+ }
+ return { timeoutMs: modelsTimeoutMs, maxAgeMs: modelsMaxAgeMs }
+}
diff --git a/packages/llm/llm-deepseek-account/src/listing.ts b/packages/llm/llm-deepseek/src/listing.ts
similarity index 88%
rename from packages/llm/llm-deepseek-account/src/listing.ts
rename to packages/llm/llm-deepseek/src/listing.ts
index ca198105a0..38c717166f 100644
--- a/packages/llm/llm-deepseek-account/src/listing.ts
+++ b/packages/llm/llm-deepseek/src/listing.ts
@@ -8,6 +8,15 @@ const LISTED_MODALITIES = ['text', 'image'] as const satisfies readonly ModelMod
/** Code of a listing deadline's timeout reason; {@link requestModelListing} reports it as `TIMEOUT`. */
export const LISTING_TIMEOUT = 'MODEL_LISTING_TIMEOUT'
+/**
+ * The listing endpoint of a Messages root.
+ * @param baseURL - Messages `baseURL`.
+ * @returns `/models` at the origin of `baseURL`, the only origin that receives the route's credential.
+ */
+export function modelListingURL(baseURL: string): string {
+ return new URL('/models', new URL(baseURL).origin).href
+}
+
/** The requirement one listing entry declares under this client's product token. */
export type ClientRequirement =
| { readonly kind: 'none' }
@@ -24,6 +33,8 @@ export interface ListedFacts {
readonly maxOutputTokens?: number
/** Messages system prompt mode, when declared; any value other than `in-history` reads as `leading-only`. */
readonly systemPromptUpdate?: 'in-history' | 'leading-only'
+ /** Messages tool update mode, when declared; any value other than `in-history` or `addition-only` reads as `leading-only`. */
+ readonly toolUpdate?: 'in-history' | 'addition-only' | 'leading-only'
}
/** Declared capabilities, or why the Messages transport cannot drive the model. */
@@ -97,14 +108,19 @@ function capabilitiesOf(entry: Fields): ListedCapabilities {
if (contextWindow === false || maxOutputTokens === false) {
return { usable: false, reason: 'declares an invalid context_window or max_output_tokens' }
}
- const update = messages.system_prompt_update
+ // `null` declares nothing, like an absent field, so a serializer that writes empty values as null keeps this build's knowledge.
+ const promptUpdate = messages.system_prompt_update ?? undefined
+ const toolUpdate = messages.tool_update ?? undefined
return {
usable: true,
facts: {
inputModalities: inputs,
...contextWindow === undefined ? {} : { contextWindow },
...maxOutputTokens === undefined ? {} : { maxOutputTokens },
- ...update === undefined ? {} : { systemPromptUpdate: update === 'in-history' ? 'in-history' : 'leading-only' },
+ ...promptUpdate === undefined ? {} : { systemPromptUpdate: promptUpdate === 'in-history' ? 'in-history' : 'leading-only' },
+ ...toolUpdate === undefined ? {} : {
+ toolUpdate: toolUpdate === 'in-history' || toolUpdate === 'addition-only' ? toolUpdate : 'leading-only',
+ },
},
}
}
diff --git a/packages/llm/llm-deepseek/src/models.ts b/packages/llm/llm-deepseek/src/models.ts
index 15d0c78253..c25d5111fd 100644
--- a/packages/llm/llm-deepseek/src/models.ts
+++ b/packages/llm/llm-deepseek/src/models.ts
@@ -1,13 +1,15 @@
/** Default DeepSeek model catalog. */
-import { DEFAULT_CONTEXT_WINDOW } from './defaults.ts'
import type { DeepSeekCatalogModel } from './types.ts'
+/** Context window of both official models, as the official `/models` listing declares it. */
+const OFFICIAL_CONTEXT_WINDOW = 1_048_576
+
/** Advisory official model entries; deployments may replace the catalog. */
export const DEFAULT_MODELS: DeepSeekCatalogModel[] = [
{
id: 'deepseek-flash',
name: 'DeepSeek-V4.1-Flash',
- contextWindow: DEFAULT_CONTEXT_WINDOW,
+ contextWindow: OFFICIAL_CONTEXT_WINDOW,
inputModalities: ['text', 'image'],
systemPromptUpdate: 'in-history',
toolUpdate: 'addition-only',
@@ -16,6 +18,6 @@ export const DEFAULT_MODELS: DeepSeekCatalogModel[] = [
id: 'deepseek-v4-pro',
name: 'DeepSeek-V4-Pro',
description: 'Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or quality-critical tasks at higher cost.',
- contextWindow: DEFAULT_CONTEXT_WINDOW,
+ contextWindow: OFFICIAL_CONTEXT_WINDOW,
},
]
diff --git a/packages/llm/llm-deepseek/tests/built-in-catalog.spec.ts b/packages/llm/llm-deepseek/tests/built-in-catalog.spec.ts
new file mode 100644
index 0000000000..5001761e90
--- /dev/null
+++ b/packages/llm/llm-deepseek/tests/built-in-catalog.spec.ts
@@ -0,0 +1,18 @@
+/** Recognizing the built-in catalog, which is how an uncustomized `models` setting resolves. */
+import { expect, it } from 'vitest'
+import { Config, isBuiltInCatalog, plainOptions, resolveAdapterOptions } from '../src/config.ts'
+import { DEFAULT_MODELS } from '../src/models.ts'
+
+it('recognizes the built-in catalog however it was supplied', () => {
+ expect(isBuiltInCatalog(resolveAdapterOptions({}).models)).toBe(true)
+ expect(isBuiltInCatalog(resolveAdapterOptions({ models: structuredClone(DEFAULT_MODELS) }).models)).toBe(true)
+ expect(isBuiltInCatalog(resolveAdapterOptions(plainOptions(Config({}))).models)).toBe(true)
+})
+
+it('treats any other catalog as customized', () => {
+ const [flash, pro] = resolveAdapterOptions({}).models
+ expect(isBuiltInCatalog([flash!])).toBe(false)
+ expect(isBuiltInCatalog([{ ...flash!, contextWindow: 524_288 }, pro!])).toBe(false)
+ expect(isBuiltInCatalog([pro!, flash!])).toBe(false)
+ expect(isBuiltInCatalog([])).toBe(false)
+})
diff --git a/packages/llm/llm-deepseek-account/tests/resolve-catalog.spec.ts b/packages/llm/llm-deepseek/tests/listed-catalog.spec.ts
similarity index 78%
rename from packages/llm/llm-deepseek-account/tests/resolve-catalog.spec.ts
rename to packages/llm/llm-deepseek/tests/listed-catalog.spec.ts
index 4306193146..dbb4354777 100644
--- a/packages/llm/llm-deepseek-account/tests/resolve-catalog.spec.ts
+++ b/packages/llm/llm-deepseek/tests/listed-catalog.spec.ts
@@ -1,9 +1,10 @@
/** Catalog resolution: version gating, this build's model knowledge, and model overrides. */
import { describe, expect, it } from 'vitest'
-import { DEFAULT_REQUEST_IMAGE_MAX_BYTES, resolveAdapterOptions } from '@deepseek-ai/dsh-llm-deepseek'
-import type { ModelOverride } from '../src/config.ts'
+import { resolveAdapterOptions } from '../src/config.ts'
+import { resolveListedCatalog } from '../src/listed-catalog.ts'
+import type { ModelOverride } from '../src/listed-catalog.ts'
import { parseModelListing } from '../src/listing.ts'
-import { resolveAccountCatalog } from '../src/resolve-catalog.ts'
+import { DEFAULT_REQUEST_IMAGE_MAX_BYTES } from '../src/request-pricing.ts'
const FLASH = {
id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash', context_window: 1_048_576, max_output_tokens: 393_216,
@@ -16,7 +17,7 @@ const PRO = {
api_capabilities: { anthropic_messages: { system_prompt_update: 'leading-only' } },
}
const requires = (version: unknown) => ({ client_requirements: { 'deepseek-harness': { min_version: version } } })
-const resolve = (rows: object[] | undefined, overrides: Record = {}) => resolveAccountCatalog({
+const resolve = (rows: object[] | undefined, overrides: Record = {}) => resolveListedCatalog({
listing: rows === undefined ? undefined : parseModelListing({ data: rows }).entries,
supported: resolveAdapterOptions({}).models,
overrides,
@@ -28,7 +29,7 @@ describe('without a listing', () => {
it('offers the bundled catalog', () => {
const catalog = resolve(undefined)
expect(catalog.offered.map(model => [model.id, model.name, model.contextWindow])).toEqual([
- ['deepseek-flash', 'DeepSeek-V4.1-Flash', 1_000_000], ['deepseek-v4-pro', 'DeepSeek-V4-Pro', 1_000_000],
+ ['deepseek-flash', 'DeepSeek-V4.1-Flash', 1_048_576], ['deepseek-v4-pro', 'DeepSeek-V4-Pro', 1_048_576],
])
expect(catalog.resolution).toEqual(catalog.offered)
expect(catalog.refusals.size).toBe(0)
@@ -56,7 +57,7 @@ describe('with a listing', () => {
it('keeps this build\'s values for fields a listing omits', () => {
const [pro] = resolve([{ id: 'deepseek-v4-pro', api_capabilities: { anthropic_messages: {} } }]).offered
- expect(pro).toMatchObject({ name: 'DeepSeek-V4-Pro', contextWindow: 1_000_000, inputModalities: ['text'] })
+ expect(pro).toMatchObject({ name: 'DeepSeek-V4-Pro', contextWindow: 1_048_576, inputModalities: ['text'] })
expect(pro).not.toHaveProperty('maxTokens')
})
@@ -74,6 +75,28 @@ describe('with a listing', () => {
expect(flash).not.toHaveProperty('systemPromptUpdate')
})
+ it.each(['in-history', 'addition-only'])('takes a listed %s tool update mode over this build\'s', (toolUpdate) => {
+ const [flash] = resolve([{ ...FLASH, api_capabilities: { anthropic_messages: { system_prompt_update: 'in-history', tool_update: toolUpdate } } }]).offered
+ expect(flash?.toolUpdate).toBe(toolUpdate)
+ })
+
+ it('removes an inherited tool update mode the listing declares unsupported', () => {
+ const [flash] = resolve([{ ...FLASH, api_capabilities: { anthropic_messages: { system_prompt_update: 'in-history', tool_update: 'leading-only' } } }]).offered
+ expect(flash).not.toHaveProperty('toolUpdate')
+ expect(flash?.systemPromptUpdate).toBe('in-history')
+ })
+
+ it('keeps this build\'s protocol modes when the listing declares them null', () => {
+ const undeclared = { system_prompt_update: null, tool_update: null }
+ const [flash] = resolve([{ ...FLASH, api_capabilities: { anthropic_messages: undeclared } }]).offered
+ expect(flash).toMatchObject({ systemPromptUpdate: 'in-history', toolUpdate: 'addition-only' })
+ })
+
+ it('declares a listed tool update mode on a vouched unknown model', () => {
+ const [model] = resolve([{ ...FLASH, id: 'deepseek-v5', ...requires('0.1.0'), api_capabilities: { anthropic_messages: { tool_update: 'in-history' } } }]).offered
+ expect(model?.toolUpdate).toBe('in-history')
+ })
+
it('drops inherited image limits from a model the listing makes text-only', () => {
const [flash] = resolve([{ ...FLASH, input_modalities: ['text'] }]).offered
expect(flash).toMatchObject({ inputModalities: ['text'] })
@@ -136,7 +159,7 @@ describe('with a listing', () => {
expect(catalog.offered[0]).not.toHaveProperty('imageMaxBytes')
expect(catalog.overrideErrors).toEqual([
expect.stringMatching(/^modelOverrides\.deepseek-v4-pro does not apply \(.*cannot declare image request limits/),
- 'modelOverrides names "retired", which neither the account listing nor the built-in catalog describes',
+ 'modelOverrides names "retired", which neither the model listing nor the built-in catalog describes',
])
})
})
diff --git a/packages/llm/llm-deepseek-account/tests/catalog.spec.ts b/packages/llm/llm-deepseek/tests/listing-cache.spec.ts
similarity index 64%
rename from packages/llm/llm-deepseek-account/tests/catalog.spec.ts
rename to packages/llm/llm-deepseek/tests/listing-cache.spec.ts
index f83519d0d2..fe2c9e5e74 100644
--- a/packages/llm/llm-deepseek-account/tests/catalog.spec.ts
+++ b/packages/llm/llm-deepseek/tests/listing-cache.spec.ts
@@ -1,7 +1,8 @@
/** Credential-scoped listing cache: first-use waits, background refresh, retention, and disposal. */
import { expect, it, vi } from 'vitest'
import { timeoutOf } from '@deepseek-ai/dsh-timeout'
-import { AccountModelCatalog } from '../src/catalog.ts'
+import { ModelListingCache } from '../src/listing-cache.ts'
+import type { ListingSettings } from '../src/listing-config.ts'
import { LISTING_TIMEOUT } from '../src/listing.ts'
import type { ModelListing } from '../src/listing.ts'
@@ -16,9 +17,9 @@ function harness(timeoutMs = 60_000, honorsAbort = true) {
const loads: Array<{ token: string; signal: AbortSignal; settle: PromiseWithResolvers }> = []
const info = vi.fn()
const warn = vi.fn()
- const catalog = new AccountModelCatalog({
- settings: () => ({ url: 'https://api.example/models', timeoutMs, maxAgeMs: 100 }),
- token: () => Promise.resolve(token),
+ const settings: ListingSettings = { url: 'https://api.example/models', timeoutMs, maxAgeMs: 100 }
+ const cache = new ModelListingCache({
+ credential: () => Promise.resolve(token),
load: (_url, value, signal) => {
const settle = Promise.withResolvers()
if (honorsAbort) signal.addEventListener('abort', () => { settle.reject(signal.reason) }, { once: true })
@@ -30,17 +31,24 @@ function harness(timeoutMs = 60_000, honorsAbort = true) {
warn,
})
const flush = () => new Promise(resolve => setImmediate(resolve))
+ // Every access passes the same configuration generation.
+ const catalog = {
+ view: (signal?: AbortSignal) => cache.view(settings, signal),
+ current: () => cache.current(),
+ invalidate: () => { cache.invalidate() },
+ dispose: () => { cache.dispose() },
+ }
return {
catalog, loads, info, warn, flush,
advance: (ms: number) => { clock += ms },
- account: (value: string | undefined) => { token = value },
+ credential: (value: string | undefined) => { token = value },
}
}
-it('reports a signed-out view without loading', async () => {
- const { catalog, loads, account } = harness()
- account(undefined)
- await expect(catalog.view()).resolves.toEqual({ signedIn: false, listing: undefined })
+it('reports an unauthenticated view without loading', async () => {
+ const { catalog, loads, credential } = harness()
+ credential(undefined)
+ await expect(catalog.view()).resolves.toEqual({ authenticated: false, listing: undefined })
expect(loads).toEqual([])
})
@@ -51,15 +59,15 @@ it('shares one first load between concurrent callers', async () => {
await flush()
expect(loads).toHaveLength(1)
loads[0]!.settle.resolve(listing('a'))
- await expect(first).resolves.toEqual({ signedIn: true, listing: listing('a') })
- await expect(second).resolves.toEqual({ signedIn: true, listing: listing('a') })
- expect(catalog.current()).toEqual({ signedIn: true, listing: listing('a') })
+ await expect(first).resolves.toEqual({ authenticated: true, listing: listing('a') })
+ await expect(second).resolves.toEqual({ authenticated: true, listing: listing('a') })
+ expect(catalog.current()).toEqual({ authenticated: true, listing: listing('a') })
expect(info).toHaveBeenCalledExactlyOnceWith('model listing from https://api.example/models loaded 1 model(s), skipped 0 row(s)')
})
it('bounds the first wait by the request deadline and serves no listing after a failure', async () => {
const { catalog, loads, warn } = harness(5)
- await expect(catalog.view()).resolves.toEqual({ signedIn: true, listing: undefined })
+ await expect(catalog.view()).resolves.toEqual({ authenticated: true, listing: undefined })
expect(timeoutOf(loads[0]!.signal, LISTING_TIMEOUT)?.timeoutMs).toBe(5)
expect(warn).toHaveBeenCalledExactlyOnceWith(expect.stringContaining('using the bundled catalog'))
})
@@ -114,11 +122,11 @@ it('refreshes a stale listing in the background and keeps it through a failing s
expect(warn).toHaveBeenCalledTimes(2)
})
-it('rescopes on another account and discards the previous account\'s result', async () => {
- const { catalog, loads, flush, account } = harness()
+it('rescopes on another credential and discards the previous credential\'s result', async () => {
+ const { catalog, loads, flush, credential } = harness()
const first = catalog.view()
await flush()
- account('token-b')
+ credential('token-b')
const second = catalog.view()
await flush()
expect(loads[0]!.signal.aborted).toBe(true)
@@ -127,8 +135,8 @@ it('rescopes on another account and discards the previous account\'s result', as
await expect(second).resolves.toMatchObject({ listing: listing('b') })
await first
expect(catalog.current().listing).toEqual(listing('b'))
- account(undefined)
- await expect(catalog.view()).resolves.toEqual({ signedIn: false, listing: undefined })
+ credential(undefined)
+ await expect(catalog.view()).resolves.toEqual({ authenticated: false, listing: undefined })
})
it('aborts in-flight work on dispose and loads nothing afterwards', async () => {
@@ -138,7 +146,7 @@ it('aborts in-flight work on dispose and loads nothing afterwards', async () =>
catalog.dispose()
expect(loads[0]!.signal.aborted).toBe(true)
await waiting
- await expect(catalog.view()).resolves.toEqual({ signedIn: true, listing: undefined })
+ await expect(catalog.view()).resolves.toEqual({ authenticated: true, listing: undefined })
expect(loads).toHaveLength(1)
expect(warn).not.toHaveBeenCalled()
})
@@ -152,3 +160,32 @@ it('drops a successful result that settles after disposal', async () => {
await waiting
expect(catalog.current().listing).toBeUndefined()
})
+
+it('discards the listing on invalidation, so the next access waits for a fresh load', async () => {
+ const { catalog, loads, flush } = harness()
+ const first = catalog.view()
+ await flush()
+ loads[0]!.settle.resolve(listing('a'))
+ await first
+ await catalog.view()
+ expect(loads).toHaveLength(1)
+ catalog.invalidate()
+ expect(catalog.current()).toEqual({ authenticated: true, listing: undefined })
+ const second = catalog.view()
+ await flush()
+ expect(loads).toHaveLength(2)
+ loads[1]!.settle.resolve(listing('b'))
+ await expect(second).resolves.toEqual({ authenticated: true, listing: listing('b') })
+})
+
+it('aborts an in-flight load on invalidation and ignores its result', async () => {
+ const { catalog, loads, flush, warn } = harness(60_000, false)
+ const waiting = catalog.view()
+ await flush()
+ catalog.invalidate()
+ expect(loads[0]!.signal.aborted).toBe(true)
+ loads[0]!.settle.resolve(listing('stale'))
+ await waiting
+ expect(catalog.current().listing).toBeUndefined()
+ expect(warn).not.toHaveBeenCalled()
+})
diff --git a/packages/llm/llm-deepseek-account/tests/listing.spec.ts b/packages/llm/llm-deepseek/tests/listing.spec.ts
similarity index 90%
rename from packages/llm/llm-deepseek-account/tests/listing.spec.ts
rename to packages/llm/llm-deepseek/tests/listing.spec.ts
index 9f7c05b693..b6be3b19e4 100644
--- a/packages/llm/llm-deepseek-account/tests/listing.spec.ts
+++ b/packages/llm/llm-deepseek/tests/listing.spec.ts
@@ -7,7 +7,7 @@ import { deadline } from '@deepseek-ai/dsh-timeout'
import { LISTING_TIMEOUT, parseModelListing, requestModelListing } from '../src/listing.ts'
import type { ListedCapabilities } from '../src/listing.ts'
-/** The account listing the service returned on 2026-09-24. */
+/** The listing the service returned to an account token on 2026-09-24. */
const LISTING = {
object: 'list',
data: [
@@ -92,6 +92,23 @@ describe('parseModelListing', () => {
})?.capabilities).toEqual({ usable: true, facts: { inputModalities: ['text'], systemPromptUpdate: 'leading-only' } })
expect(only(MESSAGES)?.capabilities).toEqual({ usable: true, facts: { inputModalities: ['text'] } })
})
+
+ it.each([
+ ['addition-only', 'addition-only'],
+ ['in-history', 'in-history'],
+ ['leading-only', 'leading-only'],
+ ['future-mode', 'leading-only'],
+ [false, 'leading-only'],
+ [1, 'leading-only'],
+ ])('reads tool_update %j as %s', (declared, toolUpdate) => {
+ expect(only({ api_capabilities: { anthropic_messages: { tool_update: declared } } })?.capabilities)
+ .toEqual({ usable: true, facts: { inputModalities: ['text'], toolUpdate } })
+ })
+
+ it('reads a null protocol mode as undeclared', () => {
+ expect(only({ api_capabilities: { anthropic_messages: { system_prompt_update: null, tool_update: null } } })?.capabilities)
+ .toEqual({ usable: true, facts: { inputModalities: ['text'] } })
+ })
})
describe('requestModelListing', () => {
diff --git a/packages/llm/llm-deepseek/tests/loader-composition.spec.ts b/packages/llm/llm-deepseek/tests/loader-composition.spec.ts
index 90db721296..01d54e98a5 100644
--- a/packages/llm/llm-deepseek/tests/loader-composition.spec.ts
+++ b/packages/llm/llm-deepseek/tests/loader-composition.spec.ts
@@ -37,6 +37,7 @@ afterEach(async () => {
root = undefined
await closeMockServers()
vi.unstubAllEnvs()
+ vi.restoreAllMocks()
})
async function loadComposition(
@@ -133,6 +134,8 @@ async function loadComposition(
describe('llm-deepseek real dynamic composition', () => {
it('updates the model catalog when the stored API key is removed or added', async () => {
vi.stubEnv('DEEPSEEK_API_KEY', '')
+ // The official endpoint's model listing is unavailable, so the route serves its built-in catalog offline.
+ vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response(null, { status: 404 })))
const { ctx } = await loadComposition({ withDynamic: true, baseURL: 'https://api.deepseek.com/anthropic' })
const credentials = ctx.get('credentials')!
const models = await ctx.llm.listModels('deepseek-official')
@@ -152,6 +155,33 @@ describe('llm-deepseek real dynamic composition', () => {
} finally { resolve.mockRestore() }
})
+ it('follows the official model listing and reloads it once the customized catalog is restored', async () => {
+ vi.stubEnv('DEEPSEEK_API_KEY', '')
+ vi.spyOn(console, 'error').mockImplementation(() => {})
+ const keys: Array = []
+ const messages = { api_capabilities: { anthropic_messages: {} } }
+ vi.spyOn(globalThis, 'fetch').mockImplementation((input, init) => {
+ const request = new Request(input, init)
+ if (request.url !== 'https://api.deepseek.com/models') return Promise.reject(new Error(`unexpected request to ${request.url}`))
+ keys.push(request.headers.get('x-api-key'))
+ return Promise.resolve(new Response(JSON.stringify({ data: [
+ { id: 'deepseek-flash', ...messages },
+ { id: 'deepseek-v5', ...messages, client_requirements: { 'deepseek-harness': { min_version: '0.0.1' } } },
+ ] })))
+ })
+ const { ctx } = await loadComposition({ withDynamic: true, baseURL: 'https://api.deepseek.com/anthropic' })
+ const ids = async () => (await ctx.llm.listModels('deepseek-official')).map(model => model.id)
+ expect(await ids()).toEqual(['deepseek-flash', 'deepseek-v5'])
+
+ // The Models page writes these path operations for an edited catalog and for Restore defaults.
+ await ctx.settings.mutate(NS, [{ op: 'set', path: ['models'], value: [{ id: 'custom' }] }])
+ expect(await ids()).toEqual(['custom'])
+ expect(keys).toEqual(['boot-key'])
+ await ctx.settings.mutate(NS, [{ op: 'unset', path: ['models'] }])
+ expect(await ids()).toEqual(['deepseek-flash', 'deepseek-v5'])
+ expect(keys).toEqual(['boot-key', 'boot-key'])
+ })
+
it('keeps package inventory on when the Loader composition disables session upload', async () => {
vi.stubEnv('DEEPSEEK_API_KEY', 'entry-key')
const server = await mockServer([{ kind: 'sse', events: textEvents }])
diff --git a/packages/llm/llm-deepseek/tests/official-route-turn.spec.ts b/packages/llm/llm-deepseek/tests/official-route-turn.spec.ts
new file mode 100644
index 0000000000..953401449b
--- /dev/null
+++ b/packages/llm/llm-deepseek/tests/official-route-turn.spec.ts
@@ -0,0 +1,41 @@
+/** Turns on the official endpoint report credential failures on the model request, after the prompt is recorded. */
+import { afterEach, expect, it, vi } from 'vitest'
+import { Context } from '@deepseek-ai/cordis'
+import LlmRuntime, { createUserMessage } from '@deepseek-ai/dsh-llm'
+import AgentRegistry from '@deepseek-ai/dsh-agent'
+import AgentLoop from '@deepseek-ai/dsh-agent-loop'
+import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
+import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
+import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
+import ToolRuntime from '@deepseek-ai/dsh-tools'
+import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native'
+import * as DeepSeek from '@deepseek-ai/dsh-llm-deepseek-api-key'
+
+const contexts: Context[] = []
+afterEach(async () => {
+ for (const ctx of contexts.splice(0)) await ctx.fiber.dispose()
+ vi.restoreAllMocks()
+ vi.unstubAllEnvs()
+})
+
+it('records the prompt and fails the request when the official endpoint has a malformed key', async () => {
+ vi.stubEnv('DEEPSEEK_API_KEY', 'invalid\nheader')
+ const fetch = vi.spyOn(globalThis, 'fetch')
+ const ctx = new Context()
+ contexts.push(ctx)
+ for (const plugin of [LlmRuntime, SessionStore, SessionProjectionRegistry, SystemPrompt, ToolRuntime, AgentRegistry]) {
+ await ctx.plugin(plugin)
+ }
+ await ctx.plugin(NativeToolExecutor)
+ await ctx.plugin(AgentLoop, { agents: [] })
+ await ctx.plugin(DeepSeek, {})
+ const agent = await ctx.agentLoop.create(SessionId('malformed-official-key'), { provider: 'deepseek-official', model: 'deepseek-flash' })
+ agent.followup(createUserMessage({ content: [{ type: 'text', text: 'hello' }], source: { kind: 'user' } }))
+ await agent.whenIdle()
+ const events = agent.session.snapshotEvents()
+ expect(events.map(event => event.type)).toContain('user/message')
+ expect(events.find(event => event.type === 'assistant/attempt')?.data).toMatchObject({
+ stream: [{ chunk: { type: 'finish', reason: { kind: 'error', failure: { code: 'INVALID_CREDENTIAL' } } } }],
+ })
+ expect(fetch).not.toHaveBeenCalled()
+})
diff --git a/packages/llm/llm-deepseek/tests/runtime.spec.ts b/packages/llm/llm-deepseek/tests/runtime.spec.ts
index 54cec3fe36..3c4676ba0d 100644
--- a/packages/llm/llm-deepseek/tests/runtime.spec.ts
+++ b/packages/llm/llm-deepseek/tests/runtime.spec.ts
@@ -1810,7 +1810,7 @@ describe('plugin registration and config', () => {
name: 'DeepSeek-V4.1-Flash',
inputModalities: ['text', 'image'],
systemPromptUpdate: 'in-history',
- context: { contextWindow: 1_000_000 },
+ context: { contextWindow: 1_048_576 },
defaultMaxTokens: 256_000,
reasoning: {
efforts: [
@@ -1832,7 +1832,7 @@ describe('plugin registration and config', () => {
expect(info).toMatchObject({
id: 'deepseek-v4-pro',
inputModalities: ['text'],
- context: { contextWindow: 1_000_000 },
+ context: { contextWindow: 1_048_576 },
defaultMaxTokens: 256_000,
})
expect(info?.systemPromptUpdate).toBeUndefined()
diff --git a/packages/sdk/server/tests/server.spec.ts b/packages/sdk/server/tests/server.spec.ts
index f5d4e785cd..36258f2d62 100644
--- a/packages/sdk/server/tests/server.spec.ts
+++ b/packages/sdk/server/tests/server.spec.ts
@@ -1005,7 +1005,7 @@ describe('HarnessSdkJsonRpcServer', () => {
const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-existing-llm-'))
const ctx = await makeHarness(storageDir)
vi.stubEnv('DEEPSEEK_API_KEY', 'test-key')
- await ctx.plugin(LlmDeepSeek)
+ await ctx.plugin(LlmDeepSeek, { models: [{ id: 'preinstalled-model' }] })
try {
const server = new HarnessSdkJsonRpcServer(ctx, new FakeTransport())
const inspect = server as unknown as { hasAdapterFor(provider: string): boolean }
@@ -1155,6 +1155,8 @@ describe('HarnessSdkJsonRpcServer', () => {
const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-unsupported-reasoning-'))
const ctx = await makeHarness(storageDir)
vi.stubEnv('DEEPSEEK_API_KEY', 'test-key')
+ // A loopback endpoint keeps the fallback-mounted official route off the network; no request is sent.
+ vi.stubEnv('DEEPSEEK_BASE_URL', 'http://127.0.0.1:9/anthropic')
try {
const server = new HarnessSdkJsonRpcServer(ctx, new FakeTransport())
await expect(server.handleRequest('initialize', {
diff --git a/packages/session/working-directory/tests/admission.spec.ts b/packages/session/working-directory/tests/admission.spec.ts
index a91413c91c..ea09e87ed0 100644
--- a/packages/session/working-directory/tests/admission.spec.ts
+++ b/packages/session/working-directory/tests/admission.spec.ts
@@ -61,7 +61,7 @@ describe.each(['pre-step', 'prepareCall'] as const)('%s context changes', (phase
expect(body).toContain(`Current working directory: ${JSON.stringify(selected)}.`)
expect(body).not.toContain(`Current working directory: ${JSON.stringify(root)}.`)
expect(body.includes('Current DSH file policy: danger-full-access')).toBe(optional)
- expect(body.includes('Approval policy: ask.')).toBe(optional)
+ expect(body.includes('Approval prompts are enabled in this session')).toBe(optional)
expect(body).not.toContain('Current DSH file policy: read-only')
expect(body).not.toContain('Approval prompts are disabled')
expect(ctx.workingDirectory.get(agent.session)).toBe(selected)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c830ca4216..f35b3645ca 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12181,6 +12181,9 @@ importers:
eventsource-parser:
specifier: ^3.1.0
version: 3.1.0
+ semver:
+ specifier: ^7.8.5
+ version: 7.8.5
devDependencies:
'@deepseek-ai/cordis':
specifier: workspace:~
@@ -12272,21 +12275,18 @@ importers:
'@deepseek-ai/dsh-tools':
specifier: workspace:*
version: link:../../core/tools
+ '@types/semver':
+ specifier: ^7.8.0
+ version: 7.8.0
packages/llm/llm-deepseek-account:
dependencies:
'@deepseek-ai/cosmokit':
specifier: link:../../../vendor/cosmokit
version: link:../../../vendor/cosmokit
- '@deepseek-ai/dsh-util-values':
- specifier: workspace:*
- version: link:../../util/values
'@deepseek-ai/schemastery':
specifier: link:../../../vendor/schemastery
version: link:../../../vendor/schemastery
- semver:
- specifier: ^7.8.5
- version: 7.8.5
devDependencies:
'@deepseek-ai/cordis':
specifier: workspace:~
@@ -12312,9 +12312,6 @@ importers:
'@deepseek-ai/dsh-timeout':
specifier: workspace:*
version: link:../../util/timeout
- '@types/semver':
- specifier: ^7.8.0
- version: 7.8.0
packages/llm/llm-deepseek-api-key:
dependencies:
diff --git a/scripts/no-unknown-casts.baseline.json b/scripts/no-unknown-casts.baseline.json
index a40a0e6812..0dbc93a074 100644
--- a/scripts/no-unknown-casts.baseline.json
+++ b/scripts/no-unknown-casts.baseline.json
@@ -459,9 +459,6 @@
"packages/client/ui-commands/tests/browser-plugin.client.spec.ts": {
"04984a2ce1486250236e638e0197da0a1a840855ed3975c59130a85358c7e5fe": 1
},
- "packages/client/ui-conversation/src/client/apply.ts": {
- "4c73f40f3ce989f285848573d7154fb295886bba707ad829e2c8c089e326aeea": 1
- },
"packages/client/ui-conversation/src/client/conversation/location-index.ts": {
"a7d8cc8d42e37ad5cc4882241492ac572ed8163c7fb8663180e893c7178e906f": 1
},
diff --git a/scripts/test-official-listing-guard.spec.ts b/scripts/test-official-listing-guard.spec.ts
new file mode 100644
index 0000000000..7345d3b5d8
--- /dev/null
+++ b/scripts/test-official-listing-guard.spec.ts
@@ -0,0 +1,26 @@
+/** Unit suites cannot reach the official DeepSeek model listing unless they answer `fetch` themselves. */
+import { createServer } from 'node:http'
+import { afterEach, expect, it } from 'vitest'
+import type { Server } from 'node:http'
+
+const servers: Server[] = []
+afterEach(async () => {
+ await Promise.all(servers.splice(0).map(server => new Promise(resolve => server.close(resolve))))
+})
+
+it.each([
+ ['string', () => fetch('https://api.deepseek.com/models')],
+ ['URL', () => fetch(new URL('https://api.deepseek.com/models'))],
+ ['Request', () => fetch(new Request('https://api.deepseek.com/models', { headers: { 'x-api-key': 'fixture-key' } }))],
+])('rejects the official model listing given as a %s', async (_kind, request) => {
+ await expect(request()).rejects.toThrow('unit suites do not reach https://api.deepseek.com/models; answer it with a fetch stub')
+})
+
+it('passes every other request to the ambient fetch', async () => {
+ const server = createServer((_request, response) => { response.end('local') })
+ servers.push(server)
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
+ const address = server.address()
+ if (address === null || typeof address === 'string') throw new Error('no port')
+ await expect((await fetch(`http://127.0.0.1:${address.port}/models`)).text()).resolves.toBe('local')
+})
diff --git a/scripts/test-official-listing-guard.ts b/scripts/test-official-listing-guard.ts
new file mode 100644
index 0000000000..bd8bc82f61
--- /dev/null
+++ b/scripts/test-official-listing-guard.ts
@@ -0,0 +1,22 @@
+/**
+ * Keep unit suites from requesting the official DeepSeek model listing.
+ *
+ * The API-key and account routes request `GET https://api.deepseek.com/models` whenever they hold a
+ * credential for the official endpoint, and suites routinely boot those routes with a dummy key or
+ * inherit a developer's real one. Such a request would reach production and, on a network that never
+ * answers, hold the route's first catalog access for its whole listing deadline. Here it fails at once,
+ * so the route serves its built-in catalog; a suite that exercises the listing replaces `fetch` itself.
+ * @module
+ */
+
+/** The listing endpoint of the official DeepSeek API. */
+const OFFICIAL_LISTING = 'https://api.deepseek.com/models'
+
+const ambientFetch = globalThis.fetch
+globalThis.fetch = (input, init) => {
+ const url = input instanceof Request ? input.url : input instanceof URL ? input.href : input
+ if (url === OFFICIAL_LISTING) {
+ return Promise.reject(new TypeError(`unit suites do not reach ${OFFICIAL_LISTING}; answer it with a fetch stub`))
+ }
+ return ambientFetch(input, init)
+}
diff --git a/snapshots/acp/escalation-approved/session.v4.jsonl b/snapshots/acp/escalation-approved/session.v4.jsonl
index 4a8b4d056b..592968765e 100644
--- a/snapshots/acp/escalation-approved/session.v4.jsonl
+++ b/snapshots/acp/escalation-approved/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"The sandbox already denied writing /tmp/dsh-escalated.txt earlier (it is outside this workspace). Retry it now exactly once: one single bash call with the command printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt, with sandbox_permissions set to danger-full-access and the justification 'the user asked to write a file outside the workspace'. Do not run it without sandbox_permissions first. I will approve the permission prompt. After the result, reply with the single word DONE and stop."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"The sandbox already denied writing","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/acp/escalation-rejected/session.v4.jsonl b/snapshots/acp/escalation-rejected/session.v4.jsonl
index cc0bf7b249..cff0c68e6c 100644
--- a/snapshots/acp/escalation-rejected/session.v4.jsonl
+++ b/snapshots/acp/escalation-rejected/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"The sandbox already denied writing /tmp/dsh-escalated.txt earlier (it is outside this workspace). Retry it now exactly once: one single bash call with the command printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt, with sandbox_permissions set to danger-full-access and the justification 'the user asked to write a file outside the workspace'. Do not run it without sandbox_permissions first. I will reject the permission prompt; after the rejection, do not retry and do not work around it — explain in one short sentence and stop."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"The sandbox already denied writing","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/acp/fs-escalation-approved/session.v4.jsonl b/snapshots/acp/fs-escalation-approved/session.v4.jsonl
index 9dbd7c6564..c074d5b397 100644
--- a/snapshots/acp/fs-escalation-approved/session.v4.jsonl
+++ b/snapshots/acp/fs-escalation-approved/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the write tool (NOT bash) to create escalated.md in the current directory containing exactly the single line: escalated. An equivalent write was denied earlier, so make this one single write call with sandbox_permissions set to danger-full-access and the justification 'the user asked to escalate this write'. Do not call write without sandbox_permissions first. I will approve the permission prompt. After the result, reply with exactly the single word DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Use the write tool (NOT","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/account-provider-signout/notifications.expected.jsonl b/snapshots/sdk/account-provider-signout/notifications.expected.jsonl
index 2c41004261..053f50ef6f 100644
--- a/snapshots/sdk/account-provider-signout/notifications.expected.jsonl
+++ b/snapshots/sdk/account-provider-signout/notifications.expected.jsonl
@@ -5,7 +5,7 @@
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly: SDK snapshot OK"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-account","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"max"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"tools":"{{tools}}"},"reason":"initial"}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-account","model":"deepseek-v4-flash","contextWindow":1000000}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Reply with exactly: SDK snapshot","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
diff --git a/snapshots/sdk/account-provider-signout/session.v4.jsonl b/snapshots/sdk/account-provider-signout/session.v4.jsonl
index 3570e94208..0a604caac4 100644
--- a/snapshots/sdk/account-provider-signout/session.v4.jsonl
+++ b/snapshots/sdk/account-provider-signout/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly: SDK snapshot OK"}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-account","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"max"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-account","model":"deepseek-v4-flash","contextWindow":1000000}}
{"type":"session/title","data":{"title":"Reply with exactly: SDK snapshot","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/approval/session.v4.jsonl b/snapshots/sdk/approval/session.v4.jsonl
index 8b6bccc5fc..2eca17324e 100644
--- a/snapshots/sdk/approval/session.v4.jsonl
+++ b/snapshots/sdk/approval/session.v4.jsonl
@@ -10,7 +10,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly the word: ONE. No tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Reply with exactly the word:","messageSeqs":[10],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/background-wait-steer/notifications.expected.jsonl b/snapshots/sdk/background-wait-steer/notifications.expected.jsonl
index da2c1c3da5..b95211d26a 100644
--- a/snapshots/sdk/background-wait-steer/notifications.expected.jsonl
+++ b/snapshots/sdk/background-wait-steer/notifications.expected.jsonl
@@ -5,7 +5,7 @@
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Wait for the barrier-controlled background job."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Wait for the barrier-controlled backgrou","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
diff --git a/snapshots/sdk/background-wait-steer/session.v4.jsonl b/snapshots/sdk/background-wait-steer/session.v4.jsonl
index ebdd1687ba..ed7d44dbc1 100644
--- a/snapshots/sdk/background-wait-steer/session.v4.jsonl
+++ b/snapshots/sdk/background-wait-steer/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Wait for the barrier-controlled background job."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Wait for the barrier-controlled backgrou","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/bash-tool/notifications.expected.jsonl b/snapshots/sdk/bash-tool/notifications.expected.jsonl
index 9a64ca16b8..fe0fc3a8f7 100644
--- a/snapshots/sdk/bash-tool/notifications.expected.jsonl
+++ b/snapshots/sdk/bash-tool/notifications.expected.jsonl
@@ -5,7 +5,7 @@
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Run this exact command with your bash tool, then reply with its stdout only: echo dsh-sdk-proof-7391"}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Run this exact command with","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
diff --git a/snapshots/sdk/bash-tool/session.v4.jsonl b/snapshots/sdk/bash-tool/session.v4.jsonl
index cf9bafc3fb..46666b2931 100644
--- a/snapshots/sdk/bash-tool/session.v4.jsonl
+++ b/snapshots/sdk/bash-tool/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Run this exact command with your bash tool, then reply with its stdout only: echo dsh-sdk-proof-7391"}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Run this exact command with","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/desktop-context/session.v4.jsonl b/snapshots/sdk/desktop-context/session.v4.jsonl
index d28b4cc849..fa35dc9aec 100644
--- a/snapshots/sdk/desktop-context/session.v4.jsonl
+++ b/snapshots/sdk/desktop-context/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly: SDK snapshot OK"}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: 2026-10-16T00:00:00+00:00[UTC]\nBrowser time zone for this request: unavailable. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: unavailable."}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: 2026-10-16T00:00:00+00:00[UTC]\nBrowser time zone for this request: unavailable. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: unavailable."}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
diff --git a/snapshots/sdk/desktop-ptc-context/session.v4.jsonl b/snapshots/sdk/desktop-ptc-context/session.v4.jsonl
index d28b4cc849..fa35dc9aec 100644
--- a/snapshots/sdk/desktop-ptc-context/session.v4.jsonl
+++ b/snapshots/sdk/desktop-ptc-context/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly: SDK snapshot OK"}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: 2026-10-16T00:00:00+00:00[UTC]\nBrowser time zone for this request: unavailable. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: unavailable."}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: 2026-10-16T00:00:00+00:00[UTC]\nBrowser time zone for this request: unavailable. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: unavailable."}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
diff --git a/snapshots/sdk/serial-created/session.v4.jsonl b/snapshots/sdk/serial-created/session.v4.jsonl
index 83f6e237af..1d0663f83b 100644
--- a/snapshots/sdk/serial-created/session.v4.jsonl
+++ b/snapshots/sdk/serial-created/session.v4.jsonl
@@ -11,7 +11,7 @@
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:3}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"id":"{{message:1}}","role":"user","content":[{"type":"text","text":"Serial agent creation completed."}],"source":{"kind":"plugin:serial-created-fixture"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly: SDK snapshot OK"}],"source":{"kind":"user"},"role":"user","id":"{{message:2}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Reply with exactly: SDK snapshot","messageSeqs":[11],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/subagent-dsh-sdk-diagnostic/notifications.expected.jsonl b/snapshots/sdk/subagent-dsh-sdk-diagnostic/notifications.expected.jsonl
index 96cd8efb6b..56d944708f 100644
--- a/snapshots/sdk/subagent-dsh-sdk-diagnostic/notifications.expected.jsonl
+++ b/snapshots/sdk/subagent-dsh-sdk-diagnostic/notifications.expected.jsonl
@@ -5,7 +5,7 @@
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Observe the DSH SDK diagnostic twice. Call subagent_dsh_sdk and subagent_dsh_sdk_second, then use their automatic failure notices to reply with exactly PARENT_OBSERVED_DSH_SDK_DIAGNOSTIC. Do not call any other tools."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Observe the DSH SDK diagnostic","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
diff --git a/snapshots/sdk/subagent-dsh-sdk-diagnostic/session.v4.jsonl b/snapshots/sdk/subagent-dsh-sdk-diagnostic/session.v4.jsonl
index 1b64a3f642..965d7a0829 100644
--- a/snapshots/sdk/subagent-dsh-sdk-diagnostic/session.v4.jsonl
+++ b/snapshots/sdk/subagent-dsh-sdk-diagnostic/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Observe the DSH SDK diagnostic twice. Call subagent_dsh_sdk and subagent_dsh_sdk_second, then use their automatic failure notices to reply with exactly PARENT_OBSERVED_DSH_SDK_DIAGNOSTIC. Do not call any other tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Observe the DSH SDK diagnostic","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/notifications.expected.jsonl b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/notifications.expected.jsonl
index 3d115de999..0a8aa43426 100644
--- a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/notifications.expected.jsonl
+++ b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/notifications.expected.jsonl
@@ -5,7 +5,7 @@
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":7,"time":0,"data":{"turn":1,"step":1}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":8,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Delegate once using the requested child route."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":10,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":10,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":11,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"mock-delegate"},"tools":"{{tools}}"},"reason":"initial"}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":12,"time":0,"data":{"provider":"deepseek-official","model":"mock-delegate"}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":13,"time":0,"data":{"title":"Delegate once using the requested","messageSeqs":[9],"source":{"kind":"fallback"}}}}}
diff --git a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/session.1.v4.jsonl b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/session.1.v4.jsonl
index 45b74313a5..587baf0870 100644
--- a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/session.1.v4.jsonl
+++ b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/session.1.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:9}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"report your route and workspace"}],"source":{"kind":"user"},"role":"user","id":"{{message:8}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:10}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:10}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"maxTokens":777,"provider":"mock","model":"mock-routed","reasoningEffort":"max"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"mock","model":"mock-routed"}}
{"type":"session/title","data":{"title":"report your route and workspace","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/session.v4.jsonl b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/session.v4.jsonl
index 528ef1fcf4..6a3190716b 100644
--- a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/session.v4.jsonl
+++ b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/session.v4.jsonl
@@ -9,7 +9,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Delegate once using the requested child route."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"mock-delegate"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"mock-delegate"}}
{"type":"session/title","data":{"title":"Delegate once using the requested","messageSeqs":[9],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/subagent-spawn-in-process/notifications.expected.jsonl b/snapshots/sdk/subagent-spawn-in-process/notifications.expected.jsonl
index b6fb517bdd..9da786f649 100644
--- a/snapshots/sdk/subagent-spawn-in-process/notifications.expected.jsonl
+++ b/snapshots/sdk/subagent-spawn-in-process/notifications.expected.jsonl
@@ -5,7 +5,7 @@
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Use the subagent tool exactly once with description 'echo probe' and prompt: Reply with exactly: child answer 42. Then reply with the subagent's final answer verbatim."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Use the subagent tool exactly","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
diff --git a/snapshots/sdk/subagent-spawn-in-process/session.v4.jsonl b/snapshots/sdk/subagent-spawn-in-process/session.v4.jsonl
index a30a5fc3ea..46d65edef5 100644
--- a/snapshots/sdk/subagent-spawn-in-process/session.v4.jsonl
+++ b/snapshots/sdk/subagent-spawn-in-process/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the subagent tool exactly once with description 'echo probe' and prompt: Reply with exactly: child answer 42. Then reply with the subagent's final answer verbatim."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Use the subagent tool exactly","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/text-turn/notifications.expected.jsonl b/snapshots/sdk/text-turn/notifications.expected.jsonl
index 93aee50296..9d1cccbcbf 100644
--- a/snapshots/sdk/text-turn/notifications.expected.jsonl
+++ b/snapshots/sdk/text-turn/notifications.expected.jsonl
@@ -5,7 +5,7 @@
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly: SDK snapshot OK"}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Reply with exactly: SDK snapshot","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
diff --git a/snapshots/sdk/text-turn/session.v4.jsonl b/snapshots/sdk/text-turn/session.v4.jsonl
index 98a26c2b50..2648df7d00 100644
--- a/snapshots/sdk/text-turn/session.v4.jsonl
+++ b/snapshots/sdk/text-turn/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly: SDK snapshot OK"}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Reply with exactly: SDK snapshot","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl b/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl
index f9340a370c..885df800f9 100644
--- a/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl
+++ b/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl
@@ -5,7 +5,7 @@
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Run three todo updates; the scheduler will reject the second call."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}
{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Run three todo updates; the","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
diff --git a/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl b/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl
index 830fa0f009..1eb28fe0c1 100644
--- a/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl
+++ b/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Run three todo updates; the scheduler will reject the second call."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Run three todo updates; the","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/background-confinement-failure/session.v4.jsonl b/snapshots/session/background-confinement-failure/session.v4.jsonl
index e6961e491c..f2bb00e94f 100644
--- a/snapshots/session/background-confinement-failure/session.v4.jsonl
+++ b/snapshots/session/background-confinement-failure/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Start printf SHOULD_NOT_RUN as one background Bash job, inspect that job with job_output, and report the asynchronous confinement failure."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Start printf SHOULD_NOT_RUN as one","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/deepseek-fetch/session.v4.jsonl b/snapshots/session/deepseek-fetch/session.v4.jsonl
index 867a951c38..bb95835661 100644
--- a/snapshots/session/deepseek-fetch/session.v4.jsonl
+++ b/snapshots/session/deepseek-fetch/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the web_fetch tool exactly once to fetch http://public.test:43117/menu.html, then reply with exactly DONE. Do not describe the content."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-pro"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-pro"}}
{"type":"session/title","data":{"title":"Use the web_fetch tool exactly","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/foreground-confinement-timeout/session.v4.jsonl b/snapshots/session/foreground-confinement-timeout/session.v4.jsonl
index 2f67c3866f..324b05328e 100644
--- a/snapshots/session/foreground-confinement-timeout/session.v4.jsonl
+++ b/snapshots/session/foreground-confinement-timeout/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Run printf SHOULD_NOT_RUN through the foreground bash tool with timeoutMs set to 10. Report the preparation timeout with PREPARATION_TIMED_OUT and stop."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Run printf SHOULD_NOT_RUN through the","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/missing-sandbox-runner/session.v4.jsonl b/snapshots/session/missing-sandbox-runner/session.v4.jsonl
index d228ffa00d..6172f1c5e9 100644
--- a/snapshots/session/missing-sandbox-runner/session.v4.jsonl
+++ b/snapshots/session/missing-sandbox-runner/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Run true once with bash in the foreground. After that fails, run true with bash in the background, read task bash-1 with job_output and wait=true, then reply with exactly RUNNER_FAILURES_SURFACED and stop."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Run true once with bash","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/partial-landlock-child-failure/session.v4.jsonl b/snapshots/session/partial-landlock-child-failure/session.v4.jsonl
index 818e64c1a7..4e7893a9ec 100644
--- a/snapshots/session/partial-landlock-child-failure/session.v4.jsonl
+++ b/snapshots/session/partial-landlock-child-failure/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the bash tool to run exactly: false. Then reply with exactly CHILD_EXIT_PRESERVED and stop."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/provider-cwd/session.v4.jsonl b/snapshots/session/provider-cwd/session.v4.jsonl
index 13409e3494..9d68e2eee6 100644
--- a/snapshots/session/provider-cwd/session.v4.jsonl
+++ b/snapshots/session/provider-cwd/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly the word: PONG. Do not use any tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Reply with exactly the word:","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/ptc-node-read-only/session.v4.jsonl b/snapshots/session/ptc-node-read-only/session.v4.jsonl
index 70068a6d08..8c1aec68b6 100644
--- a/snapshots/session/ptc-node-read-only/session.v4.jsonl
+++ b/snapshots/session/ptc-node-read-only/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use one run_code program to verify direct Node file access under the current policy, an empty Node environment, and the same workspace as bash."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Use one run_code program to","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/ptc-node-workspace/session.v4.jsonl b/snapshots/session/ptc-node-workspace/session.v4.jsonl
index 5c10e1a924..8371e07750 100644
--- a/snapshots/session/ptc-node-workspace/session.v4.jsonl
+++ b/snapshots/session/ptc-node-workspace/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use one run_code program to verify direct Node file access under the current policy, an empty Node environment, and the same workspace as bash."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Use one run_code program to","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/ptc-persistent/session.v4.jsonl b/snapshots/session/ptc-persistent/session.v4.jsonl
index 61e3043819..a202453724 100644
--- a/snapshots/session/ptc-persistent/session.v4.jsonl
+++ b/snapshots/session/ptc-persistent/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Retain a counter, imported module and tool method across run_code calls, then recover after the execution process exits. Save the observed counter and recovery in persistent-count.txt."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Retain a counter, imported module","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/ptc-policy-change/session.v4.jsonl b/snapshots/session/ptc-policy-change/session.v4.jsonl
index 45d4de63f2..65d58659e0 100644
--- a/snapshots/session/ptc-policy-change/session.v4.jsonl
+++ b/snapshots/session/ptc-policy-change/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Retain a counter, imported module and tool method across run_code calls, then recover after the execution process exits. Save the observed counter and recovery in persistent-count.txt."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Retain a counter, imported module","messageSeqs":[8],"source":{"kind":"fallback"}}}
@@ -20,7 +20,7 @@
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_wait"},"toolCallId":"call_wait","content":[{"type":"text","text":"Error: code run failed (abort): sandbox mode changed\nFile sandbox: workspace-write; enforcement: full."}],"isError":true,"id":"{{message:5}}"},"error":{"name":"CodeRunFailedError","code":"CODE_RUN_FAILED"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:6}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:6}}"},"surfaceOp":"append"}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_check","name":"run_code","arguments":"{\"description\":\"Verify the new file policy denies writes\",\"code\":\"const fileSystem = await import(\\\"node:fs/promises\\\"); let denied = false; try { await fileSystem.writeFile(\\\"denied.txt\\\", \\\"unexpected\\\") } catch (error) { denied = [\\\"EPERM\\\", \\\"EACCES\\\", \\\"EROFS\\\"].includes(error.code) }; console.log(JSON.stringify({ denied }));\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791529538136,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791529538136,"index":0,"dt":[],"id":"call_check","name":"run_code","args":["{\"description\":\"Verify the new file policy denies writes\",\"code\":\"const fileSystem = await import(\\\"node:fs/promises\\\"); let denied = false; try { await fileSystem.writeFile(\\\"denied.txt\\\", \\\"unexpected\\\") } catch (error) { denied = [\\\"EPERM\\\", \\\"EACCES\\\", \\\"EROFS\\\"].includes(error.code) }; console.log(JSON.stringify({ denied }));\"}"]},{"type":"chunk","time":1791529538136,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_check","name":"run_code","arguments":"{\"description\":\"Verify the new file policy denies writes\",\"code\":\"const fileSystem = await import(\\\"node:fs/promises\\\"); let denied = false; try { await fileSystem.writeFile(\\\"denied.txt\\\", \\\"unexpected\\\") } catch (error) { denied = [\\\"EPERM\\\", \\\"EACCES\\\", \\\"EROFS\\\"].includes(error.code) }; console.log(JSON.stringify({ denied }));\"}"}}},{"type":"chunk","time":1791529538136,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791529538136,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_check","name":"run_code","arguments":"{\"description\":\"Verify the new file policy denies writes\",\"code\":\"const fileSystem = await import(\\\"node:fs/promises\\\"); let denied = false; try { await fileSystem.writeFile(\\\"denied.txt\\\", \\\"unexpected\\\") } catch (error) { denied = [\\\"EPERM\\\", \\\"EACCES\\\", \\\"EROFS\\\"].includes(error.code) }; console.log(JSON.stringify({ denied }));\"}"}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_check"},"toolCallId":"call_check","content":[{"type":"text","text":"{\"denied\":true}"}],"isError":false,"id":"{{message:8}}"}},"sourceEventSeqs":[23],"surfaceOp":"append"}
diff --git a/snapshots/session/ptc-python-read-only/session.v4.jsonl b/snapshots/session/ptc-python-read-only/session.v4.jsonl
index b1e675052d..d2ae943fa0 100644
--- a/snapshots/session/ptc-python-read-only/session.v4.jsonl
+++ b/snapshots/session/ptc-python-read-only/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use one Python run_code program to verify direct Python file writes are denied under the current policy, host environment variables are unavailable, and Python uses the same workspace as bash."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Use one Python run_code program","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/session-sandbox-root/session.v4.jsonl b/snapshots/session/session-sandbox-root/session.v4.jsonl
index 6803af49f4..2a0f9e8664 100644
--- a/snapshots/session/session-sandbox-root/session.v4.jsonl
+++ b/snapshots/session/session-sandbox-root/session.v4.jsonl
@@ -11,7 +11,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the write tool (NOT bash) to create session-root.txt in the current directory containing exactly: session root. Then reply with exactly the single word DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Additional writable directories: [\"{{cwd}}/extra\"]. Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Additional writable directories: [\"{{cwd}}/extra\"]. Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Additional writable directories: [\"{{cwd}}/extra\"]. Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Additional writable directories: [\"{{cwd}}/extra\"]. Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Use the write tool (NOT","messageSeqs":[11],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/web-fetch/session.v4.jsonl b/snapshots/session/web-fetch/session.v4.jsonl
index 2cab7758b3..dfd6190cb9 100644
--- a/snapshots/session/web-fetch/session.v4.jsonl
+++ b/snapshots/session/web-fetch/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the web_fetch tool exactly once to fetch http://public.test:43117/menu.html, then reply with exactly DONE. Do not describe the content."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-pro"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-pro"}}
{"type":"session/title","data":{"title":"Use the web_fetch tool exactly","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/session/workflow-confinement/session.v4.jsonl b/snapshots/session/workflow-confinement/session.v4.jsonl
index ac666f1475..2a43a0d207 100644
--- a/snapshots/session/workflow-confinement/session.v4.jsonl
+++ b/snapshots/session/workflow-confinement/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the workflow tool exactly once to verify that the current read-only policy blocks direct Node filesystem writes. Do not start child agents. After the workflow returns {\"blocked\":true}, reply with WORKFLOW_CONFINEMENT_OK."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Use the workflow tool exactly","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/web/approval-composer/session.v4.jsonl b/snapshots/web/approval-composer/session.v4.jsonl
index fe3d1a423a..ec2bbc914a 100644
--- a/snapshots/web/approval-composer/session.v4.jsonl
+++ b/snapshots/web/approval-composer/session.v4.jsonl
@@ -12,7 +12,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Write a file named notes.txt in the workspace containing exactly this text on one line: tok63z tokc7y tokibx tokofw tokujv tok10nu tok16rt tok1cvs tok1izr tok1p3q tok1v7p tok21bo tok2a4 tok8e3 tokei2 tokkm1 tokqq0 tokwtz tok12xy tok191x tok1f5w tok1l9v tok1rdu tok1xht tok23ls tok4k8 tokao7 tokgs6 tokmw5 tokt04 tokz43 tok1582 tok1bc1 tok1hg0 tok1njz tok1tny tok1zrx tokqd tok6uc tokcyb tokj2a tokp69 tokva8 tok11e7 tok17i6 tok1dm5 tok1jq4 tok1pu3 tok1vy2 tok2221 tok30h tok94g tokf8f toklce tokrgd tokxkc tok13ob tok19sa tok1fw9 tok1m08 tok1s47 tok1y86 tok24c5 tok5al tokbek tokhij toknmi toktqh tokzug tok15yf tok1c2e tok1i6d tok1oac tok1ueb tok20ia tok1gq tok7kp tokdoo tokjsn tokpwm tokw0l tok124k tok188j tok1eci tok1kgh tok1qkg tok1wof tok22se tok3qu tok9ut tokfys tokm2r toks6q tokyap tok14eo tok1ain tok1gmm tok1mql tok1suk tok1yyj tok252i tok60y tokc4x toki8w tokocv tokugu tok10kt tok16os tok1csr tok1iwq tok1p0p tok1v4o tok218n tok273 tok8b2 tokef1 tokkj0 tokqmz tokwqy tok12ux tok18yw tok1f2v tok1l6u tok1rat tok1xes tok23ir tok4h7 tokal6 tokgp5 tokmt4 toksx3 tokz12 tok1551 tok1b90 tok1hcz tok1ngy tok1tkx tok1zow toknc tok6rb tokcva tokiz9 tokp38 tokv77 tok11b6 tok17f5 tok1dj4 tok1jn3 tok1pr2 tok1vv1 tok21z0 tok2xg tok91f tokf5e tokl9d tokrdc tokxhb tok13la tok19p9 tok1ft8 tok1lx7 tok1s16 tok1y55 tok2494 tok57k tokbbj tokhfi toknjh toktng tokzrf tok15ve tok1bzd tok1i3c tok1o7b tok1uba tok20f9 tok1dp tok7ho tokdln tokjpm tokptl tokvxk tok121j tok185i tok1e9h tok1kdg tok1qhf tok1wle tok22pd tok3nt tok9rs tokfvr toklzq toks3p toky7o tok14bn tok1afm tok1gjl tok1mnk tok1srj tok1yvi tok24zh tok5xx tokc1w toki5v toko9u tokudt tok10hs tok16lr tok1cpq tok1itp tok1oxo tok1v1n tok215m tok242 tok881 tokec0 tokkfz tokqjy tokwnx. Use one bash command with the literal text inline. Then reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/changed-files-turn/session.v4.jsonl b/snapshots/web/changed-files-turn/session.v4.jsonl
index a7b7d1848a..9fd913be08 100644
--- a/snapshots/web/changed-files-turn/session.v4.jsonl
+++ b/snapshots/web/changed-files-turn/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"不用先查看目录,直接做四件事:把 intro.md 里的标题「示例项目」改成「项目说明」,新建 src/util.ts 导出一个两数相加的 add 函数,新建 app.local 写一行 mode=demo,最后用 bash 在 notes.txt 末尾追加一行 done。"}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/code-language/session.v4.jsonl b/snapshots/web/code-language/session.v4.jsonl
index 4bf00d6892..84871b32e4 100644
--- a/snapshots/web/code-language/session.v4.jsonl
+++ b/snapshots/web/code-language/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the read tool exactly once per file to read sample.pyi, deploy.ps1 and table.csv in the current directory. Use these relative paths. Do not run commands or change files. Then reply exactly: Read all three files."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/cordis-tool-round/session.v3.jsonl b/snapshots/web/cordis-tool-round/session.v3.jsonl
index 5a7f451a93..2214395edc 100644
--- a/snapshots/web/cordis-tool-round/session.v3.jsonl
+++ b/snapshots/web/cordis-tool-round/session.v3.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use only Cordis tools. First call cordis_inspect_self with no arguments. Then call cordis_define with plugin kind \"new\", idPrefix \"snap\", name \"snapshot noop\", purpose \"does nothing, for the snapshot\", code.host exactly \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\" and code.client exactly \"return { inject: [\\\"slots\\\"], apply(ctx) { ctx.slots.register({ name: \\\"shell.overlay\\\", id: \\\"snapshot-probe\\\" }, () => React.createElement(\\\"div\\\", { \\\"data-snapshot-probe\\\": \\\"loaded\\\" })) } }\". Read its returned pluginId and packageId, then call cordis_run with those exact IDs and mode \"run\". After the run request returns, reply exactly CORDIS_UI_READY and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
{"type":"session/title","data":{"title":"Use only Cordis tools. First","messageSeqs":[8],"source":{"kind":"fallback"}}}
diff --git a/snapshots/web/creation-recommendations/session.v4.jsonl b/snapshots/web/creation-recommendations/session.v4.jsonl
index 4c92f8936c..8f53a26d68 100644
--- a/snapshots/web/creation-recommendations/session.v4.jsonl
+++ b/snapshots/web/creation-recommendations/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"I wish I had a small counter that stays visible between conversations."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"\nA skill is a reusable set of task-specific instructions. The following skills are available in this session:\n\n\n- `personalized-pages`: Plan an optional page or small tool when the user asks for a page, tab, panel, tool or interface in this app (even for one-off use), wants something to keep, revisit or update later, will keep changing data in your answer, or wants a live view of this app’s own data (Sessions, tasks, logs, workspace). Load this before reading any files or source; it says how to submit the proposal. Not for requested files or code, ordinary answers, existing-feature questions, quoted wishes or declined offers.\n \n\nIf the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.\nA user may also invoke a skill directly; its block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill.\n "}],"source":{"kind":"skill-catalog","form":"catalog","entries":[{"name":"personalized-pages","description":"Plan an optional page or small tool when the user asks for a page, tab, panel, tool or interface in this app (even for one-off use), wants something to keep, revisit or update later, will keep changing data in your answer, or wants a live view of this app’s own data (Sessions, tasks, logs, workspace). Load this before reading any files or source; it says how to submit the proposal. Not for requested files or code, ordinary answers, existing-feature questions, quoted wishes or declined offers."}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":["ask_user_question","bash","create_goal","edit","get_goal","glob","grep","interrupt_agent","job_kill","job_list","job_output","list_agents","list_subagent_models","plugin_manager","present","read","read_image","schedule_create","schedule_delete","schedule_list","schedule_update","send_message","skill","subagent","subagent_fork","todo_write","update_goal","web_fetch","web_search","workflow","working_directory","write"]},"reason":"initial"}}
diff --git a/snapshots/web/deepseek-messages-chat/session.v4.jsonl b/snapshots/web/deepseek-messages-chat/session.v4.jsonl
index ab6d612582..e1329c4586 100644
--- a/snapshots/web/deepseek-messages-chat/session.v4.jsonl
+++ b/snapshots/web/deepseek-messages-chat/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"只回复 MESSAGES_WEB_READY,不调用工具。"}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-messages","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-messages","model":"deepseek-v4-flash","contextWindow":1000000}}
diff --git a/snapshots/web/feedback-command/session.v4.jsonl b/snapshots/web/feedback-command/session.v4.jsonl
index 515cf0b681..efb011cc2c 100644
--- a/snapshots/web/feedback-command/session.v4.jsonl
+++ b/snapshots/web/feedback-command/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with the single word LIGHTHOUSE and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/file-upload-round/session.v4.jsonl b/snapshots/web/file-upload-round/session.v4.jsonl
index 051f46baf1..90b2668a21 100644
--- a/snapshots/web/file-upload-round/session.v4.jsonl
+++ b/snapshots/web/file-upload-round/session.v4.jsonl
@@ -9,7 +9,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"file","attachment":{"attachmentId":"sha256:29a0837077f6f3cbddd98c0605897cc6f2b4e5833fb8e1ca4a8045d89b2c8c60","name":"poem.txt","bytes":16}},{"type":"image","attachment":{"attachmentId":"sha256:b1ff9c8ea3a780bad09b346c423d2d0e46815926879b18e841d928376a946640","mediaType":"image/png","width":1,"height":1,"bytes":69,"name":"reference-1.png"}},{"type":"text","text":"Read the attached file with the read tool, reply with exactly the single word it contains, and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash-vision-exp","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash-vision-exp","contextWindow":1000000}}
diff --git a/snapshots/web/fresh-round-trip/session.v4.jsonl b/snapshots/web/fresh-round-trip/session.v4.jsonl
index ed4d0b7bfc..bbd92e9a73 100644
--- a/snapshots/web/fresh-round-trip/session.v4.jsonl
+++ b/snapshots/web/fresh-round-trip/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the bash tool to run exactly: echo WEB_E2E_OK. Then reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/goal-multi-turn-actions/session.v4.jsonl b/snapshots/web/goal-multi-turn-actions/session.v4.jsonl
index f391b15cd3..02afdb3b4c 100644
--- a/snapshots/web/goal-multi-turn-actions/session.v4.jsonl
+++ b/snapshots/web/goal-multi-turn-actions/session.v4.jsonl
@@ -11,7 +11,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"\nObjective: \"做两个turn,每个turn输出随机一个包的文件结构。注意你做完一个turn之后,直接输出内容,停止,我们的系统会帮你再开一个turn,你看着做一个类似的\"\nRound: 1/256\n\nContinue working toward the objective in this same session. Treat the current workspace, tool results, and durable session state as authoritative; inspect them instead of assuming earlier narration is still current. Make concrete progress and verify the result. Before claiming completion, gather evidence that the whole objective is achieved, read the current goal, and mark it complete. If work remains, leave the goal active for the next round. Follow the configured goal-tool policy before reporting a blocker.\n "}],"source":{"kind":"goal","goalId":"{{id:1}}","revision":1,"round":1},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial","startsSeries":true}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/lifecycle-chrome/session.v4.jsonl b/snapshots/web/lifecycle-chrome/session.v4.jsonl
index 598027d08a..45f72bc9e9 100644
--- a/snapshots/web/lifecycle-chrome/session.v4.jsonl
+++ b/snapshots/web/lifecycle-chrome/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with the single word LIGHTHOUSE and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/live-interactions/session.v4.jsonl b/snapshots/web/live-interactions/session.v4.jsonl
index 1980c673b0..2e1d8dc175 100644
--- a/snapshots/web/live-interactions/session.v4.jsonl
+++ b/snapshots/web/live-interactions/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with a one-sentence description of event sourcing, then stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/official-subagent-bundle/session.v4.jsonl b/snapshots/web/official-subagent-bundle/session.v4.jsonl
index 80558e93fd..810e2cb84d 100644
--- a/snapshots/web/official-subagent-bundle/session.v4.jsonl
+++ b/snapshots/web/official-subagent-bundle/session.v4.jsonl
@@ -9,7 +9,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Delegate a self-contained task to the official Claude Code subagent through its generated tools SDK in one run_code program. Ask it to reply with OFFICIAL_SUBAGENT_OK exactly, print its child ID, and wait for its completion notice before replying DONE."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/optional-string-editor/session.v4.jsonl b/snapshots/web/optional-string-editor/session.v4.jsonl
index 6d6ad99f96..cc6d89a87c 100644
--- a/snapshots/web/optional-string-editor/session.v4.jsonl
+++ b/snapshots/web/optional-string-editor/session.v4.jsonl
@@ -9,7 +9,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the optional string editor from the generated tools SDK in one run_code program to create edited.txt in the working directory containing exactly PTC_EDITOR_OK followed by a newline. Print the editor result, then reply DONE."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/permission-policy-context/session.v4.jsonl b/snapshots/web/permission-policy-context/session.v4.jsonl
index c5068a3295..ca83a0e2f1 100644
--- a/snapshots/web/permission-policy-context/session.v4.jsonl
+++ b/snapshots/web/permission-policy-context/session.v4.jsonl
@@ -12,7 +12,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Can you create or edit a normal file right now under the current policy? Answer directly in one sentence. Do not call a tool just to discover the policy."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
@@ -24,14 +24,14 @@
{"type":"permission/preset","data":{"preset":"danger-full-access"}}
{"type":"sandbox/mode","data":{"mode":"danger-full-access"}}
{"type":"approval/policy","data":{"policy":"never"}}
-{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"The approval policy changed from \"ask\" to \"never\" (changed by the user)."}],"source":{"kind":"user-approval"},"role":"user","id":"{{message:6}}"}]}}
+{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"The user changed the approval policy from \"ask the user for approval\" to \"never ask the user for approval\"."}],"source":{"kind":"user-approval"},"role":"user","id":"{{message:6}}"}]}}
{"type":"command/done","data":{"commandId":"{{command:2}}","kind":"success","text":"preset danger-full-access"}}
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Does the DSH file sandbox currently restrict file operations? Answer directly in one sentence. Do not call tools."}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:7}}"}]}}
{"type":"turn/start","data":{"turn":2}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","data":{"turn":2,"step":1}}
-{"type":"user/message","data":{"content":[{"type":"text","text":"The approval policy changed from \"ask\" to \"never\" (changed by the user)."}],"source":{"kind":"user-approval"},"role":"user","id":"{{message:6}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"The user changed the approval policy from \"ask the user for approval\" to \"never ask the user for approval\"."}],"source":{"kind":"user-approval"},"role":"user","id":"{{message:6}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Does the DSH file sandbox currently restrict file operations? Answer directly in one sentence. Do not call tools."}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:7}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:8}}"},"surfaceOp":"append"}
{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user is asking about the current DSH file sandbox restrictions. According to the current runtime context snapshot, the file policy is \"danger-full-access\" which means no restrictions on file operations. Also, approval prompts are disabled, so I should not request sandbox escalation.\n\nLet me answer directly in one sentence without calling tools."},{"type":"text","text":"No, the DSH file sandbox currently grants full access — it does not restrict file operations."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:9}}"},"usage":{"inputTokens":202,"outputTokens":87,"cacheReadTokens":14848,"reasoningTokens":66},"stream":[{"type":"chunk","time":1790389384815,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1790389384815,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," is"," asking"," about"," the"," current"," D","SH"," file"," sand","box"," restrictions","."," According"," to"," the"," current"," runtime"," context"," snapshot",","," the"," file"," policy"," is"," \"","danger","-full","-access","\""," which"," means"," no"," restrictions"," on"," file"," operations","."," Also",","," approval"," prompts"," are"," disabled",","," so"," I"," should"," not"," request"," sand","box"," escalation",".\n\n","Let"," me"," answer"," directly"," in"," one"," sentence"," without"," calling"," tools","."]},{"type":"chunk","time":1790389384816,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1790389384816,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["No",","," the"," D","SH"," file"," sand","box"," currently"," grants"," full"," access"," —"," it"," does"," not"," restrict"," file"," operations","."]},{"type":"chunk","time":1790389384816,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user is asking about the current DSH file sandbox restrictions. According to the current runtime context snapshot, the file policy is \"danger-full-access\" which means no restrictions on file operations. Also, approval prompts are disabled, so I should not request sandbox escalation.\n\nLet me answer directly in one sentence without calling tools."}}},{"type":"chunk","time":1790389384816,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"No, the DSH file sandbox currently grants full access — it does not restrict file operations."}}},{"type":"chunk","time":1790389384816,"chunk":{"type":"usage","usage":{"inputTokens":202,"outputTokens":87,"cacheReadTokens":14848,"reasoningTokens":66}}},{"type":"chunk","time":1790389384816,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
@@ -41,16 +41,16 @@
{"type":"permission/preset","data":{"preset":"workspace-write"}}
{"type":"sandbox/mode","data":{"mode":"workspace-write"}}
{"type":"approval/policy","data":{"policy":"ask"}}
-{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"The approval policy changed from \"never\" to \"ask\" (changed by the user)."}],"source":{"kind":"user-approval"},"role":"user","id":"{{message:10}}"}]}}
+{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"The user changed the approval policy from \"never ask the user for approval\" to \"ask the user for approval\"."}],"source":{"kind":"user-approval"},"role":"user","id":"{{message:10}}"}]}}
{"type":"command/done","data":{"commandId":"{{command:3}}","kind":"success","text":"preset workspace-write"}}
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly WORKSPACE_POLICY_SEEN. Do not call tools."}],"source":{"kind":"user","rpcId":"{{rpc:3}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:11}}"}]}}
{"type":"turn/start","data":{"turn":3}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","data":{"turn":3,"step":1}}
-{"type":"user/message","data":{"content":[{"type":"text","text":"The approval policy changed from \"never\" to \"ask\" (changed by the user)."}],"source":{"kind":"user-approval"},"role":"user","id":"{{message:10}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"The user changed the approval policy from \"never ask the user for approval\" to \"ask the user for approval\"."}],"source":{"kind":"user-approval"},"role":"user","id":"{{message:10}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly WORKSPACE_POLICY_SEEN. Do not call tools."}],"source":{"kind":"user","rpcId":"{{rpc:3}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:11}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:12}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:12}}"},"surfaceOp":"append"}
{"type":"assistant/message","data":{"turn":3,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly \"WORKSPACE_POLICY_SEEN\"."},{"type":"text","text":"WORKSPACE_POLICY_SEEN"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:13}}"},"usage":{"inputTokens":245,"outputTokens":27,"cacheReadTokens":14976,"reasoningTokens":18},"stream":[{"type":"chunk","time":1790389385287,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1790389385287,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," \"","WORK","SP","ACE","_P","OL","ICY","_SE","EN","\"."]},{"type":"chunk","time":1790389385287,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1790389385287,"index":1,"dt":[0,0,0,1,0,0,0],"texts":["WORK","SP","ACE","_P","OL","ICY","_SE","EN"]},{"type":"chunk","time":1790389385288,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly \"WORKSPACE_POLICY_SEEN\"."}}},{"type":"chunk","time":1790389385288,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"WORKSPACE_POLICY_SEEN"}}},{"type":"chunk","time":1790389385288,"chunk":{"type":"usage","usage":{"inputTokens":245,"outputTokens":27,"cacheReadTokens":14976,"reasoningTokens":18}}},{"type":"chunk","time":1790389385288,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":3,"step":1}}
{"type":"turn/end","data":{"turn":3,"reason":{"kind":"completed"}}}
@@ -63,7 +63,7 @@
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","data":{"turn":4,"step":1}}
{"type":"user/message","data":{"content":[{"type":"text","text":"Create the relative path policy-neutral.txt in the current workspace containing exactly POLICY_NEUTRAL_OK, verify its contents, then report completion."}],"source":{"kind":"user","rpcId":"{{rpc:4}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:14}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:15}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:15}}"},"surfaceOp":"append"}
{"type":"assistant/message","data":{"turn":4,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The current policy is read-only, so write tools will be denied. I should attempt it normally and follow the denial/ escalation guidance. Let me try to write the file."},{"type":"tool-call","id":"call_00_1SovMaD1sZjZwhmQuUCb7415","name":"write","arguments":"{\"file_path\": \"policy-neutral.txt\", \"content\": \"POLICY_NEUTRAL_OK\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:16}}"},"usage":{"inputTokens":265,"outputTokens":103,"cacheReadTokens":15104,"reasoningTokens":35},"stream":[{"type":"chunk","time":1790389386028,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1790389386028,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," current"," policy"," is"," read","-only",","," so"," write"," tools"," will"," be"," denied","."," I"," should"," attempt"," it"," normally"," and"," follow"," the"," denial","/"," escalation"," guidance","."," Let"," me"," try"," to"," write"," the"," file","."]},{"type":"chunk","time":1790389386028,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1790389386028,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0],"id":"call_00_1SovMaD1sZjZwhmQuUCb7415","name":"write","args":["","{","\"","file","_path","\"",": ","\"","policy","-neutral",".txt","\"",", ","\"","content","\"",": ","\"","POL","ICY","_","NE","UT","RAL","_OK","\"","}"]},{"type":"chunk","time":1790389386029,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The current policy is read-only, so write tools will be denied. I should attempt it normally and follow the denial/ escalation guidance. Let me try to write the file."}}},{"type":"chunk","time":1790389386029,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_1SovMaD1sZjZwhmQuUCb7415","name":"write","arguments":"{\"file_path\": \"policy-neutral.txt\", \"content\": \"POLICY_NEUTRAL_OK\"}"}}},{"type":"chunk","time":1790389386029,"chunk":{"type":"usage","usage":{"inputTokens":265,"outputTokens":103,"cacheReadTokens":15104,"reasoningTokens":35}}},{"type":"chunk","time":1790389386029,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":4,"step":1,"callId":"call_00_1SovMaD1sZjZwhmQuUCb7415","name":"write","arguments":"{\"file_path\": \"policy-neutral.txt\", \"content\": \"POLICY_NEUTRAL_OK\"}"}}
{"type":"tool/result","data":{"turn":4,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_1SovMaD1sZjZwhmQuUCb7415"},"toolCallId":"call_00_1SovMaD1sZjZwhmQuUCb7415","content":[{"type":"text","text":"Error: [sandbox: file access denied under read-only mode]\n[sandbox: escalation available — retry this exact operation once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]"}],"isError":true,"id":"{{message:17}}"},"error":{"name":"FsError","code":"FS_SANDBOX_DENIED"}},"sourceEventSeqs":[66],"surfaceOp":"append"}
diff --git a/snapshots/web/plan-review/session.v4.jsonl b/snapshots/web/plan-review/session.v4.jsonl
index ce244393f1..a825bc0102 100644
--- a/snapshots/web/plan-review/session.v4.jsonl
+++ b/snapshots/web/plan-review/session.v4.jsonl
@@ -11,7 +11,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Plan a small change: add a --greeting flag to a CLI. Do not read or write any files. Call exit_plan_mode with a short plan of at most five bullet points. Once the plan is approved, reply with the single word DONE and stop."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/present-svg/session.v4.jsonl b/snapshots/web/present-svg/session.v4.jsonl
index 8d69a45227..3c25b265f9 100644
--- a/snapshots/web/present-svg/session.v4.jsonl
+++ b/snapshots/web/present-svg/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"简单画一个 SVG 表示冯诺依曼架构, 保存为 von-neumann.svg"}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/present/session.v4.jsonl b/snapshots/web/present/session.v4.jsonl
index 20c168cd17..d500f73f05 100644
--- a/snapshots/web/present/session.v4.jsonl
+++ b/snapshots/web/present/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use one run_code program to do the following in order. Call present for missing.txt and catch its error without creating that file. Use bash to run exactly `printf \"DELIVERED_REPORT\\n\" > report.txt; printf \"DELIVERED_NOTE\\n\" > 说明.txt`. Call present for report.txt and 说明.txt. After present succeeds, deliberately throw the string \"AFTER_PRESENT\" (not an Error object) from that same run_code program. Do not retry the program or create any other files. Finish by mentioning `report.txt` and `说明.txt` in inline code, and put PRESENT_DONE in a separate paragraph."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/ptc-escalation-approved/session.v4.jsonl b/snapshots/web/ptc-escalation-approved/session.v4.jsonl
index 1fe54e5961..95c7db201d 100644
--- a/snapshots/web/ptc-escalation-approved/session.v4.jsonl
+++ b/snapshots/web/ptc-escalation-approved/session.v4.jsonl
@@ -12,7 +12,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use run_code with timeoutMs 120000 and direct Node filesystem access to create approved.txt in the working directory containing exactly \"approved\\n\". Use await import(\"node:fs/promises\") and writeFile; do not call nested tools. First attempt the write under the current read-only sandbox without escalation. In that first program, catch only filesystem errors with code EPERM, EACCES or EROFS and return exactly \"EXPECTED_SANDBOX_DENIAL\"; rethrow any other error. If the sandbox denies it, explicitly retry the program with sandbox_permissions \"workspace-write\" and justification \"Create the file requested by the user\". I will answer the approval prompt. After the file is written, reply DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/ptc-round/session.v4.jsonl b/snapshots/web/ptc-round/session.v4.jsonl
index 18ae5c5fa9..f7fdef7e47 100644
--- a/snapshots/web/ptc-round/session.v4.jsonl
+++ b/snapshots/web/ptc-round/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Using ONE run_code program: run bash `echo CODE_ROUND_OK`, then read the file missing.txt catching its error in the program. Return an object with both outcomes. Then reply DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/question-composer/session.v4.jsonl b/snapshots/web/question-composer/session.v4.jsonl
index 8572d51fe6..7c6dc2a624 100644
--- a/snapshots/web/question-composer/session.v4.jsonl
+++ b/snapshots/web/question-composer/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the ask_user_question tool to ask me exactly one multi-select question with id \"color\", question \"Which color do you prefer?\", header \"Pick one\", and two options: label \"Blue\" with description \"A cool recessive hue that reads as calm and trustworthy in long reading sessions and dense dashboards.\", and label \"Green\" with description \"A restful mid-spectrum hue with the highest perceived brightness, easiest on the eye over long sessions.\" Set multi_select to true. After I answer, reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/steering/session.v4.jsonl b/snapshots/web/steering/session.v4.jsonl
index d6c62a5c3e..c9fc4d3a5b 100644
--- a/snapshots/web/steering/session.v4.jsonl
+++ b/snapshots/web/steering/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the ask_user_question tool to ask me exactly one question with id \"checkpoint\", question \"Ready to continue?\", header \"Checkpoint\", and options labeled \"Yes\" and \"No\". After I answer, reply with one short sentence acknowledging my answer and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/subagent-fork-schedule/session.1.v4.jsonl b/snapshots/web/subagent-fork-schedule/session.1.v4.jsonl
index b09cd32916..2f31b7d195 100644
--- a/snapshots/web/subagent-fork-schedule/session.1.v4.jsonl
+++ b/snapshots/web/subagent-fork-schedule/session.1.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply PARENT_READY."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/subagent-fork-schedule/session.v4.jsonl b/snapshots/web/subagent-fork-schedule/session.v4.jsonl
index 710c5684d5..a1219fb8ab 100644
--- a/snapshots/web/subagent-fork-schedule/session.v4.jsonl
+++ b/snapshots/web/subagent-fork-schedule/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply PARENT_READY."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Ask the user to clarify otherwise-unqualified dates and times.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/turn-tail-actions/session.v4.jsonl b/snapshots/web/turn-tail-actions/session.v4.jsonl
index 4e5c1de5cc..27faca18e9 100644
--- a/snapshots/web/turn-tail-actions/session.v4.jsonl
+++ b/snapshots/web/turn-tail-actions/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Begin your reply with the plain sentence \"Reading the workspace now.\" as text, and in that same message call the bash tool with the command \"echo alpha\". After the tool result, reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/snapshots/web/web-search-round/session.v4.jsonl b/snapshots/web/web-search-round/session.v4.jsonl
index a6dccaac23..a7406cf400 100644
--- a/snapshots/web/web-search-round/session.v4.jsonl
+++ b/snapshots/web/web-search-round/session.v4.jsonl
@@ -8,7 +8,7 @@
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use web_search once with queries [\"DeepSeek Harness snapshot search\",\"DeepSeek Harness multi-query search\"]. Then reply exactly SEARCH_DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"{{clientTimeZone}}"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
+{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nCurrent DeepSeek Harness implementation checkout: \"{{sourceRoot}}\".\n\nCurrent DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"harness:source","text":"Current DeepSeek Harness implementation checkout: \"{{sourceRoot}}\"."},{"name":"app:web-surface","text":"Current DeepSeek Harness Web GUI URL: \"{{webUrl}}\"."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}],"source":{"kind":"time-context","form":"snapshot","sections":[{"name":"time-context","text":"Time sampled while preparing turn 1, step 1: {{timeContextTimestamp}}\nBrowser time zone for this request: {{clientTimeZone}}. Interpret otherwise-unqualified dates and times in this zone.\nElapsed since the preceding model-visible message: {{elapsed}}"}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
diff --git a/vitest.config.ts b/vitest.config.ts
index 356f836b96..87ead1a62a 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -182,7 +182,9 @@ export default defineConfig({
resolve: { alias: claudeCodeTestingAliases },
test: {
...localWorkerBudget,
- setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-dom-environment.ts'],
+ setupFiles: [
+ './scripts/test-proxy-environment.ts', './scripts/test-dom-environment.ts', './scripts/test-official-listing-guard.ts',
+ ],
// .tsx: client component specs (jsdom via per-file @vitest-environment pragma).
include: testIncludes,
exclude: platformUnsupportedTests,
@@ -200,7 +202,9 @@ export default defineConfig({
// Linux, and Windows. Forked workers avoid that shared thread path.
pool: 'forks',
...laneTestBudget,
- setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-dom-environment.ts'],
+ setupFiles: [
+ './scripts/test-proxy-environment.ts', './scripts/test-dom-environment.ts', './scripts/test-official-listing-guard.ts',
+ ],
include: testIncludes,
exclude: [
...platformUnsupportedTests,
@@ -217,7 +221,9 @@ export default defineConfig({
execArgv: vitestExecArgv,
pool: 'forks',
...laneTestBudget,
- setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-dom-environment.ts'],
+ setupFiles: [
+ './scripts/test-proxy-environment.ts', './scripts/test-dom-environment.ts', './scripts/test-official-listing-guard.ts',
+ ],
include: processBoundTests,
exclude: [
...platformUnsupportedTests,