以代码块回显项目文件正文
将聊天侧 /read 文件内容包装为安全 pre/code 补充反引号围栏处理并更新项目文件读取断言 同步工作台 PRD 与 AGC 实施计划的安全渲染约定
This commit is contained in:
@@ -272,7 +272,13 @@ export function summarizeProjectFileContent(result: LocalProjectFileResult) {
|
||||
} 字符`
|
||||
: result.content;
|
||||
|
||||
return `文件:${result.path}\n${content || '空文件'}`;
|
||||
const visibleContent = content || '空文件';
|
||||
let longestBacktickRun = 0;
|
||||
for (const match of visibleContent.matchAll(/`+/gu)) {
|
||||
longestBacktickRun = Math.max(longestBacktickRun, match[0].length);
|
||||
}
|
||||
const fence = '`'.repeat(Math.max(3, longestBacktickRun + 1));
|
||||
return `文件:${result.path}\n\n${fence}text\n${visibleContent}\n${fence}`;
|
||||
}
|
||||
|
||||
export function inferProjectFileAssetDraft(
|
||||
|
||||
@@ -923,7 +923,9 @@ export function registerProjectAssetTests() {
|
||||
submitChat('/read game/index.html');
|
||||
|
||||
expect(await screen.findByText(/文件:game\/index\.html/)).not.toBeNull();
|
||||
expect(screen.getByText(/<canvas id="game"><\/canvas>/)).not.toBeNull();
|
||||
const fileContent = screen.getByText('<canvas id="game"></canvas>');
|
||||
expect(fileContent.tagName).toBe('CODE');
|
||||
expect(fileContent.closest('pre')).not.toBeNull();
|
||||
expect(invoke).toHaveBeenCalledWith('read_local_project_file', {
|
||||
projectPath: '/tmp/authorized-game',
|
||||
relativePath: 'game/index.html',
|
||||
@@ -992,7 +994,9 @@ export function registerProjectAssetTests() {
|
||||
fireEvent.click(screen.getByRole('button', { name: '确认' }));
|
||||
|
||||
expect(await screen.findByText(/文件:game\/index\.html/)).not.toBeNull();
|
||||
expect(screen.getByText(/<canvas id="game"><\/canvas>/)).not.toBeNull();
|
||||
const fileContent = screen.getByText('<canvas id="game"></canvas>');
|
||||
expect(fileContent.tagName).toBe('CODE');
|
||||
expect(fileContent.closest('pre')).not.toBeNull();
|
||||
expect(invoke).toHaveBeenCalledWith('read_local_project_file', {
|
||||
projectPath: '/tmp/authorized-game',
|
||||
relativePath: 'game/index.html',
|
||||
|
||||
@@ -185,7 +185,7 @@ completed -> starting(nextSlice)
|
||||
idle -> focused(document|art|audio|version) -> idle
|
||||
```
|
||||
|
||||
- 文档:合法 Agent 文本回执直接使用对话投影内容;项目文件只允许读取当前 manifest 已登记资产或已完成任务产物中的 Markdown、文本、JSON、YAML、TOML,必须经过 `file.read` auto 权限、相对路径、项目边界、普通文件、符号链接 / 硬链接、读取漂移、2 MiB、UTF-8 与扩展名白名单校验。正文使用不执行 HTML、不加载远程图片、不产生可点击外链的安全 Markdown 渲染,并在中央画布内独立滚动;读取失败显示错误空态。Markdown 渲染使用 `react-markdown` 的 `skipHtml`,依赖库对代码 span / fenced code 的文本转义;不得在整段 Markdown 上预转义 HTML,否则会把代码中的 `<tag>` 双重转义为字面量 `<tag>`。
|
||||
- 文档:合法 Agent 文本回执直接使用对话投影内容;项目文件只允许读取当前 manifest 已登记资产或已完成任务产物中的 Markdown、文本、JSON、YAML、TOML,必须经过 `file.read` auto 权限、相对路径、项目边界、普通文件、符号链接 / 硬链接、读取漂移、2 MiB、UTF-8 与扩展名白名单校验。正文使用不执行 HTML、不加载远程图片、不产生可点击外链的安全 Markdown 渲染,并在中央画布内独立滚动;读取失败显示错误空态。聊天侧 `/read` 回执中的文件正文必须作为代码块渲染为 `<pre><code>`,以便用户审阅源码字面量但不执行其中的 HTML;Markdown 渲染使用 `react-markdown` 的 `skipHtml`,依赖库对代码 span / fenced code 的文本转义;不得在整段 Markdown 上预转义 HTML,否则会把代码中的 `<tag>` 双重转义为字面量 `<tag>`。
|
||||
- 美术:PNG、JPEG、WEBP、GIF、SVG、AVIF、BMP、MP4、WebM、MOV 只在资源卡本体中按既有受控读取、文件签名与解码门禁展示;中央详情不重复加载或放大图片 / 视频本体。SVG 继续拒绝脚本、事件处理器、外部资源引用和实体声明。
|
||||
- 音频:只读取 manifest 已登记音频或已成功导入且登记到 manifest 的附件,按文件签名接受 MP3、WAV、OGG / Opus、M4A、AAC、FLAC;聚焦态展示实际格式、浏览器解码后的时长以及带播放进度和暂停能力的内置播放器。音频任务声明中的未登记路径继续不得读取或播放。
|
||||
- 版本:只展示 manifest 中正式、不可变的迭代版本记录;版本卡展示项目修订、创建原因与父版本,聚焦态同时展示直接子版本和资源绑定。点击版本卡后高亮仍存在于当前资源投影中的引用资源;缺失历史资源只保留绑定身份,不生成幽灵资源卡。资源替换仍留给后续切片。
|
||||
|
||||
@@ -870,7 +870,7 @@ game-project/
|
||||
- 聊天输入 `/status` 会读取 `.agent/manifest.json` 并在聊天里汇总项目目录、任务状态、资产数量、预览状态和最近命令,不向普通用户暴露任务或文件面板。
|
||||
- 聊天输入 `/files` 会通过 `file.list` 只读列出本地项目内的文件摘要;主窗口最近项目文件可一键读取,也可一键填入 `/read` 或 `/asset-register` 草稿,但资产登记仍必须走聊天确认;`/checkpoints` 复用 `file.list` / `file.read` 只读列出最近 checkpoint id、文件数、大小和可复制的 `/diff` / `/restore` 命令,主窗口最近 checkpoint 列表也可填入对应草稿;普通用户仍不暴露文件读写面板。
|
||||
- 聊天输入 `/assets` 会读取 `.agent/manifest.json` 并在聊天里列出本地项目资产路径、类型和来源,资产列表消息和主窗口最近项目资产入口都可一键填入对应资产的 `/read` 草稿;聊天输入 `/art` 只使用当前已加载 manifest 盘点美术素材,并提供首版美术生成或读取美术清单草稿,不直接读取文件、不触发平台生成或画板同步;聊天输入 `/audio` 只使用当前已加载 manifest 盘点音频素材,并提供登记音效或读取音频清单草稿,不直接读取文件、不触发资产写入;`/asset-register 路径 [kind] [mediaType]` 可确认后登记项目内已有资产;主窗口音效快捷入口只填入 `/asset-register assets/audio/sfx.wav audio audio/wav` 草稿,不直接写 manifest;普通用户仍不暴露资产面板。
|
||||
- 聊天输入 `/read 本地相对路径` 会通过 `file.read` 只读返回项目内文本文件内容并在聊天中截断长文本;普通用户仍不暴露文件写入或删除能力。
|
||||
- 聊天输入 `/read 本地相对路径` 会通过 `file.read` 只读返回项目内文本文件内容并在聊天中截断长文本;聊天回执中的正文必须包装为安全的 `<pre><code>` 代码块,保留源码字面量但不得执行 HTML;普通用户仍不暴露文件写入或删除能力。
|
||||
- 主窗口常用生成产物入口只把 `game/index.html`、`game/game_design.md`、`game/balance.json`、`assets/manifest.art.json`、`assets/manifest.audio.json` 和 `exports/README.md` 的 `/read` 草稿填入聊天输入框;聊天输入 `/artifacts` 只列出这组固定读取命令并提供首个 `/read` 草稿,聊天输入 `/run-artifacts` 只列出最近 run trace 里的产物读取命令并提供首个 `/read` 草稿,聊天输入 `/run-files` 只列出 `.agent/output.jsonl`、`.agent/activity.jsonl` 和 `.agent/context.bundle.json` 的读取命令并提供首个 `/read` 草稿;读取仍由聊天侧 `file.read` 权限流执行。
|
||||
- 聊天输入 `/logs` 只列出 `.agent/logs/command.log`、`.agent/logs/preview.log` 和 `.agent/logs/agent.log` 对应的 `/read ...` 草稿 / 命令,并提供首个 `/read` 草稿;该命令不直接读取日志,不新增普通用户日志面板,实际读取仍由聊天侧 `file.read` 权限流执行。
|
||||
- 聊天输入 `/tasks` 会读取 `.agent/manifest.json` 并在聊天里列出专业组、角色、任务状态、产物交接和下一步可执行任务;普通用户仍不暴露任务面板。
|
||||
|
||||
Reference in New Issue
Block a user