@titan-design/session-graph 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -129,6 +129,124 @@ function addColumnIfMissing(db, table, column, type) {
129
129
  if (!present) db.exec(`ALTER TABLE "${table}" ADD COLUMN "${column}" ${type}`);
130
130
  }
131
131
 
132
+ // src/audit-schema-v5.ts
133
+ var ORIGIN_MIGRATION_NAME = "origin, episodes, prices";
134
+ var ORIGIN_TABLES = ["session_origin", "session_external_event"];
135
+ var EPISODE_TABLE = "episode";
136
+ var ORIGIN_DDL = `
137
+ CREATE TABLE IF NOT EXISTS session_origin ( -- not derived from transcripts
138
+ session_id TEXT PRIMARY KEY,
139
+ origin_system TEXT NOT NULL, -- 'agent-chat'
140
+ agent_id TEXT, agent_name TEXT, parent_name TEXT, parent_session_id TEXT,
141
+ profile TEXT, model_alias TEXT, surface TEXT, isolation TEXT, depth INTEGER,
142
+ origin_kind TEXT, -- spawned | adopted | inherited | human
143
+ config_dir TEXT, spawn_cwd TEXT, spawned_at TEXT,
144
+ brief_chars INTEGER, brief_excerpt TEXT, brief_path TEXT, launch_args TEXT,
145
+ resolved_at TEXT NOT NULL
146
+ );
147
+ CREATE INDEX IF NOT EXISTS idx_origin_parent ON session_origin(parent_session_id);
148
+
149
+ CREATE TABLE IF NOT EXISTS session_external_event ( -- teleport, handoff, retire, exit
150
+ session_id TEXT NOT NULL, ts TEXT NOT NULL, origin_system TEXT NOT NULL,
151
+ kind TEXT NOT NULL, detail TEXT,
152
+ PRIMARY KEY (session_id, ts, kind)
153
+ ) WITHOUT ROWID;
154
+
155
+ CREATE TABLE IF NOT EXISTS episode ( -- provisional, see section 9
156
+ session_id TEXT NOT NULL, episode_index INTEGER NOT NULL,
157
+ heuristic TEXT NOT NULL, heuristic_version INTEGER NOT NULL,
158
+ started_at TEXT NOT NULL, ended_at TEXT NOT NULL,
159
+ start_offset INTEGER NOT NULL, end_offset INTEGER NOT NULL,
160
+ opened_by TEXT NOT NULL, -- brief | channel_followup | idle_gap | compaction
161
+ assignment_offset INTEGER, first_deliverable_offset INTEGER, first_deliverable_signal TEXT,
162
+ first_status_offset INTEGER,
163
+ PRIMARY KEY (session_id, heuristic, episode_index)
164
+ ) WITHOUT ROWID;
165
+
166
+ CREATE TABLE IF NOT EXISTS price (
167
+ model TEXT NOT NULL, effective_from TEXT NOT NULL, table_version INTEGER NOT NULL,
168
+ input_usd_mtok REAL NOT NULL, cache_read_usd_mtok REAL NOT NULL,
169
+ cache_write_5m_usd_mtok REAL NOT NULL, cache_write_1h_usd_mtok REAL NOT NULL,
170
+ output_usd_mtok REAL NOT NULL, source TEXT,
171
+ PRIMARY KEY (model, effective_from)
172
+ ) WITHOUT ROWID;
173
+ `;
174
+ var REQUEST_COLUMNS = `transcript_id, request_id, byte_offset, session_id, message_id, ts, model,
175
+ input_tokens, cache_read_tokens, cache_creation_tokens, cache_creation_5m, cache_creation_1h,
176
+ output_tokens, thinking_tokens, context_tokens, service_tier, is_sidechain,
177
+ seq_in_session, gap_ms, ctx_delta, wake_cause, wake_delivery, wake_detail,
178
+ wake_tool_family, wake_mcp_server, wake_offset`;
179
+ var REQUEST_DEDUP = `
180
+ CREATE VIEW IF NOT EXISTS request_dedup AS
181
+ SELECT ${REQUEST_COLUMNS} FROM (
182
+ SELECT r.*, ROW_NUMBER() OVER (PARTITION BY r.request_id ORDER BY r.ts, r.transcript_id) AS copy_rank
183
+ FROM request r
184
+ ) WHERE copy_rank = 1;
185
+ `;
186
+ var REQUEST_COST = `
187
+ CREATE VIEW IF NOT EXISTS request_cost AS
188
+ WITH candidate AS (
189
+ SELECT d.request_id, p.model AS price_model, p.effective_from AS price_effective_from,
190
+ p.input_usd_mtok, p.cache_read_usd_mtok, p.cache_write_5m_usd_mtok, p.cache_write_1h_usd_mtok, p.output_usd_mtok,
191
+ ROW_NUMBER() OVER (PARTITION BY d.request_id ORDER BY length(p.model) DESC, p.effective_from DESC) AS match_rank
192
+ FROM request_dedup d
193
+ JOIN price p ON substr(d.model, 1, length(p.model)) = p.model AND p.effective_from <= d.ts
194
+ ), component AS (
195
+ SELECT d.*, c.price_model, c.price_effective_from, c.price_model IS NOT NULL AS priced,
196
+ COALESCE(d.input_tokens * c.input_usd_mtok, 0) / 1e6 AS input_cost_usd,
197
+ COALESCE(d.cache_read_tokens * c.cache_read_usd_mtok, 0) / 1e6 AS cache_read_cost_usd,
198
+ COALESCE(CASE WHEN d.cache_creation_5m + d.cache_creation_1h = 0 THEN d.cache_creation_tokens ELSE d.cache_creation_5m END
199
+ * c.cache_write_5m_usd_mtok, 0) / 1e6 AS cache_write_5m_cost_usd,
200
+ COALESCE(d.cache_creation_1h * c.cache_write_1h_usd_mtok, 0) / 1e6 AS cache_write_1h_cost_usd,
201
+ COALESCE(d.output_tokens * c.output_usd_mtok, 0) / 1e6 AS output_cost_usd
202
+ FROM request_dedup d
203
+ LEFT JOIN candidate c ON c.request_id = d.request_id AND c.match_rank = 1
204
+ )
205
+ SELECT *,
206
+ input_cost_usd + cache_read_cost_usd + cache_write_5m_cost_usd + cache_write_1h_cost_usd + output_cost_usd AS cost_usd,
207
+ (cache_read_tokens < 0.2 * context_tokens AND cache_creation_tokens >= 20000) AS is_cold,
208
+ CASE WHEN context_tokens < 50000 THEN '<50k' WHEN context_tokens < 100000 THEN '50-100k'
209
+ WHEN context_tokens < 200000 THEN '100-200k' ELSE '200k+' END AS context_band,
210
+ CASE WHEN gap_ms IS NULL THEN NULL WHEN gap_ms < 300000 THEN '<5m'
211
+ WHEN gap_ms < 3600000 THEN '5-60m' ELSE '>60m' END AS gap_band
212
+ FROM component;
213
+ `;
214
+ var ASSISTANT_OUTPUT = "'assistant_text', 'assistant_thinking', 'assistant_tool_input'";
215
+ var CONTEXT_CONTRIBUTION = `
216
+ CREATE VIEW IF NOT EXISTS context_contribution AS
217
+ WITH stream AS (
218
+ SELECT transcript_id, byte_offset, 0 AS lane, block_index, NULL AS request_id FROM context_block
219
+ UNION ALL
220
+ SELECT transcript_id, byte_offset, -1 AS lane, 0, request_id FROM request
221
+ ), ordered AS (
222
+ SELECT *, COUNT(request_id) OVER (PARTITION BY transcript_id ORDER BY byte_offset, lane, block_index ROWS UNBOUNDED PRECEDING) AS requests_before
223
+ FROM stream
224
+ ), owner AS (
225
+ SELECT b.transcript_id, b.byte_offset, b.block_index, r.request_id
226
+ FROM ordered b JOIN ordered r ON r.transcript_id = b.transcript_id AND r.lane = -1 AND r.requests_before = b.requests_before + 1
227
+ WHERE b.lane = 0
228
+ ), tool AS (
229
+ SELECT tool_use_id, name, family, mcp_server,
230
+ ROW_NUMBER() OVER (PARTITION BY tool_use_id ORDER BY transcript_id, byte_offset, block_index) AS copy_rank
231
+ FROM tool_call
232
+ )
233
+ SELECT cb.transcript_id, cb.byte_offset, cb.block_index, cb.session_id, cb.ts, cb.source,
234
+ cb.tool_use_id, cb.attachment_type, cb.chars, cb.is_media, o.request_id, d.ctx_delta,
235
+ CASE WHEN cb.source IN (${ASSISTANT_OUTPUT}) THEN NULL
236
+ ELSE d.ctx_delta * cb.chars * 1.0 / NULLIF(SUM(CASE WHEN cb.source IN (${ASSISTANT_OUTPUT}) THEN 0 ELSE cb.chars END)
237
+ OVER (PARTITION BY o.transcript_id, o.request_id), 0) END AS est_tokens,
238
+ t.name AS tool_name, t.family AS tool_family, t.mcp_server
239
+ FROM context_block cb
240
+ JOIN owner o USING (transcript_id, byte_offset, block_index)
241
+ JOIN request_dedup d ON d.transcript_id = o.transcript_id AND d.request_id = o.request_id
242
+ LEFT JOIN tool t ON t.tool_use_id = cb.tool_use_id AND t.copy_rank = 1;
243
+ `;
244
+ var ORIGIN_VIEWS = [REQUEST_DEDUP, REQUEST_COST, CONTEXT_CONTRIBUTION];
245
+ function applyOriginSchema(db) {
246
+ db.exec(ORIGIN_DDL);
247
+ for (const view of ORIGIN_VIEWS) db.exec(view);
248
+ }
249
+
132
250
  // src/normalized-schema.ts
