diff --git a/apps/web/tests/present.e2e.ts b/apps/web/tests/present.e2e.ts index 802e5eed1d..7ccaa42b68 100644 --- a/apps/web/tests/present.e2e.ts +++ b/apps/web/tests/present.e2e.ts @@ -122,6 +122,7 @@ fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify({ path, action, con const row = page.locator('[data-presented-files-row]') await row.waitFor() expect(await row.getByRole('button').count()).toBe(4) + expect(await row.getByTitle(join(cwd, 'report.txt'), { exact: true }).innerText()).toBe('report.txt') const beforeReveal = (await opened()).length await row.getByRole('button', { name: 'More file actions for report.txt', exact: true }).click() const revealResponse = page.waitForResponse(response => response.url().includes('action=reveal') && response.request().method() === 'POST') diff --git a/packages/client/ui-deliverables/README.i18n.yaml b/packages/client/ui-deliverables/README.i18n.yaml index 654494e779..5303dce4bb 100644 --- a/packages/client/ui-deliverables/README.i18n.yaml +++ b/packages/client/ui-deliverables/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-deliverables/README.md -README.md: 3196202a053ae35bb3055666a680c9d6cfc2a2f8 -README.zh.md: bb394ae756820d7b536ea9326c0837cf59a85a01 +README.md: 3ff1f50999ee7efbf8c3e3c2e996d1ac208db5d4 +README.zh.md: 23b7ca1275c4a4315ef890627d745cf5157d0660 diff --git a/packages/client/ui-deliverables/README.md b/packages/client/ui-deliverables/README.md index 3196202a05..3ff1f50999 100644 --- a/packages/client/ui-deliverables/README.md +++ b/packages/client/ui-deliverables/README.md @@ -30,7 +30,7 @@ Mount this plugin alongside `ui-conversation`; a finished turn then ends with th ### Explicit deliveries -The Web `standard`, `ptc`, and `cordis` presets expose `present` for final workspace files, including files created through Bash. Call it with `files: [{ path, description? }]` after creating the files. The [present tool](../../fs/tool-present/README.md) owns file-count limits and Session declarations. The closing turn shows responsive cards with file names, types, descriptions, workspace paths, and a split Open button. Its primary action opens the source in the Host’s default application; the menu offers Show in Finder on macOS, Show in File Explorer on Windows and WSL, or Open containing folder through the default Linux file manager. The menu names the serving Host, independently of the browser’s operating system. Matching inline-code references open the same source files without starting a browser download. Repeated declaration of a path selects its latest description before the closing reply. +The Web `standard`, `ptc`, and `cordis` presets expose `present` for final workspace files, including files created through Bash. Call it with `files: [{ path, description? }]` after creating the files. The [present tool](../../fs/tool-present/README.md) owns file-count limits and Session declarations. The closing turn shows responsive cards with file names, types, descriptions, workspace-relative paths, and a split Open button. Hovering the path shows its full location. Its primary action opens the source in the Host’s default application; the menu offers Show in Finder on macOS, Show in File Explorer on Windows and WSL, or Open containing folder through the default Linux file manager. The menu names the serving Host, independently of the browser’s operating system. Matching inline-code references open the same source files without starting a browser download. Repeated declaration of a path selects its latest description before the closing reply. The `present` tool row shows running, delivered, failed, or interrupted status; expanding a settled row reveals its recorded result. File cards include every delivered file. Both actions share pending state and show progress, acknowledgement, or an action-specific retryable error. A missing desktop disables both actions; a failed desktop-information read offers Retry. It requires a desktop and a suitable default application on the serving Host; a remote browser does not open applications on its own device. diff --git a/packages/client/ui-deliverables/README.zh.md b/packages/client/ui-deliverables/README.zh.md index bb394ae756..23b7ca1275 100644 --- a/packages/client/ui-deliverables/README.zh.md +++ b/packages/client/ui-deliverables/README.zh.md @@ -30,7 +30,7 @@ kind: "package-reference" ### 显式交付 -Web 的 `standard`、`ptc` 与 `cordis` preset 提供 `present` 用于声明交付最终工作区文件,包括通过 Bash 创建的文件。创建文件后,以 `files: [{ path, description? }]` 调用。[present 工具](../../fs/tool-present/README.zh.md)拥有文件数量限制和 Session 声明。收尾 turn 显示响应式卡片,包含文件名称、类型、说明、工作区路径和分段“打开”按钮。主按钮在 Host 默认应用中打开源文件;菜单在 macOS 上提供“在 Finder 中显示”,在 Windows 和 WSL 上提供“在文件资源管理器中显示”,在 Linux 上通过默认文件管理器“打开所在文件夹”。菜单显示实际提供服务的 Host 名称,不依赖浏览器的操作系统。匹配的行内代码引用打开相同源文件,不触发浏览器下载。同一路径重复声明时,选择收尾回复之前最近一次的说明。 +Web 的 `standard`、`ptc` 与 `cordis` preset 提供 `present` 用于声明交付最终工作区文件,包括通过 Bash 创建的文件。创建文件后,以 `files: [{ path, description? }]` 调用。[present 工具](../../fs/tool-present/README.zh.md)拥有文件数量限制和 Session 声明。收尾 turn 显示响应式卡片,包含文件名称、类型、说明、工作区相对路径和分段“打开”按钮。悬停路径可查看完整位置。主按钮在 Host 默认应用中打开源文件;菜单在 macOS 上提供“在 Finder 中显示”,在 Windows 和 WSL 上提供“在文件资源管理器中显示”,在 Linux 上通过默认文件管理器“打开所在文件夹”。菜单显示实际提供服务的 Host 名称,不依赖浏览器的操作系统。匹配的行内代码引用打开相同源文件,不触发浏览器下载。同一路径重复声明时,选择收尾回复之前最近一次的说明。 `present` 工具行显示正在交付、已交付、失败或中断状态;展开已结束的调用可查看其记录的结果。文件卡片展示全部交付文件。两个操作共享等待状态,并显示进度、请求确认或各自可重试的错误。Host 没有桌面时禁用两个操作;桌面信息读取失败时提供“重试”。服务 Host 必须具备桌面和合适的默认应用;远程浏览器不会打开其所在设备上的应用。 diff --git a/packages/client/ui-deliverables/package.json b/packages/client/ui-deliverables/package.json index 67970786a0..ca55e77f16 100644 --- a/packages/client/ui-deliverables/package.json +++ b/packages/client/ui-deliverables/package.json @@ -67,7 +67,8 @@ "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-client-ui-tool": "workspace:^", "@deepseek-ai/dsh-tool-present": "workspace:^", - "@deepseek-ai/dsh-api-session-controller": "workspace:^" + "@deepseek-ai/dsh-api-session-controller": "workspace:^", + "@deepseek-ai/dsh-util-workspace-path": "workspace:^" }, "files": [ "lib/index.js", diff --git a/packages/client/ui-deliverables/src/client/Deliverables.tsx b/packages/client/ui-deliverables/src/client/Deliverables.tsx index 420741512a..ab83af8f4e 100644 --- a/packages/client/ui-deliverables/src/client/Deliverables.tsx +++ b/packages/client/ui-deliverables/src/client/Deliverables.tsx @@ -1,7 +1,7 @@ /** Existing changed-file chips and explicitly declared files for a closing turn. */ import type { TurnTailOwnerProps } from '@deepseek-ai/dsh-client-ui-chat/client' import { Button } from '@deepseek-ai/dsh-client-ui-primitives' -import type { InjectFace, PropsLocale, SessionStandardProps } from '@deepseek-ai/dsh-client-ui-slots' +import type { GlobalStandardProps, InjectFace, PropsLocale, SessionStandardProps } from '@deepseek-ai/dsh-client-ui-slots' import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store' import type { PresentedOpenController } from './present-open.ts' import { ProducedFiles } from './ProducedFiles.tsx' @@ -39,9 +39,10 @@ export function selectDeliverables(owner: TurnTailOwnerProps): DeliverablesMatch * @param props - matched files, workspace opener, and localized copy. * @returns the closing turn's file rows. */ -export function Deliverables({ matched, openFile, t, sessionId, openPresented, usePresentedOpen, usePresentedHost, reloadPresentedHost }: Pick & { +export function Deliverables({ matched, openFile, t, sessionId, useSessions, openPresented, usePresentedOpen, usePresentedHost, reloadPresentedHost }: Pick & { matched: DeliverablesMatch -} & PropsLocale & Pick & InjectFace) { +} & PropsLocale & Pick & Pick & InjectFace) { + const cwd = useSessions(state => state.byId[sessionId]?.cwd) const states = usePresentedOpen(value => value) const host = usePresentedHost(value => value) return <> @@ -57,7 +58,7 @@ export function Deliverables({ matched, openFile, t, sessionId, openPresented, u } {host !== null && host !== 'error' && !host.available && {t('presented.unavailable')}}
- {matched.presented.map(file => { void openPresented(sessionId, file.seq, file.index, action) }} />)} diff --git a/packages/client/ui-deliverables/src/client/PresentedFileCard.tsx b/packages/client/ui-deliverables/src/client/PresentedFileCard.tsx index d3af07034a..65db02e5ca 100644 --- a/packages/client/ui-deliverables/src/client/PresentedFileCard.tsx +++ b/packages/client/ui-deliverables/src/client/PresentedFileCard.tsx @@ -1,5 +1,6 @@ /** File identity and explicit default-app or file-manager actions for one delivery. */ import { useState } from 'react' +import { relativizeToCwd, resolveWorkspacePath } from '@deepseek-ai/dsh-util-workspace-path' import { Button, Menu, LinkIcon, classifyLinkPath, IconRightUpOutline16, IconChevronDownOutline14, IconFolderOpenOutline16, @@ -13,11 +14,12 @@ import css from './Deliverables.module.css' /** * Render independent file actions without nesting buttons inside a clickable card. - * @param props - durable file metadata, Host capabilities, gesture status, and localized copy. + * @param props - durable file metadata, Session workspace root, Host capabilities, gesture status, and localized copy. * @returns the file card and its anchored action menu. */ -export function PresentedFileCard({ file, phase, host, onAction, t }: { +export function PresentedFileCard({ file, cwd, phase, host, onAction, t }: { file: PresentedPath + cwd: string | undefined phase: PresentedOpenPhase | undefined host: PresentedHost | null onAction: (action: PresentedAction) => void @@ -42,7 +44,9 @@ export function PresentedFileCard({ file, phase, host, onAction, t }: {
- {file.path} + + {relativizeToCwd(file.path, cwd)} +