Files
DevFlow/src/api/types.ts
T
lxy 05d0f89c16 修复: aichat上下文管理与信息组织P1批
逐件(证据:深查-aichat质量/信息UX-2026-10-17):
- 恢复链套娃:RECOVERY_ARTIFACT_PREFIX双侧过滤+单点拦截(E1)
- message_repo全量重写保留原created_at(E11,治1750条时间线污染)
- search_files/grep 0命中附阴性存疑提示(E6,共享negative_hit_hint)
- 项目清单三源活跃度排序+system prompt指纹对齐(E4跑题)
- 会话列表活跃/归档offset分页+侧栏加载更多(F4-1,治50硬上限)
- inject消息source标记(轮次分桶不误计发起,V52同源)
- 提示词回复风格条款中英双语(反口水话,含测试断言)
- i18n补键:复制/折叠量/发送title/历史定位提示(F1-1/F2-1/F6-1)

验收:cargo test --workspace 2281 passed 0 failed;vue-tsc EXIT 0
2026-10-18 23:15:45 +08:00

1040 lines
43 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! TypeScript 类型定义 — 与 Rust Record 结构体严格对齐
// ============================================================
// 域 ID newtype(纯类型级标记,零运行时开销)
// ============================================================
/** 项目 ID — branded newtype,替代 string */
export type ProjectId = string & { readonly __brand: 'ProjectId' }
/** 任务 ID — branded newtype,替代 string */
export type TaskId = string & { readonly __brand: 'TaskId' }
/** 对话 ID — branded newtype,替代 string */
export type ConvId = string & { readonly __brand: 'ConvId' }
/** 工程/模块 ID — branded newtype,替代 string */
export type ModuleId = string & { readonly __brand: 'ModuleId' }
/** 消息 ID — branded newtype,替代 string */
export type MessageId = string & { readonly __brand: 'MessageId' }
// ============================================================
// 灵感
// ============================================================
// IdeaRecord.status union 类型 — 值集对齐后端 IdeaStatus serde (df-types/src/types.rs:52)
// snake_case: draft | pending_review | approved | rejected | promoted | archived
export type IdeaStatus = 'draft' | 'pending_review' | 'approved' | 'rejected' | 'promoted' | 'archived'
export interface IdeaRecord {
id: string
title: string
description: string
status: IdeaStatus
priority: number
score: number | null
tags: string | null // JSON 数组字符串
source: string | null
promoted_to: string | null
ai_analysis: string | null
scores: string | null
/** 关联灵感 id JSON 数组字符串(对齐后端 related_ids Option<String>),null/空串=无关联 */
related_ids: string | null
/** 归档标记(归档功能 P1,V47 列)。归档灵感退出主视图,仅在归档箱可见。 */
archived: boolean
created_at: string
updated_at: string
}
export interface CreateIdeaInput {
title: string
description?: string
priority?: number
tags?: string
source?: string
}
/**
* 灵感多条件查询入参(F-260621-02,对齐后端 IdeaQuery)。
*
* 所有字段可选;全空 → 后端返回全量(created_at DESC)。
* - `status`:状态精确匹配(后端 WHERE,取代前端 filter)
* - `keyword`:title/description LIKE %kw%(后端 LIKE,取代前端 filter)
* - `order_by`:白名单 created_at/updated_at/priority/status/score(后端白名单校验防注入)
* - `limit`/`offset`:分页(limit 钳制上限 200)
*/
export interface IdeaQuery {
status?: string | null
keyword?: string | null
order_by?: string | null
limit?: number | null
offset?: number | null
}
/**
* 灵感评估历史记录(后端 list_idea_evaluations 返回)。
* 每次评估(启发式/LLM/降级)落一行,version 递增,按 version DESC 返回。
* ai_analysis / scores 为 JSON 字符串(对齐 IdeaRecord 同名字段)。
*/
export interface IdeaEvaluationRecord {
id: string
idea_id: string
version: number
ai_analysis: string | null
scores: string | null
score: number | null
evaluated_by: string | null
evaluated_at: string
}
// ============================================================
// 项目
// ============================================================
// ProjectRecord.status union 类型 — 值集对齐后端 ProjectStatus serde (df-types/src/types.rs:90)
// snake_case: planning | in_progress | testing | releasing | completed | paused | cancelled
// 注:前端 constants/dashboard 的 'active' 是 UI 虚拟派生态(非 record 落库值),不进此 union。
export type ProjectStatus =
| 'planning' | 'in_progress' | 'testing' | 'releasing' | 'completed' | 'paused' | 'cancelled'
export interface ProjectRecord {
id: ProjectId
name: string
description: string
status: ProjectStatus
idea_id: string | null
/** 绑定的本地代码目录绝对路径(null=未绑定) */
path: string | null
/** 技术栈 JSON 数组字符串(如 ["rust","vue"],null=未探测) */
stack: string | null
/** 归档标记(归档功能 P2,V48)。true=归档态,退出主列表仅归档箱可见;详情页据此区分「已归档」与「不存在」 */
archived?: boolean
created_at: string
updated_at: string
/**
* 最近活跃时间(问题3):COALESCE(project_events 最新事件 created_at, updated_at)。
* 反映业务活跃(任务/灵感/状态推进等事件),无事件回退 updated_at。
* 可选(旧后端/回收站等路径不返):消费方需用 ?? updated_at 兜底。
*/
last_active_at?: string
}
export interface CreateProjectInput {
name: string
description?: string
idea_id?: string
/** 绑定的本地代码目录(可选) */
path?: string
/** 技术栈 JSON 数组字符串(可选,空则后端自动探测) */
stack?: string
}
/** AI 扫描项目结果(预览用,用户确认后填入) */
export interface AiScanResult {
/** 项目摘要(空=LLM 未得出,需手填) */
description: string
/** 技术栈(规则 ∪ LLM,去重小写) */
stack: string[]
/** 项目类型(web/api/cli/library/desktop/mobile/monorepo/other) */
project_type: string | null
/** LLM 原始返回(降级时含错误信息) */
raw: string | null
}
/** 灵感晋升结果(后端 promote_idea 返回) */
export interface PromotionResult {
idea_id: string
project_id: ProjectId
promoted: boolean
reason: string
}
// ============================================================
// 任务
// ============================================================
// TaskRecord.status union 类型 — 值集对齐后端 TaskStatus serde (df-types/src/types.rs:131)
// snake_case: todo | in_progress | in_review | testing | done | blocked | cancelled | deferred
// 注:constants/project.ts 的 mapLegacyStatus 兜底老 DB 旧态(completed/review_ready/merged/abandoned),
// 仅在 taskStatusLabel/taskStatusClass 辅助函数内映射,record.status 本身经后端 is_valid 拦截无旧态。
export type TaskStatus =
| 'todo' | 'in_progress' | 'in_review' | 'testing' | 'done' | 'blocked' | 'cancelled' | 'deferred'
export interface TaskRecord {
id: TaskId
project_id: ProjectId
title: string
description: string
status: TaskStatus
priority: number // 0=critical, 1=high, 2=medium, 3=low
branch_name: string | null
assignee: string | null
workflow_def_id: string | null
base_branch: string | null
/** review 退回累计轮数(in_review→in_progress / testing→in_review 各 +1) */
review_rounds: number
/**
* 任务产出 JSON 字符串(决策 a:task 中心,产出跟 task 走)。
* schema: { text: string, model?: string, usage?: {...}, review?: {
* verdict: 'pass'|'fail'|'unknown', summary: string, suggestions: string[], ... } }
* 旧任务无产出时为 undefined。前端 parsedOutput try/catch 兜底解析。
*/
output_json?: string
/** 关联灵感 ID(F-260619-01,1对1 单向,任务→灵感)。未关联时为 undefined。 */
idea_id?: string
/**
* 管理维度池标记(知识图谱 Phase 1,V29 列)。与 status(执行维度)正交。
* 取值 backlog(需求池)/todo(待办池)/decision(待决策池)/active(执行中)/done(已完成)。
* 后端 DEFAULT 'todo' 保证老任务迁移后取值确定,前端始终收到该字段。
*/
queue: string
/** 父任务 ID(1 级嵌套,无孙任务,由 IPC 校验)。顶层任务(无父)为 null/undefined。 */
parent_id?: string | null
/**
* 结构化需求规格 JSON 字符串(知识图谱 Phase 1,V29 列)。
* 结构 { background, acceptance_criteria[], scope[], technical_design, custom_fields }。
* 老任务无结构化规格时为 undefined。
*/
content_json?: string
/**
* 归档标记(归档功能 P1,V46 列)。与 status/queue 正交的纯元数据:
* archived=true 的任务退出主视图(后端活跃查询恒过滤),仅在归档视图可见。
* 老任务后端 DEFAULT 0 保证该字段始终存在。
*/
archived: boolean
created_at: string
updated_at: string
}
export interface TaskTreeNode {
/** 父任务记录(含 parent_id/queue 等全字段) */
parent: TaskRecord
/** 父任务的直接子任务(1 级嵌套,无孙任务) */
children: TaskRecord[]
}
/** delete_task 新返回结构(破坏性变更):删除父任务时级联软删其子任务 */
export interface TaskDeleteResult {
ok: boolean
/** 级联软删的子任务数 */
cascaded: number
}
export interface CreateTaskInput {
project_id: ProjectId
title: string
description?: string
priority?: number
branch_name?: string
assignee?: string
/** 关联灵感 ID(1对1 单向,可空)。空字符串视为不关联(与后端一致)。 */
idea_id?: string
/** 管理池标记(backlog/todo/decision/active/done,可空)。不传后端默认 todo。 */
queue?: string
/** 父任务 ID(1 级嵌套,可空)。顶层任务不传;空字符串后端视为 None。 */
parent_id?: string | null
}
/**
* 任务多条件查询入参(F-260621-02,对齐后端 TaskQuery)。
*
* 所有字段可选;全空 → 后端返回全量未删任务(created_at DESC,等价改造前 list_active)。
* - `project_id`:项目精确匹配(命中 idx_tasks_project_id)
* - `status`:状态精确匹配(P1 下沉,命中 idx_tasks_status,取代前端 filter)
* - `priority`:优先级精确匹配(0=critical..3=low)
* - `assignee`:负责人精确匹配
* - `keyword`:title/description LIKE %kw%(P2,取代前端 filter)
* - `order_by`:白名单 created_at/updated_at/priority/status(后端白名单校验防注入),恒 DESC
* - `limit`/`offset`:分页(limit 钳制上限 500)
*
* 注:project_id/priority/assignee/order_by/limit/offset 为基建就绪,当前视图仅用
* status(P1)+keyword(P2);P3 排序分页 UI 待数据量增长。
*/
export interface TaskQuery {
project_id?: string | null
status?: string | null
priority?: number | null
assignee?: string | null
keyword?: string | null
order_by?: string | null
limit?: number | null
offset?: number | null
/** 管理池标记精确匹配(backlog/todo/decision/active/done,可空) */
queue?: string | null
/** 父任务 ID 精确匹配(取某父任务的全部子任务,可空) */
parent_id?: string | null
}
// ============================================================
// 工作流
// ============================================================
// WorkflowRecord.status union 类型 — 值集对齐后端 WorkflowStatus serde (df-types/src/types.rs:195)
// snake_case: pending | running | paused | completed | failed | cancelled
export type WorkflowStatus =
| 'pending' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled'
export interface WorkflowRecord {
id: string
name: string
dag_json: string
status: WorkflowStatus
triggered_by: string | null
project_id: string | null
task_id: string | null
created_at: string
completed_at: string | null
}
/** 工作流事件载荷(后端 emit 的结构) */
/** 工作流事件类型字面量联合(对应后端 WorkflowEvent serde tag=type rename_all=snake_case,R7收窄防写错 type 字符串) */
export type WorkflowEventType =
| 'node_started' | 'node_progress' | 'node_output' | 'node_completed' | 'node_failed' | 'node_cancelled'
| 'workflow_paused' | 'workflow_completed' | 'workflow_failed'
| 'human_approval_request' | 'human_approval_response'
export interface WorkflowEventPayload {
execution_id: string
event: {
type: WorkflowEventType
[key: string]: unknown
}
}
/** 工作流 DAG 节点实时状态(WorkflowDagDisplay 高亮 + workflow store 共享推导) */
export type NodeStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped'
// ============================================================
// 通用
// ============================================================
export type UpdateField = {
id: string
field: string
value: string
}
// ============================================================
// 数据变更联动刷新(AR-11 方案A)
// ============================================================
/**
* 后端 AI 工具(create/update/delete/restore/purge/bind_directory 等)执行成功后
* emit 的 "df-data-changed" 事件负载。前端 store listen 后按 entity 调对应 load 刷新列表。
*/
export interface DfDataChangedPayload {
/** 变更实体:project / task / idea */
entity: 'project' | 'task' | 'idea'
/** 变更动作:create / update / delete */
action: 'create' | 'update' | 'delete'
}
// ============================================================
// AI 聊天
// ============================================================
export interface AiProviderConfig {
id: string
name: string
provider_type: string
api_key: string
base_url: string
default_model: string
models: string | null
/** 模型能力配置数组4 维度 + 路由控制;DB TEXT 列 JSON 解析,老库可能缺失) */
model_configs?: ModelConfig[]
is_default: boolean
config: string | null
created_at: string
updated_at: string
/** provider 是否进入负载均衡池(c)。false=仅配置存在,不参与主链路由/选池。老库默认 true。 */
enabled?: boolean
/** provider 在负载均衡池中的选择权重(0-100,c)。高权重优先被选为主;同权重近似轮询。老库默认 50。 */
weight?: number
}
// ============================================================
// F-01 模型能力系统(ModelConfig,与 crates/df-ai-core/src/model.rs 严格对齐)
// ============================================================
/** 维度 1 — 模态(模型能接收什么输入)。serde snake_case 字面量。 */
export type Modality = 'text' | 'vision' | 'audio' | 'video'
/** 维度 2 — 能力(模型能做什么)。serde snake_case 字面量。 */
export type Capability = 'tool_use' | 'embedding' | 'code_gen'
/** 维度 3 — 价格分级(成本)。serde snake_case 字面量。'free' 已删(死档,预设/启发式从不赋值)。 */
export type CostTier = 'low' | 'medium' | 'high'
/** 维度 4 — 智力分级。serde snake_case 字面量。与 CostTier 同,cost/intel 已从路由硬过滤及前端标签去除,字段保留仅 DB 兼容,不再参与路由选模型。 */
export type IntelligenceTier = 'lite' | 'standard' | 'plus' | 'ultra'
/** 探测来源(只读,由后端探测器填充)。serde snake_case 字面量。 */
export type ProbeSource = 'user_set' | 'preset_table' | 'heuristic' | 'default'
/** 单模型完整配置(4 维度 + 路由控制 + 探测元数据)。 */
export interface ModelConfig {
/** 模型名(如 "glm-4v-flash") */
model_id: string
/** 启用/禁用(false=配了但不参与路由) */
enabled: boolean
/** 用户自定义别名(可选) */
label?: string
/** 模态集合 */
modalities: Modality[]
/** 能力集合 */
capabilities: Capability[]
/** 价格分级 */
cost_tier: CostTier
/** 智力分级 */
intelligence: IntelligenceTier
/** 权重(0-100,同能力候选中优先选权重高的) */
weight: number
/** 上下文窗口大小(tokens) */
context_window: number
/** 探测来源(可选) */
probe_source?: ProbeSource
}
/**
* AI 错误分类字面量集。
* 与后端 `commands::ai::ErrorType` 的 serde(rename_all = "snake_case") 严格对齐。
* 前端消费方 switch 于此常量集做差异化提示(auth 引导填 key / network 提示连接 / 等)。
*/
export type AiErrorType = 'auth' | 'network' | 'timeout' | 'provider_config' | 'unknown'
/**
* L2 统一状态机 — 对话生命周期状态字面量(对齐后端 ConvState serde rename_all=snake_case,
* crates/df-ai/.../agentic/conv_state.rs)。5 态:
* - idle:空闲,可接新请求
* - generating:生成中(含审批挂起期,审批是 loop 内暂停点非独立态)
* - stopping:停中(用户已点停止,loop 收尾中,瞬态)
* - error:出错(用户可重试回 generating)
* - compressed:压缩中(Generating/Idle 期间瞬态派生,压缩完成回原态)
*
* 批4 双轨收口后(2026-06-25):convStates(enum Map)是「会话是否生成中」的唯一真相源,
* 旧 generatingConvs bool 轨已退役。CONV_STATE_ENABLED=off 或老后端不发 AiConvStateChanged 时,
* useAiEvents.handleEvent 活跃事件兜底写 setConvState('generating') 作为 enum 写入口
* (替代旧 bool 轨 add),消费方不再回退 bool 判断。streaming 单值是另一条过渡态
* (F-09 per-conv streaming 收口前保留),不在本状态机口径内。
*/
export type ConvState = 'idle' | 'generating' | 'stopping' | 'error' | 'compressed'
/** AI 聊天事件(后端 emit 的结构,conversation_id 用于多对话流式路由) */
export type AiChatEvent = ({
type: 'AiTextDelta'; delta: string
} | {
type: 'AiToolCallStarted'; id: string; name: string; args: unknown
} | {
type: 'AiToolCallCompleted'; id: string; result: unknown
} | {
// AE-2025-04 会话级信任:同会话已批准过同类操作(同工具+同目录)自动放行,前端显示轻量 toast。
// 不写消息/不动 pending(Started/Completed 仍独立发),仅提示性事件。
type: 'AiToolAutoApproved'; id: string; tool: string; dir: string
} | {
// 技能注入反馈闭环:/ 技能 resolve 成功(ok=true)/失败(ok=false,未找到或读失败)通知,
// 前端轻 toast(成功「已注入技能《name》」/失败「技能 name 未找到」)。仅提示,不改消息流。
type: 'AiSkillInjected'; name: string; ok: boolean
} | {
// 工具执行实时流式输出(run_command shell 行级 / ask_uclaw 进度块级):
// 后端经 task-local sink(command_stream / tools/uclaw)emit,前端按 id 路由到对应
// 工具卡,在 Started→Completed 间实时展示。合批在后端(50ms/4KB 或 u-claw 2s 节流)。
// stream: "stdout"|"stderr"(来源,前端差异化着色);line:累积文本块(多行 '\n' 连接)。
type: 'AiCommandOutput'; id: string; stream: string; line: string
} | {
// path_auth 审批链阶段3b(统一审批模型):AiApprovalRequired 携带 kind 字段区分
// 'path'(F-260619-03 Phase B 路径授权挂起,走 once/session/always/deny)vs 'risk'(普通 RiskLevel 审批,
// 走 approve/reject)。后端阶段3a 未在 wire 上给该事件加 kind(老 path 挂起仍走独立
// AiDirAuthRequired 事件),前端 useAiEvents 入口处据 event.type 归一打标:risk 类默认 'risk',
// path 类(AiDirAuthRequired 归一)打 'path'。故此处 kind 为可选(老后端不传时前端默认 'risk')。
// 开关 df-ai-unified-approval:true=归一进 pendingApprovals 带 kind;false=回退 DirAuthDialog 老链路。
type: 'AiApprovalRequired'; id: string; name: string; args: unknown; reason: string; diff?: string
kind?: 'path' | 'risk'
} | {
type: 'AiApprovalResult'; id: string; approved: boolean
} | {
// UX-2025-04 / CR-30-2 / 决策 F-260616-07 a1: incomplete 标记流中途失败保文(网络中断),
// 前端据此差异化展示(如系统提示「⚠ 响应因网络中断不完整」)。正常完成/停止均为 undefined。
type: 'AiCompleted'; total_tokens: number; prompt_tokens: number; completion_tokens: number
/** token 分项显示(2026-08-02):cache 命中(低价,deepseek prompt_cache_hit/anthropic cache_read) */
prompt_cache_hit_tokens: number
/** 未命中(全价真实输入,deepseek prompt_cache_miss/anthropic cache_creation)。前端 in 显示用此 */
prompt_cache_miss_tokens: number
/** 思考(deepseek-reasoner/o1 reasoning_tokens,隐藏输出)。0=非 reasoning 模型 */
reasoning_tokens: number
/**
* G4.3:该轮 token 用量是否估算值(后端 agentic 运行时兜底:round_usage.prompt_tokens==0
* 时用 estimated_prompt 填充并打标)。true=per-response 估算(prompt 非真实,provider 未报),
* undefined/false=真实值。前端 MessageList 据此标注『估算』区分 estimated vs real。
*/
is_estimated?: boolean
/**
* 看板 953: 最终回复是否因 provider max_tokens 上限被截断(finish_reason=length)。
* true=正常收敛但内容被腰斩,MessageList 末条 assistant 气泡渲染截断提示条 + 继续输出按钮;
* undefined/false=完整(旧后端事件无此字段,按 false 兜底,向后兼容)。
*/
truncated_by_max_tokens?: boolean
/**
* incomplete 场景细分(达上限自动收尾 vs 网络中断,前端据此区分提示文案):
* "max_rounds"=达最大轮次后端自动收尾;其他/缺省=网络中断等异常。
*/
incomplete_reason?: string
incomplete?: boolean; pinned_goals?: GoalEntry[]
} | {
type: 'AiError'; error: string; error_type?: AiErrorType
} | {
// F-260622-02 跨端用户消息同步:远程(miniapp)发来的用户消息,后端 route_send_message
// 放行后 publish_event 广播。桌面 useAiEvents 收到后补 user 气泡(桌面本地 sendMessage 是
// 乐观渲染不发此事件)。handleEvent 去重防双气泡(末条已是同 content user 跳过)。
type: 'AiUserMessage'; message: string
} | {
type: 'AiAgentRound'; round: number
} | {
type: 'AiHeartbeat'
} | {
// 达 max_iterations 暂停态(后端仍 generating=true),前端展示操作卡询问继续/停止
type: 'AiMaxRoundsReached'
} | {
// F-260619-03 Phase B: 路径授权弹窗(LLM 想访问白名单外目录,后端挂起 loop 等用户决定)。
// 前端弹窗四选项:"本次"(once 执行后清)/"当前会话"(session 切会话清)/"始终放行"(persistent)/"拒绝" → ai_authorize_dir IPC。
type: 'AiDirAuthRequired'; id: string; tool: string; path: string; dir: string
} | {
// L1 求助协议(aichat 体验与 agent 能力系统化重构 §2.3,2026-06-21):
// agent 断路器熔断 / 自省时发,结构化求助(区别于 prompt 教 AI 主动问用户——LLM 不可靠)。
// 字段对齐后端 AiChatEvent::AiHelpRequired:reason(原因摘要)/context(已试情况)/options(建议选项)。
// 前端渲染求助卡显 reason + options 按钮供用户选,与 AiApprovalRequired(工具执行前审批)/
// AiMaxRoundsReached(达 max 被动截断)语义区分——求助=AI 主动「我搞不定」。
type: 'AiHelpRequired'; reason: string; context: string; options: string[]
} | {
// 无人值守安全停靠(设计 §四,挂起家族终局):六路非正常终局(审批超时取消/挂起后取消/
// 熔断/预算耗尽/Fatal/流中断)统一出口。语义:现场已保存、generating 已复位(非死等),reason 即
// 待办清单(停止原因+被拒操作提示),前端渲染停靠操作卡(继续续跑/停止归档)。
// stop_kind 词表(后端契约钉死,复用 AiAgentFinished 词表扩展):approval_timeout / cancelled /
// circuit_breaker / stall_breaker / max_rounds / fatal / midstream;后端新增变体时前端未知值中性兜底。
type: 'AiTaskParked'; reason: string; stop_kind?: string
} | {
// 无人值守进度可观测(设计 §4.3):后端主 loop 每 5 轮 emit,长任务静默期(子代理长跑/
// 大工具执行/重入退避)可见「在干活」。round 1 起算;max_rounds=0=不限轮次(进度条只显轮次
// 与 token,不显分母)。事件即逝,前端 per-conv Map 存最近快照,终局(AiCompleted/AiError/
// AiHelpRequired/AiTaskParked)与会话删除时清理。
type: 'AiTaskProgress'; round: number; max_rounds: number; tokens_used: number
} | {
// L2 统一状态机(批2 1b/1c):ConvState 经 AiChatEvent 推前端,供 status union 消费。
// conv_state 值为 idle|generating|stopping|error|compressed(snake_case,对齐后端 ConvState serde)。
// 后端在 conv_state 变化时(入口→generating / reset·drop→idle)emit;conversation_id 标识
// 哪个会话的状态变了(F-09 多会话并发下前端按 convId 路由)。批4 双轨收口后:CONV_STATE_ENABLED=off
// 或老后端不发本事件时,useAiEvents.handleEvent 活跃事件兜底写 setConvState('generating') 作为
// enum 写入口(替代旧 bool 轨 add),消费方不再回退 streaming/generating bool 判断。
type: 'AiConvStateChanged'; conv_state: ConvState
} | {
// 流式调用自动重试提示(前端可在错误气泡内显示「重试 n/m」)
type: 'AiStreamRetry'; attempt: number; max_attempts: number
} | {
// F-15 上下文管理生命周期事件(原 useAiContext 独立监听器,现合入统一分发器)
// 后端在手动/自动压缩、清空上下文时 emit,前端据此复位 loading + toast/刷新。
type: 'AiCompressing'
} | {
// 手动压缩成功(IPC compressContext 完成后 emit):带摘要供前端回填 system 消息
type: 'AiManualCompressed'; summary: string
} | {
// 自动压缩成功(loop 内超预算压缩):前端静默复位 loading(不弹 toast,不打断流)
type: 'AiAutoCompressed'; summary: string
} | {
// 上下文已清空:前端刷新消息列表 + toast 提示
type: 'AiContextCleared'
}) & {
conversation_id?: ConvId
}
/**
* 多模态消息内容片(F-260614-05 Phase 2b 前端类型,对齐后端 df-ai-core ContentPart)。
*
* 后端 serde:`#[serde(tag = "type", rename_all = "snake_case")]`,
* 前端 discriminator 用字面量 `type` 字段('text' | 'image'),与后端 wire 格式严格对齐。
* Image 片:url / base64 二选一(base64 非空时 url 忽略),media_type 仅 base64 模式必填。
*/
export type ContentPart =
| { type: 'text'; text: string }
| {
type: 'image'
url?: string | null
base64?: string | null
/** base64 模式必填(image/png|jpeg|webp|gif);url 模式可空 */
media_type?: string | null
/** 可选 alt(vision 模型/降级文本时用) */
alt?: string | null
}
/** AI 消息(前端渲染用,isError 标记错误消息用于差异化样式) */
export interface AiMessage {
id: MessageId
role: 'user' | 'assistant' | 'tool' | 'system'
content: string
/**
* 消息来源(958 溯源/961 轮次归属):undefined=null=桌面本端发起;'inject'=运行中
* 补充注入(961② 轮次分桶判据,归属当前轮不开新轮);'feishu'=飞书入站发起。
*/
source?: { channel: string; message_id?: string; chat_id?: string; sender?: string } | null
/**
* 推理过程文本(DeepSeek reasoning_content / Anthropic thinking 同通道,后端落库
* ai_messages.reasoning_content)。undefined=该轮无思考内容。历史消息 reload 时
* 由 useAiConversations parseConvMessages 透传;思考 pill 以此为数据源,content 兜底。
*/
reasoning_content?: string
isError?: boolean
/**
* 流式重试过渡标记(AiStreamRetry 推送的过渡气泡,前端内存专用不落库)。
* true=重试进行中(MessageList 渲染为琥珀单行细条,不走错误红卡);终态错误
* (AiError)不带此标记,仍走错误卡与重试按钮。会话收尾清残留按此标记匹配,
* 不再依赖文案子串(多语言切换不影响清理)。
*/
isRetry?: boolean
/**
* 消息上下文状态(F-15 手动上下文管理)。
* null | undefined | 'active' → 正常消息
* 'archived_segment' → 清空上下文归档标记(前端折叠成"已归档 N 条"分隔条)
* 'compressed' → 压缩标记(后端不再发给 LLM;前端照常展开)
* 'truncated' → switchConversation 已过滤,前端不渲染
* 字面量联合:改 status 取值时编译器拦截,避免散布 string cast 失去类型校验。
*/
status?: 'active' | 'archived_segment' | 'compressed' | 'truncated' | null
toolCalls?: AiToolCallInfo[]
/** 生成该消息的 model(仅 assistant 消息,历史消息从 DB 读) */
model?: string
/**
* 多模态内容片(F-260614-05 Phase 2b)。
* undefined/空=纯文本消息(content 即全部载荷);非空含 Image 片=多模态消息,
* 用户气泡内渲染 <img>(base64 data URI 或 url)。仅 user 消息有(parts 来自粘贴/拖拽输入)。
* 历史消息从 DB JSON 反序列化时透传(useAiConversations switchConversation)。
*/
parts?: ContentPart[]
/**
* Input Augmentation:@ mention 区间元数据(仅 user 消息)。
*
* 用户选中 @项目/@任务/@灵感/`/技能` 时按选区记 {start,length,kind,refId,label},
* 随消息透传到后端 ai_chat_send 的 mention_spans 参数(后端 resolve 投影成 Augmentation 注入)。
* 前端 MessageList 据此精确切区间渲染 chip(替代正则,防误匹配)。
*
* undefined/空=无 mention(纯文本消息零回归);历史消息从 DB 反序列化时透传。
*/
mentionSpans?: MentionSpan[]
/**
* Input Augmentation:resolve 后的增强上下文(仅展示用,后端 resolve 的回传或落库快照)。
*
* serde 镜像后端 df-types Augmentation(tag=kind, snake_case 变体名)。
* 当前透传链路仅传 mentionSpans,augmentations 主要用于落库消息回显/调试;前端宽容对待
* (不强制校验变体字段,未知 kind 忽略,对齐后端「新增变体向后兼容」设计)。
*/
augmentations?: Augmentation[]
/**
* 这一轮对话的 token 用量(仅 assistant 消息,每轮 AiCompleted 时回填)。
* undefined=未记录(历史消息/未启用 token 显示)。
*
* token 分项显示(2026-08-02):tokenUsage 扩展 cache_hit/cache_miss/reasoning。
* in 显示用 cache_miss(全价真实),非 prompt(总,含 cache_hit 掩盖命中比例)。
* 老消息(无 cache 字段)cache_hit/cache_miss/reasoning undefined,前端 fallback prompt_tokens。
*/
tokenUsage?: {
prompt: number
completion: number
/** 缓存命中(低价) */
cache_hit?: number
/** 未命中(全价真实输入,前端 in 显示用此) */
cache_miss?: number
/** 思考 token(隐藏输出) */
reasoning?: number
/**
* G4.3:该轮 token 用量是否估算值(后端 AiCompleted.is_estimated 透传)。
* true=prompt_tokens 缺失按估算展示,MessageList 标注『估算』;undefined=false=真实值。
*/
is_estimated?: boolean
}
/**
* 本轮输入 token(消息级持久化,后端 ChatMessage.prompt_tokens 镜像)。
* reload 时由 switchConversation 映射回 tokenUsage(历史 assistant 消息 token 回显)。
* 仅 assistant 消息有;undefined=老消息(无此列时 DB 返 NULL)。
*/
prompt_tokens?: number
/** 本轮输出 token(语义同 prompt_tokens)。 */
completion_tokens?: number
/** 缓存命中 token(低价)。老消息 undefined(向前兼容)。 */
prompt_cache_hit_tokens?: number
/** 未命中 token(全价真实输入,前端 in 显示用此)。老消息 undefined。 */
prompt_cache_miss_tokens?: number
/** 思考 token(deepseek-reasoner/o1 reasoning_tokens)。老消息 undefined。 */
reasoning_tokens?: number
/**
* 看板 953: 该 assistant 回复因 provider max_tokens 上限被截断(live 态 AiCompleted 事件透传标记)。
* 末条 assistant 带此标记时 MessageList 渲染截断提示条 + 继续输出按钮;用户再发消息/
* 继续输出后新气泡顶掉末条位置,标记自然失效,无需清除逻辑。仅 live 内存标记不入库
* (reload 后历史消息不带,截断提示属当轮即时性信息,可接受口径)。
*/
truncatedByMaxTokens?: boolean
timestamp: number
}
// ============================================================
// Input Augmentation 层类型(@ mention / / skill 统一增强)
// ============================================================
//
// 与后端 crates/df-types/src/augmentation.rs 严格对齐:
// - MentionSpanDto(start/length/kind/refId/label)→ 前端 MentionSpan
// - Augmentation(serde tag=kind, snake_case)→ 前端 Augmentation 宽松镜像
// - SanitizedPath(String newtype, serde transparent = 裸字符串)→ 前端 type SanitizedPath = string
//
// refId 用 camelCase(后端 serde rename = "refId",与前端 TS 风格一致);
// Augmentation 内部字段沿用后端 snake_case(path/project_name 等,与 wire 严格对齐,不做转译)。
/**
* 用户消息内 mention 区间的元数据(对齐后端 MentionSpanDto)。
*
* 前端按 {start, length} 在原文中切出区间并替换为 chip;区间被编辑破坏(长度/内容不再匹配)
* 时降级为纯文本展示,避免正则误匹配。
*
* 注意字段名:refId(camelCase,后端 serde rename)。
*/
export interface MentionSpan {
/** 区间起点(字符偏移,前端约定,后端仅透传不解释) */
start: number
/** 区间长度 */
length: number
/** kind 标签:project / task / idea / skill */
kind: 'project' | 'task' | 'idea' | 'skill'
/** 引用稳定标识(Project/Task/Idea 为 id,Skill 为 name),前端用于反查 */
refId: string
/** chip 展示文本(项目名/任务标题/技能名) */
label: string
}
/**
* 脱敏后的文件系统路径(对齐后端 SanitizedPath newtype)。
*
* 后端 serde(transparent)在 wire 上即裸字符串,前端用 string 别名即可。
* Local provider(localhost/127.0.0.1/...)保留全路径;Remote 仅 basename。
*/
export type SanitizedPath = string
/**
* Augmentation 变体集合字面量(对齐后端 serde tag=kind, rename_all=snake_case)。
*/
export type AugmentationKind = 'project' | 'task' | 'idea' | 'skill'
/**
* resolve 后的增强上下文(对齐后端 Augmentation)。
*
* 宽松镜像:tag=kind 区分变体,各变体字段按需声明(后端新增字段时前端不必同步改,
* 多余字段 JSON 解析保留不报错)。前端主要读 name/title/path 用于 chip/展示,
* 需读具体变体字段时用 type narrowing(kind === 'project' 等)。
*/
export type Augmentation = {
kind: AugmentationKind
/** 变体特有字段按需读取(用 type narrowing 访问);索引签名容纳后端新增字段 */
[field: string]: unknown
}
// AiToolCallInfo.status union 类型 — 前端构建(后端无对应 struct,经 audit log/stream event 组装)。
// 值集核验:grep src/composables/ai + src/components 实际使用点 = {running,pending_approval,completed,rejected}。
// 前端为权威源(无后端 enum 约束),保持现状仅抽 alias 便于复用。
export type AiToolCallStatus = 'running' | 'pending_approval' | 'completed' | 'rejected'
/** 工具调用信息 */
export interface AiToolCallInfo {
id: string
name: string
args: unknown
status: AiToolCallStatus
result?: unknown
/** 审批风险说明(AiApprovalRequired 时填充) */
reason?: string
/** write_file 行级 unified diff(旧文件 vs 新内容),审批卡即时预览。
* 旧文件不存在(新建)或非 write_file 为 undefined,前端回退显新 content。 */
diff?: string
/**
* path_auth 审批链阶段3b(统一审批模型):审批类型,区分两类挂起。
* - 'path'(F-260619-03 Phase B 路径授权挂起):ToolCard 显 once/session/always/deny 四选项,
* 调 aiApi.authorizeDir(once 写 once 层单次执行后清 / session 写会话临时 / always 写持久白名单 / deny 工具返 Err)。
* - 'risk'(普通 RiskLevel 审批,Med/High 风险工具):ToolCard 显 approve/reject 两选项,
* 调 aiApi.approve(一次性)。
* undefined = 未进 pending_approval 态(非挂起)或老后端 risk 类(默认 'risk')。
* 开关 df-ai-unified-approval 控制 path 类是否归一进 pendingApprovals 带此字段。
*/
kind?: 'path' | 'risk'
/**
* path_auth 审批链阶段3b:'path' 类挂起携带的目录信息(规范化后的待授权目录)。
* AiDirAuthRequired 归一为 pendingApprovals 项时透传,ToolCard 显提示"LLM 想访问 X 目录"。
* 'risk' 类为 undefined。
*/
dir?: string
/** path_auth 审批链阶段3b:'path' 类挂起携带的原始路径(工具调用参数中的 path)。
* ToolCard 审批提示文案展示用(LLM 想访问 path,父目录为 dir)。'risk' 类为 undefined。 */
path?: string
/** 工具开始执行时间(前端在 AiToolCallStarted 时记录,毫秒)。历史消息从 DB 回载无此字段,
* 后端事件协议不含耗时,卡片耗时/轨迹总耗时由前端时间差计算,缺字段时隐藏不显 0。 */
startedAt?: number
/** 工具执行完成时间(前端在 AiToolCallCompleted 时记录,毫秒)。语义同 startedAt。 */
completedAt?: number
/**
* 执行中实时流式输出(AiCommandOutput 事件累积):run_command shell 行级 /
* ask_uclaw 进度块级。数组按到达序追加,ToolCard running 态实时渲染(尾部 N 行),
* Completed 时清空(真实结果由 result 字段渲染,防双显)。
* undefined = 无流式输出的工具或历史消息(DB 回载不含此内存态)。
*/
streamLines?: string[]
/**
* A2-B10 conv-scoped 审批收尾:挂起审批所属的会话 id(前端判定用,仅挂起项设置)。
* 来源:AiApprovalRequired / AiDirAuthRequired(统一审批归一)事件透传 conversation_id
* (缺失时兜底 activeConversationId);switchConversation 恢复历史挂起时填目标会话 id。
* cleanupTerminatedConversation 据此仅清目标会话的待审批项 + 对称清其审批超时计时器,
* 不连累并发会话正在审批的卡(conv-scoped,误清其他会话审批 = 回归)。
* undefined = 非挂起项或无法归属(保守保留,不清)。对齐 PendingDirAuth.conversationId。
*/
conversationId?: string
}
/** 对话列表摘要 */
export interface AiConversationSummary {
id: ConvId
title: string | null
provider_id: string | null
model: string | null
pinned_goals?: GoalEntry[]
archived: boolean
pinned?: boolean
prompt_tokens?: number | null
completion_tokens?: number | null
created_at: string
updated_at: string
/** V49 断点续跑:running=进行中 / parked=中断可恢复 / null=无任务(2026-10-06 读侧透出) */
loop_state?: 'running' | 'parked' | null
}
/** G1 目标钉扎:单条目标记录(含状态追踪) */
export interface GoalEntry {
text: string
/** "active" | "completed" */
status: 'active' | 'completed'
}
/** 审批选择类型single=单选默认,multiple=多选) */
export type ApprovalSelectType = 'single' | 'multiple'
/** 人工审批请求 */
export interface HumanApprovalRequest {
execution_id: string
node_id: string
title: string
description: string
options: string[]
/** 缺省 single(向后兼容),multiple 时允许多选 */
select_type?: ApprovalSelectType
}
/** 人工审批响应 */
export interface HumanApprovalResponse {
execution_id: string
node_id: string
decision: string
/** 多选结果(single 时长度=1,multiple 时长度≥1) */
decisions?: string[]
comment?: string
}
/** 切换对话返回(含 messages JSON) */
export interface AiConversationDetail {
id: ConvId
title: string | null
messages: string // JSON string of ChatMessage[]
/** 生成中返回 true(后端 ai_conversation_switch 在 generating 时置),前端据此禁编辑*/
readonly?: boolean
}
/** 技能信息(本机 Claude skill,供 `/` 联想 + 注入) */
export interface SkillInfo {
name: string
description: string
argument_hint?: string
/** skill | project | command | plugin(project 为项目级 .claude/skills 扫描来源) */
source: string
/** SKILL.md 绝对路径(后端注入时读全文) */
path: string
}
/** AI 工具风险元数据(ai_list_tools 投影;risk 对齐后端 RiskLevel serde 小写) */
export interface AiToolMeta {
name: string
risk: 'low' | 'medium' | 'high'
}
// ============================================================
// 无人值守长任务(设计 docs/02-架构设计/专项设计/无人值守长任务-设计-2026-10-04.md §二/§2.3)
// 与后端 commands/ai/boundary.rs 严格对齐,snake_case 原样不做转译
// ============================================================
/** 任务预算(对齐后端 boundary::TaskBudget;null/缺省=不限,继承后端默认量纲) */
export interface TaskBudget {
max_rounds: number
max_tokens: number
wall_secs: number
}
/**
* 任务级授权边界(挂会话,随会话同亡;发起时一次声明,存续期内 AI 全自主)。
* risk_ceiling serde lowercase;入口只允许 low/medium(后端 set 把 high 钳到 medium)。
* forbid_destructive 恒 true 仅展示,后端判定不读本字段。
*/
export interface TaskBoundary {
risk_ceiling: 'low' | 'medium' | 'high'
dir_allowlist: string[]
forbid_destructive: boolean
budget: TaskBudget | null
}
// ============================================================
// 知识库
// ============================================================
/** 知识条目(与 Rust KnowledgeRecord 严格对齐) */
export interface KnowledgeRecord {
id: string
kind: string // review_rule | prompt_template | pitfall | architecture_pattern | diagnosis | deployment_note | workflow_optimization
title: string
content: string
tags: string | null // JSON 数组字符串
status: string // candidate | pending_review | published | archived
confidence: string | null // high | medium | low
reuse_count: number
verified: boolean
source_project: string | null
source_ref: string | null
/** AI 提炼判断依据("为何值得沉淀"),手动录入为 null */
reasoning: string | null
created_at: string
updated_at: string
}
/** 创建知识入参 */
export interface CreateKnowledgeInput {
kind: string
title: string
content: string
tags?: string
source_project?: string
source_ref?: string
confidence?: string
}
/** 检索知识入参 */
export interface KnowledgeSearchInput {
query: string
kind?: string
limit?: number
}
/** 知识生命线事件记录(产生/审核/引用/归档审计) */
export interface KnowledgeEventRecord {
id: string
knowledge_id: string
/** created | extracted | status_changed | referenced */
event_type: string
source_ref: string | null
/** JSON 字符串,内容因 event_type 而异(见后端 KnowledgeTimeline) */
context_json: string | null
timestamp: string
}
/** 知识详情聚合负载(基本信息 + 全部生命线事件) */
export interface KnowledgeDetailPayload {
knowledge: KnowledgeRecord
events: KnowledgeEventRecord[]
}
/** 编辑知识入参(部分更新,仅传需改字段) */
export interface UpdateKnowledgeInput {
title?: string
content?: string
tags?: string
confidence?: string
reasoning?: string
}
/** 知识库行为配置(提取 + 注入 + 向量检索) */
export interface KnowledgeConfig {
auto_extract: boolean
trigger_mode: 'on_complete' | 'manual_only'
min_messages: number
auto_inject: boolean
/** 语义检索(向量)开关,默认 false——关闭时纯 LIKE 零外部依赖 */
vector_enabled: boolean
/** embedding 用的 provider id(仅 openai_compat 类型) */
embedding_provider_id: string | null
/** embedding 模型名(如 embedding-3) */
embedding_model: string | null
}
// ============================================================
// 多 Agent 并行执行(Plan/SubTask/Conflict)
// ============================================================
/** Plan 执行状态 */
export type PlanStatus = 'planning' | 'executing' | 'merging' | 'done' | 'error'
/** SubTask 执行状态 */
export type SubTaskStatus = 'pending' | 'running' | 'done' | 'error'
/** 冲突解决状态 */
export type ConflictResolution = 'pending' | 'a' | 'b' | 'merged' | 'manual'
/** 冲突类型 */
export type ConflictType = 'file' | 'semantic'
/** Plan 记录 */
export interface PlanRecord {
id: string
conversation_id: string
user_message_id: string | null
status: PlanStatus
subtask_count: number
created_at: string
completed_at: string | null
}
/** SubTask 记录 */
export interface SubTaskRecord {
id: string
plan_id: string
persona_id: string | null
intent: string
status: SubTaskStatus
layer: number
/** 依赖的 SubTask id 列表(JSON 数组字符串,需 parse) */
deps: string | null
/** Git worktree 分支名(null=非 Git 工程降级串行) */
branch: string | null
created_at: string
completed_at: string | null
}
/** 冲突记录 */
export interface ConflictRecord {
id: string
plan_id: string
file_path: string
conflict_type: ConflictType
subtask_a: string | null
subtask_b: string | null
diff_a: string | null
diff_b: string | null
resolution: ConflictResolution
resolved_by: 'reviewer' | 'user' | null
created_at: string
resolved_at: string | null
}
/** 冲突信息(AiMergeCompleted 事件载荷) */
export interface ConflictInfo {
id: string
file_path: string
conflict_type: ConflictType
subtask_a: string | null
subtask_b: string | null
diff_a?: string | null
diff_b?: string | null
}