---
phase: 06-
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - frontend/src/core/messages/utils.ts
  - frontend/src/components/ai-elements/prompt-input.tsx
  - frontend/src/core/threads/hooks.ts
  - frontend/src/core/threads/hooks.test.ts
autonomous: true
requirements:
  - ATREF-03
must_haves:
  truths:
    - "用户发送带文件引用的消息后，消息体仍通过 additional_kwargs.files 传输，不新增并行主结构。"
    - "引用文件在提交结构中可区分来源与类型，且不破坏现有文件渲染链路。"
    - "引用项失效时会被自动剔除并提示，但文本消息仍可发送。"
  artifacts:
    - path: "frontend/src/core/messages/utils.ts"
      provides: "FileInMessage 扩展字段（引用来源/类型）与兼容解析"
    - path: "frontend/src/components/ai-elements/prompt-input.tsx"
      provides: "PromptInputMessage 新增引用文件字段契约"
    - path: "frontend/src/core/threads/hooks.ts"
      provides: "上传文件与引用文件合并提交到 additional_kwargs.files"
    - path: "frontend/src/core/threads/hooks.test.ts"
      provides: "提交结构与软失败行为的单元测试"
  key_links:
    - from: "frontend/src/components/ai-elements/prompt-input.tsx"
      to: "frontend/src/core/threads/hooks.ts"
      via: "PromptInputMessage.references"
      pattern: "references"
    - from: "frontend/src/core/threads/hooks.ts"
      to: "frontend/src/core/messages/utils.ts"
      via: "FileInMessage 扩展字段"
      pattern: "additional_kwargs:\\s*\\{\\s*files"
---

<objective>
先定义并落地“引用文件提交契约”，确保 Phase 6 的数据链路稳定可回归。

Purpose: 把最难回滚的协议与提交流程先锁定，避免后续 UI 实现完成后才发现协议不兼容。
Output: 扩展后的消息类型、提交流程、以及针对合并/软失败的自动化测试。
</objective>

<execution_context>
@/home/mt/.codex/get-shit-done/workflows/execute-plan.md
@/home/mt/.codex/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/06-/06-CONTEXT.md
@.planning/phases/06-/06-RESEARCH.md
@.planning/phases/06-/06-VALIDATION.md
@frontend/src/components/ai-elements/prompt-input.tsx
@frontend/src/core/messages/utils.ts
@frontend/src/core/threads/hooks.ts
@frontend/src/core/threads/hooks.test.ts
</context>

<interfaces>
From `frontend/src/components/ai-elements/prompt-input.tsx`:
```typescript
export type PromptInputMessage = {
  text: string;
  files?: FileUIPart[];
};
```

From `frontend/src/core/messages/utils.ts`:
```typescript
export interface FileInMessage {
  filename: string;
  size: number;
  path?: string;
  status?: "uploading" | "uploaded";
}
```