133
251
  import { conversationRef } from "@titan-design/agent-protocol";
134
252
  var NORMALIZED_DDL = `
@@ -318,6 +436,8 @@ var DERIVED_TABLES = [
318
436
  "subagent",
319
437
  ...AUDIT_TABLES,
320
438
  FACET_TABLE,
439
+ ...ORIGIN_TABLES,
440
+ EPISODE_TABLE,
321
441
  "session_model_usage",
322
442
  "session",
323
443
  "fact",
@@ -336,7 +456,8 @@ var MIGRATIONS = [
336
456
  db.exec(NORMALIZED_DDL);
337
457
  backfillClaudeAliases(db);
338
458
  } },
339
- { version: 4, name: AUDIT_MIGRATION_NAME, up: applyAuditSchema }
459
+ { version: 4, name: AUDIT_MIGRATION_NAME, up: applyAuditSchema },
460
+ { version: 5, name: ORIGIN_MIGRATION_NAME, up: applyOriginSchema }
340
461
  ];
341
462
 
342
463
  // src/graph.ts
@@ -617,7 +738,7 @@ function factIdFor(db, transcriptId, byteOffset) {
617
738
 
618
739
  // src/purge.ts
619
740
  import { sessionRef as sessionRef2 } from "@titan-design/session-read";
620
- var SESSION_SCOPED = ["turn", "permission_phase", "human_edit", "file_checkpoint", "subagent", "session_model_usage"];
741
+ var SESSION_SCOPED = ["turn", "permission_phase", "human_edit", "file_checkpoint", "subagent", "session_model_usage", EPISODE_TABLE];
621
742
  function purgeTranscript(graph, transcriptId) {
622
743
  graph.db.transaction(() => {
623
744
  const owned = graph.db.prepare("SELECT session_id FROM session WHERE transcript_id = ?").all(transcriptId);
@@ -791,15 +912,153 @@ import { promises as fs } from "fs";
791
912
  import { contentHash, resumePoint } from "@titan-design/locator";
792
913
  import { TranscriptParseError as TranscriptParseError2, extractTranscript as extractTranscript2 } from "@titan-design/session-read";
793
914
 
915
+ // src/outcomes.ts
916
+ var NO_PR_OUTCOMES = Object.freeze({ requested: 0, applied: 0, failed: false });
917
+ var PRS_NEEDING_OUTCOME = `
918
+ SELECT pr_ref, repo, number FROM pr
919
+ WHERE repo IS NOT NULL AND number IS NOT NULL AND (outcome_checked_at IS NULL OR state IS NOT 'merged')
920
+ ORDER BY pr_ref`;
921
+ var APPLY_OUTCOME = `
922
+ UPDATE pr SET
923
+ state = CASE WHEN state = 'merged' THEN state ELSE COALESCE(lower(@state), state) END,
924
+ merged_at = COALESCE(@mergedAt, merged_at),
925
+ closed_at = COALESCE(@closedAt, closed_at),
926
+ review_rounds = COALESCE(@reviewRounds, review_rounds),
927
+ outcome_checked_at = @checkedAt
928
+ WHERE pr_ref = @prRef`;
929
+ function prsNeedingOutcome(graph) {
930
+ const rows = graph.db.prepare(PRS_NEEDING_OUTCOME).all();
931
+ return rows.map((r) => ({ prRef: r.pr_ref, repo: r.repo, number: r.number }));
932
+ }
933
+ async function enrichPrs(graph, resolver) {
934
+ if (!resolver) return NO_PR_OUTCOMES;
935
+ const prs = prsNeedingOutcome(graph);
936
+ if (prs.length === 0) return NO_PR_OUTCOMES;
937
+ try {
938
+ const resolved = await resolver(prs);
939
+ return { requested: prs.length, applied: write(graph, resolved, (/* @__PURE__ */ new Date()).toISOString()), failed: false };
940
+ } catch (err) {
941
+ return { requested: prs.length, applied: 0, failed: true, error: err instanceof Error ? err.message : String(err) };
942
+ }
943
+ }
944
+ function write(graph, resolved, checkedAt) {
945
+ const apply = graph.db.prepare(APPLY_OUTCOME);
946
+ return graph.db.transaction(() => {
947
+ let applied = 0;
948
+ for (const [prRef, fields] of resolved) {
949
+ if (!fields) continue;
950
+ applied += apply.run({
951
+ prRef,
952
+ checkedAt,
953
+ state: fields.state ?? null,
954
+ mergedAt: fields.mergedAt ?? null,
955
+ closedAt: fields.closedAt ?? null,
956
+ reviewRounds: fields.reviewRounds ?? null
957
+ }).changes;
958
+ }
959
+ return applied;
960
+ })();
961
+ }
962
+
963
+ // src/origin.ts
964
+ import { RELATIONS, sessionRef as sessionRef3 } from "@titan-design/session-read";
965
+ var NO_ORIGINS = Object.freeze({ requested: 0, applied: 0, events: 0, failed: false });
966
+ var SESSIONS_NEEDING_ORIGIN = `
967
+ SELECT s.session_id FROM session s LEFT JOIN session_origin o ON o.session_id = s.session_id
968
+ WHERE o.session_id IS NULL OR o.resolved_at < s.ended_at
969
+ ORDER BY s.session_id`;
970
+ var UPSERT_ORIGIN = `
971
+ INSERT OR REPLACE INTO session_origin (session_id, origin_system, agent_id, agent_name, parent_name, parent_session_id,
972
+ profile, model_alias, surface, isolation, depth, origin_kind, config_dir, spawn_cwd, spawned_at,
973
+ brief_chars, brief_excerpt, brief_path, launch_args, resolved_at)
974
+ VALUES (@sessionId, @originSystem, @agentId, @agentName, @parentName, @parentSessionId,
975
+ @profile, @modelAlias, @surface, @isolation, @depth, @originKind, @configDir, @spawnCwd, @spawnedAt,
976
+ @briefChars, @briefExcerpt, @briefPath, @launchArgs, @resolvedAt)`;
977
+ var UPSERT_EVENT = `
978
+ INSERT INTO session_external_event (session_id, ts, origin_system, kind, detail)
979
+ VALUES (@sessionId, @ts, COALESCE(@originSystem, (SELECT origin_system FROM session_origin WHERE session_id = @sessionId), 'unknown'), @kind, @detail)
980
+ ON CONFLICT (session_id, ts, kind) DO UPDATE SET origin_system = excluded.origin_system, detail = excluded.detail`;
981
+ var PROJECT_SUBAGENTS = `
982
+ INSERT INTO subagent (agent_ref, session_id, child_session_id, agent_type, label, started_at)
983
+ SELECT 'agent:' || origin_system || ':' || agent_id, parent_session_id, session_id, profile, agent_name, spawned_at
984
+ FROM session_origin WHERE parent_session_id IS NOT NULL AND agent_id IS NOT NULL
985
+ ON CONFLICT (agent_ref) DO UPDATE SET session_id = excluded.session_id, child_session_id = excluded.child_session_id,
986
+ agent_type = excluded.agent_type, label = excluded.label, started_at = COALESCE(excluded.started_at, started_at)`;
987
+ function sessionsNeedingOrigin(graph) {
988
+ return graph.db.prepare(SESSIONS_NEEDING_ORIGIN).all().map((r) => r.session_id);
989
+ }
990
+ async function resolveOrigins(graph, resolver) {
991
+ if (!resolver) return NO_ORIGINS;
992
+ const sessionIds = sessionsNeedingOrigin(graph);
993
+ try {
994
+ const resolution = await resolver(sessionIds);
995
+ const written = writeResolution(graph, resolution, (/* @__PURE__ */ new Date()).toISOString());
996
+ return { requested: sessionIds.length, ...written, failed: false };
997
+ } catch (err) {
998
+ return { requested: sessionIds.length, applied: 0, events: 0, failed: true, error: err instanceof Error ? err.message : String(err) };
999
+ } finally {
1000
+ projectOrigins(graph);
1001
+ }
1002
+ }
1003
+ function writeResolution(graph, resolution, resolvedAt) {
1004
+ const upsertOrigin = graph.db.prepare(UPSERT_ORIGIN);
1005
+ const upsertEvent = graph.db.prepare(UPSERT_EVENT);
1006
+ return graph.db.transaction(() => {
1007
+ let applied = 0;
1008
+ for (const [sessionId, origin] of Object.entries(resolution.origins)) {
1009
+ applied += upsertOrigin.run(originRow(sessionId, origin, resolvedAt)).changes;
1010
+ }
1011
+ let events = 0;
1012
+ for (const e of resolution.externalEvents ?? []) {
1013
+ events += upsertEvent.run({ sessionId: e.sessionId, ts: e.ts, kind: e.kind, detail: e.detail ?? null, originSystem: e.originSystem ?? null }).changes;
1014
+ }
1015
+ return { applied, events };
1016
+ })();
1017
+ }
1018
+ function originRow(sessionId, o, resolvedAt) {
1019
+ return {
1020
+ sessionId,
1021
+ originSystem: o.originSystem,
1022
+ resolvedAt,
1023
+ agentId: o.agentId ?? null,
1024
+ agentName: o.agentName ?? null,
1025
+ parentName: o.parentName ?? null,
1026
+ parentSessionId: o.parentSessionId ?? null,
1027
+ profile: o.profile ?? null,
1028
+ modelAlias: o.modelAlias ?? null,
1029
+ surface: o.surface ?? null,
1030
+ isolation: o.isolation ?? null,
1031
+ depth: o.depth ?? null,
1032
+ originKind: o.originKind ?? null,
1033
+ configDir: o.configDir ?? null,
1034
+ spawnCwd: o.spawnCwd ?? null,
1035
+ spawnedAt: o.spawnedAt ?? null,
1036
+ briefChars: o.briefChars ?? null,
1037
+ briefExcerpt: o.briefExcerpt ?? null,
1038
+ briefPath: o.briefPath ?? null,
1039
+ launchArgs: o.launchArgs ?? null
1040
+ };
1041
+ }
1042
+ function projectOrigins(graph) {
1043
+ const spawned = graph.db.prepare("SELECT session_id, parent_session_id, spawned_at, resolved_at FROM session_origin WHERE parent_session_id IS NOT NULL").all();
1044
+ graph.db.transaction(() => {
1045
+ graph.db.prepare(PROJECT_SUBAGENTS).run();
1046
+ for (const o of spawned) {
1047
+ graph.edges.assert({ sourceRef: sessionRef3(o.parent_session_id), relation: RELATIONS.SPAWNED, targetRef: sessionRef3(o.session_id), tValid: o.spawned_at ?? o.resolved_at });
1048
+ }
1049
+ })();
1050
+ }
1051
+
794
1052
  // src/tasks.ts
795
1053
  import { taskRef } from "@titan-design/session-read";
796
1054
  var NO_ENRICHMENT = Object.freeze({ requested: 0, applied: 0, failed: false });
797
1055
  var UPSERT_TASK = `
