Menu
popagent
publicLatest change 6edc7e928c57dab23c0973ed3f7ee70b3ebb72d5 - Initial popagent baseline by AkurAI Build
import type { HookRunRecord } from "./hooks";
import { storage } from "./storage";
import type { HookAuditRun } from "./api-types";
import { retryableInit } from "./retryable-init";
const MAX_HOOK_RUNS_PER_SESSION = 500;
const HOOK_AUDIT_RETENTION_DAYS = 30;
export class HookAuditStore {
readonly storage = storage;
private readonly initializeOnce = retryableInit(() => this.initialize());
constructor(
private readonly maxRunsPerSession = MAX_HOOK_RUNS_PER_SESSION,
private readonly retentionDays = HOOK_AUDIT_RETENTION_DAYS,
) {}
init(): Promise<void> {
return this.initializeOnce();
}
async record(run: HookRunRecord): Promise<void> {
await this.init();
await this.storage.db.tx(async (db) => {
await db.none(`
INSERT INTO popagent_hook_runs
(id, event_id, session_id, turn_id, tool_call_id, event_name, handler_id,
started_at, completed_at, duration_ms, status, reason, error)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
`, [
run.id, run.eventId, run.sessionId, run.turnId ?? null, run.toolCallId ?? null,
run.eventName, run.handlerId, run.startedAt, run.completedAt, run.durationMs,
run.status, run.reason ?? null, run.error ?? null,
]);
});
await this.cleanupAll(run.sessionId);
}
async cleanupAll(sessionId?: string): Promise<void> {
await this.init();
await this.storage.db.tx(async (db) => {
await db.none(
"DELETE FROM popagent_hook_runs WHERE completed_at < NOW() - ($1 * INTERVAL '1 day')",
[this.retentionDays],
);
if (sessionId) await db.none(`
DELETE FROM popagent_hook_runs WHERE id IN (
SELECT id FROM popagent_hook_runs WHERE session_id = $1
ORDER BY started_at DESC, id DESC OFFSET $2
)
`, [sessionId, this.maxRunsPerSession]);
else await db.none(`
DELETE FROM popagent_hook_runs WHERE id IN (
SELECT id FROM (
SELECT id, ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY started_at DESC, id DESC) AS rn
FROM popagent_hook_runs
) ranked WHERE rn > $1
)
`, [this.maxRunsPerSession]);
});
}
async listForSession(sessionId: string): Promise<HookAuditRun[]> {
await this.init();
return this.storage.db.any<HookAuditRun>(`
WITH recent AS (
SELECT id, event_id, session_id, turn_id, tool_call_id, event_name, handler_id,
started_at, completed_at, duration_ms, status, reason, error
FROM popagent_hook_runs
WHERE session_id = $1
ORDER BY started_at DESC, id DESC
LIMIT $2
)
SELECT id, event_id AS "eventId", session_id AS "sessionId",
turn_id AS "turnId", tool_call_id AS "toolCallId",
event_name AS "eventName", handler_id AS "handlerId",
started_at::text AS "startedAt", completed_at::text AS "completedAt",
duration_ms AS "durationMs", status, reason, error
FROM recent
ORDER BY started_at ASC, id ASC
`, [sessionId, this.maxRunsPerSession]);
}
async deleteForSession(sessionId: string): Promise<void> {
await this.init();
await this.storage.db.none("DELETE FROM popagent_hook_runs WHERE session_id = $1", [sessionId]);
}
private async initialize(): Promise<void> {
await this.storage.init();
await this.storage.db.none(`
CREATE TABLE IF NOT EXISTS popagent_hook_runs (
id TEXT PRIMARY KEY,
event_id TEXT NOT NULL,
session_id TEXT NOT NULL,
turn_id TEXT,
tool_call_id TEXT,
event_name TEXT NOT NULL,
handler_id TEXT NOT NULL,
started_at TIMESTAMPTZ NOT NULL,
completed_at TIMESTAMPTZ NOT NULL,
duration_ms DOUBLE PRECISION NOT NULL,
status TEXT NOT NULL,
reason TEXT,
error TEXT
);
CREATE INDEX IF NOT EXISTS popagent_hook_runs_session_idx
ON popagent_hook_runs (session_id, started_at DESC);
CREATE INDEX IF NOT EXISTS popagent_hook_runs_completed_idx
ON popagent_hook_runs (completed_at);
`);
}
}
export const hookAudit = new HookAuditStore();