From `frontend/src/core/threads/hooks.ts`:
```typescript
const filesForSubmit: FileInMessage[] = uploadedFileInfo.map(...)
await thread.submit({
  messages: [{ type: "human", additional_kwargs: { files: filesForSubmit } }],
});
```
</interfaces>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: 扩展引用文件契约并写 RED 测试</name>
  <files>frontend/src/core/messages/utils.ts, frontend/src/components/ai-elements/prompt-input.tsx, frontend/src/core/threads/hooks.test.ts</files>
  <read_first>
    - frontend/src/core/messages/utils.ts
    - frontend/src/components/ai-elements/prompt-input.tsx
    - frontend/src/core/threads/hooks.test.ts
    - .planning/phases/06-/06-CONTEXT.md
  </read_first>
  <behavior>
    - Test 1: `PromptInputMessage` 支持 `references` 字段，类型可表达 `artifact|upload` 来源（per D-06）。
    - Test 2: `FileInMessage` 支持可选 `ref_kind/ref_source` 元数据且旧字段保持可用（per D-05, D-06）。
  </behavior>
  <action>在 `PromptInputMessage` 新增 `references` 数组字段；在 `FileInMessage` 增加 `ref_kind: "mention"` 与 `ref_source: "artifact" | "upload"` 可选字段；先在 `hooks.test.ts` 新增失败用例，断言提交 payload 含 `additional_kwargs.files[*].ref_kind/ref_source` 且不删除已有 `filename/size/path/status` 字段（按 D-05、D-06）。</action>
  <acceptance_criteria>
    - `rg -n "references\\?:" frontend/src/components/ai-elements/prompt-input.tsx` 返回至少 1 行。
    - `rg -n "ref_kind|ref_source" frontend/src/core/messages/utils.ts` 返回至少 2 行。
    - 新增测试在实现前失败（RED），失败信息包含 `ref_kind` 或 `ref_source` 字样。
  </acceptance_criteria>
  <verify>
    <automated>cd frontend && node --test src/core/threads/hooks.test.ts</automated>
  </verify>
  <done>类型契约完成并有可复现的失败测试，明确约束提交结构。</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: 在线程提交链路合并上传文件与引用文件并实现软失败</name>
  <files>frontend/src/core/threads/hooks.ts, frontend/src/core/threads/hooks.test.ts</files>
  <read_first>
    - frontend/src/core/threads/hooks.ts
    - frontend/src/core/threads/hooks.test.ts
    - frontend/src/core/uploads/api.ts
    - .planning/phases/06-/06-CONTEXT.md
    - .planning/phases/06-/06-RESEARCH.md
  </read_first>
  <behavior>
    - Test 1: 上传文件 + 引用文件会统一写入 `additional_kwargs.files`，且上传文件不被覆盖（per D-05）。
    - Test 2: 引用失效时仅剔除失效项并 toast，文本仍会继续提交（per D-07）。
  </behavior>
  <action>在 `sendMessage` 中新增引用文件合并逻辑：`uploadedFileInfo` 先转 `FileInMessage`，再追加 `message.references`（保留 `ref_kind/ref_source`）；提交前根据传入的有效引用列表进行二次过滤，失效项通过 `toast.error("部分引用已失效，已自动移除")` 提示并继续 `thread.submit`；禁止创建 `mentions` 等并行结构（按 D-05、D-07）。</action>
  <acceptance_criteria>
    - `rg -n "additional_kwargs:\\s*\\{\\s*files" frontend/src/core/threads/hooks.ts` 命中提交代码。
    - `rg -n "ref_kind|ref_source" frontend/src/core/threads/hooks.ts` 命中引用元信息写入。
    - `rg -n "已自动移除|stale" frontend/src/core/threads/hooks.ts` 命中软失败分支。
  </acceptance_criteria>
  <verify>
    <automated>cd frontend && node --test src/core/threads/hooks.test.ts</automated>
  </verify>
  <done>提交链路兼容 uploads + references，软失败生效且单测通过。</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| input-box→thread submit API | 用户可控输入跨越到后端提交 envelope |
| thread artifacts/uploads→引用元信息 | 候选文件元数据进入消息体 |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-06-01-01 | T | `frontend/src/core/threads/hooks.ts` | mitigate | 仅接受候选池中引用并在提交前二次过滤，拒绝自由路径注入（ASVS V5）。 |
| T-06-01-02 | I | `additional_kwargs.files` | mitigate | 强制 thread 范围来源，不引入全局检索，避免跨线程信息泄露（ASVS V4）。 |
| T-06-01-03 | D | `sendMessage` 合并逻辑 | mitigate | 失效引用软剔除并继续提交，避免单点失败阻断消息发送。 |
</threat_model>

<verification>
- `cd frontend && node --test src/core/threads/hooks.test.ts`
- `cd frontend && pnpm -s typecheck`
</verification>

<success_criteria>
- `additional_kwargs.files` 成为上传与引用的唯一提交结构。
- 引用元信息可被编码且不影响既有文件渲染。
- 失效引用不会导致整条消息发送失败。
</success_criteria>

<output>
After completion, create `.planning/phases/06-/06-01-SUMMARY.md`
</output>