798
- INSERT INTO task (task_ref, task_id, initiative, title, status) VALUES (@taskRef, @taskId, @initiative, @title, @status)
1056
+ INSERT INTO task (task_ref, task_id, initiative, title, status, estimate) VALUES (@taskRef, @taskId, @initiative, @title, @status, @estimate)
799
1057
  ON CONFLICT (task_ref) DO UPDATE SET
800
1058
  initiative = COALESCE(excluded.initiative, initiative),
801
1059
  title = COALESCE(excluded.title, title),
802
- status = COALESCE(excluded.status, status)`;
1060
+ status = COALESCE(excluded.status, status),
1061
+ estimate = COALESCE(excluded.estimate, estimate)`;
803
1062
  function allTaskIds(graph) {
804
1063
  return graph.db.prepare("SELECT task_id FROM task").all().map((r) => r.task_id);
805
1064
  }
@@ -808,12 +1067,12 @@ async function enrichTasks(graph, resolver, taskIds) {
808
1067
  if (!resolver) return NO_ENRICHMENT;
809
1068
  try {
810
1069
  const resolved = await resolver(unique);
811
- return { requested: unique.length, applied: write(graph, resolved), failed: false };
1070
+ return { requested: unique.length, applied: write2(graph, resolved), failed: false };
812
1071
  } catch (err) {
813
1072
  return { requested: unique.length, applied: 0, failed: true, error: err instanceof Error ? err.message : String(err) };
814
1073
  }
815
1074
  }
