Zion Boggan zionboggan.com ↗

Adapters: emit per-turn actions and thinking (shared plumbing + Codex), unlocking the verified tier for imports

cf26aa1   Zion Boggan committed on Jun 12, 2026 (1 week ago)
src/adapters/codex.js +29 -0
@@ -2,6 +2,8 @@ import {
newSession,
finalizeSession,
pushTurn,
+ addAction,
+ addThinking,
flattenParts,
looksSynthetic,
readJsonl,
@@ -27,6 +29,7 @@ export function parseCodex(text, path, sessionId) {
const session = newSession(path, sessionId);
const records = readJsonl(text);
let turn = 0;
+ let currentModel = null;
for (const rec of records) {
const ts = rec.timestamp || null;
@@ -55,6 +58,17 @@ export function parseCodex(text, path, sessionId) {
session.stats.toolUses++;
const file = filePathFromArgs(payload.arguments);
if (file) session.stats.filesTouched.add(file);
+ addAction(session, {
+ tool: payload.name || null,
+ file: file || null,
+ command: commandFromArgs(payload.name, payload.arguments),
+ model: currentModel,
+ });
+ continue;
+ }
+
+ if (rec.type === 'response_item' && payload.type === 'reasoning') {
+ addThinking(session);
continue;
}
@@ -69,12 +83,27 @@ export function parseCodex(text, path, sessionId) {
if (rec.type === 'turn_context' && payload.model) {
session.stats.models.add(payload.model);
+ currentModel = payload.model;
}
}
return finalizeSession(session);
}
+function commandFromArgs(name, args) {
+ if (!/exec|shell|bash|run|terminal|command/i.test(name || '')) return null;
+ if (!args || typeof args !== 'string') return null;
+ let parsed;
+ try {
+ parsed = JSON.parse(args);
+ } catch {
+ return null;
+ }
+ const cmd = parsed.command || parsed.cmd;
+ if (Array.isArray(cmd)) return cmd.join(' ');
+ return typeof cmd === 'string' ? cmd : null;
+}
+
function filePathFromArgs(args) {
if (!args || typeof args !== 'string') return null;
let parsed;
src/adapters/shared.js +14 -2
@@ -53,7 +53,7 @@ export function pushTurn(session, idx, text, ts, { hasImage = false, hadToolResu
session.leafUuid = uuid;
session._lastUserUuid = uuid;
session.stats.userLines++;
- session.prompts.push({
+ const prompt = {
uuid,
parentUuid,
ts: ts || null,
@@ -61,11 +61,23 @@ export function pushTurn(session, idx, text, ts, { hasImage = false, hadToolResu
hasImage,
hadToolResultContext,
afterInterruption: false,
- });
+ actions: [],
+ thinking: 0,
+ };
+ session.prompts.push(prompt);
+ session._currentPrompt = prompt;
noteTimestamp(session, ts);
return uuid;
}
+export function addAction(session, action) {
+ if (session._currentPrompt && action) session._currentPrompt.actions.push(action);
+}
+
+export function addThinking(session, n = 1) {
+ if (session._currentPrompt) session._currentPrompt.thinking += n;
+}
+
export function flattenParts(parts) {
if (typeof parts === 'string') return parts;
if (!Array.isArray(parts)) {
test/adapters.test.js +27 -0
@@ -7,6 +7,7 @@ import { dirname, join } from 'node:path';
import { adaptFrom, autoAdapt, TOOLS } from '../src/adapters/index.js';
import { classifyPrompts } from '../src/extract.js';
import { buildTree } from '../src/tree.js';
+import { analyzeTree } from '../src/analyze.js';
const DIR = join(dirname(fileURLToPath(import.meta.url)), 'fixtures', 'adapters');
const fx = (name) => join(DIR, name);
@@ -135,3 +136,29 @@ test('adaptFrom rejects an unknown tool name', () => {
assert.throws(() => adaptFrom('notatool', '{}', 'x.json'), /unknown/);
assert.ok(TOOLS.includes('codex') && TOOLS.includes('cursor'));
});
+
+test('codex import emits actions that drive a verified security signal and model attribution', () => {
+ const jsonl = [
+ { type: 'session_meta', timestamp: '2026-06-12T10:00:00Z', payload: { id: 'cdx1', originator: 'codex_cli_rs', cwd: '/repo', cli_version: '0.139.0' } },
+ { type: 'turn_context', timestamp: '2026-06-12T10:00:01Z', payload: { model: 'gpt-5.5', cwd: '/repo' } },
+ { type: 'response_item', timestamp: '2026-06-12T10:00:02Z', payload: { type: 'message', role: 'user', content: [{ type: 'text', text: 'Add rate limiting to the checkout endpoint' }] } },
+ { type: 'response_item', timestamp: '2026-06-12T10:00:03Z', payload: { type: 'reasoning', summary: [] } },
+ { type: 'response_item', timestamp: '2026-06-12T10:00:05Z', payload: { type: 'function_call', name: 'apply_patch', arguments: JSON.stringify({ path: 'src/auth/session.ts' }), call_id: 'c1' } },
+ { type: 'response_item', timestamp: '2026-06-12T10:00:06Z', payload: { type: 'message', role: 'assistant', content: [{ type: 'text', text: 'Edited session.ts' }] } },
+ ].map((r) => JSON.stringify(r)).join('\n');
+
+ const sessions = adaptFrom('codex', jsonl, fx('codex-synth.jsonl'));
+ const s = sessions[0];
+ assert.equal(s.prompts[0].actions.length, 1);
+ assert.equal(s.prompts[0].actions[0].file, 'src/auth/session.ts');
+ assert.equal(s.prompts[0].actions[0].model, 'gpt-5.5');
+ assert.equal(s.prompts[0].thinking, 1);
+
+ const { tree } = pipeline(sessions);
+ const analysis = analyzeTree(tree);
+ const sec = analysis.failures.find((f) => f.type === 'security_or_privacy_risk' && f.tier === 'verified');
+ assert.ok(sec, 'a codex import should now produce a verified security signal');
+ assert.equal(sec.model, 'gpt-5.5');
+ assert.deepEqual(analysis.summary.models, ['gpt-5.5']);
+ assert.ok(analysis.summary.thinkingBlocks >= 1);
+});