213 lines
7.3 KiB
JavaScript
213 lines
7.3 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { DatabaseSync } from 'node:sqlite';
|
|
|
|
import { assembleSessionDetail } from '../packages/core/src/session-detail.ts';
|
|
import { persist } from '../packages/core/src/persist.ts';
|
|
import { parse as parseCodex } from '../packages/core/src/providers/codex.ts';
|
|
|
|
const SCHEMA = readFileSync(new URL('../packages/core/src/schema.sql', import.meta.url), 'utf8');
|
|
|
|
function writeCodexFixture(lines) {
|
|
const dir = mkdtempSync(join(tmpdir(), 'obelisk-provider-detail-'));
|
|
const path = join(dir, 'rollout.jsonl');
|
|
writeFileSync(path, `${lines.map(line => JSON.stringify(line)).join('\n')}\n`);
|
|
return path;
|
|
}
|
|
|
|
test('a provider record stream assembles directly into session detail', () => {
|
|
const threadId = '019e8951-3e7d-7343-a3e3-05bff48a317d';
|
|
const path = writeCodexFixture([
|
|
{
|
|
type: 'session_meta',
|
|
timestamp: '2026-06-10T10:00:00Z',
|
|
payload: { id: threadId, cwd: '/proj', timestamp: '2026-06-10T10:00:00Z' },
|
|
},
|
|
{
|
|
type: 'event_msg',
|
|
timestamp: '2026-06-10T10:00:01Z',
|
|
payload: { type: 'user_message', message: 'inspect the repository' },
|
|
},
|
|
{
|
|
type: 'event_msg',
|
|
timestamp: '2026-06-10T10:00:02Z',
|
|
payload: { type: 'agent_message', message: 'I will inspect it.' },
|
|
},
|
|
{
|
|
type: 'response_item',
|
|
timestamp: '2026-06-10T10:00:03Z',
|
|
payload: { type: 'function_call', call_id: 'call_1', name: 'shell', arguments: '{"cmd":"ls"}' },
|
|
},
|
|
{
|
|
type: 'response_item',
|
|
timestamp: '2026-06-10T10:00:04Z',
|
|
payload: { type: 'function_call_output', call_id: 'call_1', output: 'package.json' },
|
|
},
|
|
]);
|
|
|
|
const records = [...parseCodex({ key: path, sessionId: '' }, null)];
|
|
const detail = assembleSessionDetail(records);
|
|
|
|
assert.deepEqual(detail.messages.map(message => message.text), [
|
|
'inspect the repository',
|
|
'I will inspect it.',
|
|
]);
|
|
assert.equal(detail.messages[1].tool_calls?.[0].name, 'shell');
|
|
assert.equal(detail.messages[1].tool_calls?.[0].result?.content, 'package.json');
|
|
|
|
const db = new DatabaseSync(':memory:');
|
|
db.exec(SCHEMA);
|
|
persist(db, { key: path, sessionId: '' }, parseCodex({ key: path, sessionId: '' }, null));
|
|
const persistedDetail = 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(),
|
|
});
|
|
assert.deepEqual(persistedDetail, detail);
|
|
db.close();
|
|
});
|
|
|
|
test('provider-classified hidden context never reaches session detail', () => {
|
|
const threadId = '019e8951-3e7d-7343-a3e3-05bff48a317e';
|
|
const path = writeCodexFixture([
|
|
{
|
|
type: 'session_meta',
|
|
timestamp: '2026-06-10T10:00:00Z',
|
|
payload: { id: threadId, cwd: '/proj', timestamp: '2026-06-10T10:00:00Z' },
|
|
},
|
|
{
|
|
type: 'response_item',
|
|
timestamp: '2026-06-10T10:00:01Z',
|
|
payload: {
|
|
type: 'message',
|
|
role: 'user',
|
|
content: [{ type: 'input_text', text: '<environment_context>\n <cwd>/proj</cwd>\n</environment_context>' }],
|
|
},
|
|
},
|
|
{
|
|
type: 'response_item',
|
|
timestamp: '2026-06-10T10:00:02Z',
|
|
payload: {
|
|
type: 'message',
|
|
role: 'user',
|
|
content: [{ type: 'input_text', text: '<codex_internal_context source="goal">\nsecret state\n</codex_internal_context>' }],
|
|
},
|
|
},
|
|
{
|
|
type: 'event_msg',
|
|
timestamp: '2026-06-10T10:00:03Z',
|
|
payload: { type: 'user_message', message: 'show the actual request' },
|
|
},
|
|
]);
|
|
|
|
const records = [...parseCodex({ key: path, sessionId: '' }, null)];
|
|
const detail = assembleSessionDetail(records);
|
|
|
|
assert.deepEqual(detail.messages.map(message => message.text), ['show the actual request']);
|
|
assert.equal(
|
|
records.filter(record => record.kind === 'message' && record.visibility === 'hidden').length,
|
|
2,
|
|
);
|
|
});
|
|
|
|
test('provider normalization removes only structural image wrappers before deduplication', () => {
|
|
const threadId = '019e8951-3e7d-7343-a3e3-05bff48a317f';
|
|
const path = writeCodexFixture([
|
|
{
|
|
type: 'session_meta',
|
|
timestamp: '2026-06-10T10:00:00Z',
|
|
payload: { id: threadId, cwd: '/proj', timestamp: '2026-06-10T10:00:00Z' },
|
|
},
|
|
{
|
|
type: 'event_msg',
|
|
timestamp: '2026-06-10T10:00:01Z',
|
|
payload: { type: 'user_message', message: 'look at this screenshot' },
|
|
},
|
|
{
|
|
type: 'response_item',
|
|
timestamp: '2026-06-10T10:00:01Z',
|
|
payload: {
|
|
type: 'message',
|
|
role: 'user',
|
|
content: [
|
|
{ type: 'input_text', text: 'look at this screenshot' },
|
|
{ type: 'input_text', text: '<image>' },
|
|
{ type: 'input_image', image_url: 'data:image/png;base64,AAAA' },
|
|
{ type: 'input_text', text: '</image>' },
|
|
],
|
|
},
|
|
},
|
|
]);
|
|
|
|
const records = [...parseCodex({ key: path, sessionId: '' }, null)];
|
|
const detail = assembleSessionDetail(records);
|
|
|
|
assert.deepEqual(detail.messages.map(message => message.text), ['look at this screenshot']);
|
|
});
|
|
|
|
test('canonical visibility survives persistence before row-based assembly', () => {
|
|
const threadId = '019e8951-3e7d-7343-a3e3-05bff48a3180';
|
|
const path = writeCodexFixture([
|
|
{
|
|
type: 'session_meta',
|
|
timestamp: '2026-06-10T10:00:00Z',
|
|
payload: { id: threadId, cwd: '/proj', timestamp: '2026-06-10T10:00:00Z' },
|
|
},
|
|
{
|
|
type: 'response_item',
|
|
timestamp: '2026-06-10T10:00:01Z',
|
|
payload: {
|
|
type: 'message',
|
|
role: 'user',
|
|
content: [{ type: 'input_text', text: '<environment_context>hidden</environment_context>' }],
|
|
},
|
|
},
|
|
{
|
|
type: 'event_msg',
|
|
timestamp: '2026-06-10T10:00:02Z',
|
|
payload: { type: 'user_message', message: 'visible request' },
|
|
},
|
|
]);
|
|
const db = new DatabaseSync(':memory:');
|
|
db.exec(SCHEMA);
|
|
|
|
persist(db, { key: path, sessionId: '' }, parseCodex({ key: path, sessionId: '' }, null));
|
|
const messages = db.prepare('SELECT * FROM messages ORDER BY timestamp, uuid').all();
|
|
const assembled = assembleSessionDetail({ messages }).messages;
|
|
|
|
assert.equal(messages[0].visibility, 'hidden');
|
|
assert.deepEqual(assembled.map(message => message.text), ['visible request']);
|
|
db.close();
|
|
});
|
|
|
|
test('provider normalization classifies Skill instructions before assembly', () => {
|
|
const threadId = '019e8951-3e7d-7343-a3e3-05bff48a3181';
|
|
const path = writeCodexFixture([
|
|
{
|
|
type: 'session_meta',
|
|
timestamp: '2026-06-10T10:00:00Z',
|
|
payload: { id: threadId, cwd: '/proj', timestamp: '2026-06-10T10:00:00Z' },
|
|
},
|
|
{
|
|
type: 'response_item',
|
|
timestamp: '2026-06-10T10:00:01Z',
|
|
payload: {
|
|
type: 'message',
|
|
role: 'user',
|
|
content: [{ type: 'input_text', text: 'Base directory for this skill: /tmp/skill\n# Instructions' }],
|
|
},
|
|
},
|
|
]);
|
|
|
|
const records = [...parseCodex({ key: path, sessionId: '' }, null)];
|
|
const message = records.find(record => record.kind === 'message');
|
|
|
|
assert.equal(message.content_type, 'skill_instructions');
|
|
assert.equal(message.is_meta, 1);
|
|
assert.equal(message.visibility, 'visible');
|
|
});
|