"Activity" (placeholder name — jobs/tasks were taken) = watch background tasks
scroll in parallel with chat. Built DB-free; NOT restarted — deploy + test in
the morning.
- NDJSON progress contract (activity/progress.ts): capabilities append
{job,cap,phase,status,pct,detail,ts,...} lines; tolerant parser treats any
JSON object with phase/status as structured progress, else a raw log line.
- Backend (activity/router.ts, owner-only, path-guarded):
- GET /api/activity/tasks — registry by scanning /tmp/claude-*/<cwd>/tasks/
*.output (harness run_in_background) + announced detached jobs.
- POST /api/activity/announce {name,path} — register a detached (setsid) job's
log so it's followable too (the setsid case is on the critical path, since
the warm worker now makes plain run_in_background the default for heavy jobs).
- GET /api/activity/stream?task=<id>|path=<abs> — SSE tail (poll + offset),
emitting {kind:'line'|'progress'} with NDJSON parsed.
- Frontend /activity screen + Radio nav item: task list (active dot) → live tail
with a phase/pct progress header + raw log, following the /system-monitor pattern.
- Retention: startChatEventRetention() prunes chat_session_events >7d every 6h
(wired in bootstrap) so the durable queue stays bounded.
Verified headlessly (no restart): parseTailLine classification, and the scan
finds 53 real task output files. Endpoints + UI untested until deploy.
Deferred (see handoff): cross-device sync + OpenCode parity (both touch the
now-stable chat path — won't ship un-restart-tested); task:progress into chat.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
23 lines
964 B
TypeScript
23 lines
964 B
TypeScript
import { pruneChatEventsOlderThan } from 'officerdb';
|
|
|
|
// The durable chat event queue (chat_session_events) grows with every turn. Prune old rows so it
|
|
// stays bounded — a session's transcript (Claude's .jsonl) is the real record; the queue only needs
|
|
// enough history to cover reconnect replay, so a few days is generous.
|
|
const RETENTION_MS = 7 * 24 * 60 * 60 * 1000; // keep 7 days
|
|
const INTERVAL_MS = 6 * 60 * 60 * 1000; // prune every 6h
|
|
|
|
export function startChatEventRetention(): void {
|
|
const tick = async () => {
|
|
try {
|
|
await pruneChatEventsOlderThan(new Date(Date.now() - RETENTION_MS));
|
|
} catch (err) {
|
|
console.error('[chat-retention] prune failed:', err instanceof Error ? err.message : err);
|
|
}
|
|
};
|
|
void tick(); // sweep once at startup
|
|
setInterval(tick, INTERVAL_MS);
|
|
console.log(
|
|
`[chat-retention] pruning chat_session_events older than ${RETENTION_MS / 86_400_000}d, every ${INTERVAL_MS / 3_600_000}h`,
|
|
);
|
|
}
|