| 项 | 内容 |
|---|---|
| 文档版本 | v1.0 |
| 更新日期 | 2026-08-10 |
| 状态 | 已确认基线 |
| 关联文档 | docs/REQUIREMENTS.md(需求) |
单进程本地应用:Python 后端提供 HTTP API + 托管前端静态资源;Vue 3 前端经 Vite 构建后由后端 serve。启动一个命令即用。
┌──────────────────────────────────────────────┐
│ 浏览器 (localhost:8000) │
│ ┌────────────────────────────────────────┐ │
│ │ Vue 3 SPA (Vite 构建产物) │ │
│ └──────────────────┬─────────────────────┘ │
└─────────────────────┼────────────────────────┘
│ HTTP /api/*
┌─────────────────────▼────────────────────────┐
│ Python 后端 (单进程, http.server) │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ 静态资源 │ │ API 路由 │ │ resume │ │
│ │ serve │ │ server.py│ │ (开终端) │ │
│ └──────────┘ └────┬─────┘ └───────────┘ │
│ │ │
│ ┌───────────▼────────────┐ │
│ │ scanner / indexer │ │
│ │ (差分同步) │ │
│ └───────────┬────────────┘ │
│ │ │
│ ┌───────────▼────────────┐ │
│ │ adapters: pi/claude/codex│ │
│ └───────────┬────────────┘ │
│ │ │
│ ┌───────────▼────────────┐ │
│ │ SQLite (data/asm.db) │ │
│ └────────────────────────┘ │
└──────────────────────────────────────────────┘
│ 读取
┌─────────────▼──────────────┐
│ ~/.pi ~/.claude ~/.codex │
└────────────────────────────┘
| 层 | 选型 | 理由 |
|---|---|---|
| 语言 | Python 3.12 | 本机已有;jsonl 解析、sqlite 标准库即用 |
| HTTP | http.server(标准库) |
零第三方依赖,本地工具足够;实测内存 ~25MB |
| 存储 | sqlite3 + FTS5(标准库) |
全文搜索白嫖标准库,本机已验证 FTS5 可用 |
| 依赖策略 | 零第三方依赖 | 复制目录即可运行;无原生编译风险 |
| 层 | 选型 | 理由 |
|---|---|---|
| 框架 | Vue 3(Composition API + <script setup>) |
模板化、组件化、TS 友好;中文社区成熟 |
| 构建 | Vite | Vue 官方一等支持;HMR 快;产物干净 |
| 语言 | TypeScript | 类型安全;后端模型可镜像成类型 |
| UI | 自写轻量组件(不引重型 UI 库) | 控制体积,避免过度依赖 |
| HTTP | fetch 封装 |
无需 axios |
选型权衡:Vue/Vite 引入 Node 构建链(开发期),换取组件化与可维护性;构建产物为纯静态文件,部署期后端仍零依赖、单进程。
四层,单向依赖,职责隔离。新增 agent 只动适配层。
┌─────────────────────────────────────────────┐
│ 表现层 Presentation │
│ frontend/ (Vue SPA) + backend server.py │
└──────────────────┬──────────────────────────┘
│ HTTP
┌──────────────────▼──────────────────────────┐
│ 应用层 Application │
│ scanner.py 扫描调度(聚合各 adapter) │
│ indexer.py 差分同步(全量差分/增量差分) │
│ resume.py 恢复调度(开终端窗口) │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 适配层 Adapter(每 agent 一个文件) │
│ adapters/base.py Adapter 抽象基类 │
│ adapters/pi.py | claude.py | codex.py │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ 基础设施层 Infrastructure │
│ models.py 数据模型 / 枚举 │
│ config.py 路径常量 / agent 注册表 │
│ db.py SQLite 连接与底层操作 │
└─────────────────────────────────────────────┘
依赖规则:上层可调下层,下层不感知上层;适配层不直接写库(交 indexer);应用层不解析具体格式(交 adapter)。
| 文件 | 职责 | 禁止 |
|---|---|---|
models.py |
Session/Message 数据类、Agent/PermissionLevel 枚举 |
碰 IO、碰 SQL |
config.py |
各 agent 数据目录路径、AGENT_REGISTRY(名字→adapter) |
含业务逻辑 |
db.py |
SQLite 连接、建表迁移、底层 CRUD | 含业务逻辑、解析 jsonl |
adapters/base.py |
Adapter 抽象基类(见 §4.2) |
— |
adapters/<agent>.py |
解析该 agent jsonl → Session;构造 resume 命令 |
直接写库 |
scanner.py |
遍历注册表,调 adapter 收集 (path, mtime, size) |
解析具体格式 |
indexer.py |
差分算法(§7)、FTS 维护、查询接口 | 解析 jsonl |
resume.py |
接收 (session, level),开终端窗口 |
构造命令 |
server.py |
HTTP 路由分发、参数校验、调应用层 | 业务逻辑 |
main.py |
入口:建库 → 首次同步 → 起 HTTP → 开浏览器 | — |
class Adapter(ABC):
name: str # "pi" | "claude" | "codex"
data_dir: Path # 该 agent session 根目录
def discover(self) -> list[Path]:
"""发现该 agent 所有 session 文件路径。"""
def fingerprint(self, path: Path) -> FileFingerprint:
"""返回 (path, mtime, size),供差分比对。"""
def parse(self, path: Path) -> Session:
"""解析单文件 → Session 元数据(不含完整消息流)。"""
def read_messages(self, path: Path) -> list[Message]:
"""读取完整消息流(详情页按需调用)。"""
def build_resume_command(self, session: Session, level: PermissionLevel) -> str:
"""构造 resume 命令(含权限档位映射)。"""
def supports_full_access(self) -> bool:
"""是否有独立 full 档(pi 返回 False)。"""frontend/
├─ index.html
├─ vite.config.ts # dev proxy /api → http://localhost:8000
├─ tsconfig.json
├─ package.json
└─ src/
├─ main.ts # 挂载入口
├─ App.vue # 根布局(侧栏 + 主区)
├─ api/
│ └─ client.ts # fetch 封装 + 类型化接口
├─ stores/
│ ├─ sessions.ts # 会话列表/筛选/视图模式状态
│ └─ index.ts # 同步状态/统计
├─ views/
│ ├─ SessionList.vue
│ └─ SessionDetail.vue
├─ components/
│ ├─ AgentFilter.vue
│ ├─ SearchBar.vue
│ ├─ SessionItem.vue
│ └─ ResumeBar.vue # 权限档位按钮
├─ composables/
│ └─ useResume.ts
└─ types/
└─ models.ts # 镜像后端 Session/Message 类型
- 状态管理:用 Vue 3 reactive 组合式(轻量场景不引 Pinia;若复杂再升级)。
- 类型同步:
types/models.ts手工镜像后端模型,保持字段一致(后续可考虑自动生成)。 - 路由:hash 路由(
#/、#/session/:id),避免后端 history fallback 配置。
| 阶段 | 命令 | 产物 |
|---|---|---|
| 开发 | npm run dev (Vite :5173) + python -m asm (:8000) |
Vite proxy /api 到后端 |
| 构建 | npm run build |
frontend/dist/(纯静态) |
| 部署 | 后端 server.py 把 frontend/dist/ 映射为静态根 |
单进程对外 |
启动
python -m asm即:serve 前端 dist + 提供/api/*+ 自动开浏览器。
@dataclass
class Session:
id: str # 原始 uuid,主键
agent: Agent # pi|claude|codex
project_path: str # 归一化 cwd
title: str # ≤80 字符
message_count: int
model: str # 可空
created_at: str # ISO8601
updated_at: str # 文件 mtime, ISO8601
file_path: str # 原始 jsonl 路径
class Agent(str, Enum): pi="pi"; claude="claude"; codex="codex"
class PermissionLevel(str, Enum): default="default"; full="full"CREATE TABLE sessions (
id TEXT PRIMARY KEY,
agent TEXT NOT NULL,
project_path TEXT NOT NULL,
title TEXT,
message_count INTEGER DEFAULT 0,
model TEXT,
created_at TEXT,
updated_at TEXT,
file_path TEXT NOT NULL,
file_mtime REAL, -- 差分比对依据
file_size INTEGER, -- 差分比对依据
indexed_at TEXT
);
CREATE INDEX idx_sessions_project ON sessions(project_path);
CREATE INDEX idx_sessions_updated ON sessions(updated_at);
CREATE VIRTUAL TABLE sessions_fts USING fts5(
session_id UNINDEXED,
title,
content,
tokenize = 'unicode61' -- 按字符切分,兼容中文
);
-- 同步水位线(增量差分用)
CREATE TABLE sync_meta (
key TEXT PRIMARY KEY,
value TEXT
); -- key: last_scan_at, last_full_scan_atv1.0 实现全量差分;v1.1 实现增量差分。两者都不做清空重插。
目标:遍历全部文件保证最终一致,但只对差集写入,未变化文件零解析。
输入: 各 adapter.discover() 得 current_files: {key: (mtime, size)}
key = (agent, file_path)
步骤:
1. scanner 收集 current_files(含 fingerprint,不解析内容)
2. 从 DB 读 indexed: {key: (mtime, size, id, ...)}
3. 计算差集:
added = current_keys - indexed_keys
removed = indexed_keys - current_keys
changed = {k ∈ current∩indexed | (mtime,size) 变了}
unchanged = (current∩indexed) - changed
4. 对 added ∪ changed:
adapter.parse(k) → Session
UPSERT sessions;删除并重建该 session 的 FTS 行
5. 对 removed:
DELETE sessions + sessions_fts(按 id)
6. 更新 sync_meta.last_full_scan_at
7. 返回 DiffStat(added, changed, removed, unchanged)
优点:能检测外部删除(最终一致);unchanged 完全跳过解析与 FTS 写入,省 IO/CPU。
目标:以水位线过滤,只处理「自上次扫描以来变化」的文件,减少比对与解析量。
输入: last_scan_at (水位线)
步骤:
1. scanner 遍历目录(文件系统无变更通知则仍需遍历),
但仅收集 mtime > last_scan_at 的文件 → changed_set
2. 对 changed_set 内每个文件:
fingerprint 对比 DB;变化则 parse + UPSERT + 重建 FTS
3. 更新 sync_meta.last_scan_at = now
4. 删除检测(增量难发现):
策略 A: 每 N 次增量后自动触发一次全量差分校准
策略 B: 提供「全量校准」按钮手动触发
说明(技术诚实):纯标准库无文件事件监听,增量仍需遍历目录;相比全量差分,主要省掉未变化文件的解析与 DB 写入。真正事件驱动增量需引入 watchdog(第三方依赖),列为 v2 可选增强。
| 触发 | 行为 |
|---|---|
| 首次启动 | 全量差分 |
| 手动「重建索引」 | 全量差分 |
| 手动「同步」(v1.1) | 增量差分 |
| 定时后台(可选) | 增量差分 + 周期全量校准 |
| Agent | default 命令 |
full 命令 |
supports_full_access |
|---|---|---|---|
| pi | pi --session <id> |
— | False |
| claude | claude --resume <id> |
claude --resume <id> --dangerously-skip-permissions |
True |
| codex | codex resume <id> |
codex resume <id> -s danger-full-access |
True |
前端 ResumeBar → POST /api/sessions/{id}/resume {level}
→ server 校验 localhost + level
→ resume.py 调 adapter.build_resume_command(session, level)
→ Windows: subprocess 调 start/wt 开新终端窗口执行命令
→ 返回 {ok, command}
default/full两按钮。- adapter
supports_full_access()=False(pi)时,full按钮置灰,tooltip 说明「该 agent 无权限边界」。 full按钮旁固定风险提示文案。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/sessions |
?view=project|timeline&agent=pi,claude&q=词 |
| GET | /api/sessions/{id} |
详情(含完整消息流) |
| POST | /api/sessions/{id}/resume |
body {"level":"default"|"full"} → 开终端 |
| POST | /api/reindex |
全量差分同步,返回 DiffStat |
| POST | /api/sync (v1.1) |
增量差分同步 |
| GET | /api/stats |
索引状态:按 agent 计数、last_scan_at、上次 DiffStat |
adapters/cursor.py实现Adapter接口。config.py的AGENT_REGISTRY加一行"cursor": CursorAdapter。- 前端
types/models.ts的 Agent 枚举加值(可选,筛选器自动渲染)。 - 其他层零改动。
models.py的PermissionLevel加值。- 各 adapter 的
build_resume_command加映射分支。 - 前端 ResumeBar 按枚举渲染按钮。
后端仅依赖 /api/* + 静态目录,前端可整体替换(如换 React)而不动后端。
agent_session_manager/
├─ backend/
│ ├─ asm/
│ │ ├─ __init__.py
│ │ ├─ main.py
│ │ ├─ server.py
│ │ ├─ scanner.py
│ │ ├─ indexer.py
│ │ ├─ resume.py
│ │ ├─ models.py
│ │ ├─ config.py
│ │ ├─ db.py
│ │ └─ adapters/{__init__,base,pi,claude,codex}.py
│ └─ pyproject.toml
├─ frontend/ # Vue 3 + Vite
│ ├─ src/...
│ ├─ vite.config.ts
│ └─ package.json
├─ docs/{REQUIREMENTS,ARCHITECTURE}.md
├─ data/ # 运行时 asm.db
└─ README.md
| 操作 | 命令 |
|---|---|
| 前端开发 | cd frontend && npm run dev |
| 后端开发 | cd backend && python -m asm |
| 前端构建 | cd frontend && npm run build → 产物入 frontend/dist |
| 一键运行(生产) | python -m asm(serve dist + API + 开浏览器) |
- 初始化 DB(建表/迁移)。
- 若索引为空 → 首次全量差分同步。
- 启
http.server监听 127.0.0.1:8000。 - 注册路由:
/api/*走 API;其余走frontend/dist静态。 webbrowser.open("http://127.0.0.1:8000")。
- codex
resume <id>已验证支持直传 id;运行时若 picker 仍弹出,回退为复制命令到剪贴板。 - FTS5
unicode61对中文按字符切分,召回尚可但非分词级;若不满意可后续接入 jieba(引入第三方依赖,v2 评估)。 - 虚拟滚动/分页阈值待会话规模实测后定。