@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/README.md +53 -3
- package/dist/index.d.ts +184 -6
- package/dist/index.js +336 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
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
|
|
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
|
-
|
|
1479
|
+
resolveOrigins,
|
|
1480
|
+
rollupSessions,
|
|
1481
|
+
sessionsNeedingOrigin,
|
|
1482
|
+
syncPrices
|
|
1157
1483
|
};
|
|
1158
1484
|
//# sourceMappingURL=index.js.map
|