31 KiB
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 entry;entry 通过 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与mysqlprovider 必须通过同一组契约测试。fileprovider 必须高强度移植 Pi 的SessionManager文件协议和纯逻辑。- 不得把 file provider 设计成
session.json + entries.jsonl双文件。 - 不得把第一版事实源拆成独立
tool_call/tool_resultentry;tool 状态作为AgentMessagepayload 或前端流式投影事件处理。 - 不得只用
content/summary字段表示消息;必须保留完整messagepayload。 - completed entry 默认不可修改;
updateEntry()只允许用于 in-flight 流式补写或明确的 patch 事件。
Pi 直接复用范围
优先从以下 Pi 文件移植:
C:/Users/lixin/Desktop/RZYX_ZT/pi-mono-main/packages/coding-agent/src/core/session-manager.tsC:/Users/lixin/Desktop/RZYX_ZT/pi-mono-main/packages/coding-agent/src/core/compaction/compaction.tsC:/Users/lixin/Desktop/RZYX_ZT/pi-mono-main/packages/coding-agent/src/core/compaction/branch-summarization.tsC:/Users/lixin/Desktop/RZYX_ZT/pi-mono-main/packages/coding-agent/src/core/compaction/utils.ts
必须移植或等价实现:
SessionHeader/SessionEntryunion。- 单文件 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()语义。
- 移植 Pi
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。
- 将 MySQL 表适配为同一
packages/backend/src/modules/netaclaw/session-tree/provider_factory.ts- 解析 agent 配置中的
sessionProvider。
- 解析 agent 配置中的
新增 TypeORM 实体:
packages/backend/src/modules/netaclaw/entity/agent_session.tspackages/backend/src/modules/netaclaw/entity/agent_session_entry.ts
修改现有文件:
packages/backend/src/modules/netaclaw/entity/agent.tspackages/backend/src/entities.ts
新增测试:
packages/backend/test/session_tree_types.test.tspackages/backend/test/session_tree_path.test.tspackages/backend/test/session_tree_context_builder.test.tspackages/backend/test/session_tree_file_provider.test.tspackages/backend/test/session_tree_mysql_provider.test.tspackages/backend/test/session_tree_provider_contract.test.tspackages/backend/test/session_tree_provider_factory.test.tspackages/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:编写失败的类型消费测试
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:运行测试确认失败
pnpm --filter @neta/backend test -- --runInBand test/session_tree_types.test.ts
预期:
-
失败,原因是
session-tree/types.js尚不存在。 -
步骤 3:实现
types.ts
核心类型必须包含:
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:运行测试确认通过
pnpm --filter @neta/backend test -- --runInBand test/session_tree_types.test.ts
预期:
-
通过。
-
步骤 5:提交变更
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:编写路径测试
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:运行测试确认通过
pnpm --filter @neta/backend test -- --runInBand test/session_tree_path.test.ts
预期:
-
通过。
-
步骤 4:提交变更
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:编写上下文构建测试
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:运行测试确认通过
pnpm --filter @neta/backend test -- --runInBand test/session_tree_context_builder.test.ts
预期:
-
通过。
-
步骤 4:提交变更
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 测试复用:
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 测试
pnpm --filter @neta/backend test -- --runInBand test/session_tree_provider_contract.test.ts
预期:
-
如果只是导出 contract 工厂且未实例化 provider,则通过。
-
步骤 4:提交变更
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 测试
测试必须验证真实文件协议:
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:运行测试确认通过
pnpm --filter @neta/backend test -- --runInBand test/session_tree_file_provider.test.ts
预期:
-
通过。
-
步骤 5:提交变更
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:扩展实体导出测试
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:运行测试确认通过
pnpm --filter @neta/backend test -- --runInBand test/entity_exports.test.ts
预期:
-
通过。
-
步骤 6:提交变更
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稳定排序。 -
验证
messageJSON 无损保存。 -
验证 compaction 与 file provider 的 context 结果一致。
-
步骤 2:实现
mysql_provider.ts
实现要求:
-
MySQL provider 是 Pi entry log 的表映射,不是独立语义。
-
appendEntry()创建新 row,默认推进 leaf。 -
appendBranchSummary()等价于 PibranchWithSummary()。 -
appendLabelChange()追加 label entry,不修改 target entry。 -
createBranchedSession()复制 root 到 leaf path 到新 session。 -
completed entry 禁止普通 update。
-
步骤 3:运行测试确认通过
pnpm --filter @neta/backend test -- --runInBand test/session_tree_mysql_provider.test.ts
预期:
-
通过。
-
步骤 4:提交变更
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 测试
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:运行测试确认通过
pnpm --filter @neta/backend test -- --runInBand test/session_tree_provider_factory.test.ts
预期:
-
通过。
-
步骤 4:提交变更
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 测试
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:运行相关旧测试
pnpm --filter @neta/backend test -- --runInBand test/netaclaw_session.test.ts test/chat_orchestrator.test.ts test/subagent_service.test.ts
预期:
-
全部通过,或明确记录旧接口将被后续计划替换导致的失败原因。
-
步骤 3:运行后端构建
pnpm --filter @neta/backend build
预期:
-
构建成功。
-
步骤 4:检查工作区
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。