From d14534b849f2aad1f18997c1548c5b0c7bac4c50 Mon Sep 17 00:00:00 2001 From: Gui-Yue Date: Wed, 1 Apr 2026 12:54:31 +0800 Subject: [PATCH 1/2] 2.7.5 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12b3899..10bb48f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@shareai-lab/kode-sdk", - "version": "2.7.4", + "version": "2.7.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@shareai-lab/kode-sdk", - "version": "2.7.4", + "version": "2.7.5", "license": "MIT", "dependencies": { "@alibaba-group/opensandbox": "~0.1.4", diff --git a/package.json b/package.json index 588aaf1..3323d34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shareai-lab/kode-sdk", - "version": "2.7.4", + "version": "2.7.5", "description": "Event-driven, long-running AI Agent development framework with enterprise-grade persistence and context management", "main": "dist/index.js", "types": "dist/index.d.ts", From 439f95a54b0131425ed825d113a543c5d7769db8 Mon Sep 17 00:00:00 2001 From: Gui-Yue Date: Wed, 15 Apr 2026 18:30:48 +0800 Subject: [PATCH 2/2] fix(sqlite): use upsert instead of replace to prevent cascade deletion of child records --- src/infra/db/sqlite/sqlite-store.ts | 21 ++++++- tests/unit/infra/db/sqlite-store.test.ts | 79 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/infra/db/sqlite/sqlite-store.ts b/src/infra/db/sqlite/sqlite-store.ts index 7984b8a..62bb411 100644 --- a/src/infra/db/sqlite/sqlite-store.ts +++ b/src/infra/db/sqlite/sqlite-store.ts @@ -60,6 +60,7 @@ export class SqliteStore implements ExtendedStore { // ========== 数据库初始化 ========== private initialize(): void { + this.db.pragma('foreign_keys = ON'); this.createTables(); this.createIndexes(); } @@ -365,10 +366,16 @@ export class SqliteStore implements ExtendedStore { async saveSnapshot(agentId: string, snapshot: Snapshot): Promise { const stmt = this.db.prepare(` - INSERT OR REPLACE INTO snapshots ( + INSERT INTO snapshots ( agent_id, snapshot_id, messages, last_sfp_index, last_bookmark, created_at, metadata ) VALUES (?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(agent_id, snapshot_id) DO UPDATE SET + messages = excluded.messages, + last_sfp_index = excluded.last_sfp_index, + last_bookmark = excluded.last_bookmark, + created_at = excluded.created_at, + metadata = excluded.metadata `); stmt.run( @@ -441,11 +448,21 @@ export class SqliteStore implements ExtendedStore { async saveInfo(agentId: string, info: AgentInfo): Promise { const stmt = this.db.prepare(` - INSERT OR REPLACE INTO agents ( + INSERT INTO agents ( agent_id, template_id, created_at, config_version, lineage, message_count, last_sfp_index, last_bookmark, breakpoint, metadata ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(agent_id) DO UPDATE SET + template_id = excluded.template_id, + created_at = excluded.created_at, + config_version = excluded.config_version, + lineage = excluded.lineage, + message_count = excluded.message_count, + last_sfp_index = excluded.last_sfp_index, + last_bookmark = excluded.last_bookmark, + breakpoint = excluded.breakpoint, + metadata = excluded.metadata `); stmt.run( diff --git a/tests/unit/infra/db/sqlite-store.test.ts b/tests/unit/infra/db/sqlite-store.test.ts index 0309c50..7ad9ac9 100644 --- a/tests/unit/infra/db/sqlite-store.test.ts +++ b/tests/unit/infra/db/sqlite-store.test.ts @@ -478,6 +478,85 @@ runner.test('aggregateStats - 统计准确性', async () => { expect.toEqual(stats.toolCallsByName!['fs_read'], 1); }); +// ========== 5.1.6b 回归测试 - saveInfo 不应级联删除子表数据 (Issue #49) ========== + +runner.test('saveInfo - 更新不应删除 messages 和 tool_calls (Issue #49)', async () => { + const agentId = 'agt-issue49'; + + // 1. 创建 Agent + await store.saveInfo(agentId, { + agentId, + templateId: 'test-template', + createdAt: new Date().toISOString(), + configVersion: 'v2.7.0', + lineage: [], + messageCount: 0, + lastSfpIndex: -1, + metadata: {} + }); + + // 2. 保存 messages + await store.saveMessages(agentId, [ + { role: 'user', content: [{ type: 'text', text: 'Hello' }] }, + { role: 'assistant', content: [{ type: 'text', text: 'Hi!' }] } + ]); + + // 3. 保存 tool_calls + await store.saveToolCallRecords(agentId, [ + { + id: 'call_issue49', + name: 'fs_read', + input: { path: '/test.txt' }, + state: 'COMPLETED' as any, + approval: { required: false }, + result: { content: 'ok' }, + isError: false, + createdAt: Date.now(), + updatedAt: Date.now(), + auditTrail: [] + } + ]); + + // 4. 保存 snapshot + await store.saveSnapshot(agentId, { + id: 'snap:issue49', + messages: [{ role: 'user', content: [{ type: 'text', text: 'snap' }] }], + lastSfpIndex: 0, + lastBookmark: { seq: 1, timestamp: Date.now() }, + createdAt: new Date().toISOString() + }); + + // 5. 再次调用 saveInfo 更新 agent 元数据(这是触发 bug 的操作) + await store.saveInfo(agentId, { + agentId, + templateId: 'test-template', + createdAt: new Date().toISOString(), + configVersion: 'v2.7.1', // 版本更新 + lineage: [], + messageCount: 2, + lastSfpIndex: 0, + metadata: { updated: true } + }); + + // 6. 验证子表数据未被删除 + const messages = await store.loadMessages(agentId); + expect.toHaveLength(messages, 2); + expect.toEqual((messages[0].content[0] as any).text, 'Hello'); + + const toolCalls = await store.loadToolCallRecords(agentId); + expect.toHaveLength(toolCalls, 1); + expect.toEqual(toolCalls[0].id, 'call_issue49'); + + const snapshots = await store.listSnapshots(agentId); + expect.toHaveLength(snapshots, 1); + expect.toEqual(snapshots[0].id, 'snap:issue49'); + + // 7. 验证 agent info 确实已更新 + const info = await store.loadInfo(agentId); + expect.toEqual(info!.configVersion, 'v2.7.1'); + expect.toDeepEqual(info!.metadata, { updated: true }); +}); + // ========== 5.1.7 测试事务一致性 ========== runner.test('saveMessages - 事务回滚测试', async () => {