feat: add enriched transcript sync — Haiku summaries, session file enrichment

Add session intelligence pipeline for team transcript sync:
- lib/transcript-sync.ts: parse history.jsonl, enrich with Claude session
  file data (tools_used, full turn count), sync marker management,
  10-concurrent push with 5-concurrent Haiku summarization
- lib/llm-summarize.ts: raw fetch() to Anthropic Messages API (no SDK dep),
  retry-after on 429, exponential backoff on 5xx, SHA-based eval-cache
- lib/sync.ts: pushTranscript() and pullTranscripts() following existing patterns
- 006_transcript_sync.sql: unique index on (team_id, session_id) for
  idempotent upsert, RLS changed from admin-only to team-wide read

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-16 00:15:19 -05:00
parent 87cb769c35
commit 0e29d7d1a3
4 changed files with 552 additions and 0 deletions
+17
View File
@@ -213,6 +213,11 @@ export function pushHeartbeat(): Promise<boolean> {
return pushWithSync('sync_heartbeats', { hostname: os.hostname() }, { addRepoSlug: false });
}
/** Push a session transcript to Supabase. repo_slug is in the data (from getRemoteSlugForPath). */
export function pushTranscript(data: Record<string, unknown>): Promise<boolean> {
return pushWithSync('session_transcripts', data, { addRepoSlug: false });
}
// --- Pull operations ---
/**
@@ -277,6 +282,18 @@ export async function pullRetros(opts?: { repoSlug?: string; limit?: number }):
return pullTable('retro_snapshots', parts.join('&'));
}
/** Pull team session transcripts. */
export async function pullTranscripts(opts?: { repoSlug?: string; limit?: number }): Promise<Record<string, unknown>[]> {
const config = resolveSyncConfig();
if (!config) return [];
const parts = [`team_id=eq.${config.auth.team_id}`, 'order=started_at.desc'];
if (opts?.repoSlug) parts.push(`repo_slug=eq.${opts.repoSlug}`);
parts.push(`limit=${opts?.limit || 50}`);
return pullTable('session_transcripts', parts.join('&'));
}
// --- Offline queue ---
function enqueue(entry: QueueEntry): void {