fix: carry paired turn attachments into review context (#201)

This commit is contained in:
Eyejoker
2026-05-31 14:49:56 +09:00
committed by GitHub
parent 37b57b20bb
commit 6eca648c47
22 changed files with 615 additions and 67 deletions

View File

@@ -180,7 +180,7 @@ export function applyBaseSchema(database: Database): void {
task_id TEXT NOT NULL,
turn_number INTEGER NOT NULL,
role TEXT NOT NULL,
output_text TEXT NOT NULL,
output_text TEXT NOT NULL, attachment_payload TEXT,
verdict TEXT,
created_at TEXT NOT NULL,
UNIQUE(task_id, turn_number, role)

View File

@@ -42,6 +42,7 @@ function getExpectedSchemaMigrations(): Array<{
{ version: 15, name: 'turn_progress_text' },
{ version: 16, name: 'room_skill_overrides' },
{ version: 17, name: 'scheduled_task_room_role' },
{ version: 18, name: 'paired_turn_output_attachments' },
];
}

View File

@@ -0,0 +1,20 @@
import type { Database } from 'bun:sqlite';
import { tableHasColumn } from './helpers.js';
import type { SchemaMigrationDefinition } from './types.js';
export const PAIRED_TURN_OUTPUT_ATTACHMENTS_MIGRATION: SchemaMigrationDefinition =
{
version: 18,
name: 'paired_turn_output_attachments',
apply(database: Database) {
if (
!tableHasColumn(database, 'paired_turn_outputs', 'attachment_payload')
) {
database.exec(`
ALTER TABLE paired_turn_outputs
ADD COLUMN attachment_payload TEXT
`);
}
},
};

View File

@@ -17,6 +17,7 @@ import { WORK_ITEM_ATTACHMENTS_MIGRATION } from './014_work-item-attachments.js'
import { TURN_PROGRESS_TEXT_MIGRATION } from './015_turn-progress-text.js';
import { ROOM_SKILL_OVERRIDES_MIGRATION } from './016_room-skill-overrides.js';
import { SCHEDULED_TASK_ROOM_ROLE_MIGRATION } from './017_scheduled-task-room-role.js';
import { PAIRED_TURN_OUTPUT_ATTACHMENTS_MIGRATION } from './018_paired-turn-output-attachments.js';
import type {
SchemaMigrationArgs,
SchemaMigrationDefinition,
@@ -42,6 +43,7 @@ const ORDERED_SCHEMA_MIGRATIONS: readonly SchemaMigrationDefinition[] = [
TURN_PROGRESS_TEXT_MIGRATION,
ROOM_SKILL_OVERRIDES_MIGRATION,
SCHEDULED_TASK_ROOM_ROLE_MIGRATION,
PAIRED_TURN_OUTPUT_ATTACHMENTS_MIGRATION,
];
function ensureSchemaMigrationsTable(database: Database): void {

View File

@@ -2,7 +2,15 @@ import { Database } from 'bun:sqlite';
import { logger } from '../logger.js';
import { parseVisibleVerdict } from '../paired-verdict.js';
import { PairedRoomRole, PairedTurnOutput } from '../types.js';
import {
OutboundAttachment,
PairedRoomRole,
PairedTurnOutput,
} from '../types.js';
import {
parseAttachmentPayload,
serializeAttachmentPayload,
} from './work-items.js';
const MAX_TURN_OUTPUT_CHARS = 50_000;
@@ -12,7 +20,10 @@ export function insertPairedTurnOutputInDatabase(
turnNumber: number,
role: PairedRoomRole,
outputText: string,
createdAt?: string,
options: {
createdAt?: string;
attachments?: OutboundAttachment[];
} = {},
): void {
if (outputText.length > MAX_TURN_OUTPUT_CHARS) {
logger.warn(
@@ -30,19 +41,33 @@ export function insertPairedTurnOutputInDatabase(
database
.prepare(
`INSERT OR REPLACE INTO paired_turn_outputs
(task_id, turn_number, role, output_text, verdict, created_at)
VALUES (?, ?, ?, ?, ?, ?)`,
(task_id, turn_number, role, output_text, attachment_payload, verdict, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
)
.run(
taskId,
turnNumber,
role,
outputText.slice(0, MAX_TURN_OUTPUT_CHARS),
serializeAttachmentPayload(options.attachments),
parseVisibleVerdict(outputText),
createdAt ?? new Date().toISOString(),
options.createdAt ?? new Date().toISOString(),
);
}
type StoredPairedTurnOutputRow = PairedTurnOutput & {
attachment_payload?: string | null;
};
function hydratePairedTurnOutputRow(
row: StoredPairedTurnOutputRow,
): PairedTurnOutput {
return {
...row,
attachments: parseAttachmentPayload(row.attachment_payload),
};
}
export function getPairedTurnOutputsFromDatabase(
database: Database,
taskId: string,
@@ -53,7 +78,8 @@ export function getPairedTurnOutputsFromDatabase(
WHERE task_id = ?
ORDER BY turn_number ASC`,
)
.all(taskId) as PairedTurnOutput[];
.all(taskId)
.map((row) => hydratePairedTurnOutputRow(row as StoredPairedTurnOutputRow));
}
const recentOutputsForChatStmtCache = new WeakMap<
@@ -78,8 +104,8 @@ export function getRecentPairedTurnOutputsForChatFromDatabase(
`);
recentOutputsForChatStmtCache.set(database, stmt);
}
const rows = stmt.all(chatJid, limit) as PairedTurnOutput[];
return rows.reverse();
const rows = stmt.all(chatJid, limit) as StoredPairedTurnOutputRow[];
return rows.reverse().map(hydratePairedTurnOutputRow);
}
export function getLatestTurnNumberFromDatabase(

View File

@@ -362,15 +362,24 @@ export function insertPairedTurnOutput(
turnNumber: number,
role: PairedRoomRole,
outputText: string,
createdAt?: string,
createdAtOrOptions?:
| string
| {
createdAt?: string;
attachments?: import('../types.js').OutboundAttachment[];
},
): void {
const options =
typeof createdAtOrOptions === 'string'
? { createdAt: createdAtOrOptions }
: (createdAtOrOptions ?? {});
insertPairedTurnOutputInDatabase(
requireDatabase(),
taskId,
turnNumber,
role,
outputText,
createdAt,
options,
);
}

View File

@@ -105,7 +105,7 @@ function hydrateWorkItemRow(row: StoredWorkItemRow): WorkItem {
};
}
function parseAttachmentPayload(
export function parseAttachmentPayload(
payload: string | null | undefined,
): OutboundAttachment[] {
if (!payload) return [];
@@ -130,7 +130,7 @@ function parseAttachmentPayload(
}
}
function serializeAttachmentPayload(
export function serializeAttachmentPayload(
attachments: OutboundAttachment[] | undefined,
): string | null {
if (!attachments?.length) return null;