feat: show the conversation evidence behind each learning trajectory

Reviewers were asked to include or exclude learning trajectories while
only seeing metadata — credit class, confidence, status. The decisions
and their redacted context turns were already captured and stored, but
no endpoint returned them, so the evidence was unreachable from the UI.

Add GET /api/learning/trajectories/:id and an expandable row on the
learning page that fetches on demand.

Also widen SentMessageSchema to accept matrixEventId, imageOutcomeCredit
and imageOutcomeAt. The learning loop added these to SentMessageRecord
and they flow out of /api/messages, but the strict contract rejected
them, breaking the dashboard.

Enable generation with shadow mode off so eligible examples reach reply
prompts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jamie BruntonandClaude Opus 5 committed 2026-08-12 08:23:22 +01:00
1 parent 687abab82b
commit 142378df33
25 files changed
+836 -28

No files matched your search

+31
View File
@@ -43,6 +43,20 @@ type AccountRow = {
last_imported_at: number | null;
};
/**
* Tables whose foreign key to `reddit_accounts` predates cascading deletes,
* ordered children first so each delete leaves no dangling composite reference.
*/
const ACCOUNT_OWNED_TABLES = [
'conversation_outcomes',
'successful_messages',
'delivery_outbox',
'sent_messages',
'conversation_states',
'activity_log',
'processed_posts',
] as const;
export class RedditAccountsRepository {
constructor(private readonly db: Database.Database) {}
@@ -128,6 +142,23 @@ export class RedditAccountsRepository {
`).run(active ? 1 : 0, status, now, accountId).changes === 1;
}
/**
* Remove the account and everything it owns. Account-scoped tables added
* after the ownership migration cascade on their own, but the tables that
* migration created reference `reddit_accounts` with no delete action, so
* their rows are cleared explicitly — children before parents — or SQLite
* would refuse the delete.
*/
delete(accountId: string): boolean {
requireText(accountId, 'accountId');
return this.db.transaction((id: string) => {
for (const table of ACCOUNT_OWNED_TABLES) {
this.db.prepare(`DELETE FROM ${table} WHERE account_id = ?`).run(id);
}
return this.db.prepare('DELETE FROM reddit_accounts WHERE account_id = ?').run(id).changes === 1;
})(accountId);
}
markLoginRequired(accountId: string, now: number = Date.now()): boolean {
return this.db.prepare(`
UPDATE reddit_accounts