diff --git a/.agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.i18n.yaml b/.agents/notes/archived/feature/2026-09-08-web-explicit-file-delivery.i18n.yaml similarity index 68% rename from .agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.i18n.yaml rename to .agents/notes/archived/feature/2026-09-08-web-explicit-file-delivery.i18n.yaml index 0925919414..71c2bd9db9 100644 --- a/.agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.i18n.yaml +++ b/.agents/notes/archived/feature/2026-09-08-web-explicit-file-delivery.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 .agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.md -2026-09-08-web-explicit-file-delivery.md: 5ab211d8e8ea3c7f008cf39307f1ddfdd77acfee -2026-09-08-web-explicit-file-delivery.zh.md: 3077b23a78f0617a1a377bfc37de201169bbdc82 +2026-09-08-web-explicit-file-delivery.md: ff2ddeb59ded05b70006dce217df2966eab9d4f2 +2026-09-08-web-explicit-file-delivery.zh.md: 85b09ac82c83365b1c198168b78aa7ac84ef216f diff --git a/.agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.md b/.agents/notes/archived/feature/2026-09-08-web-explicit-file-delivery.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.md rename to .agents/notes/archived/feature/2026-09-08-web-explicit-file-delivery.md index 5ab211d8e8..ff2ddeb59d 100644 --- a/.agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.md +++ b/.agents/notes/archived/feature/2026-09-08-web-explicit-file-delivery.md @@ -1,6 +1,7 @@ # Agent Note: Web delivers explicit file snapshots Status: implemented +Archived: 2026-09-08 English | [中文](2026-09-08-web-explicit-file-delivery.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.zh.md b/.agents/notes/archived/feature/2026-09-08-web-explicit-file-delivery.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.zh.md rename to .agents/notes/archived/feature/2026-09-08-web-explicit-file-delivery.zh.md index 3077b23a78..85b09ac82c 100644 --- a/.agents/notes/implemented/feature/2026-09-08-web-explicit-file-delivery.zh.md +++ b/.agents/notes/archived/feature/2026-09-08-web-explicit-file-delivery.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web 显式交付文件快照 Status: implemented +Archived: 2026-09-08 [English](2026-09-08-web-explicit-file-delivery.md) | 中文 diff --git a/.agents/notes/archived/manifest.json b/.agents/notes/archived/manifest.json index f540320056..cded9f6c7e 100644 --- a/.agents/notes/archived/manifest.json +++ b/.agents/notes/archived/manifest.json @@ -1381,6 +1381,9 @@ "feature/2026-09-01-web-superellipse-corner-smoothing.i18n.yaml": "sha256:50afdbe5b5e19889918af6d86ab3218c05205be35938b6d33d158c60777e3b58", "feature/2026-09-01-web-superellipse-corner-smoothing.md": "sha256:b1445101c49e74bbcb4f607af850cd6df105d4034828d0dd47081e8079148f15", "feature/2026-09-01-web-superellipse-corner-smoothing.zh.md": "sha256:1a278c417c0d7de3b4c3c35061b419303b4a1a0707831c283d8f862ab9b6fd23", + "feature/2026-09-08-web-explicit-file-delivery.i18n.yaml": "sha256:99daae539cc8fd7376ce0265538bee21e1e33f3c0d77c8cc4e011f94b4e9568a", + "feature/2026-09-08-web-explicit-file-delivery.md": "sha256:bb416b1e8be081e6cb6af17792eb1a442172ff114c3a3577e5ef06a77eb57093", + "feature/2026-09-08-web-explicit-file-delivery.zh.md": "sha256:00a642380e1f6ac9d5cd840e021f4e3e4ae68a289cb6344eb3dcd73a6fd81f4d", "process/2026-06-11-doc-sync-enforcement.i18n.yaml": "sha256:33b6d5874427bd7a2bd82e7e2f4f482b12448b2464aef15a9c57975edb48554d", "process/2026-06-11-doc-sync-enforcement.md": "sha256:aa2fe83d519fc30d48dff19e596e83c8922aacc9e063e14fe2cc35b769b9100e", "process/2026-06-11-doc-sync-enforcement.zh.md": "sha256:698017bd35f030fdea3eac51df9e43138c48140f504739d687b7251d13fced2b", diff --git a/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.i18n.yaml b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.i18n.yaml new file mode 100644 index 0000000000..27af3c5438 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.md +2026-09-08-present-workspace-source-files.md: 6239c9bd920849f3c8cf4fdee2e1ded4b758b01d +2026-09-08-present-workspace-source-files.zh.md: 7aa5bebc97558b9b0cb74406303d038483c39d94 diff --git a/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.md b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.md new file mode 100644 index 0000000000..6239c9bd92 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.md @@ -0,0 +1,37 @@ +# Agent Note: Present declares workspace source files + +Status: implemented + +English | [中文](2026-09-08-present-workspace-source-files.zh.md) + +## Problem + +Users need to open and edit the files produced in their workspace, including shell-created files that have no editor mutation records. Preserving an independent delivered version adds content storage, copy verification, temporary-file retention, and a second editing destination to this workflow. + +## Decision + +The [present tool](../../../../packages/fs/tool-present/README.md) declares existing regular files inside the calling Session's workspace. It records paths and optional descriptions without reading or copying contents. The [deliverables plugin](../../../../packages/client/ui-deliverables/README.md) opens current workspace sources in the Host's default application. Edits are visible on the next open; deletion or movement makes the declaration unavailable. File-content preservation and copy-on-write storage are deferred until a persistence design owns them. + +The tool remains an ordinary package with shared filesystem and tool error classes. Its pure type entry owns the delivery event without importing Host code into the browser. The `standard`, `ptc`, and `cordis` presets mount it; `minimal` retains its two tools. Each plugin instance correlates its executions with successful final `tools/result` notifications before appending `deliverables/presented`. Native and nested calls share this rule. A later enclosing program failure does not revoke a completed nested declaration; blocked results publish none, and same-name scoped replacements cannot publish another instance's results. + +An authenticated POST selects a declaration by viewed Session, event sequence, and original file index. The event carries no owning Session ID; relative paths in inherited history resolve against the viewed Session's workspace. The Host rechecks canonical workspace containment and regular-file existence before native opening. Route disposal cancels and awaits pending commands. The existing produced-file row retains its separate text-preview behavior. + +## Alternatives considered + +**Immutable attachment snapshots and editable temporary copies** preserve delivered versions after source edits or deletion, but make desktop edits diverge from workspace files and introduce retention work without a current product requirement. This decision supersedes the [snapshot-delivery design](../../archived/feature/2026-09-08-web-explicit-file-delivery.md). Neither a download endpoint nor a fallback copy remains; both require an explicit future product decision. + +**Opening attachment-store files directly** lets editors mutate immutable objects. A future persistent delivery system needs an owned editing and retention policy, such as copy-on-write, before exposing saved versions to applications. + +**Generic artifact fields or a Host tool subpath inside the UI package** broaden unrelated APIs or couple preset installation to browser packaging. A tool-owned event and ordinary package preserve existing extension points and publication rules. + +**Tool text as the durable index** cannot survive post-processing or result spill reliably. Execution identity and final successful results retain declaration ownership independently of displayed tool text. + +**Descriptor-bound filesystem extensions** would change every provider without making an external desktop application's later path lookup atomic. Current checks reject ordinary escapes; concurrent swap-and-restore remains outside the path API's guarantees. + +## Consequences + +The Session log persists declarations but no attachment references or file contents from `present`. Session ZIP exports contain these declarations; transferring the log does not transfer workspace files. The event remains required-on-read because silently losing delivery declarations would alter reconstructed or forked history. Released Session format generations remain unchanged. + +The removed file-size cap has no role in a metadata-only declaration; the configurable file-count limit still bounds result size. Cards show file names, types, and descriptions without stale byte-size metadata. No artifact service or speculative storage fallback is introduced. + +Focused tests cover content-free declarations, invalid inputs, blocked results, source-path identity, current bytes after edits, missing files, workspace escapes, fork-relative paths, retry, cancellation, and disposal. The recorded Web scenario covers nested completion followed by enclosing failure, source edits, reload, deletion errors, card and prose opens without browser downloads, and content-free Session export. diff --git a/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.zh.md b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.zh.md new file mode 100644 index 0000000000..7aa5bebc97 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.zh.md @@ -0,0 +1,37 @@ +# Agent Note:Present 声明交付工作区源文件 + +Status: implemented + +[English](2026-09-08-present-workspace-source-files.md) | 中文 + +## 问题 + +用户需要打开并编辑工作区中产出的文件,包括没有编辑器修改记录的 shell 产出文件。保存独立交付版本会为这一流程增加内容存储、副本校验、临时文件保留,以及第二个编辑目标。 + +## 决策 + +[present 工具](../../../../packages/fs/tool-present/README.zh.md)声明交付调用方 Session 工作区中已存在的普通文件。它记录路径和可选说明,不读取或复制内容。[交付插件](../../../../packages/client/ui-deliverables/README.zh.md)使用 Host 默认应用打开当前工作区源文件。下次打开会看到编辑后的内容;删除或移动文件会使声明不可用。文件内容保留与写时复制存储延期到有持久化设计负责时实现。 + +工具保持为普通包,共享文件系统和工具错误类型。其纯类型入口拥有交付事件,不向浏览器导入 Host 代码。`standard`、`ptc` 与 `cordis` preset 挂载工具;`minimal` 保持两个工具。每个插件实例将其执行与成功的最终 `tools/result` 通知关联,再追加 `deliverables/presented`。原生与嵌套调用遵循同一规则。外层程序随后失败不会撤销已完成的嵌套声明;被阻止的结果不发布声明,同名作用域替换也不能发布其他实例的结果。 + +经过认证的 POST 按当前查看的 Session、事件序号和原始文件索引选择声明。事件不携带所属 Session ID;继承历史中的相对路径按当前查看的 Session 工作区解析。Host 在原生打开前重新检查规范路径的工作区包含关系和普通文件是否存在。路由释放时取消并等待进行中的命令。原有产出文件行保留独立的文本预览行为。 + +## 考虑过的替代方案 + +**不可变附件快照和可编辑临时副本**可在源文件编辑或删除后保留交付版本,但会使桌面编辑与工作区文件分离,并在缺少当前产品需求时引入保留工作。本决策取代[快照交付设计](../../archived/feature/2026-09-08-web-explicit-file-delivery.md)。不保留下载端点或回退副本;两者都需要未来明确的产品决策。 + +**直接打开附件存储文件**会让编辑器修改不可变对象。未来持久化交付系统需要先明确编辑和保留策略,例如写时复制,再将保存版本暴露给应用。 + +**通用 artifact 字段或 UI 包内的 Host 工具子路径**会扩展无关 API,或将 preset 安装与浏览器打包耦合。工具拥有的事件与普通包保留现有扩展点和发布规则。 + +**以工具文本作为持久索引**无法可靠应对后处理或结果溢出。执行身份与最终成功结果使声明归属独立于展示的工具文本。 + +**绑定文件描述符的文件系统扩展**会改动所有提供方,却无法使外部桌面应用随后按路径打开的动作原子化。当前检查拒绝普通越界;并发替换后复原仍不在路径 API 的保证范围内。 + +## 影响 + +Session 日志持久化声明,不保存来自 `present` 的附件引用或文件内容。Session ZIP 导出包含这些声明;转移日志不会转移工作区文件。该事件仍要求读取端识别,因为静默丢失交付声明会改变重建或 fork 的历史。已发布 Session 格式代际保持不变。 + +仅声明元数据不需要文件大小上限,因此删除该限制;可配置的文件数量上限仍限制结果大小。卡片展示文件名称、类型和说明,不展示可能过时的字节大小。不引入 artifact 服务或推测性的存储回退。 + +定向测试覆盖不读取内容的声明、无效输入、被阻止的结果、源路径身份、编辑后的当前字节、缺失文件、工作区越界、fork 相对路径、重试、取消与释放。录制的 Web 场景覆盖嵌套成功后外层失败、源文件编辑、重新加载、删除错误、卡片与正文打开且无浏览器下载,以及不包含交付内容的 Session 导出。 diff --git a/apps/web/tests/present.e2e.ts b/apps/web/tests/present.e2e.ts index 0f48cac2f2..dd55988dc2 100644 --- a/apps/web/tests/present.e2e.ts +++ b/apps/web/tests/present.e2e.ts @@ -1,5 +1,5 @@ -/** Recorded delivery, source deletion, reload, and Session ZIP behavior. */ -import { readFile, unlink, mkdir, mkdtemp, writeFile, rm } from 'node:fs/promises' +/** Recorded source-file delivery, edits, reload, deletion, and Session ZIP behavior. */ +import { readFile, unlink, mkdir, mkdtemp, writeFile, rm, realpath } from 'node:fs/promises' import { join, delimiter } from 'node:path' import { fileURLToPath } from 'node:url' import { chromium, type Browser, type Page } from 'playwright' @@ -35,7 +35,7 @@ describe.skipIf(process.platform === 'win32' || release().toLowerCase().includes const events: SessionEvent[] = [] let nativeRoot: string | undefined let openLog: string - const opened = async (): Promise => (await readFile(openLog, 'utf8')).split('\n').filter(Boolean).map(line => JSON.parse(line) as string) + const opened = async (): Promise> => (await readFile(openLog, 'utf8')).split('\n').filter(Boolean).map(line => JSON.parse(line) as { path: string; content: string }) const downloads: string[] = [] beforeAll(async () => { @@ -46,7 +46,7 @@ describe.skipIf(process.platform === 'win32' || release().toLowerCase().includes const command = process.platform === 'darwin' ? 'open' : 'xdg-open' await writeFile(join(nativeRoot, command), `#!${process.execPath} const fs = require('node:fs'); -fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify(fs.readFileSync(process.argv[2], 'utf8')) + '\\n'); +fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify({ path: process.argv[2], content: fs.readFileSync(process.argv[2], 'utf8') }) + '\\n'); `, { mode: 0o700 }) vi.stubEnv('PATH', `${nativeRoot}${delimiter}${process.env.PATH ?? ''}`) await mkdir(DIR, { recursive: true }) @@ -79,7 +79,7 @@ fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify(fs.readFileSync(pro } }) - it('delivers nested snapshots even when the enclosing program subsequently fails', async () => { + it('declares nested deliveries even when the enclosing program subsequently fails', async () => { if (MODE !== 'record') expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT]) const settled = scaffold.whenTurnSettled() const input = page.locator('[data-composer-input]').first() @@ -94,13 +94,21 @@ fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify(fs.readFileSync(pro await assertFinalWorkspaceSnapshot(DIR, cwd) expect(events.filter(event => event.type === 'deliverables/presented').flatMap(event => event.data.files.map(file => file.path))) .toEqual(['report.txt', '说明.txt']) + for (const event of events) { + if (event.type === 'deliverables/presented') { + expect(event.data.files).toEqual([ + { path: 'report.txt', description: 'delivered report' }, + { path: '说明.txt', description: 'delivered note' }, + ]) + } + } expect(events.some(event => event.type === 'tool/code-dispatch' && event.data.name === 'present' && event.data.isError)).toBe(true) expect(events.some(event => event.type === 'tool/result' && event.data.message.content[0].isError)).toBe(true) }, 200_000) - it('opens saved copies after source deletion and reload, while Session ZIP contains only references', async () => { - await unlink(join(cwd, 'report.txt')) - await unlink(join(cwd, '说明.txt')) + it('opens current source files after edits and reload, and reports deletion without downloading', async () => { + await writeFile(join(cwd, 'report.txt'), 'EDITED_REPORT\n') + await writeFile(join(cwd, '说明.txt'), 'EDITED_NOTE\n') for (const reload of [false, true]) { if (reload) { const warningStart = tripwire.warnings.length @@ -111,14 +119,14 @@ fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify(fs.readFileSync(pro const row = page.locator('[data-presented-files-row]') await row.waitFor() expect(await row.getByRole('button').count()).toBe(2) - for (const [name, bytes] of [['report.txt', 'DELIVERED_REPORT\n'], ['说明.txt', 'DELIVERED_NOTE\n']]) { + for (const [name, bytes] of [['report.txt', 'EDITED_REPORT\n'], ['说明.txt', 'EDITED_NOTE\n']] as const) { const count = (await opened()).length const response = page.waitForResponse(response => response.url().includes('/api/present.open?') && response.request().method() === 'POST') await row.getByRole('button', { name: `Open ${name} in default app`, exact: true }).click() expect((await response).status()).toBe(204) await page.waitForFunction(() => document.querySelector('[data-presented-files-row] button:disabled') === null) expect(await opened()).toHaveLength(count + 1) - expect((await opened()).at(-1)).toBe(bytes) + expect((await opened()).at(-1)).toEqual({ path: await realpath(join(cwd, name)), content: bytes }) } } const count = (await opened()).length @@ -127,13 +135,22 @@ fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify(fs.readFileSync(pro await page.waitForFunction(() => document.querySelector('[data-presented-files-row] button:disabled') === null) expect((await openedResponse).status()).toBe(204) expect(await opened()).toHaveLength(count + 1) - expect((await opened()).at(-1)).toBe('DELIVERED_REPORT\n') + expect((await opened()).at(-1)).toEqual({ path: await realpath(join(cwd, 'report.txt')), content: 'EDITED_REPORT\n' }) expect(downloads).toEqual([]) const response = await page.request.get(new URL(`/api/session.export?sessionId=${sessionId}`, scaffold.authenticatedUrl).href) expect(response.status()).toBe(200) const entries = unzipSync(await response.body()) expect(Object.keys(entries)).toHaveLength(1) - expect(strFromU8(Object.values(entries)[0]!)).toContain('deliverables/presented') + const exported = strFromU8(Object.values(entries)[0]!) + expect(exported).toContain('deliverables/presented') + const declarations = exported.trim().split('\n').map(line => JSON.parse(line) as SessionEvent) + .filter(event => event.type === 'deliverables/presented') + expect(declarations).toHaveLength(1) + expect(declarations[0]!.data.files).toEqual([ + { path: 'report.txt', description: 'delivered report' }, + { path: '说明.txt', description: 'delivered note' }, + ]) + expect(exported).not.toContain('EDITED_REPORT') if (MODE !== 'record') { const aria = await captureExpandedTurnProcessAria(page, '[class*="centerCol"]', scaffold.workspaceCwd) await compareOrRefreshGolden(join(DIR, 'ui.expected.md'), aria, MODE) @@ -155,6 +172,14 @@ fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify(fs.readFileSync(pro expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(480) } } + const beforeDelete = (await opened()).length + await unlink(join(cwd, 'report.txt')) + const missing = page.waitForResponse(response => response.url().includes('/api/present.open?')) + await page.locator('[data-presented-files-row]').getByRole('button', { name: 'Open report.txt in default app', exact: true }).click() + expect((await missing).status()).toBe(404) + await page.getByText('Could not open. Click to retry.', { exact: true }).waitFor() + expect(await opened()).toHaveLength(beforeDelete) + expect(downloads).toEqual([]) expect(tripwire.pageErrors).toEqual([]) expect(tripwire.warnings).toEqual([]) }) diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index 9006428dc3..7fa075c800 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.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 docs/config-catalog.md -config-catalog.md: 89eac550fce4b767023d80d519ca3d60849a7e52 -config-catalog.zh.md: 7231b0867faa9998712e97be8889dc5e6f38a9ed +config-catalog.md: 0a06bdf478ccc25b3a3cedb45df18ee0dca285a9 +config-catalog.zh.md: 580c11bd2b3cf6c598206eaa6b1bb0398e85713d diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 89eac550fc..0a06bdf478 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -2873,19 +2873,17 @@ Source: [`packages/lsp/tool-lsp/src/index.ts:57`](../packages/lsp/tool-lsp/src/i ## `@deepseek-ai/dsh-tool-present` -Requires: `tools` · `fs` · `attachments` · `sessionProjections` +Requires: `tools` · `fs` · `sessionProjections` ```ts config-catalog -/** Per-call snapshot limits. */ +/** Per-call delivery limit. */ export interface Config { - /** Inclusive per-file byte cap; at most 100 MiB. */ - maxFileBytes: number /** Maximum number of files in one call. */ maxFiles: number } ``` -Source: [`packages/fs/tool-present/src/index.ts:16`](../packages/fs/tool-present/src/index.ts) +Source: [`packages/fs/tool-present/src/index.ts:15`](../packages/fs/tool-present/src/index.ts) @@ -3459,7 +3457,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co - `@deepseek-ai/dsh-client-ui-commands` ([`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts)) - `@deepseek-ai/dsh-client-ui-conversation` ([`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts)) - `@deepseek-ai/dsh-client-ui-cordis` ([`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts)) -- `@deepseek-ai/dsh-client-ui-deliverables` — requires `systemPrompt` · `connection` · `sessionQuery` · `attachments` · `sessionController` ([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts)) +- `@deepseek-ai/dsh-client-ui-deliverables` — requires `systemPrompt` · `connection` · `sessionQuery` · `sessionController` ([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts)) - `@deepseek-ai/dsh-client-ui-directory-picker-browse` ([`packages/client/ui-directory-picker-browse/src/index.ts`](../packages/client/ui-directory-picker-browse/src/index.ts)) - `@deepseek-ai/dsh-client-ui-directory-picker-native` ([`packages/client/ui-directory-picker-native/src/index.ts`](../packages/client/ui-directory-picker-native/src/index.ts)) - `@deepseek-ai/dsh-client-ui-goal` ([`packages/client/ui-goal/src/index.ts`](../packages/client/ui-goal/src/index.ts)) diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 7231b0867f..580c11bd2b 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -2875,19 +2875,17 @@ export interface Config { ## `@deepseek-ai/dsh-tool-present` -依赖: `tools` · `fs` · `attachments` · `sessionProjections` +依赖: `tools` · `fs` · `sessionProjections` ```ts config-catalog -/** Per-call snapshot limits. */ +/** Per-call delivery limit. */ export interface Config { - /** Inclusive per-file byte cap; at most 100 MiB. */ - maxFileBytes: number /** Maximum number of files in one call. */ maxFiles: number } ``` -来源: [`packages/fs/tool-present/src/index.ts:16`](../packages/fs/tool-present/src/index.ts) +来源: [`packages/fs/tool-present/src/index.ts:15`](../packages/fs/tool-present/src/index.ts) @@ -3461,7 +3459,7 @@ export interface Config { - `@deepseek-ai/dsh-client-ui-commands`([`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts)) - `@deepseek-ai/dsh-client-ui-conversation`([`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts)) - `@deepseek-ai/dsh-client-ui-cordis`([`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts)) -- `@deepseek-ai/dsh-client-ui-deliverables` — 需要 `systemPrompt` · `connection` · `sessionQuery` · `attachments` · `sessionController`([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts)) +- `@deepseek-ai/dsh-client-ui-deliverables` — 需要 `systemPrompt` · `connection` · `sessionQuery` · `sessionController`([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts)) - `@deepseek-ai/dsh-client-ui-directory-picker-browse`([`packages/client/ui-directory-picker-browse/src/index.ts`](../packages/client/ui-directory-picker-browse/src/index.ts)) - `@deepseek-ai/dsh-client-ui-directory-picker-native`([`packages/client/ui-directory-picker-native/src/index.ts`](../packages/client/ui-directory-picker-native/src/index.ts)) - `@deepseek-ai/dsh-client-ui-goal`([`packages/client/ui-goal/src/index.ts`](../packages/client/ui-goal/src/index.ts)) diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index c3b0738000..8049e2d430 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.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 docs/persistence-catalog.md -persistence-catalog.md: 1749faa99beb39940e2581bc58d0543ea5984fd1 -persistence-catalog.zh.md: b3e13c5f4bb9cf973249eaf7b8cf7d93366be96f +persistence-catalog.md: a9e1221a564a4ad1af1353278f1215bb33fb9c4f +persistence-catalog.zh.md: 2b74b03b4b783f848385e66e15c40d73f50a788b diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 1749faa99b..a9e1221a56 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -403,13 +403,13 @@ Source: [`packages/compaction/compaction/src/types.ts:34`](../packages/compactio #### `deliverables/presented` — log-only ```ts persistence-catalog -/** Saved deliveries from a successful final present result, including nested calls. */ +/** Declared workspace files from a successful final present result, including nested calls. */ 'deliverables/presented': { turn: number; callId: ToolCallId; files: PresentedFile[] } ``` Types: [ToolCallId](subsystems/core.md) -Source: [`packages/fs/tool-present/src/types.ts:16`](../packages/fs/tool-present/src/types.ts) +Source: [`packages/fs/tool-present/src/types.ts:15`](../packages/fs/tool-present/src/types.ts) ### `feedback/*` diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index b3e13c5f4b..2b74b03b4b 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -405,13 +405,13 @@ export type SessionEvent = { #### `deliverables/presented` — 仅日志 ```ts persistence-catalog -/** Saved deliveries from a successful final present result, including nested calls. */ +/** Declared workspace files from a successful final present result, including nested calls. */ 'deliverables/presented': { turn: number; callId: ToolCallId; files: PresentedFile[] } ``` 类型: [ToolCallId](subsystems/core.zh.md) -来源: [`packages/fs/tool-present/src/types.ts:16`](../packages/fs/tool-present/src/types.ts) +来源: [`packages/fs/tool-present/src/types.ts:15`](../packages/fs/tool-present/src/types.ts) ### `feedback/*` diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index c43efdd85f..49f6610bd2 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.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 docs/tool-catalog.md -tool-catalog.md: 5a09b07e0a7a24e654fc45a7a477838ce21f30d7 -tool-catalog.zh.md: 528f3a37680ecb5f9482606c5671c4fb056092f1 +tool-catalog.md: c2ea95ff460b65fbba789738b16b0c67a7c91948 +tool-catalog.zh.md: 41fc1eaa3c6a0acf17bde4b69c97413b64872930 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 5a09b07e0a..c2ea95ff46 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -19,7 +19,7 @@ This table connects model-visible tool names to the plugin package and service s | `@deepseek-ai/dsh-tools` | `run_code` | `ctx.tools`, `ctx.codeRuntime (execution time)`, `ctx.systemPrompt` | `tool/call`, `one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`, `tool/result` | - | Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: ptc` / `mode: both` (see the PTC mode Agent Note). Under `ptc` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. | | `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`, `ctx.systemPrompt`, `ctx.userQuestions (execution time, opportunistic)` | `tool/call`, `plan/mode inactive on an approved review`, `tool/result` | - | exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. | | `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs at call time for run_in_background` | `tool/call`, `tool/result` | - | The bash tool is the model-facing consumer of the bash executor seam. A `run_in_background` run registers with the generic `ctx.jobs` runtime and is collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; the `enableRunInBackground` config (default true) removes the parameter entirely when disabled. | -| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.tools`, `ctx.fs`, `ctx.attachments`, `ctx.sessionProjections` | `tool/call`, `deliverables/presented after a successful final result`, `tool/result` | - | Deliveries belong to the calling Session; Web ui-deliverables supplies authenticated downloads and cards. | +| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.tools`, `ctx.fs`, `ctx.sessionProjections` | `tool/call`, `deliverables/presented after a successful final result`, `tool/result` | - | Deliveries belong to the calling Session; Web ui-deliverables supplies source-file opening and cards. | | `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs at call time for run_in_background` | `tool/call`, `tool/result` | - | The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.shell`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.jobs` runtime and are collected/stopped through the `job_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-shell-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\...` paths and `$env:NAME` variables. | | `@deepseek-ai/dsh-tool-cordis` | `cordis_define`, `cordis_inspect_list`, `cordis_inspect_query`, `cordis_inspect_self`, `cordis_run`, `cordis_stop`, `cordis_undefine` | `ctx.tools`, `ctx.dynamicCordisRunner` | `tool/call`, `tool/result`, `process-local dynamic package lifecycle` | - | Not in any shipped tree (a deliberate opt-in — dynamic package code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). The toolset injects `ctx.dynamicCordisRunner` from `@deepseek-ai/dsh-cordis-host-runner`, which owns the definition registry and the vm sandbox; a composition missing it never activates the tools. A running package may register ADDITIONAL model-visible tools until it is stopped, undefined, or DSH restarts; a full changed request header logs those tool-set changes. | | `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools`, `ctx.terminals`, `an owning Agent at execution time` | `tool/call`, `PTY shell state`, `tool/result` | - | One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description. | @@ -225,7 +225,7 @@ The bash tool is the model-facing consumer of the bash executor seam. A `run_in_ ### `present` -Deliver final files to the user. Saves a snapshot of each existing workspace file so it remains downloadable after edits or deletion. Create the files before calling this tool. +Declare existing workspace files as final deliverables. The user opens the current source files; their contents are not copied or preserved. Create the files before calling this tool. ```json { @@ -260,7 +260,7 @@ Deliver final files to the user. Saves a snapshot of each existing workspace fil Source: [`packages/fs/tool-present/src/index.ts`](../packages/fs/tool-present/src/index.ts) -Deliveries belong to the calling Session; Web ui-deliverables supplies authenticated downloads and cards. +Deliveries belong to the calling Session; Web ui-deliverables supplies source-file opening and cards. diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index 528f3a3768..41fc1eaa3c 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -23,7 +23,7 @@ | `@deepseek-ai/dsh-tools` | `run_code` | `ctx.tools`、`ctx.codeRuntime (execution time)`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | 在 `mode: ptc`/`mode: both` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 PTC mode Agent Note)。在 `ptc` 下,它是注册表对协议格式(wire format)的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 | | `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`、`ctx.systemPrompt`、`ctx.userQuestions (execution time, opportunistic)` | `tool/call`、`plan/mode inactive on an approved review`、`tool/result` | - | 规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。 | | `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs at call time for run_in_background` | `tool/call`、`tool/result` | - | bash 工具是 bash 执行器 seam 面向模型的消费方。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具(来自 `@deepseek-ai/dsh-tool-jobs`)收集/停止;禁用 `enableRunInBackground` 配置(默认为 true)后,该参数会被完全移除。 | -| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.tools`, `ctx.fs`, `ctx.attachments`, `ctx.sessionProjections` | `tool/call`, `deliverables/presented 在成功的最终结果之后`, `tool/result` | - | 交付归调用方 Session 所有;Web ui-deliverables 提供认证下载与卡片。 | +| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.tools`, `ctx.fs`, `ctx.sessionProjections` | `tool/call`, `deliverables/presented 在成功的最终结果之后`, `tool/result` | - | 交付归调用方 Session 所有;Web ui-deliverables 提供源文件打开与卡片。 | | `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs at call time for run_in_background` | `tool/call`、`tool/result` | - | pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费方(由 `@deepseek-ai/dsh-pwsh-local` 等 PowerShell 执行器为 `ctx.shell` 提供后端);除沙箱接口外,它逐项对应 bash 工具调用。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具收集/停止;托管的 `DSH_*` 环境来自 `@deepseek-ai/dsh-shell-env`。每次调用都在新进程中运行,不使用持久 PTY 会话。路径采用原生 `C:\...` 形式,变量采用 `$env:NAME`。 | | `@deepseek-ai/dsh-tool-cordis` | `cordis_define`、`cordis_inspect_list`、`cordis_inspect_query`、`cordis_inspect_self`、`cordis_run`、`cordis_stop`、`cordis_undefine` | `ctx.tools`、`ctx.dynamicCordisRunner` | `tool/call`、`tool/result`、`process-local dynamic package lifecycle` | - | 不在任何随产品发布的树中,需要显式选择启用;动态 Package 代码可以访问真实运行时,见 .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md。该工具集注入 `@deepseek-ai/dsh-cordis-host-runner` 提供的 `ctx.dynamicCordisRunner`,后者拥有定义注册表和 vm 沙箱;组合缺少它时这些工具不会激活。运行中的 Package 在停止、undefine 或 DSH 重启前可以注册**额外的**模型可见工具;发生这类工具集变化时,系统会记录完整且有变动的请求头。 | | `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools`、`ctx.terminals`、`an owning Agent at execution time` | `tool/call`、`PTY shell state`、`tool/result` | - | 一个按所有者隔离的持久 bash 工具;部署组合提供 PTY 后端,并可覆盖面向模型的环境描述。 | @@ -229,7 +229,7 @@ bash 工具是 bash 执行器 seam 面向模型的消费方。使用 `run_in_bac ### `present` -向用户交付最终文件。保存每个已有工作区文件的快照,使其在编辑或删除后仍可下载。调用工具前先创建文件。 +声明交付已有的工作区文件。用户打开当前源文件;不复制或保存其内容。调用工具前先创建文件。 ```json { @@ -264,7 +264,7 @@ bash 工具是 bash 执行器 seam 面向模型的消费方。使用 `run_in_bac 来源: [`packages/fs/tool-present/src/index.ts`](../packages/fs/tool-present/src/index.ts) -交付归调用方 Session 所有;Web ui-deliverables 提供认证下载与卡片。 +交付归调用方 Session 所有;Web ui-deliverables 提供源文件打开与卡片。 diff --git a/packages/client/ui-deliverables/README.i18n.yaml b/packages/client/ui-deliverables/README.i18n.yaml index d32df8879e..9cc543ee66 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: abf944d0115934d781ae44bf176914263822277e -README.zh.md: e8f556a7469800898b660fe1d81e16db02062b27 +README.md: ae42abb5a69d29332bd7cfd6d2d2a1191381e79a +README.zh.md: 664dfa3cd2e0d526b43464a80290604a8ecf958c diff --git a/packages/client/ui-deliverables/README.md b/packages/client/ui-deliverables/README.md index abf944d011..ae42abb5a6 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 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 snapshot creation, limits, and Session delivery records. The closing turn shows responsive file cards with names, types, sizes, descriptions, and buttons that open a saved copy in the Host’s default application. Matching inline-code references open the same snapshots after source edits, deletion, or reload, without starting a browser download. Forks authorize opening through the viewed Session. Repeated delivery of a path selects its latest successful snapshot 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, and buttons that open the source in the Host’s default application. 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. Opening shows progress, confirmation, or a retryable error on the card. It requires a desktop and a suitable default application on the serving Host; a remote browser does not open applications on its own device. @@ -52,7 +52,7 @@ The closing prose carries the same vocabulary: an inline-code token resolves by The Node half registers the static `ui:deliverable-file-references` system-prompt section asking the model to mention primary files from successful creation or modification calls and to write those and any other changed-file references as Markdown inline code. The browser half registers a wrapper around `ProducedFiles` and explicit deliveries into the chat view's `conversation.chat.turnTail` hole. `deliverablesDefinition` folds each Turn's successful first-party mutation calls into `DeliverablesTurnData` from the validated raw arguments of `write`, `edit`, and mutating `str_replace_editor` commands. Reads, deletes, unsupported tools, malformed calls, and failed results contribute nothing. A new mutation tool needs an explicit Client contribution before it joins the list. The package also provides the `chatFileMentions` service the chat view consults per closing message; composing the plugin out removes both surfaces and leaves the view's empty chain at zero cost. -Native opening uses an authenticated POST addressed by Session, event sequence, and original file index. The Host streams the saved bytes into a private temporary copy and verifies the complete attachment before launching the default application. Each gesture creates a separate copy, so application edits cannot change the stored snapshot. Failed opens remove their copies; successful copies remain until plugin disposal because applications may read lazily. Disposal cancels and awaits pending work before cleanup. The authenticated GET download endpoint remains available to byte consumers. +Native opening uses an authenticated POST addressed by the viewed Session, event sequence, and original file index. The Host resolves the declaration against that Session’s workspace and checks the current file exists within it before launching the default application. Edits affect subsequent opens; deletion returns an error. No file-content copy or attachment is created. Plugin disposal cancels and awaits pending native-open requests. @@ -95,8 +95,8 @@ The section is static at first-party order 9000 for the lifetime of the package These limits define the current deliverables vocabulary. They are current package constraints, not a general file-linking comparison or a task backlog. - **Mention matching is exact path or unique basename only** — a suffix mention stays inert; widening the matcher is deferred until a real closing-message shape needs it. -- **Terminal-created files require explicit delivery** — call `present` to make their saved snapshots available. -- **Transferred Session exports contain no delivered bytes** — delivery actions require the same snapshots in the serving host’s attachment store; missing or pruned snapshots return 404. +- **Terminal-created files require explicit delivery** — call `present` to declare them for native opening. +- **Declarations do not preserve file contents** — reopening or transferring a Session requires the source files in the viewed Session’s workspace. Missing files return 404; paths resolving outside the workspace return 403. - **Directories have no destination** — chips open files in the right Sidebar's text preview, which shows files only; the former native folder handoff is gone rather than replaced. @@ -109,4 +109,4 @@ None. -**Runtime invariant:** No companion is published. The prompt section, slot, dictionary, file-action routes, and optional service registrations are effect-owned with disposal proven by their plugin specs; the attachment service owns saved bytes, and the Session log owns delivery references. +**Runtime invariant:** No companion is published. Prompt, slot, dictionary, file-action route, and optional service registrations are effect-owned; the Session log owns declarations and the workspace owns file contents. diff --git a/packages/client/ui-deliverables/README.zh.md b/packages/client/ui-deliverables/README.zh.md index e8f556a746..664dfa3cd2 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 默认应用中打开保存副本的按钮。匹配的行内代码引用也打开相同快照;修改或删除源文件、重新加载后仍可打开,不触发浏览器下载。Fork 通过当前查看的 Session 授权打开。同一路径重复交付时,选择收尾回复之前最近一次成功的快照。 +Web 的 `standard`、`ptc` 与 `cordis` preset 提供 `present` 用于声明交付最终工作区文件,包括通过 Bash 创建的文件。创建文件后,以 `files: [{ path, description? }]` 调用。[present 工具](../../fs/tool-present/README.zh.md)拥有文件数量限制和 Session 声明。收尾 turn 显示响应式卡片,包含文件名称、类型、说明和在 Host 默认应用中打开源文件的按钮。匹配的行内代码引用打开相同源文件,不触发浏览器下载。同一路径重复声明时,选择收尾回复之前最近一次的说明。 `present` 工具行显示正在交付、已交付、失败或中断状态;展开已结束的调用可查看其记录的结果。文件卡片展示全部交付文件。打开时,卡片显示进度、成功确认或可重试的错误。服务 Host 必须具备桌面和合适的默认应用;远程浏览器不会打开其所在设备上的应用。 @@ -52,7 +52,7 @@ Web 的 `standard`、`ptc` 与 `cordis` preset 提供 `present` 用于交付最 Node 半部注册静态 `ui:deliverable-file-references` 系统提示词段,要求模型点名成功创建或修改的主要文件,并把这些文件以及正文中提到的其他本轮变更文件写成 Markdown 行内代码。浏览器半部把组合 `ProducedFiles` 与显式交付的包装组件注册进 chat 视图的 `conversation.chat.turnTail` 洞。`deliverablesDefinition` 根据 `write`、`edit` 和有修改作用的 `str_replace_editor` 命令中经过校验的原始参数,把每个轮次成功的第一方修改调用折叠进 `DeliverablesTurnData`。读取、删除、不受支持的工具、格式错误的调用和失败结果不贡献任何条目。新的修改工具必须增加显式 Client contribution 才能加入列表。本包还提供 chat 视图按收尾消息查询的 `chatFileMentions` 服务;把插件组合出去会同时移除两个表面,视图的空链以零成本留下。 -原生打开使用经过认证的 POST,通过 Session、事件序号和原始文件索引定位文件。Host 将保存的字节流写入私有临时副本,完整校验 attachment 后才启动默认应用。每次操作创建独立副本,因此应用内的编辑不会修改已保存的快照。打开失败时删除副本;成功副本保留到插件释放,因为应用可能延迟读取。释放时先取消并等待进行中的操作,再执行清理。经过认证的 GET 下载端点仍供字节读取方使用。 +原生打开使用经过认证的 POST,通过当前查看的 Session、事件序号和原始文件索引定位声明。Host 按该 Session 的工作区解析路径,检查当前文件存在且位于工作区内,再启动默认应用。编辑会影响后续打开的内容;删除后返回错误。不创建文件内容副本或附件。插件释放时取消并等待进行中的原生打开请求。 @@ -95,9 +95,9 @@ Node 半部注册静态 `ui:deliverable-file-references` 系统提示词段, 这些限制界定了当前产出物词表。它们是当前包约束,不是通用文件链接对比或任务积压。 - **提及匹配只认精确路径或唯一 basename**——后缀式提及保持惰性;等真实的收尾消息形态产生需求后再放宽匹配规则。 -- **终端创建的文件需要显式交付**——调用 `present` 使其快照可供下载。 -- **转移的 Session 导出不含交付字节** — 下载链接依赖服务主机附件存储中的同一快照;快照缺失或被清理时返回 404。 -- **原生文件夹交接以 Host 桌面为目标**——经非 loopback authority 访问的浏览器会省略该动作,报告没有原生打开器的部署也一样;若 SSH 转发让远端 Host 看似 loopback 本地,部署必须为 Session Controller 设置 `nativeOpen: false`。 +- **终端创建的文件需要显式交付**——调用 `present` 声明文件,以便原生打开。 +- **声明不保存文件内容**——重新打开或转移 Session 后,需要当前查看的 Session 工作区中仍有源文件。文件缺失返回 404;解析到工作区外的路径返回 403。 +- **目录没有打开目标**——标签项在右侧 Sidebar 的文本预览中打开文件,该预览仅支持文件,不提供原生文件夹打开动作。 ### 开发备注 @@ -109,4 +109,4 @@ Node 半部注册静态 `ui:deliverable-file-references` 系统提示词段, -**运行时不变式:** 不发布伴生入口。prompt section、slot、dictionary、文件操作路由与可选 service 注册都归 effect 所有,释放由插件测试证明;attachment 服务拥有保存的字节,Session 日志拥有交付引用。 +**运行时不变式:** 不发布伴生入口。提示词、slot、dictionary、文件操作路由与可选 service 注册归 effect 所有;Session 日志拥有声明,工作区拥有文件内容。 diff --git a/packages/client/ui-deliverables/package.json b/packages/client/ui-deliverables/package.json index e43f6745b5..67970786a0 100644 --- a/packages/client/ui-deliverables/package.json +++ b/packages/client/ui-deliverables/package.json @@ -63,10 +63,8 @@ "@deepseek-ai/dsh-client-ui-primitives": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", - "@deepseek-ai/dsh-attachment": "workspace:^", "@deepseek-ai/dsh-session-query": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", - "@deepseek-ai/dsh-attachment-local": "workspace:^", "@deepseek-ai/dsh-client-ui-tool": "workspace:^", "@deepseek-ai/dsh-tool-present": "workspace:^", "@deepseek-ai/dsh-api-session-controller": "workspace:^" diff --git a/packages/client/ui-deliverables/src/client/Deliverables.tsx b/packages/client/ui-deliverables/src/client/Deliverables.tsx index 8f2dab5156..2faf259ad4 100644 --- a/packages/client/ui-deliverables/src/client/Deliverables.tsx +++ b/packages/client/ui-deliverables/src/client/Deliverables.tsx @@ -1,6 +1,6 @@ -/** Existing changed-file chips and explicitly delivered snapshots for a closing turn. */ +/** Existing changed-file chips and explicitly declared files for a closing turn. */ import type { TurnTailOwnerProps } from '@deepseek-ai/dsh-client-ui-chat/client' -import { LinkIcon, classifyLinkPath, fileSizeText, IconRightUpOutline16 } from '@deepseek-ai/dsh-client-ui-primitives' +import { LinkIcon, classifyLinkPath, IconRightUpOutline16 } from '@deepseek-ai/dsh-client-ui-primitives' import type { 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' @@ -19,7 +19,7 @@ export interface DeliverablesInjected { } /** - * Claim turns containing modified paths or presented snapshots. + * Claim turns containing modified paths or declared files. * @param owner - closing turn. * @returns matched files, or null for an empty turn. */ @@ -30,7 +30,7 @@ export function selectDeliverables(owner: TurnTailOwnerProps): DeliverablesMatch } /** - * Render workspace file actions and default-application buttons for saved deliveries. + * Render workspace file actions and default-application buttons for declared files. * @param props - matched files, workspace opener, and localized copy. * @returns the closing turn's file rows. */ @@ -44,7 +44,7 @@ export function Deliverables({ matched, openFile, t, sessionId, openPresented, u {t('presented.label')}
{matched.presented.map((file) => { - const phase = states[presentedFileUrl(sessionId, file.seq, file.index, 'open')] + const phase = states[presentedFileUrl(sessionId, file.seq, file.index)] return