From e40fb2b673dac1c06aeae8a36c13f12ee0c87c2c Mon Sep 17 00:00:00 2001 From: Sopaco Date: Mon, 30 Mar 2026 00:32:42 +0800 Subject: [PATCH 1/7] Add AI context maintenance guidelines --- litho.docs/context-aware/adr-guide.md | 105 +++++++++++ litho.docs/context-aware/maintenance.md | 158 ++++++++++++++++ litho.docs/context-aware/methodology.md | 180 +++++++++++++++++++ litho.docs/context-aware/templates.md | 229 ++++++++++++++++++++++++ 4 files changed, 672 insertions(+) create mode 100644 litho.docs/context-aware/adr-guide.md create mode 100644 litho.docs/context-aware/maintenance.md create mode 100644 litho.docs/context-aware/methodology.md create mode 100644 litho.docs/context-aware/templates.md diff --git a/litho.docs/context-aware/adr-guide.md b/litho.docs/context-aware/adr-guide.md new file mode 100644 index 0000000..369fcbc --- /dev/null +++ b/litho.docs/context-aware/adr-guide.md @@ -0,0 +1,105 @@ +# Architecture Decisions Record (ADR) 编写指南 + +## 什么是ADR? + +记录**反直觉的设计决策**——那些AI无法从代码推断的"为什么"。 + +## 什么需要记录? + +| 需要记录 | 不需要记录 | +|---------|-----------| +| 违反常见模式 | 遵循最佳实践 | +| 有非显而易见的原因 | 原因显而易见 | +| 会让人困惑的设计 | 直观的设计 | +| 有替代方案但放弃了 | 没有其他选择 | + +## ADR模板 + +```markdown +## ADR-XXX: [简短标题] + +**Decision**: 做了什么决定 + +**Reason**: 为什么这样做(最重要) + +**Impact**: 影响哪些代码/模块 + +**Do not**: 什么不要做(可选) + +**Limitation**: 有什么限制(可选) +``` + +## 示例 + +### ✅ 好的ADR + +```markdown +## ADR-001: LoopAgent max_iterations=1 + +**Decision**: 所有Actor-Critic循环使用max_iterations=1 + +**Reason**: SequentialAgent有bug,exit_loop()会终止整个链而非仅循环。 +用max_iterations=1让LoopAgent自然完成。 + +**Impact**: 所有critical stages (PRD, Design, Plan, Coding) + +**Do not**: 修改此参数或迁移到SequentialAgent +``` + +### ❌ 不需要记录的 + +```markdown +## ADR-XXX: 使用async/await + +**Decision**: 使用async/await处理异步 + +**Reason**: 这是Rust标准做法 + +→ 这是最佳实践,不需要记录 +``` + +## 示例场景 + +### 场景1:性能换简洁 + +```markdown +## ADR-XXX: JSON存储而非SQLite + +**Decision**: 用JSON文件存储数据 + +**Reason**: 简化调试、易于检查、无外部依赖。数据量小、无并发需求。 + +**Limitation**: 不适合大规模并发场景 +``` + +### 场景2:Bug workaround + +```markdown +## ADR-XXX: 两步知识提升 + +**Decision**: Insight→Decision需要两个工具 + +**Reason**: 并非所有insight都应成为decision。 +中间步骤让人review确保质量。 + +**Impact**: memory tools, knowledge workflow +``` + +### 场景3:安全权衡 + +```markdown +## ADR-XXX: HITL超时默认Pass + +**Decision**: HITL确认超时时默认继续(Pass) + +**Reason**: 比无限阻塞好。用户可用PM Agent回退。 + +**Impact**: Stage执行, GUI超时处理 +``` + +## 维护原则 + +1. **只加不减**:ADR是历史记录,不建议删除 +2. **简短有力**:每个ADR不超过100字 +3. **聚焦原因**:Reason字段最重要 +4. **保持数量少**:一个项目通常只有5-15个ADR diff --git a/litho.docs/context-aware/maintenance.md b/litho.docs/context-aware/maintenance.md new file mode 100644 index 0000000..867724b --- /dev/null +++ b/litho.docs/context-aware/maintenance.md @@ -0,0 +1,158 @@ +# AI上下文维护实践 + +## 会话启动检测 + +在AGENTS.md中添加启动检测指令: + +```markdown +## 每次开始编程任务前 + +1. 运行: git diff --name-only HEAD~10 +2. 如果变更涉及关键目录,提醒用户更新文档 +``` + +### 关键目录映射 + +| 目录变更 | 提醒更新 | +|---------|---------| +| `tools/*.rs` | tools.md | +| `pipeline/stages/*.rs` | pipeline.md | +| `domain/*.rs` | domain-logic.md | +| 新增安全规则 | constraints.md | + +--- + +## 更新工作流 + +### 用户主动更新 + +``` +用户: "Update .ai-context because I added a new tool" + +Agent执行: +1. 读取 manifest.yaml 了解更新规则 +2. 读取对应文件(如 tools.md) +3. 添加条目到合适位置 +4. 保持格式一致 +``` + +### Agent检测更新 + +``` +Agent在会话启动时: +1. git diff --name-only HEAD~10 +2. 检查是否涉及关键目录 +3. 如有,提醒:"检测到[目录]变更,是否更新.ai-context?" +``` + +--- + +## 文档一致性检查 + +### 手动检查清单 + +- [ ] 新增工具是否在tools.md中有条目? +- [ ] 新增Stage是否在pipeline.md中有记录? +- [ ] 架构决策是否记录在architecture-decisions.md? +- [ ] 文档中的路径是否仍然有效? + +### 自动化检查(可选) + +```bash +# 检查tools.md中提到的文件是否存在 +grep -o "tools/[^.]*\.rs" .ai-context/domains/tools.md | while read f; do + [ -f "crates/cowork-core/src/$f" ] || echo "Missing: $f" +done +``` + +--- + +## 常见更新场景 + +### 场景1:新增工具 + +``` +用户: "我刚加了一个ValidateSchemaTool" + +Agent: +1. 读取 tools.md +2. 判断工具属于哪个分类(Validation) +3. 在对应表格添加:| ValidateSchemaTool | Validate JSON schema | +``` + +### 场景2:架构决策 + +``` +用户: "我决定用Redis替代内存缓存" + +Agent: +1. 读取 architecture-decisions.md +2. 添加ADR: + ## ADR-XXX: Redis for Caching + Decision: 使用Redis替代进程内缓存 + Reason: 需要跨进程共享、持久化 + Impact: memory模块 +``` + +### 场景3:新增模块 + +``` +用户: "我加了一个新的reporting模块" + +Agent: +1. 更新 modules.map 模块列表 +2. 更新 project.snapshot 结构说明 +3. 如有新的Store,更新 storage structure +``` + +--- + +## 不需要更新的场景 + +| 场景 | 原因 | +|------|------| +| 重命名变量 | AI读代码即可 | +| 提取函数 | 不影响结构 | +| struct增减字段 | 文档不记录字段 | +| 函数签名变化 | AI直接看代码 | +| 移动文件位置 | 如不影响模块关系,可不更新 | + +--- + +## 文档过时了怎么办? + +### 轻度过时(10-20%) + +不影响使用,AI能从代码推断正确信息。 + +### 中度过时(20-40%) + +AI可能定位错误,需要用户手动更新。 + +### 重度过时(>40%) + +建议重新生成文档或手动全面更新。 + +--- + +## CI集成(可选) + +```yaml +# .github/workflows/ai-context-check.yml +name: AI Context Check +on: [pull_request] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check for significant changes + run: | + CHANGED=$(git diff --name-only origin/main) + if echo "$CHANGED" | grep -q "tools/\|pipeline/stages/\|domain/"; then + echo "::warning::Significant changes detected. Consider updating .ai-context/" + fi +``` + +这只是提醒,不阻塞CI。 diff --git a/litho.docs/context-aware/methodology.md b/litho.docs/context-aware/methodology.md new file mode 100644 index 0000000..ec19ce4 --- /dev/null +++ b/litho.docs/context-aware/methodology.md @@ -0,0 +1,180 @@ +# AI友好的项目上下文方法论 + +## 核心理念 + +让Coding Agent理解项目,不需要每次都消耗大量Token从头阅读代码。 + +### 关键矛盾 + +| 需求 | 约束 | +|------|------| +| 信息足够让AI理解代码 | 文档不能太细,维护成本高 | +| 保持准确性 | 更新本身消耗Token | +| 覆盖关键决策 | 不能每次都从头读代码 | + +### 解决思路:分层 + 衰减 + +``` +高价值/低变化 → 详细文档,长期有效 +高价值/高变化 → 简要索引,指向代码 +低价值/低变化 → 不记录 +低价值/高变化 → 不记录(AI直接读代码) +``` + +--- + +## 设计原则 + +### 1. 记录"为什么",不记录"是什么" + +AI可以从代码读出"是什么",但无法推断"为什么"。 + +``` +❌ 不记录: +struct Iteration { + id: String, + number: u32, + ... +} + +✅ 记录: +为什么LoopAgent用max_iterations=1? +因为SequentialAgent有bug,exit_loop会提前终止整个链。 +``` + +### 2. 删除精确引用 + +精确行号容易过时,用概念定位代替。 + +``` +❌ 位置: crates/cowork-core/src/pipeline/executor/mod.rs:72 + +✅ 入口函数: IterationExecutor::execute() + AI可以通过grep自己定位 +``` + +### 3. 合并相关内容 + +减少文件跳转,降低认知负担。 + +``` +合并前: +- domains/pipeline.md (Pipeline) +- domains/agents.md (Agents) +- domains/interaction.md (Interaction) + +合并后: +- domains/pipeline.md (Pipeline + Agents) +- core/constraints.md (约束 + Interaction规则) +``` + +### 4. 精简工具列表 + +工具名已经足够说明功能。 + +``` +❌ | ReadFileTool | 读取文件内容 | + | WriteFileTool | 写入文件内容 | + +✅ File Tools: ReadFile, WriteFile, ListFiles +``` + +--- + +## 文档结构 + +### 最小化结构 + +``` +.ai-context/ +├── manifest.yaml # 索引 + 维护指南 +├── project.snapshot # 项目结构 + 存储 + 配置 +├── architecture-decisions.md # 反直觉设计决策 +├── core/ +│ ├── modules.map # 模块依赖 + 导航 +│ └── constraints.md # 安全约束 + 限制规则 +├── domains/ +│ ├── pipeline.md # 流程 + Agent +│ ├── domain-logic.md # 核心实体关系 +│ └── tools.md # 工具分类 +└── prompts/ + └── coding-context.md # 代码风格 +``` + +### Token预算 + +| 文件 | 预算 | 用途 | +|------|------|------| +| project.snapshot | ~800 | 项目概览 | +| modules.map | ~800 | 导航定位 | +| constraints.md | ~400 | 约束规则 | +| pipeline.md | ~500 | 流程理解 | +| domain-logic.md | ~400 | 实体关系 | +| tools.md | ~400 | 工具索引 | +| architecture-decisions.md | ~500 | 决策理解 | +| **总计** | **~4000** | | + +--- + +## 维护策略 + +### 需要更新 vs 不需要更新 + +| 变化类型 | 是否更新文档 | +|---------|-------------| +| 新增工具 | ✅ 更新 tools.md | +| 新增Stage | ✅ 更新 pipeline.md | +| 新增模块 | ✅ 更新 modules.map | +| 新增约束 | ✅ 更新 constraints.md | +| 架构决策 | ✅ 更新 architecture-decisions.md | +| struct字段变化 | ❌ 不更新 | +| 函数签名变化 | ❌ 不更新 | +| 重构代码 | ❌ 不更新 | +| 行号变化 | ❌ 不记录 | + +### 更新指令 + +``` +用户: "Update .ai-context because I added a new tool" +用户: "Update .ai-context because I made an architecture decision" +``` + +### 变更检测 + +Agent可在会话启动时检测: +```bash +git diff --name-only HEAD~10 +``` + +检测到关键目录变更时提醒用户更新文档。 + +--- + +## 实践检查清单 + +### 创建AI上下文时 + +- [ ] 是否只记录"为什么"而非"是什么"? +- [ ] 是否删除了精确行号引用? +- [ ] 是否合并了相关内容? +- [ ] 是否删除了struct字段定义? +- [ ] 总Token是否控制在4000以内? + +### 维护AI上下文时 + +- [ ] 是否只在高价值变化时更新? +- [ ] 是否避免了结构体字段的同步? +- [ ] 是否保持了"指向代码"的原则? + +--- + +## 效果评估 + +| 准确度 | 效果 | +|--------|------| +| 70% | Agent能理解项目结构 | +| 80% | Agent能找到关键代码 | +| 90% | Agent能遵循核心约束 | +| 100% | 不现实,维护成本过高 | + +**核心目标**:让Agent快速定位,需要精确信息时自己去读代码。 diff --git a/litho.docs/context-aware/templates.md b/litho.docs/context-aware/templates.md new file mode 100644 index 0000000..dfa6ffb --- /dev/null +++ b/litho.docs/context-aware/templates.md @@ -0,0 +1,229 @@ +# AI上下文模板 + +## 项目快照模板 (project.snapshot) + +```markdown +# [项目名] - AI Context Snapshot + +## PROJECT OVERVIEW + +name: [项目名] +type: [rust-workspace | monorepo | single-app] +version: [版本] +description: [一句话描述] + +## STRUCTURE + +### [核心模块名] +path: [路径] +key_modules: + - [模块1]: [作用] + - [模块2]: [作用] + +### [CLI/前端等其他模块] +path: [路径] + +--- + +## STORAGE STRUCTURE + +[存储目录结构,简化版] + +| Store | File | Purpose | +|-------|------|---------| +| [Store名] | [文件路径] | [用途] | + +--- + +## CONFIGURATION + +[配置文件位置和关键字段] + +--- + +## EXTERNAL INTEGRATIONS + +[外部集成,如API、SDK等] + +--- +**Note**: For detailed implementations, see corresponding source files. +``` + +--- + +## 模块地图模板 (modules.map) + +```markdown +# Module Dependency Map +# Note: For detailed function signatures, grep the code directly + +## ARCHITECTURE LAYERS + +``` +[ASCII架构图,展示分层] +``` + +## MODULE DEPENDENCY FLOW + +``` +[依赖流向,一行表示] +``` + +## KEY TRAITS + +| Trait | Location | Purpose | +|-------|----------|---------| +| [Trait名] | [位置] | [用途] | + +## CORE FLOWS + +### [流程名] +``` +[流程步骤] +``` + +## MODULE FILES REFERENCE + +### [模块名] +`[路径]` +- [文件1] - [作用] +- [文件2] - [作用] + +## QUICK NAVIGATION BY TASK + +| Task | Location | +|------|----------| +| [任务描述] | [文件路径] | +``` + +--- + +## 约束模板 (constraints.md) + +```markdown +# Constraints & Boundaries + +## Security Constraints + +| Constraint | Rule | +|------------|------| +| [约束名] | [规则] | + +## Rate Limiting + +| Resource | Limit | +|----------|-------| +| [资源名] | [限制] | + +## [业务] Constraints + +| Constraint | Value | +|------------|-------| +| [约束] | [值] | + +## Error Handling + +| Type | Behavior | +|------|----------| +| [错误类型] | [处理方式] | +``` + +--- + +## 领域文档模板 (domains/*.md) + +```markdown +# [领域名] Domain + +## Core Concepts + +| Concept | Location | Purpose | +|---------|----------|---------| +| [概念] | [位置] | [用途] | + +## Relationships + +``` +[实体关系图] +``` + +## [关键机制] + +| Type | Description | +|------|-------------| +| [类型] | [描述] | + +## Code Locations + +| Component | Location | +|-----------|----------| +| [组件] | [位置] | + +--- +**Note**: For details, read source files directly. +``` + +--- + +## 工具索引模板 (tools.md) + +```markdown +# Tools Domain + +## Tool Categories + +| Category | File | Key Tools | +|----------|------|-----------| +| [分类] | [文件] | [工具列表] | + +## Security + +[安全约束摘要] + +## Location + +[工具目录路径] +``` + +--- + +## Manifest模板 (manifest.yaml) + +```yaml +# AI Context Manifest +# Version: [版本] +# Project: [项目名] + +manifest_version: "[版本]" +project: + name: [项目名] + version: [版本] + language: [语言] + +documents: + - path: [文件路径] + purpose: [用途] + tokens: ~[估算] + +usage: + coding_context: + - [文件列表] + + debug_context: + - [文件列表] + +total_tokens: ~[总计] + +# ============================================================================= +# MAINTENANCE GUIDE +# ============================================================================= +# +# | Code Change | Update File | +# |--------------------------|------------------------| +# | [变更类型] | [更新文件] | +# +# | NO UPDATE: [不需要更新的情况] | +# +# Command: "Update .ai-context because I [action] [thing]" +# ============================================================================= +``` From 77918a54040a5df66473df211c7fe6557e9f2827 Mon Sep 17 00:00:00 2001 From: Sopaco Date: Mon, 30 Mar 2026 15:22:04 +0800 Subject: [PATCH 2/7] Add expandable modal textarea to chat input --- .../src/components/chat/InputArea.tsx | 231 +++++++++++++++--- 1 file changed, 203 insertions(+), 28 deletions(-) diff --git a/crates/cowork-gui/src/components/chat/InputArea.tsx b/crates/cowork-gui/src/components/chat/InputArea.tsx index 72adb9a..b409651 100644 --- a/crates/cowork-gui/src/components/chat/InputArea.tsx +++ b/crates/cowork-gui/src/components/chat/InputArea.tsx @@ -1,8 +1,10 @@ -import React, { memo, useCallback } from 'react'; -import { Button, Space, Input } from 'antd'; -import { CopyOutlined } from '@ant-design/icons'; +import React, { memo, useCallback, useState, useRef, useEffect } from 'react'; +import { Button, Space, Input, Modal } from 'antd'; +import { CopyOutlined, ExpandOutlined, CompressOutlined } from '@ant-design/icons'; import type { InputRequest, InputOption } from '../../stores'; +const { TextArea } = Input; + interface InputAreaProps { userInput: string; onUserInputChange: (value: string) => void; @@ -28,27 +30,158 @@ const InputAreaInner: React.FC = ({ disabled, mode, }) => { - const handleInputChange = useCallback((e: React.ChangeEvent) => { + const [expanded, setExpanded] = useState(false); + const textAreaRef = useRef(null); + const modalTextAreaRef = useRef(null); + + const handleInputChange = useCallback((e: React.ChangeEvent) => { onUserInputChange(e.target.value); }, [onUserInputChange]); + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + // Shift + Enter: 展开输入框 + if (e.shiftKey && e.key === 'Enter') { + e.preventDefault(); + setExpanded(true); + return; + } + // Enter alone: 发送消息 + if (!e.shiftKey && e.key === 'Enter') { + e.preventDefault(); + if (userInput.trim() && !disabled) { + onSend(); + } + } + }, [userInput, disabled, onSend]); + + const handleExpandClick = useCallback(() => { + setExpanded(true); + }, []); + + const handleCollapse = useCallback(() => { + setExpanded(false); + // 折叠后聚焦回主输入框 + setTimeout(() => { + textAreaRef.current?.focus(); + }, 100); + }, []); + + const handleModalSend = useCallback(() => { + if (userInput.trim() && !disabled) { + onSend(); + setExpanded(false); + } + }, [userInput, disabled, onSend]); + + // 展开时聚焦 Modal 内的 TextArea + useEffect(() => { + if (expanded) { + setTimeout(() => { + modalTextAreaRef.current?.focus(); + // 将光标移到末尾 + const len = userInput.length; + modalTextAreaRef.current?.setSelectionRange(len, len); + }, 100); + } + }, [expanded, userInput]); + + const handleModalKeyDown = useCallback((e: React.KeyboardEvent) => { + // Shift + Enter: 在展开模式下输入换行 + if (e.shiftKey && e.key === 'Enter') { + // 默认行为,输入换行 + return; + } + // Ctrl/Cmd + Enter: 发送消息 + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + e.preventDefault(); + if (userInput.trim() && !disabled) { + onSend(); + setExpanded(false); + } + } + // Escape: 关闭展开模式 + if (e.key === 'Escape') { + e.preventDefault(); + setExpanded(false); + } + }, [userInput, disabled, onSend]); + + const renderInputWithExpand = (placeholder: string) => ( +
+