feat: release v0.84.0 with full pending changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
coso
2026-03-12 00:34:23 +08:00
co-authored by Claude Opus 4.6
parent bfacf764b7
commit 2aa83ee7fc
268 changed files with 21727 additions and 6006 deletions
+4
View File
@@ -11,6 +11,7 @@ AI Agent 专用文档目录,提供模块级别的详细说明。
### 核心系统
- `overview.md` - 项目架构概览
- `governance.md` - **治理第一原则**(新旧并存、迁移收口、禁止回流)
- `providers.md` - Provider 系统(OAuth/API Key 认证)
- `credential-pool.md` - 凭证池管理(负载均衡、健康检查)
- `converter.md` - 协议转换(OpenAI ↔ CW/Claude)
@@ -47,6 +48,9 @@ AI Agent 在处理特定模块时,应先阅读对应的 aiprompts 文档:
# 处理 Provider 相关任务
→ 先读 docs/aiprompts/providers.md
# 处理新旧并存、迁移、重构、架构收口
→ 先读 docs/aiprompts/governance.md
# 处理凭证池相关任务
→ 先读 docs/aiprompts/credential-pool.md
+33 -25
View File
@@ -5,12 +5,14 @@
ProxyCast 已完整集成 aster-rust 框架,包括凭证池桥接。
**后端模块** (`src-tauri/src/agent/`):
- `aster_state.rs` - Agent 状态管理
- `aster_agent.rs` - Agent 包装器
- `event_converter.rs` - 事件转换器
- `credential_bridge.rs` - 凭证池桥接
**Tauri 命令** (`src-tauri/src/commands/aster_agent_cmd.rs`):
- `aster_agent_init` - 初始化 Agent
- `aster_agent_configure_provider` - 手动配置 Provider
- `aster_agent_configure_from_pool` - 从凭证池配置 Provider(推荐)
@@ -67,37 +69,43 @@ ProxyCast 已完整集成 aster-rust 框架,包括凭证池桥接。
### 支持的凭证类型映射
| ProxyCast 凭证类型 | Aster Provider |
|-------------------|----------------|
| OpenAIKey | openai |
| ClaudeKey / AnthropicKey | anthropic |
| KiroOAuth | bedrock |
| GeminiOAuth / GeminiApiKey | google |
| VertexKey | gcpvertexai |
| CodexOAuth | codex |
| ClaudeOAuth | anthropic |
| AntigravityOAuth | google |
| ProxyCast 凭证类型 | Aster Provider |
| -------------------------- | -------------- |
| OpenAIKey | openai |
| ClaudeKey / AnthropicKey | anthropic |
| KiroOAuth | bedrock |
| GeminiOAuth / GeminiApiKey | google |
| VertexKey | gcpvertexai |
| CodexOAuth | codex |
| ClaudeOAuth | anthropic |
| AntigravityOAuth | google |
### 使用方式
> 治理约定:前端业务层不要直接 `invoke('aster_*')`,统一通过 `src/lib/api/agentRuntime.ts` 调用现役 Aster API。
```typescript
// 从凭证池配置(推荐)
const status = await invoke('aster_agent_configure_from_pool', {
request: {
provider_type: 'openai',
model_name: 'gpt-4',
},
session_id: 'my-session',
});
import {
configureAsterProvider,
sendAsterMessageStream,
} from "@/lib/api/agentRuntime";
// 配置 Provider
const status = await configureAsterProvider(
{
provider_name: "openai",
model_name: "gpt-4",
},
"my-session",
);
// 流式对话
await invoke('aster_agent_chat_stream', {
request: {
message: 'Hello',
session_id: 'my-session',
event_name: 'agent_stream',
},
});
await sendAsterMessageStream(
"Hello",
"my-session",
"agent_stream",
"workspace-id",
);
```
## 相关文档
+41 -35
View File
@@ -82,6 +82,7 @@ export function generateContentCreationPrompt(
</write_file>
**重要规则**:
- 标签前:先写一句引导语
- 标签后:写完成总结
- 标签内的内容会实时流式显示在右侧画布
@@ -104,11 +105,12 @@ interface ParseResult {
// 解析 AI 响应
export function parseAIResponse(
content: string,
isStreaming: boolean
isStreaming: boolean,
): ParseResult;
```
**支持的标签类型**:
- `write_file` - 完整的文件写入
- `pending_write_file` - 流式传输中的文件写入
@@ -128,12 +130,12 @@ interface UseAgentChatOptions {
const sendMessage = async (content: string, ...) => {
let messageToSend = content;
const isFirstMessage = messages.filter(m => m.role === "user").length === 0;
if (systemPrompt && isFirstMessage) {
messageToSend = `${systemPrompt}\n\n---\n\n用户请求:${content}`;
}
await sendAgentMessageStream(messageToSend, ...);
await sendAsterMessageStream(messageToSend, ...);
};
```
@@ -154,7 +156,7 @@ interface Props {
// 解析 write_file 并触发回调
useEffect(() => {
if (!onWriteFile) return;
for (const part of parsedContent.parts) {
if (part.type === "write_file" && part.filePath) {
onWriteFile(part.content, part.filePath);
@@ -170,44 +172,47 @@ useEffect(() => {
```typescript
// src/components/agent/chat/index.tsx
const handleWriteFile = useCallback((content: string, fileName: string) => {
// General 主题使用专门的画布
if (activeTheme === "general") {
setGeneralCanvasState({
isOpen: true,
contentType: "markdown",
content,
filename: fileName,
});
const handleWriteFile = useCallback(
(content: string, fileName: string) => {
// General 主题使用专门的画布
if (activeTheme === "general") {
setGeneralCanvasState({
isOpen: true,
contentType: "markdown",
content,
filename: fileName,
});
setLayoutMode("chat-canvas");
return;
}
// 其他主题使用 CanvasFactory
setCanvasState(createInitialDocumentState(content));
setLayoutMode("chat-canvas");
return;
}
// 其他主题使用 CanvasFactory
setCanvasState(createInitialDocumentState(content));
setLayoutMode("chat-canvas");
}, [activeTheme]);
},
[activeTheme],
);
```
## 主题类型
| 主题 | 说明 | 文件体系 |
|------|------|----------|
| general | 通用对话 | 无固定文件 |
| social-media | 社媒内容 | brief.md → draft.md → article.md |
| poster | 图文海报 | brief.md → copywriting.md → design.md |
| music | 歌词曲谱 | song-spec.md → lyrics-draft.md → lyrics-final.txt |
| video | 短视频 | brief.md → outline.md → script.md |
| novel | 小说创作 | brief.md → outline.md → chapter.md |
| document | 办公文档 | brief.md → outline.md → draft.md |
| 主题 | 说明 | 文件体系 |
| ------------ | -------- | ------------------------------------------------- |
| general | 通用对话 | 无固定文件 |
| social-media | 社媒内容 | brief.md → draft.md → article.md |
| poster | 图文海报 | brief.md → copywriting.md → design.md |
| music | 歌词曲谱 | song-spec.md → lyrics-draft.md → lyrics-final.txt |
| video | 短视频 | brief.md → outline.md → script.md |
| novel | 小说创作 | brief.md → outline.md → chapter.md |
| document | 办公文档 | brief.md → outline.md → draft.md |
## 创作模式
| 模式 | 说明 | AI 行为 |
|------|------|---------|
| guided | 引导模式 | 通过表单逐步引导用户创作 |
| fast | 快速模式 | 收集需求后直接生成完整内容 |
| hybrid | 混合模式 | AI 写框架,用户填核心内容 |
| 模式 | 说明 | AI 行为 |
| --------- | -------- | --------------------------- |
| guided | 引导模式 | 通过表单逐步引导用户创作 |
| fast | 快速模式 | 收集需求后直接生成完整内容 |
| hybrid | 混合模式 | AI 写框架,用户填核心内容 |
| framework | 框架模式 | 用户提供框架,AI 按框架填充 |
## 注意事项
@@ -215,6 +220,7 @@ const handleWriteFile = useCallback((content: string, fileName: string) => {
### Aster 框架限制
Aster 框架的 `SessionConfig` 不支持 session 级别的 system prompt,因此采用**消息注入**方案:
- 在第一条用户消息前注入 systemPrompt
- 后续消息不再注入(避免重复)
+190
View File
@@ -0,0 +1,190 @@
# 治理第一原则
## 核心规则
**同一种能力,在同一时期只能存在一个继续演进的事实源。**
其余实现必须被明确归类为:
- `current`:当前唯一主路径,后续需求只允许往这里收
- `compat`:兼容层,只允许做委托/适配,不允许继续长新逻辑
- `deprecated`:废弃层,只允许迁移,不允许新增依赖
- `dead`:无入口或已停用,尽快删除
如果做不到这件事,系统就会持续膨胀而不是持续演进。
## 适用场景
当出现以下任一情况时,必须先读本文件,再决定是否改代码:
- 新旧 Hook、新旧组件、新旧命令并存
- 前端已经有新抽象,Rust 后端仍保留多套入口
- 新服务已经落地,但旧数据表、旧 DAO、旧旁路查询仍在使用
- 需求迭代后,AI 倾向继续沿用旧实现
- 想“先补功能,后面再统一”
## 强制执行规则
### 1. 先盘点,再修改
开始改动前,必须先盘点这项能力在 4 层里的实际分布:
- 入口层:页面、组件、Hook、前端 API 调用
- 服务层:Tauri 命令、Service、Workflow、事件入口
- 存储层:表、DAO、Repository、缓存
- 旁路层:统计、记忆、搜索、审计、报表、任务系统
如果没有盘点清楚,禁止直接开始“统一”。
### 2. 先定事实源,再谈迁移
必须先明确一句话:
> 从现在开始,这个能力以后只允许向哪里收敛。
这个事实源可以是:
- 一个 Hook
- 一个组件入口
- 一组 Rust 命令
- 一个 Service / Repository
- 一组数据表
没有唯一事实源,任何迁移都会继续长出新分支。
### 3. 兼容层只能做收口,不能做增强
兼容层存在的唯一理由是迁移。
兼容层允许:
- 参数转换
- 返回值适配
- 委托到新实现
- 迁移期埋点和告警
兼容层禁止:
- 新增业务逻辑
- 新增状态来源
- 新增独立存储
- 新增旁路能力
一旦兼容层承载新需求,它就不再是兼容层,而是新的分叉点。
### 4. 禁止回流,优先于“推荐新方案”
治理不能靠口头约定,必须靠守卫机制。
至少建立以下一种或多种守卫:
- ESLint / 静态规则禁止 import 旧入口
- Rust 对旧命令输出 `warn` 与调用统计
- CI 阻止新代码继续引用废弃路径
- 脚本扫描旧表、旧 DAO、旧命令、旧 Hook 的新增使用点
原则只有一句:
**不是鼓励走新路,而是封住老路。**
### 5. 主链路和旁路必须一起治理
如果只迁:
- 页面
- Hook
- 主命令
但没有迁:
- 统计查询
- 记忆系统
- 搜索召回
- 报表分析
那么旧表、旧命令、旧 DAO 永远删不掉。
治理完成的标准不是“页面能跑”,而是“系统生态都已收口”。
### 6. 删除必须有退出条件
每一个 `compat` 或 `deprecated` 路径,都必须有明确退出条件:
- 哪些调用迁完即可删
- 哪个版本必须删除
- 删除前要验证哪些指标
没有退出条件的兼容层,最终一定会常驻。
## 禁止事项
出现以下行为,视为违反治理原则:
- 在旧 Hook / 旧组件 / 旧命令上继续叠加新需求
- 新增与现役路径平级的第二套实现
- 前端迁了新入口,但 Rust 仍保留旧主逻辑继续演进
- 已有统一 Service,却继续让命令层各自写 SQL
- 主链路改到新表,旁路系统仍直接查旧表
- 看到“旧代码还能用”,就继续让 AI 沿旧上下文生成
## 推荐工作流
### 第一步:出迁移地图
至少列清楚:
- 当前主路径
- 兼容路径
- 废弃路径
- 无入口路径
### 第二步:写一句事实源声明
例如:
> 聊天能力后续统一收敛到 `useUnifiedChat + chat_* + ChatDao`。
### 第三步:让旧路径变成壳
旧入口不再承载真正逻辑,只负责:
- 兼容参数
- 委托新实现
- 输出告警
### 第四步:加守卫
至少加一条能自动失败的规则,阻止旧路径继续增长。
### 第五步:迁旁路
确认统计、记忆、搜索、报表等不再依赖旧实现。
### 第六步:删除
只有当新增依赖被封住、调用量清零、旁路迁完,才允许删旧路径。
## Proxycast 中的典型判断方式
以聊天系统为例,遇到新旧并存时,必须同时问这几个问题:
- 前端唯一入口是不是 `useUnifiedChat`,还是 `useChat` / `useAgentChat` 还在继续长逻辑?
- Rust 唯一入口是不是 `chat_*`,还是 `general_chat_*` / `agent_*` / `aster_agent_*` 还在平行演进?
- 数据事实源是不是同一组表 / 同一套 Repository,还是还在同时写 `agent_*` 与 `general_chat_*`?
- 统计、记忆等旁路是不是已经切到新路径,还是还在读旧表?
只要其中任意一个答案是否定的,就说明治理还没完成。
## AI 执行要求
未来 AI 在处理“新旧并存、迁移、重构、统一”类任务时,默认遵守以下要求:
1. 不允许直接在旧路径上继续扩展新功能,除非用户明确要求做兼容补丁。
2. 必须优先识别唯一事实源,并围绕事实源收口,而不是继续新增平级实现。
3. 必须显式说明当前改动属于 `current`、`compat`、`deprecated`、`dead` 中哪一类。
4. 如果发现主链路与旁路系统割裂,必须指出,不得假装治理已经完成。
5. 如果无法在本次改动中完成收口,至少要建立守卫,阻止问题继续扩散。
## 一句话总结
**治理不是继续写一个“更新的版本”,而是让系统以后只能向一个版本收敛。**
+36 -36
View File
@@ -91,13 +91,13 @@ pub struct AgentConstraints {
pub trait ToolExecutor: Send + Sync {
/// 执行工具
async fn execute(&self, input: ToolInput) -> Result<ToolOutput, ToolError>;
/// 工具名称
fn name(&self) -> &str;
/// 工具描述
fn description(&self) -> &str;
/// 参数 Schema
fn parameters_schema(&self) -> serde_json::Value;
}
@@ -135,19 +135,19 @@ impl AgentRuntime {
/// 执行 Agent 循环
pub async fn run(&mut self, user_input: &str) -> Result<AgentResponse, AgentError> {
self.messages.push(Message::user(user_input));
loop {
// 1. 调用 LLM
let response = self.provider.chat(&self.messages).await?;
// 2. 检查是否有工具调用
if let Some(tool_calls) = response.tool_calls {
// 3. 执行工具
let results = self.execute_tools(tool_calls).await?;
// 4. 将结果加入对话
self.messages.extend(results);
// 5. 检查约束
if self.check_constraints().is_err() {
break;
@@ -161,7 +161,6 @@ impl AgentRuntime {
}
```
---
## 三、消息格式与协议转换
@@ -204,10 +203,10 @@ pub struct ToolCall {
pub trait ProtocolConverter {
/// 转换为 Provider 格式
fn to_provider(&self, messages: &[Message]) -> ProviderRequest;
/// 从 Provider 格式转换
fn from_provider(&self, response: ProviderResponse) -> Message;
/// 转换工具定义
fn convert_tools(&self, tools: &[ToolDefinition]) -> Vec<ProviderTool>;
}
@@ -215,7 +214,7 @@ pub trait ProtocolConverter {
/// OpenAI 格式转换器
pub struct OpenAIConverter;
/// Claude 格式转换器
/// Claude 格式转换器
pub struct ClaudeConverter;
/// Gemini 格式转换器
@@ -241,11 +240,11 @@ impl StreamProcessor {
/// 处理流式数据块
pub fn process_chunk(&mut self, chunk: &str) -> Vec<StreamEvent> {
let mut events = Vec::new();
// 解析数据块
// 处理文本、工具调用等
// 生成事件
events
}
}
@@ -266,14 +265,14 @@ pub enum StreamEvent {
### 4.1 内置工具
| 工具 | 描述 | 参数 |
|------|------|------|
| `read_file` | 读取文件内容 | `path: string` |
| `write_file` | 写入文件 | `path: string, content: string` |
| `list_directory` | 列出目录内容 | `path: string, pattern?: string` |
| `search_files` | 搜索文件内容 | `pattern: string, path?: string` |
| `shell_command` | 执行 Shell 命令 | `command: string, cwd?: string` |
| `http_request` | 发送 HTTP 请求 | `url: string, method: string, ...` |
| 工具 | 描述 | 参数 |
| ---------------- | --------------- | ---------------------------------- |
| `read_file` | 读取文件内容 | `path: string` |
| `write_file` | 写入文件 | `path: string, content: string` |
| `list_directory` | 列出目录内容 | `path: string, pattern?: string` |
| `search_files` | 搜索文件内容 | `pattern: string, path?: string` |
| `shell_command` | 执行 Shell 命令 | `command: string, cwd?: string` |
| `http_request` | 发送 HTTP 请求 | `url: string, method: string, ...` |
### 4.2 工具注册机制
@@ -293,12 +292,12 @@ impl ToolRegistry {
self.register(Box::new(ShellCommandTool::new()));
// ...
}
/// 注册自定义工具
pub fn register(&mut self, tool: Box<dyn ToolExecutor>) {
self.tools.insert(tool.name().to_string(), tool);
}
/// 获取工具
pub fn get(&self, name: &str) -> Option<&dyn ToolExecutor> {
self.tools.get(name).map(|t| t.as_ref())
@@ -329,7 +328,7 @@ impl SecurityPolicy {
// 检查路径是否在允许范围内
// 防止路径遍历攻击
}
/// 检查命令是否允许
pub fn check_command(&self, command: &str) -> Result<(), SecurityError> {
// 检查命令是否在黑名单中
@@ -383,18 +382,20 @@ pub struct TokenUsage {
### 5.2 前端状态同步
```typescript
// src/stores/agentStore.ts
// 历史示例:现代实现请优先使用
// `src/lib/api/agentRuntime.ts` + `src/lib/api/agentStream.ts`
// 不要在业务层直接 invoke Agent/Aster 命令。
interface AgentState {
// Agent 定义
agents: AgentDefinition[];
currentAgent: string | null;
// 运行状态
isRunning: boolean;
phase: AgentPhase;
messages: Message[];
// 统计
tokenUsage: TokenUsage;
toolCallCount: number;
@@ -406,26 +407,25 @@ export const useAgentStore = create<AgentState>((set, get) => ({
agents: [],
currentAgent: null,
isRunning: false,
phase: 'idle',
phase: "idle",
messages: [],
tokenUsage: { input: 0, output: 0 },
toolCallCount: 0,
// Actions
startAgent: async (agentId: string, input: string) => {
set({ isRunning: true, phase: 'thinking' });
set({ isRunning: true, phase: "thinking" });
// 调用 Tauri 命令
await invoke('run_agent', { agentId, input });
await invoke("run_agent", { agentId, input });
},
stopAgent: async () => {
await invoke('stop_agent');
set({ isRunning: false, phase: 'idle' });
await invoke("stop_agent");
set({ isRunning: false, phase: "idle" });
},
}));
```
---
## 六、开发路线图
@@ -512,4 +512,4 @@ agent/
---
*本文档定义了 ProxyCast AI Agent 功能的架构设计,随着开发进展会持续更新。*
_本文档定义了 ProxyCast AI Agent 功能的架构设计,随着开发进展会持续更新。_