816
- function write(graph, resolved) {
1075
+ function write2(graph, resolved) {
817
1076
  const upsert = graph.db.prepare(UPSERT_TASK);
818
1077
  return graph.db.transaction(() => {
819
1078
  let applied = 0;
@@ -824,7 +1083,8 @@ function write(graph, resolved) {
824
1083
  taskId,
825
1084
  initiative: fields.initiative ?? null,
826
1085
  title: fields.title ?? null,
827
- status: fields.status ?? null
1086
+ status: fields.status ?? null,
1087
+ estimate: fields.estimate ?? null
828
1088
  }).changes;
829
1089
  }
830
1090
  return applied;
@@ -874,7 +1134,7 @@ async function indexTranscript(graph, transcript, options = {}) {
874
1134
  }
875
1135
  }
876
1136
  async function refreshCorpus(graph, transcripts, options = {}) {
877
- const { resolveTasks, full, facetLimit, ...perTranscript } = options;
1137
+ const { resolveTasks, resolveOrigins: originResolver, resolvePrs, full, facetLimit, ...perTranscript } = options;
878
1138
  const counts = { indexed: 0, unchanged: 0, rewound: 0, missing: 0, quarantined: 0 };
879
1139
  const touched = [];
880
1140
  let facts = 0;
@@ -887,11 +1147,13 @@ async function refreshCorpus(graph, transcripts, options = {}) {
887
1147
  const facets = await backfillFacets(graph, transcripts, { limit: facetLimit });
888
1148
  touched.push(...facets.sessionIds);
889
1149
  const turnsRolledUp = rollupSessions(graph, full ? allSessionIds(graph) : touched);
1150
+ const origins = await resolveOrigins(graph, originResolver);
890
1151
  const reconciled = reconcile(graph);
1152
+ const prs = await enrichPrs(graph, resolvePrs);
891
1153
  const tasks = await enrichTasks(graph, resolveTasks, allTaskIds(graph));
892
1154
  const markedMissing = await markMissing(graph, transcripts);
893
1155
  const facetSummary = { facetsBackfilled: facets.backfilled, facetBacklog: facets.backlog };
894
- return { transcripts: transcripts.length, ...counts, facts, turnsRolledUp, reconciled, tasks, markedMissing, ...facetSummary };
1156
+ return { transcripts: transcripts.length, ...counts, facts, turnsRolledUp, reconciled, tasks, origins, prs, markedMissing, ...facetSummary };
895
1157
  }
896
1158
  async function markMissing(graph, discovered) {
897
1159
  const present = new Set(discovered.map((t) => t.displayPath));
@@ -916,6 +1178,57 @@ async function exists(absolutePath) {
916
1178
  }
917
1179
  }
918
1180
 
1181
+ // src/episodes.ts
1182
+ var DELETE_EPISODES = `DELETE FROM ${EPISODE_TABLE} WHERE session_id = ? AND heuristic = ?`;
1183
+ var INSERT_EPISODE = `
1184
+ INSERT INTO ${EPISODE_TABLE} (session_id, episode_index, heuristic, heuristic_version, started_at, ended_at,
1185
+ start_offset, end_offset, opened_by, assignment_offset, first_deliverable_offset, first_deliverable_signal, first_status_offset)
1186
+ VALUES (@sessionId, @episodeIndex, @heuristic, @heuristicVersion, @startedAt, @endedAt,
1187
+ @startOffset, @endOffset, @openedBy, @assignmentOffset, @firstDeliverableOffset, @firstDeliverableSignal, @firstStatusOffset)`;
1188
+ function replaceEpisodes(graph, sessionId, heuristic, rows) {
1189
+ const remove = graph.db.prepare(DELETE_EPISODES);
1190
+ const insert = graph.db.prepare(INSERT_EPISODE);
1191
+ return graph.db.transaction(() => {
1192
+ remove.run(sessionId, heuristic);
1193
+ for (const row of rows) insert.run(episodeParams(sessionId, heuristic, row));
1194
+ return rows.length;
1195
+ })();
1196
+ }
1197
+ function episodeParams(sessionId, heuristic, row) {
1198
+ return {
1199
+ sessionId,
1200
+ heuristic,
1201
+ episodeIndex: row.episodeIndex,
1202
+ heuristicVersion: row.heuristicVersion,
1203
+ startedAt: row.startedAt,
1204
+ endedAt: row.endedAt,
1205
+ startOffset: row.startOffset,
1206
+ endOffset: row.endOffset,
1207
+ openedBy: row.openedBy,
1208
+ assignmentOffset: row.assignmentOffset ?? null,
1209
+ firstDeliverableOffset: row.firstDeliverableOffset ?? null,
1210
+ firstDeliverableSignal: row.firstDeliverableSignal ?? null,
1211
+ firstStatusOffset: row.firstStatusOffset ?? null
1212
+ };
1213
+ }
1214
+
1215
+ // src/prices.ts
1216
+ function syncPrices(graph, rows, options) {
1217
+ const insert = graph.db.prepare(
1218
+ `INSERT INTO price (model, effective_from, table_version, input_usd_mtok, cache_read_usd_mtok,
1219
+ cache_write_5m_usd_mtok, cache_write_1h_usd_mtok, output_usd_mtok, source)
1220
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
1221
+ );
1222
+ const source = options.source ?? null;
1223
+ graph.db.transaction(() => {
1224
+ graph.db.exec("DELETE FROM price");
1225
+ for (const r of rows) {
1226
+ insert.run(r.modelPrefix, r.effectiveFrom, options.tableVersion, r.input, r.cacheRead, r.cacheWrite5m, r.cacheWrite1h, r.output, source);
1227
+ }
1228
+ })();
1229
+ return rows.length;
1230
+ }
1231
+
919
1232
  // src/normalized-index.ts
920
1233
  import { randomUUID } from "crypto";
921
1234
  import { stat } from "fs/promises";
@@ -1132,27 +1445,40 @@ export {
1132
1445
  DEFAULT_FACET_LIMIT,
1133
1446
  DERIVED_TABLES,
1134
1447
  DOMAIN_DDL,
1448
+ EPISODE_TABLE,
1135
1449
  FACET_TABLE,
1136
1450
  KIT,
1137
1451
  MIGRATIONS,
1138
1452
  NO_ENRICHMENT,
1453
+ NO_ORIGINS,
1454
+ NO_PR_OUTCOMES,
1455
+ ORIGIN_DDL,
1456
+ ORIGIN_MIGRATION_NAME,
1457
+ ORIGIN_TABLES,
1458
+ ORIGIN_VIEWS,
1139
1459
  allSessionIds,
1140
1460
  allTaskIds,
1141
1461
  applyAudit,
1142
1462
  applyDelta,
1143
1463
  backfillFacets,
1464
+ enrichPrs,
1144
1465
  enrichTasks,
1145
1466
  indexCodexSource,
1146
1467
  indexTranscript,
1147
1468
  normalizedSessions,
1148
1469
  normalizedUsage,
1149
1470
  openSessionGraph,
1471
+ prsNeedingOutcome,
1150
1472
  purgeTranscript,
1151
1473
  readIndexedText,
1152
1474
  reconcile,
1153
1475
  refreshCorpus,
1476
+ replaceEpisodes,
1154
1477
  resetIndex,
1155
1478
  resolveConversationAlias,
1156
- rollupSessions
1479
+ resolveOrigins,
1480
+ rollupSessions,
1481
+ sessionsNeedingOrigin,
1482
+ syncPrices
1157
1483
  };
1158
1484
  //# sourceMappingURL=index.js.map