Add structured Discord attachments
This commit is contained in:
@@ -39,6 +39,7 @@ export function applyBaseSchema(database: Database): void {
|
||||
start_seq INTEGER,
|
||||
end_seq INTEGER,
|
||||
result_payload TEXT NOT NULL,
|
||||
attachment_payload TEXT,
|
||||
delivery_attempts INTEGER NOT NULL DEFAULT 0,
|
||||
delivery_message_id TEXT,
|
||||
last_error TEXT,
|
||||
|
||||
@@ -38,6 +38,7 @@ function getExpectedSchemaMigrations(): Array<{
|
||||
{ version: 11, name: 'owner_failure_count' },
|
||||
{ version: 12, name: 'paired_verdict_and_step_telemetry' },
|
||||
{ version: 13, name: 'message_source_kind' },
|
||||
{ version: 14, name: 'work_item_attachments' },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
17
src/db/migrations/014_work-item-attachments.ts
Normal file
17
src/db/migrations/014_work-item-attachments.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Database } from 'bun:sqlite';
|
||||
|
||||
import { tableHasColumn } from './helpers.js';
|
||||
import type { SchemaMigrationDefinition } from './types.js';
|
||||
|
||||
export const WORK_ITEM_ATTACHMENTS_MIGRATION: SchemaMigrationDefinition = {
|
||||
version: 14,
|
||||
name: 'work_item_attachments',
|
||||
apply(database: Database) {
|
||||
if (!tableHasColumn(database, 'work_items', 'attachment_payload')) {
|
||||
database.exec(`
|
||||
ALTER TABLE work_items
|
||||
ADD COLUMN attachment_payload TEXT
|
||||
`);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -13,6 +13,7 @@ import { PAIRED_TURN_PROVENANCE_UPGRADE_MIGRATION } from './010_paired-turn-prov
|
||||
import { OWNER_FAILURE_COUNT_MIGRATION } from './011_owner-failure-count.js';
|
||||
import { PAIRED_VERDICT_AND_STEP_TELEMETRY_MIGRATION } from './012_paired-verdict-and-step-telemetry.js';
|
||||
import { MESSAGE_SOURCE_KIND_MIGRATION } from './013_message-source-kind.js';
|
||||
import { WORK_ITEM_ATTACHMENTS_MIGRATION } from './014_work-item-attachments.js';
|
||||
import type {
|
||||
SchemaMigrationArgs,
|
||||
SchemaMigrationDefinition,
|
||||
@@ -34,6 +35,7 @@ const ORDERED_SCHEMA_MIGRATIONS: readonly SchemaMigrationDefinition[] = [
|
||||
OWNER_FAILURE_COUNT_MIGRATION,
|
||||
PAIRED_VERDICT_AND_STEP_TELEMETRY_MIGRATION,
|
||||
MESSAGE_SOURCE_KIND_MIGRATION,
|
||||
WORK_ITEM_ATTACHMENTS_MIGRATION,
|
||||
];
|
||||
|
||||
function ensureSchemaMigrationsTable(database: Database): void {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
inferRoleFromServiceShadow,
|
||||
resolveRoleServiceShadow,
|
||||
} from '../role-service-shadow.js';
|
||||
import { AgentType, PairedRoomRole } from '../types.js';
|
||||
import { AgentType, OutboundAttachment, PairedRoomRole } from '../types.js';
|
||||
|
||||
export interface WorkItem {
|
||||
id: number;
|
||||
@@ -19,6 +19,7 @@ export interface WorkItem {
|
||||
start_seq: number | null;
|
||||
end_seq: number | null;
|
||||
result_payload: string;
|
||||
attachments?: OutboundAttachment[];
|
||||
delivery_attempts: number;
|
||||
delivery_message_id: string | null;
|
||||
last_error: string | null;
|
||||
@@ -29,10 +30,11 @@ export interface WorkItem {
|
||||
|
||||
interface StoredWorkItemRow extends Omit<
|
||||
WorkItem,
|
||||
'agent_type' | 'service_id'
|
||||
'agent_type' | 'service_id' | 'attachments'
|
||||
> {
|
||||
agent_type: string;
|
||||
service_id?: string | null;
|
||||
attachment_payload?: string | null;
|
||||
}
|
||||
|
||||
export interface CreateProducedWorkItemInput {
|
||||
@@ -44,6 +46,7 @@ export interface CreateProducedWorkItemInput {
|
||||
start_seq: number | null;
|
||||
end_seq: number | null;
|
||||
result_payload: string;
|
||||
attachments?: OutboundAttachment[];
|
||||
}
|
||||
|
||||
function normalizeStoredAgentType(
|
||||
@@ -96,9 +99,48 @@ function hydrateWorkItemRow(row: StoredWorkItemRow): WorkItem {
|
||||
...row,
|
||||
agent_type: agentType,
|
||||
service_id: readStoredWorkItemServiceId(row),
|
||||
attachments: parseAttachmentPayload(row.attachment_payload),
|
||||
};
|
||||
}
|
||||
|
||||
function parseAttachmentPayload(
|
||||
payload: string | null | undefined,
|
||||
): OutboundAttachment[] {
|
||||
if (!payload) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(payload) as unknown;
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed
|
||||
.filter(
|
||||
(item): item is OutboundAttachment =>
|
||||
item !== null &&
|
||||
typeof item === 'object' &&
|
||||
!Array.isArray(item) &&
|
||||
typeof (item as { path?: unknown }).path === 'string',
|
||||
)
|
||||
.map((item) => ({
|
||||
path: item.path,
|
||||
...(typeof item.name === 'string' ? { name: item.name } : {}),
|
||||
...(typeof item.mime === 'string' ? { mime: item.mime } : {}),
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function serializeAttachmentPayload(
|
||||
attachments: OutboundAttachment[] | undefined,
|
||||
): string | null {
|
||||
if (!attachments?.length) return null;
|
||||
return JSON.stringify(
|
||||
attachments.map((attachment) => ({
|
||||
path: attachment.path,
|
||||
...(attachment.name ? { name: attachment.name } : {}),
|
||||
...(attachment.mime ? { mime: attachment.mime } : {}),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
function resolvePreferredWorkItemRole(
|
||||
serviceId: string | null | undefined,
|
||||
): PairedRoomRole | null {
|
||||
@@ -220,10 +262,11 @@ export function createProducedWorkItemInDatabase(
|
||||
start_seq,
|
||||
end_seq,
|
||||
result_payload,
|
||||
attachment_payload,
|
||||
delivery_attempts,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, 'produced', ?, ?, ?, 0, ?, ?)`,
|
||||
) VALUES (?, ?, ?, ?, ?, 'produced', ?, ?, ?, ?, 0, ?, ?)`,
|
||||
)
|
||||
.run(
|
||||
input.group_folder,
|
||||
@@ -234,6 +277,7 @@ export function createProducedWorkItemInDatabase(
|
||||
input.start_seq,
|
||||
input.end_seq,
|
||||
input.result_payload,
|
||||
serializeAttachmentPayload(input.attachments),
|
||||
now,
|
||||
now,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user