test(ai-vault-search): cover OpenCode SQLite session freshness

Share the OpenCode DB fixture builders instead of keeping them inline in the
SQLite scanner suite, then drive discovery + a required-mode parse over a
session that gains a message: the synthetic candidate's mtime follows
time_updated, so the cache re-parses and the sink replaces the indexed rows.
An unchanged session still reuses the cached parse and leaves the index alone.
This commit is contained in:
Jinwoo-H
2026-09-06 04:48:04 -04:00
parent 4c6db749e2
commit 64d886916f
3 changed files with 404 additions and 205 deletions
@@ -0,0 +1,176 @@
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AiVaultScanIssue } from '../../shared/ai-vault-types'
import { sessionCandidatesFromDiscoveries } from '../ai-vault/session-scanner-candidates'
import {
applyOpenCodeSqliteSchema,
insertOpenCodeMessage,
insertOpenCodePart,
insertOpenCodeSession,
touchOpenCodeSession
} from '../ai-vault/session-scanner-opencode-sqlite-fixtures'
import {
createSessionParseStats,
parseAgentSessionFileCached,
resetSessionParseCacheForTests,
type SessionParseStats
} from '../ai-vault/session-scanner-parse-cache'
import { discoverAiVaultSessionSources } from '../ai-vault/session-scanner-source-discovery'
import { isolatedScanRoots } from '../ai-vault/session-scanner-test-fixtures'
import type { AiVaultScanOptions } from '../ai-vault/session-scanner-types'
import {
registerSessionSearchIndexSink,
withSessionSearchIndexRequired
} from '../ai-vault/session-search-capture'
import Database from '../sqlite/sync-database'
import { SessionSearchStore } from './session-search-store'
// Why: a source-level suite has no built worker bundle, so the SQLite reads run
// inline here; production fails closed when the bundle is absent.
vi.mock('../ai-vault/session-scanner-opencode-sqlite-worker-spawn', async () => {
const [{ listOpenCodeSqliteSessions }, { parseOpenCodeSqliteSession }] = await Promise.all([
import('../ai-vault/session-scanner-opencode-sqlite-list'),
import('../ai-vault/session-scanner-opencode-sqlite')
])
return {
listOpenCodeSqliteSessionsViaWorker: listOpenCodeSqliteSessions,
parseOpenCodeSqliteSessionViaWorker: parseOpenCodeSqliteSession
}
})
const SESSION_ID = 'ses_fresh0000000000000000000000'
const CREATED_MS = 1_777_634_000_000
let tempRoots: string[] = []
let store: SessionSearchStore
let apply: ReturnType<typeof vi.spyOn>
beforeEach(async () => {
resetSessionParseCacheForTests()
store = new SessionSearchStore(join(await makeTempDir(), 'index.sqlite'), (error) => {
throw error
})
apply = vi.spyOn(store, 'apply')
registerSessionSearchIndexSink(store)
})
afterEach(async () => {
registerSessionSearchIndexSink(null)
store.close()
await Promise.all(tempRoots.map((root) => rm(root, { recursive: true, force: true })))
tempRoots = []
})
async function makeTempDir(): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'orca-opencode-freshness-'))
tempRoots.push(root)
return root
}
/** One opencode.db holding a single session with its first user turn. */
async function createOpenCodeDb(): Promise<string> {
const dbPath = join(await makeTempDir(), 'opencode.db')
const db = new Database(dbPath)
applyOpenCodeSqliteSchema(db)
insertOpenCodeSession(db, {
id: SESSION_ID,
title: 'Ballast planning',
timeCreated: CREATED_MS,
timeUpdated: CREATED_MS + 1_000
})
insertOpenCodeMessage(db, {
id: 'msg_1',
sessionId: SESSION_ID,
role: 'user',
timeCreated: CREATED_MS + 500
})
insertOpenCodePart(db, {
id: 'prt_1',
messageId: 'msg_1',
sessionId: SESSION_ID,
timeCreated: CREATED_MS + 500,
text: 'how do we raise the vacuum quota for the intake pump'
})
db.close()
return dbPath
}
/** Appends an assistant turn and bumps the session row, as OpenCode does. */
async function appendAssistantTurn(dbPath: string): Promise<void> {
const db = new Database(dbPath)
insertOpenCodeMessage(db, {
id: 'msg_2',
sessionId: SESSION_ID,
role: 'assistant',
timeCreated: CREATED_MS + 1_500
})
insertOpenCodePart(db, {
id: 'prt_2',
messageId: 'msg_2',
sessionId: SESSION_ID,
timeCreated: CREATED_MS + 1_500,
text: 'drain the ballast tanks before the next calibration run'
})
touchOpenCodeSession(db, { id: SESSION_ID, timeUpdated: CREATED_MS + 2_000 })
db.close()
}
/** Mirrors SessionSearchService.refreshRecent: discover, then parse in required mode. */
async function refreshRecent(dbPath: string): Promise<SessionParseStats> {
const options: AiVaultScanOptions = {
...isolatedScanRoots(await makeTempDir()),
opencodeDbPaths: [dbPath]
}
const issues: AiVaultScanIssue[] = []
const discoveries = await discoverAiVaultSessionSources({ options, limitPerAgent: 12, issues })
const candidates = await sessionCandidatesFromDiscoveries(discoveries, options)
const stats = createSessionParseStats()
await withSessionSearchIndexRequired(async () => {
for (const candidate of candidates) {
await parseAgentSessionFileCached(candidate, process.platform, stats)
}
})
return stats
}
describe('OpenCode SQLite session freshness', () => {
it('re-indexes a session that gained a message since the last parse', async () => {
const dbPath = await createOpenCodeDb()
const first = await refreshRecent(dbPath)
expect(first.fullParses).toBe(1)
expect(store.search({ query: 'vacuum quota' }).hits).toMatchObject([
{ agent: 'opencode', sessionId: SESSION_ID }
])
expect(store.coverage().messagesIndexed).toBe(1)
await appendAssistantTurn(dbPath)
const second = await refreshRecent(dbPath)
expect(second.reused).toBe(0)
expect(second.fullParses).toBe(1)
expect(apply).toHaveBeenCalledTimes(2)
expect(apply.mock.calls[1]?.[0]).toMatchObject({ mode: 'replace' })
expect(store.search({ query: 'ballast tanks' }).hits).toMatchObject([
{ agent: 'opencode', sessionId: SESSION_ID }
])
// The replace re-emits the whole session, so the first turn stays searchable.
expect(store.search({ query: 'vacuum quota' }).hits).toHaveLength(1)
expect(store.coverage().messagesIndexed).toBe(2)
})
it('reuses the cached parse and leaves the index alone when nothing changed', async () => {
const dbPath = await createOpenCodeDb()
await refreshRecent(dbPath)
expect(apply).toHaveBeenCalledTimes(1)
const second = await refreshRecent(dbPath)
expect(second.reused).toBe(1)
expect(second.fullParses).toBe(0)
expect(apply).toHaveBeenCalledTimes(1)
expect(store.coverage().messagesIndexed).toBe(1)
})
})
@@ -0,0 +1,180 @@
import type Database from '../sqlite/sync-database'
// OpenCode 1.17.x DB shape used by the SQLite scanner suites: the schema plus
// row builders for `session`, `message` and `part`. Shared so the discovery,
// parser and search-freshness tests all exercise one fixture definition.
/** Full 1.17.x schema, including the columns the scanner treats as optional. */
export function applyOpenCodeSqliteSchema(db: Database.Database): void {
db.exec(`
CREATE TABLE session (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
parent_id TEXT,
slug TEXT NOT NULL,
directory TEXT NOT NULL,
title TEXT NOT NULL,
version TEXT NOT NULL,
share_url TEXT,
summary_additions INTEGER,
summary_deletions INTEGER,
summary_files INTEGER,
summary_diffs TEXT,
revert TEXT,
permission TEXT,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL,
time_compacting INTEGER,
time_archived INTEGER,
workspace_id TEXT,
path TEXT,
agent TEXT,
model TEXT,
cost REAL DEFAULT 0 NOT NULL,
tokens_input INTEGER DEFAULT 0 NOT NULL,
tokens_output INTEGER DEFAULT 0 NOT NULL,
tokens_reasoning INTEGER DEFAULT 0 NOT NULL,
tokens_cache_read INTEGER DEFAULT 0 NOT NULL,
tokens_cache_write INTEGER DEFAULT 0 NOT NULL,
metadata TEXT
);
CREATE TABLE message (
id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL,
data TEXT NOT NULL
);
CREATE TABLE part (
id TEXT PRIMARY KEY,
message_id TEXT NOT NULL,
session_id TEXT NOT NULL,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL,
data TEXT NOT NULL
);
CREATE TABLE project (
id TEXT PRIMARY KEY,
worktree TEXT NOT NULL,
vcs TEXT,
name TEXT,
icon_url TEXT,
icon_color TEXT,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL,
time_initialized INTEGER,
sandboxes TEXT NOT NULL,
commands TEXT,
icon_url_override TEXT
);
`)
}
/** Only the columns the list reader requires, for the older-schema paths. */
export function applyMinimalOpenCodeSqliteSchema(db: Database.Database): void {
db.exec(`CREATE TABLE session (
id TEXT PRIMARY KEY,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL
);`)
}
export function insertOpenCodeSession(
db: Database.Database,
args: {
id: string
title?: string
directory?: string
timeCreated: number
timeUpdated: number
parentId?: string | null
timeArchived?: number | null
model?: string | null
agent?: string | null
tokensInput?: number
tokensOutput?: number
tokensReasoning?: number
tokensCacheRead?: number
cost?: number
}
): void {
db.prepare(
`INSERT INTO session (id, project_id, parent_id, slug, directory, title, version,
time_created, time_updated, time_archived, model, agent, cost,
tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write)
VALUES (?, 'proj-1', ?, ?, ?, ?, '1.0.0',
?, ?, ?, ?, ?, ?,
?, ?, ?, ?, 0)`
).run(
args.id,
args.parentId ?? null,
`slug-${args.id}`,
args.directory ?? '/tmp/opencode',
args.title ?? 'OpenCode title',
args.timeCreated,
args.timeUpdated,
args.timeArchived ?? null,
args.model ?? JSON.stringify({ id: 'glm-5.2', providerID: 'zai-coding-plan' }),
args.agent ?? 'build',
args.cost ?? 0,
args.tokensInput ?? 100,
args.tokensOutput ?? 40,
args.tokensReasoning ?? 10,
args.tokensCacheRead ?? 5
)
}
export function insertOpenCodeMessage(
db: Database.Database,
args: {
id: string
sessionId: string
role: 'user' | 'assistant'
timeCreated: number
summaryTitle?: string | null
summaryBody?: string | null
}
): void {
const data = JSON.stringify({
role: args.role,
time: { created: args.timeCreated },
agent: 'build',
summary:
args.summaryTitle || args.summaryBody
? { title: args.summaryTitle ?? null, body: args.summaryBody ?? null }
: undefined
})
db.prepare(
`INSERT INTO message (id, session_id, time_created, time_updated, data)
VALUES (?, ?, ?, ?, ?)`
).run(args.id, args.sessionId, args.timeCreated, args.timeCreated, data)
}
export function insertOpenCodePart(
db: Database.Database,
args: {
id: string
messageId: string
sessionId: string
timeCreated: number
type?: 'text' | 'tool' | 'reasoning'
text?: string
}
): void {
const data = JSON.stringify({
type: args.type ?? 'text',
text: args.text ?? 'hello world'
})
db.prepare(
`INSERT INTO part (id, message_id, session_id, time_created, time_updated, data)
VALUES (?, ?, ?, ?, ?, ?)`
).run(args.id, args.messageId, args.sessionId, args.timeCreated, args.timeCreated, data)
}
/** Mirrors OpenCode bumping the session row when a message lands. */
export function touchOpenCodeSession(
db: Database.Database,
args: { id: string; timeUpdated: number }
): void {
db.prepare('UPDATE session SET time_updated = ? WHERE id = ?').run(args.timeUpdated, args.id)
}
@@ -3,6 +3,13 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import Database from '../sqlite/sync-database'
import {
applyMinimalOpenCodeSqliteSchema,
applyOpenCodeSqliteSchema,
insertOpenCodeMessage,
insertOpenCodePart,
insertOpenCodeSession
} from './session-scanner-opencode-sqlite-fixtures'
import { buildOpenCodeSqliteCandidatePath } from './session-scanner-opencode-sqlite-paths'
import { listOpenCodeSqliteSessions } from './session-scanner-opencode-sqlite-discovery'
import { parseOpenCodeSqliteSession } from './session-scanner-opencode-sqlite'
@@ -25,181 +32,17 @@ function createTempDb(): { db: Database.Database; path: string } {
return { db: new Database(path), path }
}
function applyOpenCodeSchema(db: Database.Database): void {
db.exec(`
CREATE TABLE session (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
parent_id TEXT,
slug TEXT NOT NULL,
directory TEXT NOT NULL,
title TEXT NOT NULL,
version TEXT NOT NULL,
share_url TEXT,
summary_additions INTEGER,
summary_deletions INTEGER,
summary_files INTEGER,
summary_diffs TEXT,
revert TEXT,
permission TEXT,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL,
time_compacting INTEGER,
time_archived INTEGER,
workspace_id TEXT,
path TEXT,
agent TEXT,
model TEXT,
cost REAL DEFAULT 0 NOT NULL,
tokens_input INTEGER DEFAULT 0 NOT NULL,
tokens_output INTEGER DEFAULT 0 NOT NULL,
tokens_reasoning INTEGER DEFAULT 0 NOT NULL,
tokens_cache_read INTEGER DEFAULT 0 NOT NULL,
tokens_cache_write INTEGER DEFAULT 0 NOT NULL,
metadata TEXT
);
CREATE TABLE message (
id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL,
data TEXT NOT NULL
);
CREATE TABLE part (
id TEXT PRIMARY KEY,
message_id TEXT NOT NULL,
session_id TEXT NOT NULL,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL,
data TEXT NOT NULL
);
CREATE TABLE project (
id TEXT PRIMARY KEY,
worktree TEXT NOT NULL,
vcs TEXT,
name TEXT,
icon_url TEXT,
icon_color TEXT,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL,
time_initialized INTEGER,
sandboxes TEXT NOT NULL,
commands TEXT,
icon_url_override TEXT
);
`)
}
function applyMinimalOpenCodeSchema(db: Database.Database): void {
db.exec(`CREATE TABLE session (
id TEXT PRIMARY KEY,
time_created INTEGER NOT NULL,
time_updated INTEGER NOT NULL
);`)
}
function insertSession(
db: Database.Database,
args: {
id: string
title?: string
directory?: string
timeCreated: number
timeUpdated: number
parentId?: string | null
timeArchived?: number | null
model?: string | null
agent?: string | null
tokensInput?: number
tokensOutput?: number
tokensReasoning?: number
tokensCacheRead?: number
cost?: number
}
): void {
db.prepare(
`INSERT INTO session (id, project_id, parent_id, slug, directory, title, version,
time_created, time_updated, time_archived, model, agent, cost,
tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write)
VALUES (?, 'proj-1', ?, ?, ?, ?, '1.0.0',
?, ?, ?, ?, ?, ?,
?, ?, ?, ?, 0)`
).run(
args.id,
args.parentId ?? null,
`slug-${args.id}`,
args.directory ?? '/tmp/opencode',
args.title ?? 'OpenCode title',
args.timeCreated,
args.timeUpdated,
args.timeArchived ?? null,
args.model ?? JSON.stringify({ id: 'glm-5.2', providerID: 'zai-coding-plan' }),
args.agent ?? 'build',
args.cost ?? 0,
args.tokensInput ?? 100,
args.tokensOutput ?? 40,
args.tokensReasoning ?? 10,
args.tokensCacheRead ?? 5
)
}
function insertMessage(
db: Database.Database,
args: {
id: string
sessionId: string
role: 'user' | 'assistant'
timeCreated: number
summaryTitle?: string | null
summaryBody?: string | null
}
): void {
const data = JSON.stringify({
role: args.role,
time: { created: args.timeCreated },
agent: 'build',
summary:
args.summaryTitle || args.summaryBody
? { title: args.summaryTitle ?? null, body: args.summaryBody ?? null }
: undefined
})
db.prepare(
`INSERT INTO message (id, session_id, time_created, time_updated, data)
VALUES (?, ?, ?, ?, ?)`
).run(args.id, args.sessionId, args.timeCreated, args.timeCreated, data)
}
function insertPart(
db: Database.Database,
args: {
id: string
messageId: string
sessionId: string
timeCreated: number
type?: 'text' | 'tool' | 'reasoning'
text?: string
}
): void {
const data = JSON.stringify({
type: args.type ?? 'text',
text: args.text ?? 'hello world'
})
db.prepare(
`INSERT INTO part (id, message_id, session_id, time_created, time_updated, data)
VALUES (?, ?, ?, ?, ?, ?)`
).run(args.id, args.messageId, args.sessionId, args.timeCreated, args.timeCreated, data)
}
describe('listOpenCodeSqliteSessions', () => {
it('returns candidates sorted by time_updated desc via the synthesized mtimeMs', async () => {
const { db, path } = createTempDb()
applyOpenCodeSchema(db)
insertSession(db, {
applyOpenCodeSqliteSchema(db)
insertOpenCodeSession(db, {
id: 'ses_old',
title: 'Old',
timeCreated: 1_777_634_000_000,
timeUpdated: 1_777_634_001_000
})
insertSession(db, {
insertOpenCodeSession(db, {
id: 'ses_new',
title: 'New',
timeCreated: 1_777_634_002_000,
@@ -223,8 +66,8 @@ describe('listOpenCodeSqliteSessions', () => {
it('dedups matching session ids across databases and keeps the newest row', async () => {
const { db: oldDb, path: oldPath } = createTempDb()
applyOpenCodeSchema(oldDb)
insertSession(oldDb, {
applyOpenCodeSqliteSchema(oldDb)
insertOpenCodeSession(oldDb, {
id: 'ses_duplicate',
title: 'Old duplicate',
timeCreated: 1_777_634_000_000,
@@ -233,8 +76,8 @@ describe('listOpenCodeSqliteSessions', () => {
oldDb.close()
const { db: newDb, path: newPath } = createTempDb()
applyOpenCodeSchema(newDb)
insertSession(newDb, {
applyOpenCodeSqliteSchema(newDb)
insertOpenCodeSession(newDb, {
id: 'ses_duplicate',
title: 'New duplicate',
timeCreated: 1_777_634_002_000,
@@ -252,19 +95,19 @@ describe('listOpenCodeSqliteSessions', () => {
})
it('excludes archived and child sessions', async () => {
const { db, path } = createTempDb()
applyOpenCodeSchema(db)
insertSession(db, {
applyOpenCodeSqliteSchema(db)
insertOpenCodeSession(db, {
id: 'ses_normal',
timeCreated: 1_777_634_000_000,
timeUpdated: 1_777_634_001_000
})
insertSession(db, {
insertOpenCodeSession(db, {
id: 'ses_archived',
timeCreated: 1_777_634_000_000,
timeUpdated: 1_777_634_002_000,
timeArchived: 1_777_634_002_500
})
insertSession(db, {
insertOpenCodeSession(db, {
id: 'ses_child',
timeCreated: 1_777_634_000_000,
timeUpdated: 1_777_634_003_000,
@@ -309,7 +152,7 @@ describe('listOpenCodeSqliteSessions', () => {
it('lists sessions from a minimal readable session table', async () => {
const { db, path } = createTempDb()
applyMinimalOpenCodeSchema(db)
applyMinimalOpenCodeSqliteSchema(db)
db.prepare(`INSERT INTO session VALUES ('ses_minimal', 1777634000000, 1777634001000)`).run()
db.close()
@@ -328,8 +171,8 @@ describe('listOpenCodeSqliteSessions', () => {
describe('parseOpenCodeSqliteSession', () => {
it('builds an AiVaultSession with title, cwd, model, tokens, and resume command', async () => {
const { db, path } = createTempDb()
applyOpenCodeSchema(db)
insertSession(db, {
applyOpenCodeSqliteSchema(db)
insertOpenCodeSession(db, {
id: 'ses_1',
title: 'OpenCode title',
directory: '/tmp/opencode',
@@ -341,27 +184,27 @@ describe('parseOpenCodeSqliteSession', () => {
tokensCacheRead: 5,
cost: 0.01
})
insertMessage(db, {
insertOpenCodeMessage(db, {
id: 'msg_1',
sessionId: 'ses_1',
role: 'user',
timeCreated: 1_777_634_000_500,
summaryTitle: 'OpenCode title'
})
insertPart(db, {
insertOpenCodePart(db, {
id: 'prt_1',
messageId: 'msg_1',
sessionId: 'ses_1',
timeCreated: 1_777_634_000_500,
text: 'Plan the work'
})
insertMessage(db, {
insertOpenCodeMessage(db, {
id: 'msg_2',
sessionId: 'ses_1',
role: 'assistant',
timeCreated: 1_777_634_000_900
})
insertPart(db, {
insertOpenCodePart(db, {
id: 'prt_2',
messageId: 'msg_2',
sessionId: 'ses_1',
@@ -396,21 +239,21 @@ describe('parseOpenCodeSqliteSession', () => {
it('falls back to summary.body for title when session.title is empty', async () => {
const { db, path } = createTempDb()
applyOpenCodeSchema(db)
insertSession(db, {
applyOpenCodeSqliteSchema(db)
insertOpenCodeSession(db, {
id: 'ses_2',
title: '',
timeCreated: 1_777_634_000_000,
timeUpdated: 1_777_634_001_000
})
insertMessage(db, {
insertOpenCodeMessage(db, {
id: 'msg_1',
sessionId: 'ses_2',
role: 'user',
timeCreated: 1_777_634_000_500,
summaryBody: 'fallback title from summary'
})
insertPart(db, {
insertOpenCodePart(db, {
id: 'prt_1',
messageId: 'msg_1',
sessionId: 'ses_2',
@@ -430,8 +273,8 @@ describe('parseOpenCodeSqliteSession', () => {
it('returns null when the session id is not found', async () => {
const { db, path } = createTempDb()
applyOpenCodeSchema(db)
insertSession(db, {
applyOpenCodeSqliteSchema(db)
insertOpenCodeSession(db, {
id: 'ses_real',
timeCreated: 1_777_634_000_000,
timeUpdated: 1_777_634_001_000
@@ -459,7 +302,7 @@ describe('parseOpenCodeSqliteSession', () => {
it('parses a minimal readable session table without optional columns or messages', async () => {
const { db, path } = createTempDb()
applyMinimalOpenCodeSchema(db)
applyMinimalOpenCodeSqliteSchema(db)
db.prepare(`INSERT INTO session VALUES ('ses_minimal', 1777634000000, 1777634001000)`).run()
db.close()
@@ -481,8 +324,8 @@ describe('parseOpenCodeSqliteSession', () => {
it('extracts model from older modelID schema', async () => {
const { db, path } = createTempDb()
applyOpenCodeSchema(db)
insertSession(db, {
applyOpenCodeSqliteSchema(db)
insertOpenCodeSession(db, {
id: 'ses_3',
timeCreated: 1_777_634_000_000,
timeUpdated: 1_777_634_001_000,
@@ -500,26 +343,26 @@ describe('parseOpenCodeSqliteSession', () => {
it('captures every text part of the earliest user message and no later turn', async () => {
const { db, path } = createTempDb()
applyOpenCodeSchema(db)
insertSession(db, {
applyOpenCodeSqliteSchema(db)
insertOpenCodeSession(db, {
id: 'ses_fp',
timeCreated: 1_777_634_000_000,
timeUpdated: 1_777_634_900_000
})
insertMessage(db, {
insertOpenCodeMessage(db, {
id: 'msg_1',
sessionId: 'ses_fp',
role: 'user',
timeCreated: 1_777_634_000_000
})
insertPart(db, {
insertOpenCodePart(db, {
id: 'part_1a',
messageId: 'msg_1',
sessionId: 'ses_fp',
timeCreated: 10,
text: 'first ask line one'
})
insertPart(db, {
insertOpenCodePart(db, {
id: 'part_1b',
messageId: 'msg_1',
sessionId: 'ses_fp',
@@ -527,7 +370,7 @@ describe('parseOpenCodeSqliteSession', () => {
text: 'first ask line two'
})
// Non-text parts of the same message must not leak into the copied prompt.
insertPart(db, {
insertOpenCodePart(db, {
id: 'part_1c',
messageId: 'msg_1',
sessionId: 'ses_fp',
@@ -535,13 +378,13 @@ describe('parseOpenCodeSqliteSession', () => {
type: 'tool',
text: 'tool output blob'
})
insertMessage(db, {
insertOpenCodeMessage(db, {
id: 'msg_2',
sessionId: 'ses_fp',
role: 'user',
timeCreated: 1_777_634_500_000
})
insertPart(db, {
insertOpenCodePart(db, {
id: 'part_2a',
messageId: 'msg_2',
sessionId: 'ses_fp',
@@ -559,19 +402,19 @@ describe('parseOpenCodeSqliteSession', () => {
it('skips an earliest user message that has no text parts', async () => {
const { db, path } = createTempDb()
applyOpenCodeSchema(db)
insertSession(db, {
applyOpenCodeSqliteSchema(db)
insertOpenCodeSession(db, {
id: 'ses_fp2',
timeCreated: 1_777_634_000_000,
timeUpdated: 1_777_634_900_000
})
insertMessage(db, {
insertOpenCodeMessage(db, {
id: 'msg_1',
sessionId: 'ses_fp2',
role: 'user',
timeCreated: 1_777_634_000_000
})
insertPart(db, {
insertOpenCodePart(db, {
id: 'part_1a',
messageId: 'msg_1',
sessionId: 'ses_fp2',
@@ -579,13 +422,13 @@ describe('parseOpenCodeSqliteSession', () => {
type: 'tool',
text: 'tool only'
})
insertMessage(db, {
insertOpenCodeMessage(db, {
id: 'msg_2',
sessionId: 'ses_fp2',
role: 'user',
timeCreated: 1_777_634_500_000
})
insertPart(db, {
insertOpenCodePart(db, {
id: 'part_2a',
messageId: 'msg_2',
sessionId: 'ses_fp2',