GPU_GUARD_MONOREPO/docs/superpowers/plans/2026-04-19-neta-agent-runtime-kernel.md
2026-05-20 21:39:12 +08:00

892 lines
31 KiB
Markdown
Raw Permalink 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.

# Neta Agent 运行时内核实施计划
> **给自动化实施代理:** 必须使用子技能:`superpowers:subagent-driven-development`(推荐)或 `superpowers:executing-plans`,按任务逐项实施本计划。步骤使用复选框(`- [ ]`)语法跟踪进度。
**目标:** 建立 Pi-first 树状会话内核,包括 Pi 兼容 session entry tree、`SessionTreeProvider` 抽象、默认 file provider、MySQL provider、leaf 驱动上下文构建、snapshot 和 agent 级 session provider 选择。
**架构:** 第一原则是“先复刻 Pi 的 session 语义,再适配 Neta provider”。File provider 必须采用 Pi 单文件 JSONL session 协议:第一行 session header后续每行 session entryentry 通过 `id/parentId` 组成树;`leafId` 只表示当前位置;历史 entry 原则上 append-only。MySQL provider 必须复刻同一语义,不能反过来要求 file provider 适配数据库表思维。
**技术栈:** Midway/NestJS 风格 service、TypeScript、TypeORM、MySQL、Node fs JSONL、Jest、pnpm。
---
## 架构硬约束
本计划实施时必须遵守以下约束:
- 不兼容旧线性 message/session 接口。
- 旧历史数据允许删除,不做迁移脚本。
- 数据库结构修改实施阶段使用 MCP 直接执行,不新增 SQL 文件作为主路径。
- `file``mysql` provider 必须通过同一组契约测试。
- `file` provider 必须高强度移植 Pi 的 `SessionManager` 文件协议和纯逻辑。
- 不得把 file provider 设计成 `session.json + entries.jsonl` 双文件。
- 不得把第一版事实源拆成独立 `tool_call/tool_result` entrytool 状态作为 `AgentMessage` payload 或前端流式投影事件处理。
- 不得只用 `content/summary` 字段表示消息;必须保留完整 `message` payload。
- completed entry 默认不可修改;`updateEntry()` 只允许用于 in-flight 流式补写或明确的 patch 事件。
## Pi 直接复用范围
优先从以下 Pi 文件移植:
- `C:/Users/lixin/Desktop/RZYX_ZT/pi-mono-main/packages/coding-agent/src/core/session-manager.ts`
- `C:/Users/lixin/Desktop/RZYX_ZT/pi-mono-main/packages/coding-agent/src/core/compaction/compaction.ts`
- `C:/Users/lixin/Desktop/RZYX_ZT/pi-mono-main/packages/coding-agent/src/core/compaction/branch-summarization.ts`
- `C:/Users/lixin/Desktop/RZYX_ZT/pi-mono-main/packages/coding-agent/src/core/compaction/utils.ts`
必须移植或等价实现:
- `SessionHeader` / `SessionEntry` union。
- 单文件 JSONL session 协议。
- `parseSessionEntries()``loadEntriesFromFile()` 的容错解析。
- leaf-to-root path 构建。
- `buildSessionContext()` 的 compaction、branch summary、custom message、thinking/model setting 处理。
- `branch()``resetLeaf()``branchWithSummary()``createBranchedSession()` 的纯逻辑。
- compaction 的 `firstKeptEntryId`、split turn、previous summary 合并策略。
## 文件结构
新增后端内核模块:
- `packages/backend/src/modules/netaclaw/session-tree/types.ts`
- 定义 Pi 兼容 session header、entry union、provider DTO、snapshot DTO。
- `packages/backend/src/modules/netaclaw/session-tree/id.ts`
- 定义 session id 与 entry id 生成器。
- `packages/backend/src/modules/netaclaw/session-tree/path.ts`
- 定义 entry index、leaf path、tree、common ancestor、label 解析纯函数。
- `packages/backend/src/modules/netaclaw/session-tree/context_builder.ts`
- 移植 Pi `buildSessionContext()` 语义。
- `packages/backend/src/modules/netaclaw/session-tree/snapshot.ts`
- 构建前端/API snapshot payload。
- `packages/backend/src/modules/netaclaw/session-tree/provider.ts`
- 定义 `SessionTreeProvider` 契约。
- `packages/backend/src/modules/netaclaw/session-tree/pi_session_file.ts`
- 封装 Pi 单文件 JSONL session 文件读写与纯逻辑。
- `packages/backend/src/modules/netaclaw/session-tree/file_provider.ts`
-`pi_session_file.ts` 适配为 `SessionTreeProvider`
- `packages/backend/src/modules/netaclaw/session-tree/mysql_provider.ts`
- 将 MySQL 表适配为同一 `SessionTreeProvider`
- `packages/backend/src/modules/netaclaw/session-tree/provider_factory.ts`
- 解析 agent 配置中的 `sessionProvider`
新增 TypeORM 实体:
- `packages/backend/src/modules/netaclaw/entity/agent_session.ts`
- `packages/backend/src/modules/netaclaw/entity/agent_session_entry.ts`
修改现有文件:
- `packages/backend/src/modules/netaclaw/entity/agent.ts`
- `packages/backend/src/entities.ts`
新增测试:
- `packages/backend/test/session_tree_types.test.ts`
- `packages/backend/test/session_tree_path.test.ts`
- `packages/backend/test/session_tree_context_builder.test.ts`
- `packages/backend/test/session_tree_file_provider.test.ts`
- `packages/backend/test/session_tree_mysql_provider.test.ts`
- `packages/backend/test/session_tree_provider_contract.test.ts`
- `packages/backend/test/session_tree_provider_factory.test.ts`
- `packages/backend/test/entity_exports.test.ts`
## 任务 1定义 Pi 兼容会话类型
**文件:**
- 新增:`packages/backend/src/modules/netaclaw/session-tree/types.ts`
- 测试:`packages/backend/test/session_tree_types.test.ts`
- [ ] **步骤 1编写失败的类型消费测试**
```typescript
import type {
SessionTreeEntry,
SessionTreeHeader,
SessionTreeSession,
} from '../src/modules/netaclaw/session-tree/types.js';
describe('session tree types', () => {
it('represents a Pi-compatible session header and entries', () => {
const header: SessionTreeHeader = {
type: 'session',
version: 1,
id: 's1',
timestamp: '2026-04-19T00:00:00.000Z',
cwd: 'C:/workspace',
};
const entry: SessionTreeEntry = {
type: 'message',
id: 'e1',
parentId: null,
timestamp: '2026-04-19T00:00:00.000Z',
message: { role: 'user', content: 'hello' },
};
const session: SessionTreeSession = {
sessionId: header.id,
provider: 'file',
rootEntryId: entry.id,
leafEntryId: entry.id,
cwd: header.cwd,
status: 'active',
createdAt: header.timestamp,
updatedAt: header.timestamp,
};
expect(session.leafEntryId).toBe(entry.id);
});
});
```
- [ ] **步骤 2运行测试确认失败**
```bash
pnpm --filter @neta/backend test -- --runInBand test/session_tree_types.test.ts
```
预期:
- 失败,原因是 `session-tree/types.js` 尚不存在。
- [ ] **步骤 3实现 `types.ts`**
核心类型必须包含:
```typescript
export type SessionTreeProviderKind = 'file' | 'mysql';
export interface SessionTreeHeader {
type: 'session';
version: number;
id: string;
timestamp: string;
cwd: string;
parentSession?: string;
}
export interface SessionTreeEntryBase {
type: string;
id: string;
parentId: string | null;
timestamp: string;
}
export interface SessionTreeMessage {
role: string;
content?: unknown;
provider?: string;
model?: string;
usage?: Record<string, unknown>;
stopReason?: string;
timestamp?: number;
[key: string]: unknown;
}
export interface SessionMessageEntry extends SessionTreeEntryBase {
type: 'message';
message: SessionTreeMessage;
}
export interface ThinkingLevelChangeEntry extends SessionTreeEntryBase {
type: 'thinking_level_change';
thinkingLevel: string;
}
export interface ModelChangeEntry extends SessionTreeEntryBase {
type: 'model_change';
provider: string;
modelId: string;
}
export interface CompactionEntry<T = unknown> extends SessionTreeEntryBase {
type: 'compaction';
summary: string;
firstKeptEntryId: string;
tokensBefore: number;
details?: T;
fromHook?: boolean;
}
export interface BranchSummaryEntry<T = unknown> extends SessionTreeEntryBase {
type: 'branch_summary';
fromId: string;
summary: string;
details?: T;
fromHook?: boolean;
}
export interface CustomEntry<T = unknown> extends SessionTreeEntryBase {
type: 'custom';
customType: string;
data?: T;
}
export interface CustomMessageEntry<T = unknown> extends SessionTreeEntryBase {
type: 'custom_message';
customType: string;
content: unknown;
details?: T;
display: boolean;
}
export interface LabelEntry extends SessionTreeEntryBase {
type: 'label';
targetId: string;
label: string | undefined;
}
export interface SessionInfoEntry extends SessionTreeEntryBase {
type: 'session_info';
name?: string;
}
export type SessionTreeEntry =
| SessionMessageEntry
| ThinkingLevelChangeEntry
| ModelChangeEntry
| CompactionEntry
| BranchSummaryEntry
| CustomEntry
| CustomMessageEntry
| LabelEntry
| SessionInfoEntry;
export interface SessionTreeSession {
sessionId: string;
provider: SessionTreeProviderKind;
rootEntryId: string | null;
leafEntryId: string | null;
cwd?: string | null;
sessionFile?: string | null;
parentSessionId?: string | null;
agentId?: number | null;
userId?: string | null;
title?: string | null;
status: 'active' | 'archived' | 'deleted';
metadata?: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}
```
- [ ] **步骤 4运行测试确认通过**
```bash
pnpm --filter @neta/backend test -- --runInBand test/session_tree_types.test.ts
```
预期:
- 通过。
- [ ] **步骤 5提交变更**
```bash
git add packages/backend/src/modules/netaclaw/session-tree/types.ts packages/backend/test/session_tree_types.test.ts
git commit -m "feat(agent-runtime): add pi-compatible session tree types"
```
## 任务 2实现 ID 与树路径纯函数
**文件:**
- 新增:`packages/backend/src/modules/netaclaw/session-tree/id.ts`
- 新增:`packages/backend/src/modules/netaclaw/session-tree/path.ts`
- 测试:`packages/backend/test/session_tree_path.test.ts`
- [ ] **步骤 1编写路径测试**
```typescript
import {
buildEntryIndex,
findCommonAncestorEntryId,
getPathToLeaf,
groupChildrenByParent,
resolveLatestLabels,
} from '../src/modules/netaclaw/session-tree/path.js';
import type { SessionTreeEntry } from '../src/modules/netaclaw/session-tree/types.js';
const entry = (id: string, parentId: string | null): SessionTreeEntry => ({
type: 'message',
id,
parentId,
timestamp: `2026-04-19T00:00:0${id.length}.000Z`,
message: { role: 'user', content: id },
});
describe('session tree path utilities', () => {
it('builds root-to-leaf path', () => {
expect(getPathToLeaf([entry('a', null), entry('b', 'a'), entry('c', 'b')], 'c').map(e => e.id)).toEqual([
'a',
'b',
'c',
]);
});
it('rejects missing leaf and cycles', () => {
expect(() => getPathToLeaf([entry('a', null)], 'missing')).toThrow('Entry missing not found');
expect(() => getPathToLeaf([entry('a', 'c'), entry('b', 'a'), entry('c', 'b')], 'c')).toThrow('Cycle detected');
});
it('finds common ancestor', () => {
const entries = [entry('a', null), entry('b', 'a'), entry('c', 'b'), entry('d', 'b')];
expect(findCommonAncestorEntryId(entries, 'c', 'd')).toBe('b');
});
it('groups children and resolves latest labels', () => {
const label1: SessionTreeEntry = {
type: 'label',
id: 'l1',
parentId: 'a',
timestamp: '2026-04-19T00:00:02.000Z',
targetId: 'a',
label: 'old',
};
const label2: SessionTreeEntry = {
type: 'label',
id: 'l2',
parentId: 'l1',
timestamp: '2026-04-19T00:00:03.000Z',
targetId: 'a',
label: 'new',
};
expect(groupChildrenByParent([entry('a', null), entry('b', 'a')]).__root__).toEqual(['a']);
expect(resolveLatestLabels([entry('a', null), label1, label2]).get('a')?.label).toBe('new');
expect(buildEntryIndex([entry('a', null)]).get('a')?.id).toBe('a');
});
});
```
- [ ] **步骤 2实现 `id.ts` 与 `path.ts`**
实现要求:
- `createSessionTreeSessionId()` 使用 `uuidv7()` 或等价时间有序 UUID。
- `createSessionTreeEntryId(existingIds)` 生成短 ID并检查碰撞。
- `getPathToLeaf()` 必须检测 missing leaf、missing parent、cycle。
- `groupChildrenByParent()` 必须以 append 顺序稳定排序。
- `resolveLatestLabels()` 必须以后写 label 覆盖旧 label。
- [ ] **步骤 3运行测试确认通过**
```bash
pnpm --filter @neta/backend test -- --runInBand test/session_tree_path.test.ts
```
预期:
- 通过。
- [ ] **步骤 4提交变更**
```bash
git add packages/backend/src/modules/netaclaw/session-tree/id.ts packages/backend/src/modules/netaclaw/session-tree/path.ts packages/backend/test/session_tree_path.test.ts
git commit -m "feat(agent-runtime): add session tree path utilities"
```
## 任务 3移植上下文构建语义
**文件:**
- 新增:`packages/backend/src/modules/netaclaw/session-tree/context_builder.ts`
- 测试:`packages/backend/test/session_tree_context_builder.test.ts`
- [ ] **步骤 1编写上下文构建测试**
```typescript
import { buildSessionContext } from '../src/modules/netaclaw/session-tree/context_builder.js';
import type { SessionTreeEntry } from '../src/modules/netaclaw/session-tree/types.js';
const message = (id: string, parentId: string | null, role: string, content: string): SessionTreeEntry => ({
type: 'message',
id,
parentId,
timestamp: `2026-04-19T00:00:00.000Z`,
message: { role, content },
});
describe('session tree context builder', () => {
it('walks the active path and excludes sibling branches', () => {
const entries = [
message('u1', null, 'user', 'root'),
message('a1', 'u1', 'assistant', 'branch a'),
message('a2', 'u1', 'assistant', 'branch b'),
];
expect(buildSessionContext(entries, 'a2').messages.map(m => m.content)).toEqual(['root', 'branch b']);
});
it('handles compaction using firstKeptEntryId', () => {
const entries: SessionTreeEntry[] = [
message('u1', null, 'user', 'old'),
message('u2', 'u1', 'user', 'kept'),
{
type: 'compaction',
id: 'c1',
parentId: 'u2',
timestamp: '2026-04-19T00:00:01.000Z',
summary: 'summary',
firstKeptEntryId: 'u2',
tokensBefore: 1000,
},
message('a1', 'c1', 'assistant', 'after'),
];
expect(buildSessionContext(entries, 'a1').messages.map(m => m.role)).toEqual([
'compactionSummary',
'user',
'assistant',
]);
expect(buildSessionContext(entries, 'a1').messages.map(m => m.content ?? m.summary)).toEqual([
'summary',
'kept',
'after',
]);
});
it('tracks thinking and model settings without adding them as messages', () => {
const entries: SessionTreeEntry[] = [
message('u1', null, 'user', 'hello'),
{ type: 'thinking_level_change', id: 't1', parentId: 'u1', timestamp: '2026-04-19T00:00:01.000Z', thinkingLevel: 'high' },
{ type: 'model_change', id: 'm1', parentId: 't1', timestamp: '2026-04-19T00:00:02.000Z', provider: 'openai', modelId: 'gpt-x' },
];
const context = buildSessionContext(entries, 'm1');
expect(context.messages).toEqual([{ role: 'user', content: 'hello' }]);
expect(context.thinkingLevel).toBe('high');
expect(context.model).toEqual({ provider: 'openai', modelId: 'gpt-x' });
});
});
```
- [ ] **步骤 2实现 `context_builder.ts`**
实现要求:
- 以 Pi `buildSessionContext(entries, leafId, byId)` 为基准移植。
- 必须先从 leaf 回溯到 root 得到 path。
- 必须解析 `thinking_level_change``model_change`、assistant message provider/model。
- 必须识别 active path 上最新 compaction。
- 有 compaction 时输出compaction summary、`firstKeptEntryId` 到 compaction 前的保留消息、compaction 后消息。
- 无 compaction 时输出message、custom_message、branch_summary。
- label、session_info、custom、thinking_level_change、model_change 不直接生成 LLM message。
- 返回值包含 `messages``thinkingLevel``model``sourceEntryIds`
- [ ] **步骤 3运行测试确认通过**
```bash
pnpm --filter @neta/backend test -- --runInBand test/session_tree_context_builder.test.ts
```
预期:
- 通过。
- [ ] **步骤 4提交变更**
```bash
git add packages/backend/src/modules/netaclaw/session-tree/context_builder.ts packages/backend/test/session_tree_context_builder.test.ts
git commit -m "feat(agent-runtime): add pi-style session context builder"
```
## 任务 4定义 Provider 契约与 Snapshot
**文件:**
- 新增:`packages/backend/src/modules/netaclaw/session-tree/provider.ts`
- 新增:`packages/backend/src/modules/netaclaw/session-tree/snapshot.ts`
- 测试:`packages/backend/test/session_tree_provider_contract.test.ts`
- [ ] **步骤 1编写 provider contract 工厂**
契约测试必须作为函数导出,由 file/mysql provider 测试复用:
```typescript
import type { SessionTreeProvider } from '../src/modules/netaclaw/session-tree/provider.js';
export function runSessionTreeProviderContract(name: string, createProvider: () => SessionTreeProvider) {
describe(`${name} session tree provider contract`, () => {
it('creates a session, appends messages, and advances leaf', async () => {
const provider = createProvider();
await provider.createSession({ sessionId: 's1', provider: name === 'mysql' ? 'mysql' : 'file', cwd: 'C:/workspace' });
const user = await provider.appendMessage('s1', { role: 'user', content: 'hello' });
const assistant = await provider.appendMessage('s1', { role: 'assistant', content: 'hi' });
expect((await provider.getSession('s1'))?.leafEntryId).toBe(assistant.id);
expect((await provider.getActivePath('s1')).map(e => e.id)).toEqual([user.id, assistant.id]);
});
it('branches without deleting sibling history', async () => {
const provider = createProvider();
await provider.createSession({ sessionId: 's1', provider: name === 'mysql' ? 'mysql' : 'file' });
const root = await provider.appendMessage('s1', { role: 'user', content: 'root' });
const branchA = await provider.appendMessage('s1', { role: 'assistant', content: 'a' });
await provider.switchLeaf('s1', root.id);
const branchB = await provider.appendMessage('s1', { role: 'assistant', content: 'b' });
expect((await provider.listEntries('s1')).map(e => e.id).sort()).toEqual([root.id, branchA.id, branchB.id].sort());
expect((await provider.getActivePath('s1')).map(e => e.id)).toEqual([root.id, branchB.id]);
});
it('supports branch summary, compaction, labels, and resetLeaf', async () => {
const provider = createProvider();
await provider.createSession({ sessionId: 's1', provider: name === 'mysql' ? 'mysql' : 'file' });
const root = await provider.appendMessage('s1', { role: 'user', content: 'root' });
await provider.appendLabelChange('s1', root.id, '入口');
const kept = await provider.appendMessage('s1', { role: 'user', content: 'kept' });
await provider.appendCompaction('s1', { summary: 'summary', firstKeptEntryId: kept.id, tokensBefore: 1000 });
await provider.switchLeaf('s1', root.id);
const summary = await provider.appendBranchSummary('s1', { branchFromEntryId: root.id, summary: 'branch summary' });
expect(summary.type).toBe('branch_summary');
await provider.resetLeaf('s1');
const newRoot = await provider.appendMessage('s1', { role: 'user', content: 'new root' });
expect(newRoot.parentId).toBeNull();
expect((await provider.getSnapshot('s1')).labelsByEntryId[root.id]?.label).toBe('入口');
});
});
}
```
- [ ] **步骤 2实现 `provider.ts` 与 `snapshot.ts`**
Provider 必须包含:
- `createSession()`
- `getSession()`
- `updateSession()`
- `deleteSession()`
- `listEntries()`
- `appendEntry()`
- `appendMessage()`
- `appendThinkingLevelChange()`
- `appendModelChange()`
- `appendCompaction()`
- `appendBranchSummary()`
- `appendLabelChange()`
- `appendSessionInfo()`
- `updateInFlightEntry()`
- `switchLeaf()`
- `resetLeaf()`
- `createBranchedSession()`
- `getActivePath()`
- `getSnapshot()`
Snapshot 必须包含:
- `session`
- `entries`
- `activePath`
- `childrenByParentId`
- `labelsByEntryId`
- `runtimeContext`
- [ ] **步骤 3运行契约 scaffold 测试**
```bash
pnpm --filter @neta/backend test -- --runInBand test/session_tree_provider_contract.test.ts
```
预期:
- 如果只是导出 contract 工厂且未实例化 provider则通过。
- [ ] **步骤 4提交变更**
```bash
git add packages/backend/src/modules/netaclaw/session-tree/provider.ts packages/backend/src/modules/netaclaw/session-tree/snapshot.ts packages/backend/test/session_tree_provider_contract.test.ts
git commit -m "feat(agent-runtime): define session tree provider contract"
```
## 任务 5实现 Pi 单文件 JSONL File Provider
**文件:**
- 新增:`packages/backend/src/modules/netaclaw/session-tree/pi_session_file.ts`
- 新增:`packages/backend/src/modules/netaclaw/session-tree/file_provider.ts`
- 测试:`packages/backend/test/session_tree_file_provider.test.ts`
- [ ] **步骤 1编写 file provider 测试**
测试必须验证真实文件协议:
```typescript
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { FileSessionTreeProvider } from '../src/modules/netaclaw/session-tree/file_provider.js';
import { runSessionTreeProviderContract } from './session_tree_provider_contract.js';
describe('FileSessionTreeProvider', () => {
let dir: string;
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), 'neta-session-tree-'));
});
afterEach(() => {
rmSync(dir, { recursive: true, force: true });
});
runSessionTreeProviderContract('file', () => new FileSessionTreeProvider({ rootDir: dir }));
it('stores one JSONL file with session header as first line', async () => {
const provider = new FileSessionTreeProvider({ rootDir: dir });
await provider.createSession({ sessionId: 's1', provider: 'file', cwd: 'C:/workspace' });
await provider.appendMessage('s1', { role: 'user', content: 'hello' });
const session = await provider.getSession('s1');
const content = readFileSync(session!.sessionFile!, 'utf-8').trim().split(/\r?\n/).map(line => JSON.parse(line));
expect(content[0].type).toBe('session');
expect(content[1].type).toBe('message');
expect(content[1].parentId).toBeNull();
});
});
```
- [ ] **步骤 2实现 `pi_session_file.ts`**
实现要求:
- 迁移 Pi 的 `parseSessionEntries()``loadEntriesFromFile()``getTree()``branch()``resetLeaf()``branchWithSummary()``createBranchedSession()` 思路。
- 单 session 对应一个 `.jsonl` 文件。
- header 第一行只写一次。
- append entry 只追加一行。
- label 通过 label entry 解析,不修改 target entry。
- `updateInFlightEntry()` 不得频繁重写整个文件;首版可以将 in-flight entry 在内存中合并,完成时追加 completed entry。
- [ ] **步骤 3实现 `file_provider.ts`**
实现要求:
- `FileSessionTreeProvider` 只做 `SessionTreeProvider``pi_session_file.ts` 的适配。
- `appendMessage()` 等语义方法必须调用同一 append entry 通道。
- `createBranchedSession()` 必须复制 root 到 leaf 路径并保留路径内 label。
- [ ] **步骤 4运行测试确认通过**
```bash
pnpm --filter @neta/backend test -- --runInBand test/session_tree_file_provider.test.ts
```
预期:
- 通过。
- [ ] **步骤 5提交变更**
```bash
git add packages/backend/src/modules/netaclaw/session-tree/pi_session_file.ts packages/backend/src/modules/netaclaw/session-tree/file_provider.ts packages/backend/test/session_tree_file_provider.test.ts
git commit -m "feat(agent-runtime): add pi-style file session provider"
```
## 任务 6新增 MySQL 实体并通过 MCP 更新数据库
**文件:**
- 新增:`packages/backend/src/modules/netaclaw/entity/agent_session.ts`
- 新增:`packages/backend/src/modules/netaclaw/entity/agent_session_entry.ts`
- 修改:`packages/backend/src/entities.ts`
- 测试:`packages/backend/test/entity_exports.test.ts`
- [ ] **步骤 1扩展实体导出测试**
```typescript
import { entities } from '../src/entities.js';
import { NetaClawAgentSessionEntity } from '../src/modules/netaclaw/entity/agent_session.js';
import { NetaClawAgentSessionEntryEntity } from '../src/modules/netaclaw/entity/agent_session_entry.js';
describe('entities exports', () => {
it('exports session tree entities', () => {
expect(entities).toContain(NetaClawAgentSessionEntity);
expect(entities).toContain(NetaClawAgentSessionEntryEntity);
});
});
```
- [ ] **步骤 2新增实体**
实体要求:
- `agent_session.ts` 字段:`sessionId``provider``rootEntryId``leafEntryId``cwd``sessionFile``parentSessionId``agentId``userId``title``status``metadata`
- `agent_session_entry.ts` 字段:`sessionId``entryId``parentEntryId``entrySeq``type``message``summary``firstKeptEntryId``tokensBefore``thinkingLevel``provider``modelId``customType``data``display``targetEntryId``label``details``sourceRuntime``status`
- 两个实体继承 `packages/backend/src/modules/base/entity/base.ts` 中的 `BaseEntity`
- `sessionId + entryId` 必须唯一。
- `entrySeq` 用于稳定排序,不能只依赖 `createTime` 字符串。
- [ ] **步骤 3更新实体导出**
`packages/backend/src/entities.ts` 中按现有风格导入并展开两个实体模块。
- [ ] **步骤 4使用 MCP 修改数据库**
执行要求:
- 使用 MCP MySQL 工具直接删除旧历史会话/消息数据。
- 使用 MCP MySQL 工具创建或重建 `neta_agent_session``neta_agent_session_entry`
- 不新增 SQL migration 文件作为主路径。
- 修改后用 MCP `describe_table` 确认字段存在。
- [ ] **步骤 5运行测试确认通过**
```bash
pnpm --filter @neta/backend test -- --runInBand test/entity_exports.test.ts
```
预期:
- 通过。
- [ ] **步骤 6提交变更**
```bash
git add packages/backend/src/modules/netaclaw/entity/agent_session.ts packages/backend/src/modules/netaclaw/entity/agent_session_entry.ts packages/backend/src/entities.ts packages/backend/test/entity_exports.test.ts
git commit -m "feat(agent-runtime): add session tree mysql entities"
```
## 任务 7实现 MySQL Provider
**文件:**
- 新增:`packages/backend/src/modules/netaclaw/session-tree/mysql_provider.ts`
- 测试:`packages/backend/test/session_tree_mysql_provider.test.ts`
- [ ] **步骤 1编写 MySQL provider 测试**
测试要求:
- 复用 `runSessionTreeProviderContract('mysql', ...)`
- 使用 repository mock 或 Midway mock repository。
- 验证 `entrySeq` 稳定排序。
- 验证 `message` JSON 无损保存。
- 验证 compaction 与 file provider 的 context 结果一致。
- [ ] **步骤 2实现 `mysql_provider.ts`**
实现要求:
- MySQL provider 是 Pi entry log 的表映射,不是独立语义。
- `appendEntry()` 创建新 row默认推进 leaf。
- `appendBranchSummary()` 等价于 Pi `branchWithSummary()`
- `appendLabelChange()` 追加 label entry不修改 target entry。
- `createBranchedSession()` 复制 root 到 leaf path 到新 session。
- completed entry 禁止普通 update。
- [ ] **步骤 3运行测试确认通过**
```bash
pnpm --filter @neta/backend test -- --runInBand test/session_tree_mysql_provider.test.ts
```
预期:
- 通过。
- [ ] **步骤 4提交变更**
```bash
git add packages/backend/src/modules/netaclaw/session-tree/mysql_provider.ts packages/backend/test/session_tree_mysql_provider.test.ts
git commit -m "feat(agent-runtime): add mysql session tree provider"
```
## 任务 8接入 Agent 配置中的 Session Provider
**文件:**
- 新增:`packages/backend/src/modules/netaclaw/session-tree/provider_factory.ts`
- 修改:`packages/backend/src/modules/netaclaw/entity/agent.ts`
- 测试:`packages/backend/test/session_tree_provider_factory.test.ts`
- [ ] **步骤 1编写 provider factory 测试**
```typescript
import { resolveSessionProviderKind } from '../src/modules/netaclaw/session-tree/provider_factory.js';
describe('session tree provider factory', () => {
it('defaults to file provider', () => {
expect(resolveSessionProviderKind(null)).toBe('file');
expect(resolveSessionProviderKind({})).toBe('file');
});
it('accepts file and mysql only', () => {
expect(resolveSessionProviderKind({ sessionProvider: 'file' })).toBe('file');
expect(resolveSessionProviderKind({ sessionProvider: 'mysql' })).toBe('mysql');
expect(resolveSessionProviderKind({ sessionProvider: 'redis' })).toBe('file');
});
});
```
- [ ] **步骤 2实现 provider factory 与 agent 字段**
实现要求:
- `resolveSessionProviderKind()` 只接受 `file``mysql`
- `NetaClawAgentEntity` 新增 `sessionProvider` 字段,默认 `file`
- 字段注释使用中文,避免现有乱码注释继续扩散。
- [ ] **步骤 3运行测试确认通过**
```bash
pnpm --filter @neta/backend test -- --runInBand test/session_tree_provider_factory.test.ts
```
预期:
- 通过。
- [ ] **步骤 4提交变更**
```bash
git add packages/backend/src/modules/netaclaw/session-tree/provider_factory.ts packages/backend/src/modules/netaclaw/entity/agent.ts packages/backend/test/session_tree_provider_factory.test.ts
git commit -m "feat(agent-runtime): add session provider selection"
```
## 任务 9运行聚焦验证
**文件:**
- 不预期修改源码。
- [ ] **步骤 1运行新 session-tree 测试**
```bash
pnpm --filter @neta/backend test -- --runInBand test/session_tree_types.test.ts test/session_tree_path.test.ts test/session_tree_context_builder.test.ts test/session_tree_file_provider.test.ts test/session_tree_mysql_provider.test.ts test/session_tree_provider_factory.test.ts test/entity_exports.test.ts
```
预期:
- 全部通过。
- [ ] **步骤 2运行相关旧测试**
```bash
pnpm --filter @neta/backend test -- --runInBand test/netaclaw_session.test.ts test/chat_orchestrator.test.ts test/subagent_service.test.ts
```
预期:
- 全部通过,或明确记录旧接口将被后续计划替换导致的失败原因。
- [ ] **步骤 3运行后端构建**
```bash
pnpm --filter @neta/backend build
```
预期:
- 构建成功。
- [ ] **步骤 4检查工作区**
```bash
git status --short
```
预期:
- 只包含本任务相关变更。
## 自检
- 设计覆盖:本计划覆盖运行时内核子集,包括 Pi 兼容 entry tree、file provider、MySQL provider、context builder、snapshot 和 agent provider 选择。
- 直接复用:明确要求移植 Pi `SessionManager`、context builder、branch、compaction 关键语义。
- 范围控制:不包含对话 WebSocket 协议、前端 UI、subagent 进程编排、skill/tool/model 资源层、管理后台页面和视觉系统。
- 占位内容检查:没有 `TBD``TODO``implement later`
- 类型一致性:统一使用 `id/parentId` 表达 Pi entry使用 `sessionId/rootEntryId/leafEntryId` 表达 Neta session meta。
- 架构防错:明确禁止 `session.json + entries.jsonl`、禁止 completed entry 任意 update、禁止把 tool_call/tool_result 作为第一版事实源 entry。