2151 lines
77 KiB
JavaScript
2151 lines
77 KiB
JavaScript
import test from 'node:test';
|
|||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import {
|
||
|
|
mkdirSync,
|
||
|
|
mkdtempSync,
|
||
|
|
readFileSync,
|
||
|
|
renameSync,
|
||
|
|
statSync,
|
||
|
|
symlinkSync,
|
||
|
|
unlinkSync,
|
||
|
|
utimesSync,
|
||
|
|
writeFileSync,
|
||
|
|
} from 'node:fs';
|
||
|
|
import { tmpdir } from 'node:os';
|
||
|
|
import { dirname, join } from 'node:path';
|
||
|
|
import { DatabaseSync } from 'node:sqlite';
|
||
|
|
|
||
|
|
import {
|
||
|
|
createPiProvider,
|
||
|
|
piSessionId,
|
||
|
|
resolveDefaultPiRoot,
|
||
|
|
} from '../packages/core/src/providers/pi.ts';
|
||
|
|
import { createProviderRegistry } from '../packages/core/src/providers/registry.ts';
|
||
|
|
import { assembleSessionDetail } from '../packages/core/src/session-detail.ts';
|
||
|
|
import { persist } from '../packages/core/src/persist.ts';
|
||
|
|
import { createQueryApi } from '../packages/core/src/query.ts';
|
||
|
|
|
||
|
|
const SCHEMA = readFileSync(new URL('../packages/core/src/schema.sql', import.meta.url), 'utf8');
|
||
|
|
const FIXTURES = new URL('./fixtures/pi/', import.meta.url);
|
||
|
|
|
||
|
|
function drain(generator) {
|
||
|
|
const values = [];
|
||
|
|
let step = generator.next();
|
||
|
|
while (!step.done) {
|
||
|
|
values.push(step.value);
|
||
|
|
step = generator.next();
|
||
|
|
}
|
||
|
|
return { values, cursor: step.value };
|
||
|
|
}
|
||
|
|
|
||
|
|
function fixture(name) {
|
||
|
|
return readFileSync(new URL(name, FIXTURES), 'utf8');
|
||
|
|
}
|
||
|
|
|
||
|
|
function writeSession(content, { root, relativePath = 'project/session.jsonl' } = {}) {
|
||
|
|
const sessionRoot = root ?? mkdtempSync(join(tmpdir(), 'obelisk-pi-'));
|
||
|
|
const path = join(sessionRoot, relativePath);
|
||
|
|
mkdirSync(dirname(path), { recursive: true });
|
||
|
|
writeFileSync(path, content);
|
||
|
|
return { root: sessionRoot, path };
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseOnly(root) {
|
||
|
|
const provider = createPiProvider({ rootDir: root });
|
||
|
|
const units = provider.discover({ lastCursor: () => null });
|
||
|
|
assert.equal(units.length, 1);
|
||
|
|
return { provider, unit: units[0], ...drain(provider.parse(units[0], null)) };
|
||
|
|
}
|
||
|
|
|
||
|
|
function jsonl(records, trailingNewline = true) {
|
||
|
|
return records.map(record => JSON.stringify(record)).join('\n') + (trailingNewline ? '\n' : '');
|
||
|
|
}
|
||
|
|
|
||
|
|
function header(overrides = {}) {
|
||
|
|
return {
|
||
|
|
type: 'session',
|
||
|
|
version: 3,
|
||
|
|
id: 'pi-test-session',
|
||
|
|
timestamp: '2026-08-02T10:00:00.000Z',
|
||
|
|
cwd: '/tmp/pi-test-project',
|
||
|
|
...overrides,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function userEntry(id, parentId, text, timestamp = '2026-08-02T10:00:01.000Z') {
|
||
|
|
return {
|
||
|
|
type: 'message',
|
||
|
|
id,
|
||
|
|
parentId,
|
||
|
|
timestamp,
|
||
|
|
message: { role: 'user', content: text, timestamp: Date.parse(timestamp) },
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
test('Pi root resolution follows official precedence and rejects ambiguous relative roots', () => {
|
||
|
|
const home = mkdtempSync(join(tmpdir(), 'obelisk-pi-home-'));
|
||
|
|
const projectCwd = join(home, 'project');
|
||
|
|
mkdirSync(projectCwd, { recursive: true });
|
||
|
|
assert.deepEqual(resolveDefaultPiRoot({ env: {}, homeDir: home, cwd: projectCwd }), {
|
||
|
|
root: join(home, '.pi', 'agent', 'sessions'),
|
||
|
|
requiresExplicitRoot: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
const agentDir = join(home, 'custom-agent');
|
||
|
|
const absoluteSessions = join(home, 'absolute-sessions');
|
||
|
|
mkdirSync(agentDir, { recursive: true });
|
||
|
|
writeFileSync(join(agentDir, 'settings.json'), JSON.stringify({ sessionDir: absoluteSessions }));
|
||
|
|
assert.deepEqual(resolveDefaultPiRoot({
|
||
|
|
env: { PI_CODING_AGENT_DIR: agentDir },
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
}), {
|
||
|
|
root: absoluteSessions,
|
||
|
|
requiresExplicitRoot: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
writeFileSync(join(agentDir, 'settings.json'), JSON.stringify({ sessionDir: '.pi/relative-sessions' }));
|
||
|
|
const relativeSetting = resolveDefaultPiRoot({
|
||
|
|
env: { PI_CODING_AGENT_DIR: agentDir },
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
});
|
||
|
|
assert.equal(relativeSetting.requiresExplicitRoot, true);
|
||
|
|
assert.equal(relativeSetting.root, join(agentDir, 'sessions'));
|
||
|
|
assert.doesNotMatch(relativeSetting.root, /relative-sessions/);
|
||
|
|
|
||
|
|
const relativeEnv = resolveDefaultPiRoot({
|
||
|
|
env: { PI_CODING_AGENT_SESSION_DIR: 'cwd-relative-sessions' },
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
});
|
||
|
|
assert.equal(relativeEnv.requiresExplicitRoot, true);
|
||
|
|
assert.equal(relativeEnv.root, join(home, '.pi', 'agent', 'sessions'));
|
||
|
|
|
||
|
|
const envSessions = join(home, 'env-sessions');
|
||
|
|
assert.deepEqual(resolveDefaultPiRoot({
|
||
|
|
env: {
|
||
|
|
PI_CODING_AGENT_SESSION_DIR: envSessions,
|
||
|
|
PI_CODING_AGENT_DIR: agentDir,
|
||
|
|
},
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
}), {
|
||
|
|
root: envSessions,
|
||
|
|
requiresExplicitRoot: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
const originalSessionDir = process.env.PI_CODING_AGENT_SESSION_DIR;
|
||
|
|
process.env.PI_CODING_AGENT_SESSION_DIR = 'cwd-relative-sessions';
|
||
|
|
try {
|
||
|
|
const unresolved = createPiProvider();
|
||
|
|
assert.equal(unresolved.descriptor.requiresExplicitRoot, true);
|
||
|
|
assert.deepEqual(unresolved.watchRoots(unresolved.descriptor.defaultRoot), []);
|
||
|
|
assert.deepEqual(unresolved.discover({ lastCursor: () => null }), []);
|
||
|
|
|
||
|
|
const explicitlySelected = createPiProvider({
|
||
|
|
rootDir: join(process.cwd(), '.pi', 'agent', 'sessions'),
|
||
|
|
});
|
||
|
|
assert.equal(explicitlySelected.rootResolution.requiresExplicitRoot, false);
|
||
|
|
} finally {
|
||
|
|
if (originalSessionDir === undefined) delete process.env.PI_CODING_AGENT_SESSION_DIR;
|
||
|
|
else process.env.PI_CODING_AGENT_SESSION_DIR = originalSessionDir;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Pi project settings override global sessionDir using the official launch cwd', () => {
|
||
|
|
const home = mkdtempSync(join(tmpdir(), 'obelisk-pi-project-settings-'));
|
||
|
|
const agentDir = join(home, 'agent');
|
||
|
|
const projectCwd = join(home, 'project');
|
||
|
|
const globalSessions = join(home, 'global-sessions');
|
||
|
|
const projectSessions = join(home, 'project-sessions');
|
||
|
|
mkdirSync(join(projectCwd, '.pi'), { recursive: true });
|
||
|
|
mkdirSync(agentDir, { recursive: true });
|
||
|
|
writeFileSync(join(agentDir, 'settings.json'), JSON.stringify({ sessionDir: globalSessions }));
|
||
|
|
writeFileSync(join(projectCwd, '.pi', 'settings.json'), JSON.stringify({
|
||
|
|
sessionDir: projectSessions,
|
||
|
|
}));
|
||
|
|
|
||
|
|
assert.deepEqual(resolveDefaultPiRoot({
|
||
|
|
env: { PI_CODING_AGENT_DIR: agentDir },
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
}), {
|
||
|
|
root: projectSessions,
|
||
|
|
requiresExplicitRoot: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
writeFileSync(join(projectCwd, '.pi', 'settings.json'), JSON.stringify({
|
||
|
|
sessionDir: '.pi/project-sessions',
|
||
|
|
}));
|
||
|
|
assert.deepEqual(resolveDefaultPiRoot({
|
||
|
|
env: { PI_CODING_AGENT_DIR: agentDir },
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
}), {
|
||
|
|
root: join(projectCwd, '.pi', 'project-sessions'),
|
||
|
|
requiresExplicitRoot: false,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('malformed Pi settings fall back to the remaining official settings scope', () => {
|
||
|
|
const home = mkdtempSync(join(tmpdir(), 'obelisk-pi-malformed-settings-'));
|
||
|
|
const agentDir = join(home, 'agent');
|
||
|
|
const projectCwd = join(home, 'project');
|
||
|
|
const projectSettingsDir = join(projectCwd, '.pi');
|
||
|
|
const globalSessions = join(home, 'global-sessions');
|
||
|
|
const projectSessions = join(home, 'project-sessions');
|
||
|
|
mkdirSync(agentDir, { recursive: true });
|
||
|
|
mkdirSync(projectSettingsDir, { recursive: true });
|
||
|
|
|
||
|
|
writeFileSync(join(agentDir, 'settings.json'), '{broken');
|
||
|
|
assert.deepEqual(resolveDefaultPiRoot({
|
||
|
|
env: { PI_CODING_AGENT_DIR: agentDir },
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
}), {
|
||
|
|
root: join(agentDir, 'sessions'),
|
||
|
|
requiresExplicitRoot: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
writeFileSync(join(agentDir, 'settings.json'), JSON.stringify({
|
||
|
|
sessionDir: globalSessions,
|
||
|
|
}));
|
||
|
|
writeFileSync(join(projectSettingsDir, 'settings.json'), '{broken');
|
||
|
|
assert.deepEqual(resolveDefaultPiRoot({
|
||
|
|
env: { PI_CODING_AGENT_DIR: agentDir },
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
}), {
|
||
|
|
root: globalSessions,
|
||
|
|
requiresExplicitRoot: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
writeFileSync(join(agentDir, 'settings.json'), '{broken');
|
||
|
|
writeFileSync(join(projectSettingsDir, 'settings.json'), JSON.stringify({
|
||
|
|
sessionDir: projectSessions,
|
||
|
|
}));
|
||
|
|
assert.deepEqual(resolveDefaultPiRoot({
|
||
|
|
env: { PI_CODING_AGENT_DIR: agentDir },
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
}), {
|
||
|
|
root: projectSessions,
|
||
|
|
requiresExplicitRoot: false,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('non-string Pi sessionDir values never select the default corpus', () => {
|
||
|
|
const home = mkdtempSync(join(tmpdir(), 'obelisk-pi-invalid-settings-'));
|
||
|
|
const agentDir = join(home, 'agent');
|
||
|
|
const projectCwd = join(home, 'project');
|
||
|
|
mkdirSync(join(projectCwd, '.pi'), { recursive: true });
|
||
|
|
mkdirSync(agentDir, { recursive: true });
|
||
|
|
writeFileSync(join(projectCwd, '.pi', 'settings.json'), '{}');
|
||
|
|
|
||
|
|
for (const sessionDir of [false, 0]) {
|
||
|
|
writeFileSync(join(agentDir, 'settings.json'), JSON.stringify({ sessionDir }));
|
||
|
|
const resolved = resolveDefaultPiRoot({
|
||
|
|
env: { PI_CODING_AGENT_DIR: agentDir },
|
||
|
|
homeDir: home,
|
||
|
|
cwd: projectCwd,
|
||
|
|
});
|
||
|
|
assert.equal(resolved.requiresExplicitRoot, true);
|
||
|
|
assert.match(resolved.reason, /sessionDir must be a string/);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Pi discovery never certifies an invalid session root as empty', () => {
|
||
|
|
const parent = mkdtempSync(join(tmpdir(), 'obelisk-pi-invalid-root-'));
|
||
|
|
const root = join(parent, 'sessions');
|
||
|
|
writeFileSync(root, 'not a directory');
|
||
|
|
let status = 'unknown';
|
||
|
|
const units = createPiProvider({ rootDir: root }).discover({
|
||
|
|
lastCursor: () => null,
|
||
|
|
reportCompleteInventory: () => { status = 'complete'; },
|
||
|
|
reportIncompleteInventory: () => { status = 'incomplete'; },
|
||
|
|
});
|
||
|
|
assert.deepEqual(units, []);
|
||
|
|
assert.equal(status, 'incomplete');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Pi tool fixture projects canonical messages, usage, file paths, raw lines, and persistence', () => {
|
||
|
|
const { root, path } = writeSession(fixture('tool-session.jsonl'));
|
||
|
|
const { provider, unit, values, cursor } = parseOnly(root);
|
||
|
|
const byKind = kind => values.filter(record => record.kind === kind);
|
||
|
|
|
||
|
|
assert.match(cursor, /^\d+(?:\.\d+)?:0:pi-snapshot-v1:/);
|
||
|
|
assert.deepEqual(values[0], { kind: 'delete-session', sessionId: unit.sessionId });
|
||
|
|
assert.deepEqual(
|
||
|
|
byKind('message').map(record => [record.role, record.content_type, record.text]),
|
||
|
|
[
|
||
|
|
['user', 'text', 'Read probe.txt and report it'],
|
||
|
|
['assistant', 'tool_use', null],
|
||
|
|
['toolResult', 'tool_result', 'real-pi-tool-result\n'],
|
||
|
|
['assistant', 'text', 'The read tool returned real-pi-tool-result.'],
|
||
|
|
],
|
||
|
|
);
|
||
|
|
assert.equal(byKind('message')[1].input_tokens, 17);
|
||
|
|
assert.equal(byKind('message')[1].output_tokens, 5);
|
||
|
|
const toolUseMessage = byKind('message')[1];
|
||
|
|
assert.deepEqual(byKind('tool_call').map(record => ({
|
||
|
|
id: record.id,
|
||
|
|
name: record.name,
|
||
|
|
file_path: record.file_path,
|
||
|
|
})), [{
|
||
|
|
id: `${toolUseMessage.uuid}:tool`,
|
||
|
|
name: 'read',
|
||
|
|
file_path: 'probe.txt',
|
||
|
|
}]);
|
||
|
|
assert.equal(byKind('tool_result')[0].file_path, 'probe.txt');
|
||
|
|
assert.equal(byKind('session')[0].title, 'Tool probe');
|
||
|
|
assert.equal(byKind('session')[0].message_count, 4);
|
||
|
|
assert.equal(byKind('session')[0].countMode, 'total');
|
||
|
|
|
||
|
|
const raw = provider.raw({
|
||
|
|
source: 'pi',
|
||
|
|
messageUuid: byKind('message')[1].uuid,
|
||
|
|
session: { id: byKind('session')[0].id, jsonl_path: path },
|
||
|
|
agentId: null,
|
||
|
|
cursor,
|
||
|
|
});
|
||
|
|
assert.match(raw.text, /"type":"toolCall"/);
|
||
|
|
assert.equal(raw.messageText, null);
|
||
|
|
|
||
|
|
const direct = assembleSessionDetail(values);
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
const persisted = assembleSessionDetail({
|
||
|
|
session: db.prepare('SELECT * FROM sessions').get(),
|
||
|
|
messages: db.prepare('SELECT * FROM messages ORDER BY timestamp,uuid').all(),
|
||
|
|
toolCalls: db.prepare('SELECT * FROM tool_calls').all(),
|
||
|
|
toolResults: db.prepare('SELECT * FROM tool_results').all(),
|
||
|
|
summaries: db.prepare('SELECT * FROM summaries').all(),
|
||
|
|
});
|
||
|
|
assert.deepEqual(persisted, direct);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('raw Pi lookup resolves the exact canonical block and rejects replacement provenance', () => {
|
||
|
|
const longText = 'long-block-'.repeat(1100);
|
||
|
|
const written = writeSession(jsonl([
|
||
|
|
header({ id: 'raw-block-session' }),
|
||
|
|
userEntry('raw-user', null, 'raw prompt'),
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'raw-assistant',
|
||
|
|
parentId: 'raw-user',
|
||
|
|
timestamp: '2026-08-02T10:00:02.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'assistant',
|
||
|
|
content: [
|
||
|
|
{ type: 'thinking', thinking: 'exact private thought' },
|
||
|
|
{ type: 'text', text: longText },
|
||
|
|
{ type: 'toolCall', id: 'raw-call', name: 'read', arguments: { path: 'raw.ts' } },
|
||
|
|
],
|
||
|
|
errorMessage: 'exact synthetic error',
|
||
|
|
responseModel: 'probe',
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:02.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
]));
|
||
|
|
const { provider, values, cursor } = parseOnly(written.root);
|
||
|
|
const session = values.find(record => record.kind === 'session');
|
||
|
|
const messages = values.filter(record => (
|
||
|
|
record.kind === 'message'
|
||
|
|
&& record.role === 'assistant'
|
||
|
|
));
|
||
|
|
const lookup = message => provider.raw({
|
||
|
|
source: 'pi',
|
||
|
|
messageUuid: message.uuid,
|
||
|
|
session: { id: session.id, jsonl_path: written.path },
|
||
|
|
agentId: null,
|
||
|
|
cursor,
|
||
|
|
});
|
||
|
|
|
||
|
|
assert.equal(lookup(messages.find(message => message.content_type === 'thinking')).messageText, 'exact private thought');
|
||
|
|
assert.equal(lookup(messages.find(message => message.content_type === 'text')).messageText, longText);
|
||
|
|
assert.equal(messages.find(message => message.content_type === 'text').text.length, 10000);
|
||
|
|
assert.equal(lookup(messages.find(message => message.content_type === 'tool_use')).messageText, null);
|
||
|
|
assert.equal(lookup(messages.find(message => message.content_type === 'error')).messageText, 'exact synthetic error');
|
||
|
|
assert.equal(provider.raw({
|
||
|
|
source: 'pi',
|
||
|
|
messageUuid: `${session.id}:entry:000002:message:block:9999`,
|
||
|
|
session: { id: session.id, jsonl_path: written.path },
|
||
|
|
agentId: null,
|
||
|
|
cursor,
|
||
|
|
}), null);
|
||
|
|
assert.equal(provider.raw({
|
||
|
|
source: 'pi',
|
||
|
|
messageUuid: messages[0].uuid,
|
||
|
|
session: { id: session.id, jsonl_path: written.root },
|
||
|
|
agentId: null,
|
||
|
|
cursor,
|
||
|
|
}), null);
|
||
|
|
|
||
|
|
writeFileSync(written.path, jsonl([
|
||
|
|
header({ id: 'raw-block-session' }),
|
||
|
|
userEntry('replacement-user', null, 'replacement lure'),
|
||
|
|
userEntry('replacement-second', 'replacement-user', 'unrelated same-header replacement'),
|
||
|
|
]));
|
||
|
|
assert.equal(lookup(messages[0]), null);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('structured tools remain branch-local when Pi reuses a native tool id', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'branch-tool-id-reuse' }),
|
||
|
|
userEntry('root', null, 'shared prompt'),
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'active-call',
|
||
|
|
parentId: 'root',
|
||
|
|
timestamp: '2026-08-02T10:00:02.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'assistant',
|
||
|
|
content: [{
|
||
|
|
type: 'toolCall',
|
||
|
|
id: 'shared-native-id',
|
||
|
|
name: 'read',
|
||
|
|
arguments: { path: 'active.ts' },
|
||
|
|
}],
|
||
|
|
model: 'probe',
|
||
|
|
usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 },
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:02.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'active-result',
|
||
|
|
parentId: 'active-call',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'toolResult',
|
||
|
|
toolCallId: 'shared-native-id',
|
||
|
|
toolName: 'read',
|
||
|
|
content: [{ type: 'text', text: 'ACTIVE RESULT' }],
|
||
|
|
isError: false,
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:03.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'abandoned-call',
|
||
|
|
parentId: 'root',
|
||
|
|
timestamp: '2026-08-02T10:00:04.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'assistant',
|
||
|
|
content: [{
|
||
|
|
type: 'toolCall',
|
||
|
|
id: 'shared-native-id',
|
||
|
|
name: 'read',
|
||
|
|
arguments: { path: 'abandoned.ts' },
|
||
|
|
}],
|
||
|
|
model: 'probe',
|
||
|
|
usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 },
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:04.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'abandoned-result',
|
||
|
|
parentId: 'abandoned-call',
|
||
|
|
timestamp: '2026-08-02T10:00:05.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'toolResult',
|
||
|
|
toolCallId: 'shared-native-id',
|
||
|
|
toolName: 'read',
|
||
|
|
content: [{ type: 'text', text: 'ABANDONED RESULT' }],
|
||
|
|
isError: true,
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:05.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'leaf',
|
||
|
|
id: 'select-active',
|
||
|
|
parentId: 'abandoned-result',
|
||
|
|
targetId: 'active-result',
|
||
|
|
timestamp: '2026-08-02T10:00:06.000Z',
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const { provider, unit, values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
const activeToolUse = values.find(record => (
|
||
|
|
record.kind === 'message'
|
||
|
|
&& record.content_type === 'tool_use'
|
||
|
|
&& record.visibility === 'visible'
|
||
|
|
));
|
||
|
|
const inactiveToolUse = values.find(record => (
|
||
|
|
record.kind === 'message'
|
||
|
|
&& record.content_type === 'tool_use'
|
||
|
|
&& record.visibility === 'inactive'
|
||
|
|
));
|
||
|
|
const calls = values.filter(record => record.kind === 'tool_call');
|
||
|
|
const results = values.filter(record => record.kind === 'tool_result');
|
||
|
|
const activeCall = calls.find(record => record.file_path === 'active.ts');
|
||
|
|
const inactiveCall = calls.find(record => record.file_path === 'abandoned.ts');
|
||
|
|
const activeResult = results.find(record => record.content === 'ACTIVE RESULT');
|
||
|
|
const inactiveResult = results.find(record => record.content === 'ABANDONED RESULT');
|
||
|
|
|
||
|
|
assert.equal(calls.length, 2);
|
||
|
|
assert.deepEqual(
|
||
|
|
(({ id, message_uuid, file_path }) => ({ id, message_uuid, file_path }))(activeCall),
|
||
|
|
{
|
||
|
|
id: `${activeToolUse.uuid}:tool`,
|
||
|
|
message_uuid: activeToolUse.uuid,
|
||
|
|
file_path: 'active.ts',
|
||
|
|
},
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
(({ id, message_uuid, file_path }) => ({ id, message_uuid, file_path }))(inactiveCall),
|
||
|
|
{
|
||
|
|
id: `${inactiveToolUse.uuid}:tool`,
|
||
|
|
message_uuid: inactiveToolUse.uuid,
|
||
|
|
file_path: 'abandoned.ts',
|
||
|
|
},
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
(({ tool_use_id, content, file_path, is_error }) => ({
|
||
|
|
tool_use_id,
|
||
|
|
content,
|
||
|
|
file_path,
|
||
|
|
is_error,
|
||
|
|
}))(activeResult),
|
||
|
|
{
|
||
|
|
tool_use_id: activeCall.id,
|
||
|
|
content: 'ACTIVE RESULT',
|
||
|
|
file_path: 'active.ts',
|
||
|
|
is_error: 0,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
(({ tool_use_id, content, file_path, is_error }) => ({
|
||
|
|
tool_use_id,
|
||
|
|
content,
|
||
|
|
file_path,
|
||
|
|
is_error,
|
||
|
|
}))(inactiveResult),
|
||
|
|
{
|
||
|
|
tool_use_id: inactiveCall.id,
|
||
|
|
content: 'ABANDONED RESULT',
|
||
|
|
file_path: 'abandoned.ts',
|
||
|
|
is_error: 1,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
assert.equal(db.prepare('SELECT COUNT(*) AS count FROM tool_calls').get().count, 2);
|
||
|
|
assert.equal(db.prepare('SELECT COUNT(*) AS count FROM tool_results').get().count, 2);
|
||
|
|
assert.deepEqual(
|
||
|
|
db.prepare('SELECT file_path FROM tool_calls ORDER BY file_path').all().map(row => row.file_path),
|
||
|
|
['abandoned.ts', 'active.ts'],
|
||
|
|
);
|
||
|
|
const query = createQueryApi(db);
|
||
|
|
assert.deepEqual(query.fileHistory('abandoned.ts'), []);
|
||
|
|
assert.deepEqual(
|
||
|
|
query.fileHistory('abandoned.ts', { includeInactive: true })
|
||
|
|
.map(record => [record.toolCall.id, record.visibility]),
|
||
|
|
[[inactiveCall.id, 'inactive']],
|
||
|
|
);
|
||
|
|
assert.deepEqual(query.failures(unit.sessionId), []);
|
||
|
|
assert.deepEqual(
|
||
|
|
query.failures({ sessionId: unit.sessionId, includeInactive: true })
|
||
|
|
.map(record => [record.toolCall.id, record.visibility]),
|
||
|
|
[[inactiveCall.id, 'inactive']],
|
||
|
|
);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('an empty retainedTail checkpoint does not link a later result to discarded tool scope', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'checkpoint-tool-scope' }),
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'discarded-call',
|
||
|
|
parentId: null,
|
||
|
|
timestamp: '2026-08-02T10:00:01.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'assistant',
|
||
|
|
content: [{
|
||
|
|
type: 'toolCall',
|
||
|
|
id: 'discarded-native-id',
|
||
|
|
name: 'read',
|
||
|
|
arguments: { path: 'discarded.ts' },
|
||
|
|
}],
|
||
|
|
model: 'probe',
|
||
|
|
usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 },
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:01.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'empty-checkpoint',
|
||
|
|
parentId: 'discarded-call',
|
||
|
|
timestamp: '2026-08-02T10:00:02.000Z',
|
||
|
|
summary: 'No retained tool context.',
|
||
|
|
firstKeptEntryId: 'discarded-call',
|
||
|
|
tokensBefore: 10,
|
||
|
|
retainedTail: [],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'unresolved-result',
|
||
|
|
parentId: 'empty-checkpoint',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'toolResult',
|
||
|
|
toolCallId: 'discarded-native-id',
|
||
|
|
toolName: 'read',
|
||
|
|
content: [{ type: 'text', text: 'UNRESOLVED RESULT' }],
|
||
|
|
isError: false,
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:03.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const { values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
|
||
|
|
const discardedCalls = values.filter(record => record.kind === 'tool_call');
|
||
|
|
assert.equal(discardedCalls.length, 1);
|
||
|
|
const discardedCall = discardedCalls[0];
|
||
|
|
assert.equal(
|
||
|
|
values.find(record => (
|
||
|
|
record.kind === 'message'
|
||
|
|
&& record.uuid === discardedCall.message_uuid
|
||
|
|
)).visibility,
|
||
|
|
'inactive',
|
||
|
|
);
|
||
|
|
assert.equal(values.filter(record => record.kind === 'tool_result').length, 0);
|
||
|
|
assert.equal(
|
||
|
|
values.find(record => record.kind === 'message' && record.text === 'UNRESOLVED RESULT').visibility,
|
||
|
|
'visible',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a legacy compaction does not link a visible result to an inactive tool call', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'legacy-tool-scope' }),
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'discarded-call',
|
||
|
|
parentId: null,
|
||
|
|
timestamp: '2026-08-02T10:00:01.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'assistant',
|
||
|
|
content: [{
|
||
|
|
type: 'toolCall',
|
||
|
|
id: 'discarded-native-id',
|
||
|
|
name: 'read',
|
||
|
|
arguments: { path: '/secret/inactive' },
|
||
|
|
}],
|
||
|
|
model: 'probe',
|
||
|
|
usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 },
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:01.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
userEntry('kept-user', 'discarded-call', 'kept context', '2026-08-02T10:00:02.000Z'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'legacy-compaction',
|
||
|
|
parentId: 'kept-user',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
summary: 'Discard the tool call.',
|
||
|
|
firstKeptEntryId: 'kept-user',
|
||
|
|
tokensBefore: 10,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'standalone-result',
|
||
|
|
parentId: 'legacy-compaction',
|
||
|
|
timestamp: '2026-08-02T10:00:04.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'toolResult',
|
||
|
|
toolCallId: 'discarded-native-id',
|
||
|
|
toolName: 'read',
|
||
|
|
content: [{ type: 'text', text: 'VISIBLE STANDALONE FAILURE' }],
|
||
|
|
isError: true,
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:04.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const { provider, unit, values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
const discardedCall = values.find(record => record.kind === 'tool_call');
|
||
|
|
const callMessage = values.find(record => (
|
||
|
|
record.kind === 'message'
|
||
|
|
&& record.uuid === discardedCall.message_uuid
|
||
|
|
));
|
||
|
|
const resultMessage = values.find(record => (
|
||
|
|
record.kind === 'message'
|
||
|
|
&& record.text === 'VISIBLE STANDALONE FAILURE'
|
||
|
|
));
|
||
|
|
|
||
|
|
assert.equal(callMessage.visibility, 'inactive');
|
||
|
|
assert.equal(resultMessage.visibility, 'visible');
|
||
|
|
assert.equal(values.filter(record => record.kind === 'tool_result').length, 0);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
const query = createQueryApi(db);
|
||
|
|
assert.deepEqual(query.failures(unit.sessionId), []);
|
||
|
|
assert.deepEqual(query.fileHistory('/secret/inactive'), []);
|
||
|
|
assert.deepEqual(
|
||
|
|
query.fileHistory('/secret/inactive', { includeInactive: true })
|
||
|
|
.map(record => record.visibility),
|
||
|
|
['inactive'],
|
||
|
|
);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('real-model-derived fixture preserves error, image, cache, and reasoning semantics', () => {
|
||
|
|
const { root } = writeSession(fixture('real-model-session.jsonl'));
|
||
|
|
const { values } = parseOnly(root);
|
||
|
|
const messages = values.filter(record => record.kind === 'message');
|
||
|
|
|
||
|
|
assert.deepEqual(messages.map(record => [record.role, record.content_type, record.text]), [
|
||
|
|
['user', 'text', 'Reply with the probe marker'],
|
||
|
|
['user', 'image', '[image image/png; base64 chars=8]'],
|
||
|
|
['assistant', 'error', '404 not found'],
|
||
|
|
['user', 'text', 'Try the responses protocol'],
|
||
|
|
['assistant', 'text', 'REAL_PI_PROBE_OK'],
|
||
|
|
]);
|
||
|
|
const success = messages.at(-1);
|
||
|
|
assert.equal(success.input_tokens, 4818);
|
||
|
|
assert.equal(success.output_tokens, 20);
|
||
|
|
assert.equal(messages.some(record => record.content_type === 'thinking'), false);
|
||
|
|
assert.equal(values.find(record => record.kind === 'session').message_count, 5);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('durable leaf and null leaf control active visibility without deleting physical evidence', () => {
|
||
|
|
const source = writeSession(fixture('harness-source.jsonl'));
|
||
|
|
const parsed = parseOnly(source.root);
|
||
|
|
const parsedSource = parsed.values;
|
||
|
|
const sourceMessages = parsedSource.filter(record => record.kind === 'message');
|
||
|
|
assert.equal(sourceMessages.length, 6, 'complete retainedTail must not be projected twice');
|
||
|
|
assert.deepEqual(
|
||
|
|
sourceMessages.filter(record => record.visibility === 'visible').map(record => record.text),
|
||
|
|
['Harness first user turn'],
|
||
|
|
);
|
||
|
|
assert.equal(
|
||
|
|
parsedSource.find(record => record.kind === 'session').message_count,
|
||
|
|
1,
|
||
|
|
);
|
||
|
|
assert.equal(assembleSessionDetail(parsedSource).messages.length, 1);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, parsed.unit, parsed.provider.parse(parsed.unit, null));
|
||
|
|
assert.equal(createQueryApi(db).sessions({ source: 'pi' })[0].message_count, 1);
|
||
|
|
db.close();
|
||
|
|
|
||
|
|
const nullLeaf = writeSession(fixture('harness-null-leaf.jsonl'));
|
||
|
|
const parsedNull = parseOnly(nullLeaf.root).values;
|
||
|
|
const physical = parsedNull.find(record => record.kind === 'message');
|
||
|
|
assert.equal(physical.visibility, 'inactive');
|
||
|
|
assert.equal(parsedNull.find(record => record.kind === 'session').message_count, 0);
|
||
|
|
assert.equal(
|
||
|
|
parsedNull.find(record => record.kind === 'session').title,
|
||
|
|
'This physical message is no longer active',
|
||
|
|
);
|
||
|
|
assert.deepEqual(assembleSessionDetail(parsedNull).messages, []);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('an empty latest session name clears it and falls back to the physical first user', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'physical-session-title' }),
|
||
|
|
{
|
||
|
|
...userEntry('physical-user', null, ''),
|
||
|
|
message: {
|
||
|
|
role: 'user',
|
||
|
|
content: [
|
||
|
|
{ type: 'image', mimeType: 'image/png', data: 'AAAA' },
|
||
|
|
{ type: 'text', text: 'physical' },
|
||
|
|
{ type: 'text', text: 'fallback' },
|
||
|
|
],
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:01.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'session_info',
|
||
|
|
id: 'named',
|
||
|
|
parentId: 'physical-user',
|
||
|
|
timestamp: '2026-08-02T10:00:02.000Z',
|
||
|
|
name: ' Named session ',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'session_info',
|
||
|
|
id: 'blank-name',
|
||
|
|
parentId: 'named',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
name: ' ',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'leaf',
|
||
|
|
id: 'null-leaf',
|
||
|
|
parentId: 'blank-name',
|
||
|
|
targetId: null,
|
||
|
|
timestamp: '2026-08-02T10:00:04.000Z',
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const { values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
|
||
|
|
assert.equal(values.find(record => record.kind === 'session').title, 'physical fallback');
|
||
|
|
assert.equal(
|
||
|
|
values.find(record => record.kind === 'message' && record.text === 'physical').visibility,
|
||
|
|
'inactive',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('retainedTail replaces complete ancestors when its compaction is on the active branch', () => {
|
||
|
|
const records = fixture('harness-source.jsonl')
|
||
|
|
.trim()
|
||
|
|
.split('\n')
|
||
|
|
.map(line => JSON.parse(line))
|
||
|
|
.filter(record => record.type !== 'leaf');
|
||
|
|
const { provider, unit, values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
const messages = values.filter(record => record.kind === 'message');
|
||
|
|
const visibleText = messages
|
||
|
|
.filter(record => record.visibility === 'visible')
|
||
|
|
.map(record => record.text);
|
||
|
|
|
||
|
|
assert.equal(messages.length, 8, 'physical ancestors and active retained context are both preserved');
|
||
|
|
assert.deepEqual(visibleText, [
|
||
|
|
'Harness retained user turn',
|
||
|
|
'Harness retained assistant turn',
|
||
|
|
'Harness post-compaction user turn',
|
||
|
|
'Harness post-compaction assistant turn',
|
||
|
|
]);
|
||
|
|
assert.equal(
|
||
|
|
messages.find(record => record.text === 'Harness first user turn').visibility,
|
||
|
|
'inactive',
|
||
|
|
);
|
||
|
|
assert.equal(
|
||
|
|
messages.find(record => record.text === 'Harness retained user turn' && !record.uuid.includes(':tail:')).visibility,
|
||
|
|
'inactive',
|
||
|
|
);
|
||
|
|
assert.equal(values.find(record => record.kind === 'summary').visibility, 'visible');
|
||
|
|
assert.deepEqual(assembleSessionDetail(values).messages.map(message => message.text), visibleText);
|
||
|
|
assert.deepEqual(
|
||
|
|
messages.reduce(
|
||
|
|
(total, message) => ({
|
||
|
|
input: total.input + (message.input_tokens ?? 0),
|
||
|
|
output: total.output + (message.output_tokens ?? 0),
|
||
|
|
}),
|
||
|
|
{ input: 0, output: 0 },
|
||
|
|
),
|
||
|
|
{ input: 369, output: 69 },
|
||
|
|
'retained context copies must not duplicate model usage',
|
||
|
|
);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
const query = createQueryApi(db);
|
||
|
|
assert.equal(query.search('Harness first').length, 0);
|
||
|
|
assert.deepEqual(query.thread(unit.sessionId).map(message => message.text), visibleText);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('legacy compaction exposes only firstKeptEntryId ancestors and later descendants', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'first-kept-context' }),
|
||
|
|
userEntry('old-root', null, 'old root'),
|
||
|
|
userEntry('old-near', 'old-root', 'old near', '2026-08-02T10:00:02.000Z'),
|
||
|
|
userEntry('kept-start', 'old-near', 'kept start', '2026-08-02T10:00:03.000Z'),
|
||
|
|
userEntry('kept-end', 'kept-start', 'kept end', '2026-08-02T10:00:04.000Z'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'legacy-compaction',
|
||
|
|
parentId: 'kept-end',
|
||
|
|
timestamp: '2026-08-02T10:00:05.000Z',
|
||
|
|
summary: 'Earlier context summary',
|
||
|
|
firstKeptEntryId: 'kept-start',
|
||
|
|
tokensBefore: 100,
|
||
|
|
},
|
||
|
|
userEntry('post-compaction', 'legacy-compaction', 'post compaction', '2026-08-02T10:00:06.000Z'),
|
||
|
|
];
|
||
|
|
const { provider, unit, values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
const messages = values.filter(record => record.kind === 'message');
|
||
|
|
assert.deepEqual(
|
||
|
|
messages.filter(record => record.visibility === 'visible').map(record => record.text),
|
||
|
|
['kept start', 'kept end', 'post compaction'],
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
messages.filter(record => record.visibility === 'inactive').map(record => record.text),
|
||
|
|
['old root', 'old near'],
|
||
|
|
);
|
||
|
|
assert.equal(values.find(record => record.kind === 'summary').visibility, 'visible');
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
const query = createQueryApi(db);
|
||
|
|
assert.equal(query.search('old near').length, 0);
|
||
|
|
assert.deepEqual(
|
||
|
|
query.thread(unit.sessionId).map(message => message.text),
|
||
|
|
['kept start', 'kept end', 'post compaction'],
|
||
|
|
);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a later legacy compaction can retain context across an earlier compaction', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'nested-compaction-context' }),
|
||
|
|
userEntry('root', null, 'root'),
|
||
|
|
userEntry('near-first', 'root', 'near first', '2026-08-02T10:00:02.000Z'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'first-compaction',
|
||
|
|
parentId: 'near-first',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
summary: 'First summary',
|
||
|
|
firstKeptEntryId: 'near-first',
|
||
|
|
tokensBefore: 50,
|
||
|
|
},
|
||
|
|
userEntry('after-first', 'first-compaction', 'after first', '2026-08-02T10:00:04.000Z'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'second-compaction',
|
||
|
|
parentId: 'after-first',
|
||
|
|
timestamp: '2026-08-02T10:00:05.000Z',
|
||
|
|
summary: 'Second summary',
|
||
|
|
firstKeptEntryId: 'root',
|
||
|
|
tokensBefore: 100,
|
||
|
|
},
|
||
|
|
userEntry('after-second', 'second-compaction', 'after second', '2026-08-02T10:00:06.000Z'),
|
||
|
|
];
|
||
|
|
const { values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
|
||
|
|
assert.deepEqual(
|
||
|
|
values
|
||
|
|
.filter(record => record.kind === 'message' && record.visibility === 'visible')
|
||
|
|
.map(record => record.text),
|
||
|
|
['root', 'near first', 'after first', 'after second'],
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
values
|
||
|
|
.filter(record => record.kind === 'summary' && record.visibility === 'visible')
|
||
|
|
.map(record => record.content),
|
||
|
|
['First summary', 'Second summary'],
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a retained-tail checkpoint bounds a later legacy compaction like Pi 0.83 storage', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'mixed-compaction-context' }),
|
||
|
|
userEntry('root', null, 'discarded root'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'checkpoint',
|
||
|
|
parentId: 'root',
|
||
|
|
timestamp: '2026-08-02T10:00:02.000Z',
|
||
|
|
summary: 'Checkpoint summary',
|
||
|
|
firstKeptEntryId: 'root',
|
||
|
|
tokensBefore: 50,
|
||
|
|
retainedTail: [{
|
||
|
|
role: 'user',
|
||
|
|
content: 'checkpoint tail',
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:02.000Z'),
|
||
|
|
}],
|
||
|
|
},
|
||
|
|
userEntry('after-checkpoint', 'checkpoint', 'discarded after checkpoint', '2026-08-02T10:00:03.000Z'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'later-legacy',
|
||
|
|
parentId: 'after-checkpoint',
|
||
|
|
timestamp: '2026-08-02T10:00:04.000Z',
|
||
|
|
summary: 'Later legacy summary',
|
||
|
|
firstKeptEntryId: 'root',
|
||
|
|
tokensBefore: 100,
|
||
|
|
},
|
||
|
|
userEntry('head', 'later-legacy', 'active head', '2026-08-02T10:00:05.000Z'),
|
||
|
|
];
|
||
|
|
const { values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
|
||
|
|
assert.deepEqual(
|
||
|
|
values
|
||
|
|
.filter(record => record.kind === 'message' && record.visibility === 'visible')
|
||
|
|
.map(record => record.text),
|
||
|
|
['active head'],
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
values
|
||
|
|
.filter(record => record.kind === 'summary' && record.visibility === 'visible')
|
||
|
|
.map(record => record.content),
|
||
|
|
['Later legacy summary'],
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('legacy checkpoint fork accepts a truncated parent before firstKeptEntryId', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'legacy-checkpoint-fork' }),
|
||
|
|
userEntry('kept-start', 'omitted-parent', 'kept start'),
|
||
|
|
userEntry('kept-end', 'kept-start', 'kept end', '2026-08-02T10:00:02.000Z'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'legacy-compaction',
|
||
|
|
parentId: 'kept-end',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
summary: 'Omitted source context summary',
|
||
|
|
firstKeptEntryId: 'kept-start',
|
||
|
|
tokensBefore: 100,
|
||
|
|
},
|
||
|
|
userEntry('post-compaction', 'legacy-compaction', 'post compaction', '2026-08-02T10:00:04.000Z'),
|
||
|
|
];
|
||
|
|
const { values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
|
||
|
|
assert.deepEqual(
|
||
|
|
values
|
||
|
|
.filter(record => record.kind === 'message' && record.visibility === 'visible')
|
||
|
|
.map(record => record.text),
|
||
|
|
['kept start', 'kept end', 'post compaction'],
|
||
|
|
);
|
||
|
|
assert.equal(values.find(record => record.kind === 'summary').visibility, 'visible');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('checkpoint fork materializes retainedTail exactly once and reconnects later parent identity', () => {
|
||
|
|
const { root, path } = writeSession(fixture('harness-checkpoint-fork.jsonl'));
|
||
|
|
const { provider, unit, values, cursor } = parseOnly(root);
|
||
|
|
const messages = values.filter(record => record.kind === 'message');
|
||
|
|
assert.deepEqual(messages.map(record => [record.content_type, record.text]), [
|
||
|
|
['text', 'Harness retained user turn'],
|
||
|
|
['thinking', 'retained reasoning'],
|
||
|
|
['text', 'Harness retained assistant turn'],
|
||
|
|
['text', 'Harness post-compaction user turn'],
|
||
|
|
['text', 'Harness post-compaction assistant turn'],
|
||
|
|
]);
|
||
|
|
assert.equal(messages[1].input_tokens, null);
|
||
|
|
assert.equal(messages[2].input_tokens, null);
|
||
|
|
assert.equal(messages[2].output_tokens, null);
|
||
|
|
assert.equal(messages[4].input_tokens, 123);
|
||
|
|
assert.equal(messages[4].output_tokens, 23);
|
||
|
|
assert.equal(messages[3].parent_uuid, messages[2].uuid);
|
||
|
|
assert.equal(values.filter(record => record.kind === 'summary').length, 1);
|
||
|
|
assert.deepEqual(assembleSessionDetail(values).messages.map(message => message.text), [
|
||
|
|
'Harness retained user turn',
|
||
|
|
'Harness retained assistant turn',
|
||
|
|
'Harness post-compaction user turn',
|
||
|
|
'Harness post-compaction assistant turn',
|
||
|
|
]);
|
||
|
|
const raw = message => provider.raw({
|
||
|
|
source: 'pi',
|
||
|
|
messageUuid: message.uuid,
|
||
|
|
session: { id: unit.sessionId, jsonl_path: path },
|
||
|
|
agentId: null,
|
||
|
|
cursor,
|
||
|
|
});
|
||
|
|
assert.doesNotMatch(raw(messages[1]).text, /"retainedTail"/);
|
||
|
|
assert.equal(JSON.parse(raw(messages[1]).text).role, 'assistant');
|
||
|
|
assert.equal(raw(messages[1]).messageText, 'retained reasoning');
|
||
|
|
assert.equal(raw(messages[2]).messageText, 'Harness retained assistant turn');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('retained-tail raw evidence never exposes hidden sibling messages', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'retained-raw-visibility' }),
|
||
|
|
userEntry('root', null, 'discarded root'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'checkpoint',
|
||
|
|
parentId: 'root',
|
||
|
|
timestamp: '2026-08-02T10:00:02.000Z',
|
||
|
|
summary: 'Checkpoint summary',
|
||
|
|
firstKeptEntryId: 'root',
|
||
|
|
tokensBefore: 50,
|
||
|
|
retainedTail: [
|
||
|
|
{
|
||
|
|
role: 'user',
|
||
|
|
content: 'visible retained evidence',
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:02.000Z'),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
role: 'custom',
|
||
|
|
content: 'HIDDEN RETAINED SIBLING',
|
||
|
|
display: false,
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:03.000Z'),
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const { root } = writeSession(jsonl(records));
|
||
|
|
const { provider, unit, values } = parseOnly(root);
|
||
|
|
const visible = values.find(record => (
|
||
|
|
record.kind === 'message'
|
||
|
|
&& record.visibility === 'visible'
|
||
|
|
&& record.text === 'visible retained evidence'
|
||
|
|
));
|
||
|
|
const hidden = values.find(record => (
|
||
|
|
record.kind === 'message'
|
||
|
|
&& record.visibility === 'hidden'
|
||
|
|
));
|
||
|
|
assert.ok(visible);
|
||
|
|
assert.ok(hidden);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
const query = createQueryApi(db, {
|
||
|
|
providerRegistry: createProviderRegistry([provider]),
|
||
|
|
});
|
||
|
|
const raw = query.raw(visible.uuid);
|
||
|
|
assert.equal(raw.text, JSON.stringify(records[2].retainedTail[0]));
|
||
|
|
assert.doesNotMatch(raw.text, /HIDDEN RETAINED SIBLING/);
|
||
|
|
assert.equal(query.raw(hidden.uuid), null);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a retained tail beginning with a tool result remains standalone active evidence', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'retained-tool-result' }),
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'physical-call',
|
||
|
|
parentId: null,
|
||
|
|
timestamp: '2026-08-02T10:00:01.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'assistant',
|
||
|
|
content: [{
|
||
|
|
type: 'toolCall',
|
||
|
|
id: 'split-call',
|
||
|
|
name: 'read',
|
||
|
|
arguments: { path: 'probe.txt' },
|
||
|
|
}],
|
||
|
|
usage: { input: 5, output: 2 },
|
||
|
|
timestamp: 1785664801000,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'checkpoint',
|
||
|
|
parentId: 'physical-call',
|
||
|
|
timestamp: '2026-08-02T10:00:02.000Z',
|
||
|
|
summary: 'The preceding tool turn was split.',
|
||
|
|
firstKeptEntryId: 'physical-call',
|
||
|
|
tokensBefore: 100,
|
||
|
|
retainedTail: [{
|
||
|
|
role: 'toolResult',
|
||
|
|
toolCallId: 'split-call',
|
||
|
|
toolName: 'read',
|
||
|
|
content: [{ type: 'text', text: 'RETAINED RESULT' }],
|
||
|
|
isError: false,
|
||
|
|
timestamp: 1785664802000,
|
||
|
|
}],
|
||
|
|
},
|
||
|
|
userEntry('after-checkpoint', 'checkpoint', 'after', '2026-08-02T10:00:03.000Z'),
|
||
|
|
];
|
||
|
|
const { provider, unit, values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
assert.deepEqual(
|
||
|
|
values
|
||
|
|
.filter(record => record.kind === 'message' && record.visibility === 'visible')
|
||
|
|
.map(record => record.text),
|
||
|
|
['RETAINED RESULT', 'after'],
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
assembleSessionDetail(values).messages.map(message => message.text),
|
||
|
|
['RETAINED RESULT', 'after'],
|
||
|
|
);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
assert.deepEqual(
|
||
|
|
assembleSessionDetail({
|
||
|
|
session: db.prepare('SELECT * FROM sessions').get(),
|
||
|
|
messages: db.prepare('SELECT * FROM messages ORDER BY timestamp,uuid').all(),
|
||
|
|
toolCalls: db.prepare('SELECT * FROM tool_calls').all(),
|
||
|
|
toolResults: db.prepare('SELECT * FROM tool_results').all(),
|
||
|
|
summaries: db.prepare('SELECT * FROM summaries').all(),
|
||
|
|
}).messages.map(message => message.text),
|
||
|
|
['RETAINED RESULT', 'after'],
|
||
|
|
);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('full tree marks abandoned messages inactive while indexing every summary and explicit user bash action', () => {
|
||
|
|
const records = [
|
||
|
|
header(),
|
||
|
|
userEntry('root-user', null, 'shared root'),
|
||
|
|
userEntry('abandoned-user', 'root-user', 'abandoned branch'),
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'abandoned-assistant',
|
||
|
|
parentId: 'abandoned-user',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'assistant',
|
||
|
|
content: [{ type: 'text', text: 'discarded answer' }],
|
||
|
|
model: 'probe',
|
||
|
|
usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 },
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:03.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'custom_message',
|
||
|
|
id: 'inactive-custom',
|
||
|
|
parentId: 'abandoned-assistant',
|
||
|
|
timestamp: '2026-08-02T10:00:03.500Z',
|
||
|
|
customType: 'probe',
|
||
|
|
content: 'superseded extension context',
|
||
|
|
display: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'branch_summary',
|
||
|
|
id: 'branch-summary',
|
||
|
|
parentId: 'root-user',
|
||
|
|
timestamp: '2026-08-02T10:00:04.000Z',
|
||
|
|
fromId: 'abandoned-assistant',
|
||
|
|
summary: 'The abandoned branch tried a discarded answer.',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'custom_message',
|
||
|
|
id: 'hidden-custom',
|
||
|
|
parentId: 'branch-summary',
|
||
|
|
timestamp: '2026-08-02T10:00:05.000Z',
|
||
|
|
customType: 'probe',
|
||
|
|
content: 'hidden extension context',
|
||
|
|
display: false,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'custom_message',
|
||
|
|
id: 'visible-custom',
|
||
|
|
parentId: 'hidden-custom',
|
||
|
|
timestamp: '2026-08-02T10:00:06.000Z',
|
||
|
|
customType: 'probe',
|
||
|
|
content: 'visible extension context',
|
||
|
|
display: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'bash',
|
||
|
|
parentId: 'visible-custom',
|
||
|
|
timestamp: '2026-08-02T10:00:07.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'bashExecution',
|
||
|
|
command: 'printf probe',
|
||
|
|
output: 'x'.repeat(12_000),
|
||
|
|
exitCode: 0,
|
||
|
|
cancelled: false,
|
||
|
|
truncated: true,
|
||
|
|
fullOutputPath: '/tmp/must-not-be-read',
|
||
|
|
excludeFromContext: true,
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:07.000Z'),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'compaction',
|
||
|
|
parentId: 'bash',
|
||
|
|
timestamp: '2026-08-02T10:00:08.000Z',
|
||
|
|
summary: 'Earlier active work.',
|
||
|
|
firstKeptEntryId: 'root-user',
|
||
|
|
tokensBefore: 42,
|
||
|
|
},
|
||
|
|
userEntry('active-user', 'compaction', 'active request', '2026-08-02T10:00:09.000Z'),
|
||
|
|
];
|
||
|
|
const { root } = writeSession(jsonl(records));
|
||
|
|
const { provider, unit, values } = parseOnly(root);
|
||
|
|
const messages = values.filter(record => record.kind === 'message');
|
||
|
|
|
||
|
|
assert.equal(messages.find(record => record.text === 'abandoned branch').visibility, 'inactive');
|
||
|
|
assert.equal(messages.find(record => record.text === 'abandoned branch').is_meta, 0);
|
||
|
|
assert.equal(messages.find(record => record.text === 'superseded extension context').visibility, 'inactive');
|
||
|
|
assert.equal(messages.find(record => record.text === 'hidden extension context').visibility, 'hidden');
|
||
|
|
assert.equal(messages.find(record => record.text === 'visible extension context').visibility, 'visible');
|
||
|
|
const bash = messages.find(record => record.role === 'bashExecution');
|
||
|
|
assert.equal(bash.type, 'user');
|
||
|
|
assert.equal(bash.content_type, 'bash_execution');
|
||
|
|
assert.equal(bash.is_meta, 0);
|
||
|
|
assert.equal(bash.text.length, 10_000);
|
||
|
|
assert.match(bash.text, /\[Output truncated\. Full output: \/tmp\/must-not-be-read\]$/);
|
||
|
|
assert.equal(values.filter(record => record.kind === 'tool_call').length, 0);
|
||
|
|
assert.deepEqual(
|
||
|
|
values.filter(record => record.kind === 'summary').map(record => record.source),
|
||
|
|
['pi:branch_summary', 'pi:compaction'],
|
||
|
|
);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
const query = createQueryApi(db);
|
||
|
|
assert.equal(query.search('abandoned').length, 0);
|
||
|
|
assert.equal(query.search('abandoned', { includeInactive: true })[0].message.visibility, 'inactive');
|
||
|
|
assert.equal(query.search('superseded extension').length, 0);
|
||
|
|
assert.equal(query.search('superseded extension', { includeInactive: true }).length, 0);
|
||
|
|
assert.equal(
|
||
|
|
query.search('superseded extension', {
|
||
|
|
includeInactive: true,
|
||
|
|
includeMeta: true,
|
||
|
|
})[0].message.visibility,
|
||
|
|
'inactive',
|
||
|
|
);
|
||
|
|
assert.equal(query.search('active request').length, 1);
|
||
|
|
assert.equal(query.thread(unit.sessionId).some(message => message.text === 'abandoned branch'), false);
|
||
|
|
assert.equal(
|
||
|
|
query.thread(unit.sessionId, { includeInactive: true })
|
||
|
|
.find(message => message.text === 'abandoned branch').visibility,
|
||
|
|
'inactive',
|
||
|
|
);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('inactive Pi summaries remain available for accounting and explicit historical queries', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'summary-visibility' }),
|
||
|
|
userEntry('root', null, 'shared root'),
|
||
|
|
userEntry('abandoned', 'root', 'abandoned prompt'),
|
||
|
|
{
|
||
|
|
type: 'branch_summary',
|
||
|
|
id: 'abandoned-summary',
|
||
|
|
parentId: 'abandoned',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
fromId: 'abandoned',
|
||
|
|
summary: 'ABANDONED SUMMARY SECRET',
|
||
|
|
usage: { input: 9, output: 2, cacheRead: 3, cacheWrite: 4 },
|
||
|
|
},
|
||
|
|
userEntry('active', 'root', 'active prompt', '2026-08-02T10:00:04.000Z'),
|
||
|
|
];
|
||
|
|
const { provider, unit, values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
const summary = values.find(record => record.kind === 'summary');
|
||
|
|
assert.equal(summary.visibility, 'inactive');
|
||
|
|
assert.equal(summary.input_tokens, 16);
|
||
|
|
assert.deepEqual(assembleSessionDetail(values).summaries, []);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
const query = createQueryApi(db);
|
||
|
|
assert.deepEqual(query.summaries(unit.sessionId), []);
|
||
|
|
assert.equal(
|
||
|
|
query.summaries({ sessionId: unit.sessionId, includeInactive: true })[0].visibility,
|
||
|
|
'inactive',
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
assembleSessionDetail({ summaries: db.prepare('SELECT * FROM summaries').all() }).summaries,
|
||
|
|
[],
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
{ ...db.prepare('SELECT content,visibility,input_tokens,output_tokens FROM summaries').get() },
|
||
|
|
{
|
||
|
|
content: 'ABANDONED SUMMARY SECRET',
|
||
|
|
visibility: 'inactive',
|
||
|
|
input_tokens: 16,
|
||
|
|
output_tokens: 2,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('lowercase read, edit, and write arguments.path populate call and matching result file paths', () => {
|
||
|
|
const records = [header()];
|
||
|
|
let parent = null;
|
||
|
|
for (const [index, name] of ['read', 'edit', 'write'].entries()) {
|
||
|
|
const callEntry = `call-entry-${index}`;
|
||
|
|
const callId = `call-${index}`;
|
||
|
|
records.push({
|
||
|
|
type: 'message',
|
||
|
|
id: callEntry,
|
||
|
|
parentId: parent,
|
||
|
|
timestamp: `2026-08-02T10:00:0${index + 1}.000Z`,
|
||
|
|
message: {
|
||
|
|
role: 'assistant',
|
||
|
|
content: [{ type: 'toolCall', id: callId, name, arguments: { path: `src/${name}.ts` } }],
|
||
|
|
model: 'probe',
|
||
|
|
usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 },
|
||
|
|
timestamp: 1785664800000 + index,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
records.push({
|
||
|
|
type: 'message',
|
||
|
|
id: `result-entry-${index}`,
|
||
|
|
parentId: callEntry,
|
||
|
|
timestamp: `2026-08-02T10:00:1${index + 1}.000Z`,
|
||
|
|
message: {
|
||
|
|
role: 'toolResult',
|
||
|
|
toolCallId: callId,
|
||
|
|
toolName: name,
|
||
|
|
content: [{ type: 'text', text: `${name} result` }],
|
||
|
|
isError: false,
|
||
|
|
timestamp: 1785664801000 + index,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
parent = `result-entry-${index}`;
|
||
|
|
}
|
||
|
|
const { root } = writeSession(jsonl(records));
|
||
|
|
const { values } = parseOnly(root);
|
||
|
|
assert.deepEqual(
|
||
|
|
values.filter(record => record.kind === 'tool_call').map(record => record.file_path),
|
||
|
|
['src/read.ts', 'src/edit.ts', 'src/write.ts'],
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
values.filter(record => record.kind === 'tool_result').map(record => record.file_path),
|
||
|
|
['src/read.ts', 'src/edit.ts', 'src/write.ts'],
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('tool-result nested model usage is retained on its canonical message', () => {
|
||
|
|
const records = [
|
||
|
|
header(),
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'result',
|
||
|
|
parentId: null,
|
||
|
|
timestamp: '2026-08-02T10:00:01.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'toolResult',
|
||
|
|
toolCallId: 'nested-call',
|
||
|
|
toolName: 'agent',
|
||
|
|
content: [{ type: 'text', text: 'nested result' }],
|
||
|
|
usage: { input: 11, output: 7, cacheRead: 3, cacheWrite: 2 },
|
||
|
|
isError: false,
|
||
|
|
timestamp: 1785664801000,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const { values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
const result = values.find(record => record.kind === 'message');
|
||
|
|
assert.equal(result.input_tokens, 16);
|
||
|
|
assert.equal(result.output_tokens, 7);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('compaction and branch-summary model usage survives canonical persistence', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'summary-usage' }),
|
||
|
|
userEntry('user', null, 'summarize this'),
|
||
|
|
{
|
||
|
|
type: 'branch_summary',
|
||
|
|
id: 'branch-summary',
|
||
|
|
parentId: 'user',
|
||
|
|
timestamp: '2026-08-02T10:00:02.000Z',
|
||
|
|
fromId: 'user',
|
||
|
|
summary: 'Branch summary',
|
||
|
|
usage: { input: 10, output: 4, cacheRead: 2, cacheWrite: 3 },
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'compaction',
|
||
|
|
parentId: 'branch-summary',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
summary: 'Compaction summary',
|
||
|
|
firstKeptEntryId: 'user',
|
||
|
|
tokensBefore: 100,
|
||
|
|
usage: { input: 20, output: 5, cacheRead: 6, cacheWrite: 7 },
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const { provider, unit, values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
assert.deepEqual(
|
||
|
|
values.filter(record => record.kind === 'summary').map(record => ({
|
||
|
|
source: record.source,
|
||
|
|
input: record.input_tokens,
|
||
|
|
output: record.output_tokens,
|
||
|
|
})),
|
||
|
|
[
|
||
|
|
{ source: 'pi:branch_summary', input: 15, output: 4 },
|
||
|
|
{ source: 'pi:compaction', input: 33, output: 5 },
|
||
|
|
],
|
||
|
|
);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
assert.deepEqual(
|
||
|
|
db.prepare('SELECT input_tokens,output_tokens FROM summaries ORDER BY timestamp').all()
|
||
|
|
.map(row => ({ ...row })),
|
||
|
|
[
|
||
|
|
{ input_tokens: 15, output_tokens: 4 },
|
||
|
|
{ input_tokens: 33, output_tokens: 5 },
|
||
|
|
],
|
||
|
|
);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('retained-tail summaries keep distinct canonical identities through persistence', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'retained-summary-identities' }),
|
||
|
|
userEntry('root', null, 'summarize retained context'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'checkpoint',
|
||
|
|
parentId: 'root',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
summary: 'Outer compaction',
|
||
|
|
firstKeptEntryId: 'root',
|
||
|
|
tokensBefore: 100,
|
||
|
|
retainedTail: [
|
||
|
|
{
|
||
|
|
role: 'branchSummary',
|
||
|
|
summary: 'First retained summary',
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:01.000Z'),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
role: 'branchSummary',
|
||
|
|
summary: 'Second retained summary',
|
||
|
|
timestamp: Date.parse('2026-08-02T10:00:02.000Z'),
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const { provider, unit, values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
const retained = values.filter(record => (
|
||
|
|
record.kind === 'summary'
|
||
|
|
&& record.source === 'pi:branch_summary'
|
||
|
|
));
|
||
|
|
assert.deepEqual(retained.map(record => record.content), [
|
||
|
|
'First retained summary',
|
||
|
|
'Second retained summary',
|
||
|
|
]);
|
||
|
|
assert.equal(new Set(retained.map(record => record.id)).size, 2);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
assert.deepEqual(
|
||
|
|
db.prepare("SELECT content FROM summaries WHERE source='pi:branch_summary' ORDER BY timestamp")
|
||
|
|
.all().map(row => row.content),
|
||
|
|
retained.map(record => record.content),
|
||
|
|
);
|
||
|
|
const byTimestamp = (left, right) => String(left.timestamp).localeCompare(String(right.timestamp));
|
||
|
|
assert.deepEqual(
|
||
|
|
assembleSessionDetail({
|
||
|
|
summaries: db.prepare('SELECT * FROM summaries ORDER BY timestamp').all(),
|
||
|
|
}).summaries.sort(byTimestamp),
|
||
|
|
assembleSessionDetail(values).summaries.sort(byTimestamp),
|
||
|
|
);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('malformed physical JSON lines follow Pi while structural errors fail atomically', () => {
|
||
|
|
const complete = jsonl([header(), userEntry('user', null, 'no newline')], false);
|
||
|
|
assert.equal(parseOnly(writeSession(complete).root).values.filter(record => record.kind === 'message').length, 1);
|
||
|
|
|
||
|
|
const completedCorruption = [
|
||
|
|
JSON.stringify(header()),
|
||
|
|
JSON.stringify(userEntry('before', null, 'before malformed line')),
|
||
|
|
'{broken}',
|
||
|
|
JSON.stringify(userEntry('after', 'before', 'after malformed line')),
|
||
|
|
'',
|
||
|
|
].join('\n');
|
||
|
|
assert.deepEqual(
|
||
|
|
parseOnly(writeSession(completedCorruption).root).values
|
||
|
|
.filter(record => record.kind === 'message')
|
||
|
|
.map(record => record.text),
|
||
|
|
['before malformed line', 'after malformed line'],
|
||
|
|
);
|
||
|
|
|
||
|
|
const terminalGarbage = `${jsonl([header(), userEntry('user', null, 'prefix')])}not-json`;
|
||
|
|
assert.deepEqual(
|
||
|
|
parseOnly(writeSession(terminalGarbage).root).values
|
||
|
|
.filter(record => record.kind === 'message')
|
||
|
|
.map(record => record.text),
|
||
|
|
['prefix'],
|
||
|
|
);
|
||
|
|
|
||
|
|
const invalidValue = writeSession(`${JSON.stringify(header())}\n42\n`);
|
||
|
|
const valueProvider = createPiProvider({ rootDir: invalidValue.root });
|
||
|
|
const valueUnit = valueProvider.discover({ lastCursor: () => null })[0];
|
||
|
|
assert.throws(
|
||
|
|
() => drain(valueProvider.parse(valueUnit, null)),
|
||
|
|
/Malformed Pi JSONL value at line 2/,
|
||
|
|
);
|
||
|
|
|
||
|
|
const invalidMessage = writeSession(jsonl([
|
||
|
|
header(),
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'invalid-message',
|
||
|
|
parentId: null,
|
||
|
|
timestamp: '2026-08-02T10:00:01.000Z',
|
||
|
|
message: null,
|
||
|
|
},
|
||
|
|
]));
|
||
|
|
const messageProvider = createPiProvider({ rootDir: invalidMessage.root });
|
||
|
|
const messageUnit = messageProvider.discover({ lastCursor: () => null })[0];
|
||
|
|
assert.throws(
|
||
|
|
() => drain(messageProvider.parse(messageUnit, null)),
|
||
|
|
/Malformed Pi message/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('official v1 migration preserves canonical identities and v2 hookMessage becomes custom metadata', () => {
|
||
|
|
const v1 = [
|
||
|
|
{
|
||
|
|
type: 'session',
|
||
|
|
id: 'legacy-v1',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
timestamp: '2026-08-02T10:00:01.000Z',
|
||
|
|
message: { role: 'user', content: 'legacy user', timestamp: 1785664800000 },
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
timestamp: '2026-08-02T10:00:02.000Z',
|
||
|
|
summary: 'legacy summary',
|
||
|
|
firstKeptEntryIndex: 1,
|
||
|
|
tokensBefore: 100,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
message: { role: 'user', content: 'legacy tail', timestamp: 1785664803000 },
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const written = writeSession(`${v1.map(record => JSON.stringify(record)).join('\n\n')}\n`);
|
||
|
|
const firstParse = parseOnly(written.root);
|
||
|
|
const first = firstParse.values;
|
||
|
|
const migrated = [
|
||
|
|
{ ...v1[0], version: 3 },
|
||
|
|
{ ...v1[1], id: 'random-migrated-1', parentId: null },
|
||
|
|
{
|
||
|
|
...v1[2],
|
||
|
|
id: 'random-migrated-2',
|
||
|
|
parentId: 'random-migrated-1',
|
||
|
|
firstKeptEntryId: 'random-migrated-1',
|
||
|
|
firstKeptEntryIndex: undefined,
|
||
|
|
},
|
||
|
|
{ ...v1[3], id: 'random-migrated-3', parentId: 'random-migrated-2' },
|
||
|
|
];
|
||
|
|
writeFileSync(written.path, jsonl(migrated));
|
||
|
|
const secondParse = parseOnly(written.root);
|
||
|
|
const second = secondParse.values;
|
||
|
|
assert.deepEqual(
|
||
|
|
first.filter(record => record.kind === 'message').map(record => record.uuid),
|
||
|
|
second.filter(record => record.kind === 'message').map(record => record.uuid),
|
||
|
|
);
|
||
|
|
assert.deepEqual(
|
||
|
|
first.filter(record => record.kind === 'summary').map(record => record.id),
|
||
|
|
second.filter(record => record.kind === 'summary').map(record => record.id),
|
||
|
|
);
|
||
|
|
const firstSession = first.find(record => record.kind === 'session');
|
||
|
|
const secondSession = second.find(record => record.kind === 'session');
|
||
|
|
assert.equal(firstSession.version, 'session-v1');
|
||
|
|
assert.equal(secondSession.version, 'session-v3');
|
||
|
|
assert.equal(firstSession.started_at, null);
|
||
|
|
assert.equal(firstSession.project, null);
|
||
|
|
assert.equal(firstSession.id, secondSession.id);
|
||
|
|
assert.ok(first.filter(record => record.kind === 'message').every(record => record.cwd === null));
|
||
|
|
const rawAfterMigration = firstParse.provider.raw({
|
||
|
|
source: 'pi',
|
||
|
|
messageUuid: first.find(record => record.kind === 'message').uuid,
|
||
|
|
session: { id: firstSession.id, jsonl_path: written.path },
|
||
|
|
agentId: null,
|
||
|
|
cursor: secondParse.cursor,
|
||
|
|
});
|
||
|
|
assert.deepEqual(JSON.parse(rawAfterMigration.text), migrated[1].message);
|
||
|
|
assert.equal(rawAfterMigration.messageText, 'legacy user');
|
||
|
|
|
||
|
|
const v2 = [
|
||
|
|
header({ version: 2, id: 'legacy-v2' }),
|
||
|
|
{
|
||
|
|
type: 'message',
|
||
|
|
id: 'hook',
|
||
|
|
parentId: null,
|
||
|
|
timestamp: '2026-08-02T10:00:01.000Z',
|
||
|
|
message: {
|
||
|
|
role: 'hookMessage',
|
||
|
|
customType: 'legacy-hook',
|
||
|
|
content: 'extension context',
|
||
|
|
display: true,
|
||
|
|
timestamp: 1785664800000,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
];
|
||
|
|
const hook = parseOnly(writeSession(jsonl(v2)).root).values.find(record => record.kind === 'message');
|
||
|
|
assert.deepEqual(
|
||
|
|
(({ type, role, content_type, is_meta, visibility }) => ({ type, role, content_type, is_meta, visibility }))(hook),
|
||
|
|
{ type: 'system', role: 'custom', content_type: 'custom', is_meta: 1, visibility: 'visible' },
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('invalid tree identity, cycle, and leaf target fail closed', () => {
|
||
|
|
const cases = [
|
||
|
|
{
|
||
|
|
pattern: /Duplicate Pi entry id/,
|
||
|
|
records: [header(), userEntry('same', null, 'one'), userEntry('same', 'same', 'two')],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
pattern: /cycle/,
|
||
|
|
records: [header(), userEntry('a', 'b', 'a'), userEntry('b', 'a', 'b')],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
pattern: /cycle/,
|
||
|
|
records: [
|
||
|
|
header(),
|
||
|
|
userEntry('a', 'b', 'a'),
|
||
|
|
userEntry('b', 'a', 'b'),
|
||
|
|
{
|
||
|
|
type: 'compaction',
|
||
|
|
id: 'unrelated-compaction',
|
||
|
|
parentId: null,
|
||
|
|
timestamp: '2026-08-02T10:00:03.000Z',
|
||
|
|
summary: 'Must not mask the cycle',
|
||
|
|
firstKeptEntryId: 'a',
|
||
|
|
tokensBefore: 10,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
pattern: /leaf target .* does not exist/,
|
||
|
|
records: [
|
||
|
|
header(),
|
||
|
|
userEntry('a', null, 'a'),
|
||
|
|
{ type: 'leaf', id: 'leaf', parentId: 'a', targetId: 'missing', timestamp: '2026-08-02T10:00:02.000Z' },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
];
|
||
|
|
for (const { records, pattern } of cases) {
|
||
|
|
const { root } = writeSession(jsonl(records));
|
||
|
|
const provider = createPiProvider({ rootDir: root });
|
||
|
|
const unit = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
assert.throws(() => drain(provider.parse(unit, null)), pattern);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('missing parents form official Pi orphan roots without exposing inactive branches', () => {
|
||
|
|
const records = [
|
||
|
|
header({ id: 'orphan-root' }),
|
||
|
|
userEntry('inactive-root', null, 'inactive root'),
|
||
|
|
userEntry('orphan', 'omitted-parent', 'orphan root', '2026-08-02T10:00:02.000Z'),
|
||
|
|
userEntry('orphan-child', 'orphan', 'orphan child', '2026-08-02T10:00:03.000Z'),
|
||
|
|
];
|
||
|
|
const { provider, unit, values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
assert.deepEqual(
|
||
|
|
values
|
||
|
|
.filter(record => record.kind === 'message' && record.visibility === 'visible')
|
||
|
|
.map(record => record.text),
|
||
|
|
['orphan root', 'orphan child'],
|
||
|
|
);
|
||
|
|
assert.equal(
|
||
|
|
values.find(record => record.kind === 'message' && record.text === 'inactive root').visibility,
|
||
|
|
'inactive',
|
||
|
|
);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
persist(db, unit, provider.parse(unit, null));
|
||
|
|
const query = createQueryApi(db);
|
||
|
|
assert.deepEqual(query.thread(unit.sessionId).map(message => message.text), [
|
||
|
|
'orphan root',
|
||
|
|
'orphan child',
|
||
|
|
]);
|
||
|
|
assert.deepEqual(assembleSessionDetail(values).messages.map(message => message.text), [
|
||
|
|
'orphan root',
|
||
|
|
'orphan child',
|
||
|
|
]);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('discovery deduplicates identical same-project copies and rejects divergent copies', () => {
|
||
|
|
const root = mkdtempSync(join(tmpdir(), 'obelisk-pi-copies-'));
|
||
|
|
const original = fixture('tool-session.jsonl');
|
||
|
|
writeSession(original, { root, relativePath: 'a/session.jsonl' });
|
||
|
|
writeSession(original, { root, relativePath: 'b/session.jsonl' });
|
||
|
|
const provider = createPiProvider({ rootDir: root });
|
||
|
|
assert.equal(provider.discover({ lastCursor: () => null }).length, 1);
|
||
|
|
|
||
|
|
writeSession(`${original}${JSON.stringify({
|
||
|
|
type: 'session_info',
|
||
|
|
id: 'divergent-name',
|
||
|
|
parentId: '9db96a87',
|
||
|
|
timestamp: '2026-08-02T09:42:39.000Z',
|
||
|
|
name: 'Diverged copy',
|
||
|
|
})}\n`, { root, relativePath: 'b/session.jsonl' });
|
||
|
|
const divergent = provider.discover({ lastCursor: () => null });
|
||
|
|
assert.equal(divergent.length, 2);
|
||
|
|
for (const unit of divergent) {
|
||
|
|
assert.throws(() => drain(provider.parse(unit, null)), /Divergent Pi session copies/);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
test('discovery follows readable Pi JSONL file symlinks without retracting provenance', () => {
|
||
|
|
const written = writeSession(jsonl([
|
||
|
|
header({ id: 'symlink-session' }),
|
||
|
|
userEntry('symlink-user', null, 'symlink evidence'),
|
||
|
|
]));
|
||
|
|
const provider = createPiProvider({ rootDir: written.root });
|
||
|
|
const original = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
const parsed = drain(provider.parse(original, null));
|
||
|
|
const targetDir = mkdtempSync(join(tmpdir(), 'obelisk-pi-symlink-target-'));
|
||
|
|
const target = join(targetDir, 'session.jsonl');
|
||
|
|
renameSync(written.path, target);
|
||
|
|
symlinkSync(target, written.path);
|
||
|
|
|
||
|
|
const units = provider.discover({
|
||
|
|
lastCursor: key => key === written.path ? parsed.cursor : null,
|
||
|
|
changedPaths: [written.path],
|
||
|
|
indexedSessions: () => [{
|
||
|
|
sessionId: original.sessionId,
|
||
|
|
jsonlPath: written.path,
|
||
|
|
}],
|
||
|
|
});
|
||
|
|
|
||
|
|
assert.equal(units.some(unit => unit.meta?.kind === 'pi-tombstone'), false);
|
||
|
|
assert.equal(units.length, 1);
|
||
|
|
assert.equal(units[0].sessionId, original.sessionId);
|
||
|
|
assert.equal(
|
||
|
|
drain(provider.parse(units[0], null)).values
|
||
|
|
.find(record => record.kind === 'message').text,
|
||
|
|
'symlink evidence',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a malformed Pi file does not force unrelated unchanged sessions to reparse', () => {
|
||
|
|
const root = mkdtempSync(join(tmpdir(), 'obelisk-pi-bad-file-scope-'));
|
||
|
|
const damaged = writeSession(jsonl([
|
||
|
|
header({ id: 'damaged-session' }),
|
||
|
|
userEntry('damaged-user', null, 'damaged source'),
|
||
|
|
]), { root, relativePath: 'a/session.jsonl' });
|
||
|
|
const unchanged = writeSession(jsonl([
|
||
|
|
header({ id: 'unchanged-session' }),
|
||
|
|
userEntry('unchanged-user', null, 'unchanged source'),
|
||
|
|
]), { root, relativePath: 'b/session.jsonl' });
|
||
|
|
const provider = createPiProvider({ rootDir: root });
|
||
|
|
const cursors = new Map();
|
||
|
|
const indexed = [];
|
||
|
|
for (const unit of provider.discover({ lastCursor: () => null })) {
|
||
|
|
cursors.set(unit.key, drain(provider.parse(unit, null)).cursor);
|
||
|
|
indexed.push({ sessionId: unit.sessionId, jsonlPath: unit.key });
|
||
|
|
}
|
||
|
|
|
||
|
|
writeFileSync(damaged.path, '{broken}\n');
|
||
|
|
const units = provider.discover({
|
||
|
|
lastCursor: key => cursors.get(key) ?? null,
|
||
|
|
indexedSessions: () => indexed,
|
||
|
|
});
|
||
|
|
|
||
|
|
assert.deepEqual(units.map(unit => unit.key), [damaged.path]);
|
||
|
|
assert.equal(units.some(unit => unit.key === unchanged.path), false);
|
||
|
|
assert.throws(() => drain(provider.parse(units[0], null)), /Empty Pi session/);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a moved session with an unreadable identity cannot retract its last good snapshot', () => {
|
||
|
|
const written = writeSession(jsonl([
|
||
|
|
header({ id: 'moved-torn-session' }),
|
||
|
|
userEntry('moved-user', null, 'last good evidence'),
|
||
|
|
]));
|
||
|
|
const provider = createPiProvider({ rootDir: written.root });
|
||
|
|
const original = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
const parsed = drain(provider.parse(original, null));
|
||
|
|
const movedPath = join(written.root, 'moved', 'session.jsonl');
|
||
|
|
mkdirSync(dirname(movedPath), { recursive: true });
|
||
|
|
renameSync(written.path, movedPath);
|
||
|
|
writeFileSync(movedPath, '{torn header\n');
|
||
|
|
let incomplete = false;
|
||
|
|
|
||
|
|
const units = provider.discover({
|
||
|
|
lastCursor: key => key === written.path ? parsed.cursor : null,
|
||
|
|
indexedSessions: () => [{
|
||
|
|
sessionId: original.sessionId,
|
||
|
|
jsonlPath: written.path,
|
||
|
|
}],
|
||
|
|
reportIncompleteInventory: () => { incomplete = true; },
|
||
|
|
});
|
||
|
|
|
||
|
|
assert.equal(incomplete, false, 'a bad identity is not an incomplete filesystem traversal');
|
||
|
|
assert.deepEqual(units.map(unit => unit.key), [movedPath]);
|
||
|
|
assert.equal(units[0].retractSessionIds, undefined);
|
||
|
|
assert.equal(units.some(unit => unit.meta?.kind === 'pi-tombstone'), false);
|
||
|
|
assert.throws(() => drain(provider.parse(units[0], null)), /Empty Pi session/);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a bad selected copy forces an unchanged valid duplicate to refresh provenance', () => {
|
||
|
|
const root = mkdtempSync(join(tmpdir(), 'obelisk-pi-bad-copy-'));
|
||
|
|
const content = fixture('tool-session.jsonl');
|
||
|
|
const second = writeSession(content, { root, relativePath: 'b/session.jsonl' });
|
||
|
|
const provider = createPiProvider({ rootDir: root });
|
||
|
|
const initialUnit = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
const initial = drain(provider.parse(initialUnit, null));
|
||
|
|
const cursors = new Map([[second.path, initial.cursor]]);
|
||
|
|
|
||
|
|
const first = writeSession(content, { root, relativePath: 'a/session.jsonl' });
|
||
|
|
const selected = provider.discover({
|
||
|
|
lastCursor: key => cursors.get(key) ?? null,
|
||
|
|
})[0];
|
||
|
|
assert.equal(selected.key, first.path);
|
||
|
|
const selectedParse = drain(provider.parse(selected, null));
|
||
|
|
cursors.set(first.path, selectedParse.cursor);
|
||
|
|
|
||
|
|
writeFileSync(first.path, '{broken}\n');
|
||
|
|
const recovery = provider.discover({
|
||
|
|
lastCursor: key => cursors.get(key) ?? null,
|
||
|
|
indexedSessions: () => [{
|
||
|
|
sessionId: initialUnit.sessionId,
|
||
|
|
jsonlPath: first.path,
|
||
|
|
}],
|
||
|
|
});
|
||
|
|
const survivor = recovery.find(unit => unit.key === second.path);
|
||
|
|
|
||
|
|
assert.ok(survivor, 'the valid copy must bypass its unchanged cursor');
|
||
|
|
assert.equal(survivor.sessionId, initialUnit.sessionId);
|
||
|
|
assert.equal(
|
||
|
|
drain(provider.parse(survivor, null)).values
|
||
|
|
.find(record => record.kind === 'session').jsonl_path,
|
||
|
|
second.path,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('the same official project-local session id remains distinct across cwd namespaces', () => {
|
||
|
|
const root = mkdtempSync(join(tmpdir(), 'obelisk-pi-project-ids-'));
|
||
|
|
const firstHeader = header({ id: 'shared-custom-id', cwd: '/tmp/pi-project-a' });
|
||
|
|
const secondHeader = header({ id: 'shared-custom-id', cwd: '/tmp/pi-project-b' });
|
||
|
|
writeSession(jsonl([
|
||
|
|
firstHeader,
|
||
|
|
userEntry('project-a-user', null, 'project A evidence'),
|
||
|
|
]), { root, relativePath: 'a/session.jsonl' });
|
||
|
|
writeSession(jsonl([
|
||
|
|
secondHeader,
|
||
|
|
userEntry('project-b-user', null, 'project B evidence'),
|
||
|
|
]), { root, relativePath: 'b/session.jsonl' });
|
||
|
|
|
||
|
|
const provider = createPiProvider({ rootDir: root });
|
||
|
|
const units = provider.discover({ lastCursor: () => null });
|
||
|
|
assert.equal(units.length, 2);
|
||
|
|
assert.deepEqual(
|
||
|
|
new Set(units.map(unit => unit.sessionId)),
|
||
|
|
new Set([piSessionId(firstHeader), piSessionId(secondHeader)]),
|
||
|
|
);
|
||
|
|
|
||
|
|
const db = new DatabaseSync(':memory:');
|
||
|
|
db.exec(SCHEMA);
|
||
|
|
for (const unit of units) persist(db, unit, provider.parse(unit, null));
|
||
|
|
assert.deepEqual(
|
||
|
|
new Set(db.prepare("SELECT id FROM sessions WHERE source='pi'").all().map(row => row.id)),
|
||
|
|
new Set([piSessionId(firstHeader), piSessionId(secondHeader)]),
|
||
|
|
);
|
||
|
|
assert.equal(createQueryApi(db).search('project evidence').length, 2);
|
||
|
|
db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('identity migration retracts a legacy row attached to a non-selected identical copy', () => {
|
||
|
|
const root = mkdtempSync(join(tmpdir(), 'obelisk-pi-identity-migration-'));
|
||
|
|
const content = fixture('tool-session.jsonl');
|
||
|
|
const first = writeSession(content, { root, relativePath: 'a/session.jsonl' });
|
||
|
|
const second = writeSession(content, { root, relativePath: 'b/session.jsonl' });
|
||
|
|
const provider = createPiProvider({ rootDir: root });
|
||
|
|
const initialUnit = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
const initial = drain(provider.parse(initialUnit, null));
|
||
|
|
const sourceHeader = JSON.parse(content.split('\n')[0]);
|
||
|
|
const legacyId = `pi:${sourceHeader.id}`;
|
||
|
|
|
||
|
|
const migration = provider.discover({
|
||
|
|
lastCursor: key => key === first.path ? initial.cursor : null,
|
||
|
|
indexedSessions: () => [{ sessionId: legacyId, jsonlPath: second.path }],
|
||
|
|
});
|
||
|
|
assert.equal(migration.length, 1);
|
||
|
|
assert.equal(migration[0].key, first.path);
|
||
|
|
assert.equal(migration[0].sessionId, piSessionId(sourceHeader));
|
||
|
|
assert.deepEqual(migration[0].retractSessionIds, [legacyId]);
|
||
|
|
assert.deepEqual(
|
||
|
|
drain(provider.parse(migration[0], null)).values
|
||
|
|
.filter(record => record.kind === 'delete-session')
|
||
|
|
.map(record => record.sessionId),
|
||
|
|
[legacyId, piSessionId(sourceHeader)],
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('unlinking a deduplicated Pi copy reparses the surviving source instead of deleting the session', () => {
|
||
|
|
const root = mkdtempSync(join(tmpdir(), 'obelisk-pi-copy-move-'));
|
||
|
|
const content = fixture('tool-session.jsonl');
|
||
|
|
const first = writeSession(content, { root, relativePath: 'a/session.jsonl' });
|
||
|
|
const second = writeSession(content, { root, relativePath: 'b/session.jsonl' });
|
||
|
|
const provider = createPiProvider({ rootDir: root });
|
||
|
|
const original = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
const parsed = drain(provider.parse(original, null));
|
||
|
|
|
||
|
|
unlinkSync(first.path);
|
||
|
|
const survivors = provider.discover({
|
||
|
|
lastCursor: key => key === first.path ? parsed.cursor : null,
|
||
|
|
changedPaths: [first.path],
|
||
|
|
indexedSessions: () => [{ sessionId: original.sessionId, jsonlPath: first.path }],
|
||
|
|
});
|
||
|
|
assert.equal(survivors.length, 1);
|
||
|
|
assert.equal(survivors[0].key, second.path);
|
||
|
|
assert.deepEqual(survivors[0].retractSessionIds, undefined);
|
||
|
|
assert.equal(
|
||
|
|
drain(provider.parse(survivors[0], null)).values.find(record => record.kind === 'session').jsonl_path,
|
||
|
|
second.path,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('replacing one Pi copy preserves the original identity when another copy survives', () => {
|
||
|
|
const root = mkdtempSync(join(tmpdir(), 'obelisk-pi-copy-replace-'));
|
||
|
|
const content = fixture('tool-session.jsonl');
|
||
|
|
const first = writeSession(content, { root, relativePath: 'a/session.jsonl' });
|
||
|
|
const second = writeSession(content, { root, relativePath: 'b/session.jsonl' });
|
||
|
|
const provider = createPiProvider({ rootDir: root });
|
||
|
|
const original = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
drain(provider.parse(original, null));
|
||
|
|
|
||
|
|
writeFileSync(first.path, jsonl([
|
||
|
|
header({ id: 'replacement-copy' }),
|
||
|
|
userEntry('replacement-user', null, 'replacement'),
|
||
|
|
]));
|
||
|
|
const units = provider.discover({
|
||
|
|
lastCursor: () => null,
|
||
|
|
changedPaths: [first.path],
|
||
|
|
indexedSessions: () => [{ sessionId: original.sessionId, jsonlPath: first.path }],
|
||
|
|
});
|
||
|
|
|
||
|
|
assert.deepEqual(
|
||
|
|
units.map(unit => ({ path: unit.key, sessionId: unit.sessionId, retract: unit.retractSessionIds })),
|
||
|
|
[
|
||
|
|
{
|
||
|
|
path: first.path,
|
||
|
|
sessionId: piSessionId(header({ id: 'replacement-copy' })),
|
||
|
|
retract: undefined,
|
||
|
|
},
|
||
|
|
{ path: second.path, sessionId: original.sessionId, retract: undefined },
|
||
|
|
],
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('changed-path discovery bypasses cursors while an unchanged passive pull is skipped', () => {
|
||
|
|
const first = writeSession(fixture('tool-session.jsonl'));
|
||
|
|
const provider = createPiProvider({ rootDir: first.root });
|
||
|
|
const unit = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
const { cursor } = drain(provider.parse(unit, null));
|
||
|
|
|
||
|
|
assert.deepEqual(provider.discover({ lastCursor: () => cursor }), []);
|
||
|
|
assert.deepEqual(
|
||
|
|
provider.discover({ lastCursor: () => cursor, changedPaths: [first.path] }).map(candidate => candidate.key),
|
||
|
|
[first.path],
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('Pi snapshot cursors detect same-identity and replacement writes that preserve mtime', () => {
|
||
|
|
const written = writeSession(jsonl([
|
||
|
|
header({ id: 'preserved-mtime' }),
|
||
|
|
userEntry('before', null, 'before'),
|
||
|
|
]));
|
||
|
|
const provider = createPiProvider({ rootDir: written.root });
|
||
|
|
const initialUnit = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
const initial = drain(provider.parse(initialUnit, null));
|
||
|
|
|
||
|
|
const replaceAtSameMtime = (content) => {
|
||
|
|
const before = statSync(written.path);
|
||
|
|
const replacement = `${written.path}.replacement`;
|
||
|
|
writeFileSync(replacement, content);
|
||
|
|
utimesSync(replacement, before.atime, before.mtime);
|
||
|
|
renameSync(replacement, written.path);
|
||
|
|
utimesSync(written.path, before.atime, before.mtime);
|
||
|
|
};
|
||
|
|
|
||
|
|
replaceAtSameMtime(jsonl([
|
||
|
|
header({ id: 'preserved-mtime' }),
|
||
|
|
userEntry('after', null, 'after!'),
|
||
|
|
]));
|
||
|
|
const sameIdentity = provider.discover({
|
||
|
|
lastCursor: () => initial.cursor,
|
||
|
|
indexedSessions: () => [{ sessionId: initialUnit.sessionId, jsonlPath: written.path }],
|
||
|
|
});
|
||
|
|
assert.equal(sameIdentity.length, 1);
|
||
|
|
assert.equal(sameIdentity[0].sessionId, initialUnit.sessionId);
|
||
|
|
const updated = drain(provider.parse(sameIdentity[0], initial.cursor));
|
||
|
|
|
||
|
|
replaceAtSameMtime(jsonl([
|
||
|
|
header({ id: 'replacement-mtime' }),
|
||
|
|
userEntry('replacement', null, 'replacement'),
|
||
|
|
]));
|
||
|
|
const replacement = provider.discover({
|
||
|
|
lastCursor: () => updated.cursor,
|
||
|
|
indexedSessions: () => [{ sessionId: initialUnit.sessionId, jsonlPath: written.path }],
|
||
|
|
});
|
||
|
|
assert.equal(replacement.length, 1);
|
||
|
|
assert.equal(replacement[0].sessionId, piSessionId(header({ id: 'replacement-mtime' })));
|
||
|
|
assert.deepEqual(replacement[0].retractSessionIds, [initialUnit.sessionId]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('incomplete Pi inventories never infer deletion from changed paths', () => {
|
||
|
|
const parent = mkdtempSync(join(tmpdir(), 'obelisk-pi-incomplete-'));
|
||
|
|
const unreadableRoot = join(parent, 'sessions');
|
||
|
|
writeFileSync(unreadableRoot, 'not a directory');
|
||
|
|
const indexedPath = join(unreadableRoot, 'project', 'session.jsonl');
|
||
|
|
const provider = createPiProvider({ rootDir: unreadableRoot });
|
||
|
|
|
||
|
|
assert.deepEqual(provider.discover({
|
||
|
|
lastCursor: () => null,
|
||
|
|
changedPaths: [unreadableRoot],
|
||
|
|
indexedSessions: () => [{ sessionId: 'pi:preserve-me', jsonlPath: indexedPath }],
|
||
|
|
}), []);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a file disappearing during discovery marks the inventory incomplete without throwing', () => {
|
||
|
|
const written = writeSession(jsonl([
|
||
|
|
header({ id: 'discovery-race' }),
|
||
|
|
userEntry('message', null, 'vanishing source'),
|
||
|
|
]));
|
||
|
|
const provider = createPiProvider({ rootDir: written.root });
|
||
|
|
let incomplete = false;
|
||
|
|
|
||
|
|
const units = provider.discover({
|
||
|
|
lastCursor: () => null,
|
||
|
|
indexedSessions: () => {
|
||
|
|
unlinkSync(written.path);
|
||
|
|
return [];
|
||
|
|
},
|
||
|
|
reportIncompleteInventory: () => { incomplete = true; },
|
||
|
|
});
|
||
|
|
|
||
|
|
assert.deepEqual(units, []);
|
||
|
|
assert.equal(incomplete, true);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('indexed provenance retracts a replaced identity and emits an unlink tombstone', () => {
|
||
|
|
const written = writeSession(jsonl([header({ id: 'before' }), userEntry('a', null, 'before')]));
|
||
|
|
const provider = createPiProvider({ rootDir: written.root });
|
||
|
|
const beforeUnit = provider.discover({ lastCursor: () => null })[0];
|
||
|
|
const before = drain(provider.parse(beforeUnit, null));
|
||
|
|
const beforeSession = before.values.find(record => record.kind === 'session').id;
|
||
|
|
|
||
|
|
writeFileSync(written.path, jsonl([header({ id: 'after' }), userEntry('b', null, 'after')]));
|
||
|
|
const afterUnit = provider.discover({
|
||
|
|
lastCursor: () => before.cursor,
|
||
|
|
changedPaths: [written.path],
|
||
|
|
indexedSessions: () => [{ sessionId: beforeSession, jsonlPath: written.path }],
|
||
|
|
})[0];
|
||
|
|
assert.deepEqual(afterUnit.retractSessionIds, [beforeSession]);
|
||
|
|
const after = drain(provider.parse(afterUnit, null));
|
||
|
|
const afterSession = after.values.find(record => record.kind === 'session').id;
|
||
|
|
assert.notEqual(afterSession, beforeSession);
|
||
|
|
assert.deepEqual(
|
||
|
|
after.values.filter(record => record.kind === 'delete-session').map(record => record.sessionId),
|
||
|
|
[beforeSession, afterSession],
|
||
|
|
);
|
||
|
|
|
||
|
|
unlinkSync(written.path);
|
||
|
|
const tombstones = provider.discover({
|
||
|
|
lastCursor: () => after.cursor,
|
||
|
|
changedPaths: [written.path],
|
||
|
|
indexedSessions: () => [{ sessionId: afterSession, jsonlPath: written.path }],
|
||
|
|
});
|
||
|
|
assert.equal(tombstones.length, 1);
|
||
|
|
assert.deepEqual(tombstones[0].retractSessionIds, [afterSession]);
|
||
|
|
const tombstone = drain(provider.parse(tombstones[0], after.cursor));
|
||
|
|
assert.deepEqual(tombstone.values, [{ kind: 'delete-session', sessionId: afterSession }]);
|
||
|
|
assert.equal(tombstone.cursor, '0:0');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('session identity combines Pi project-local header id with stable cwd namespace', () => {
|
||
|
|
const source = JSON.parse(fixture('tool-session.jsonl').split('\n')[0]);
|
||
|
|
const migrated = {
|
||
|
|
...source,
|
||
|
|
version: source.version === 3 ? 2 : 3,
|
||
|
|
timestamp: '2030-01-01T00:00:00.000Z',
|
||
|
|
parentSession: '/tmp/parent.jsonl',
|
||
|
|
};
|
||
|
|
assert.equal(piSessionId(source), piSessionId(migrated));
|
||
|
|
assert.notEqual(piSessionId(source), piSessionId({ ...source, id: 'different-session' }));
|
||
|
|
assert.notEqual(piSessionId(source), piSessionId({ ...source, cwd: '/tmp/other-project' }));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('long linear Pi trees validate without repeated prefix walks', () => {
|
||
|
|
const records = [header({ id: 'long-linear' })];
|
||
|
|
let parentId = null;
|
||
|
|
for (let index = 0; index < 5000; index++) {
|
||
|
|
const id = `entry-${index}`;
|
||
|
|
records.push(userEntry(id, parentId, `message ${index}`));
|
||
|
|
parentId = id;
|
||
|
|
}
|
||
|
|
const { values } = parseOnly(writeSession(jsonl(records)).root);
|
||
|
|
assert.equal(values.filter(record => record.kind === 'message').length, 5000);
|
||
|
|
});
|