devrage 0.5.6 → 0.5.8

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/cli.js CHANGED
@@ -670,6 +670,7 @@ async function* parseCodexUsageJsonl(filePath, context) {
670
670
  let previousUsageSignature = null;
671
671
  let session = context.session;
672
672
  let sawSessionMeta = false;
673
+ let forkReplayStartedAt = null;
673
674
  for await (const line of rl) {
674
675
  if (!line.trim()) {
675
676
  continue;
@@ -682,6 +683,15 @@ async function* parseCodexUsageJsonl(filePath, context) {
682
683
  if (metaSession && !sawSessionMeta) {
683
684
  session = metaSession;
684
685
  sawSessionMeta = true;
686
+ if (payload?.["thread_source"] === "subagent") {
687
+ forkReplayStartedAt = uuidV7Timestamp(metaSession) ?? timestampMilliseconds(entry["timestamp"]) ?? timestampMilliseconds(payload["timestamp"]);
688
+ }
689
+ }
690
+ continue;
691
+ }
692
+ if (forkReplayStartedAt !== null) {
693
+ if (isLiveForkTaskStart(entry, payload, forkReplayStartedAt)) {
694
+ forkReplayStartedAt = null;
685
695
  }
686
696
  continue;
687
697
  }
@@ -813,6 +823,35 @@ function numberValue2(value) {
813
823
  function stringValue2(value) {
814
824
  return typeof value === "string" && value.trim() ? value : void 0;
815
825
  }
826
+ function isLiveForkTaskStart(entry, payload, forkStartedAt) {
827
+ if (entry["type"] !== "event_msg" || payload?.["type"] !== "task_started") {
828
+ return false;
829
+ }
830
+ const taskIdStartedAt = uuidV7Timestamp(stringValue2(payload["turn_id"]));
831
+ if (taskIdStartedAt !== null) {
832
+ return taskIdStartedAt >= forkStartedAt;
833
+ }
834
+ const taskStartedAt = timestampMilliseconds(payload["started_at"]);
835
+ return taskStartedAt !== null && taskStartedAt >= Math.floor(forkStartedAt / 1e3) * 1e3;
836
+ }
837
+ function uuidV7Timestamp(value) {
838
+ const normalized = value?.replaceAll("-", "");
839
+ if (!normalized || !/^[0-9a-f]{12}7/i.test(normalized)) {
840
+ return null;
841
+ }
842
+ const timestamp = Number.parseInt(normalized.slice(0, 12), 16);
843
+ return Number.isSafeInteger(timestamp) ? timestamp : null;
844
+ }
845
+ function timestampMilliseconds(value) {
846
+ if (typeof value === "number" && Number.isFinite(value)) {
847
+ return value >= 1e12 ? value : value * 1e3;
848
+ }
849
+ if (typeof value === "string") {
850
+ const timestamp = Date.parse(value);
851
+ return Number.isFinite(timestamp) ? timestamp : null;
852
+ }
853
+ return null;
854
+ }
816
855
  function asRecord3(value) {
817
856
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
818
857
  return null;
@@ -825,6 +864,97 @@ import { existsSync as existsSync2 } from "node:fs";
825
864
  import { readdir as readdir5 } from "node:fs/promises";
826
865
  import { homedir as homedir5 } from "node:os";
827
866
  import { join as join5 } from "node:path";
867
+
868
+ // src/adapters/sqlite.ts
869
+ async function openReadonlySqliteDatabase(dbPath) {
870
+ const requestedDriver = process.env["DEVRAGE_SQLITE_DRIVER"];
871
+ if (requestedDriver) {
872
+ const loader = driverLoader(requestedDriver);
873
+ if (!loader) {
874
+ return null;
875
+ }
876
+ try {
877
+ return await loader(dbPath);
878
+ } catch {
879
+ return null;
880
+ }
881
+ }
882
+ const loaders = isBunRuntime() ? [openWithBunSqlite, openWithNodeSqlite, openWithBetterSqlite3] : [openWithNodeSqlite, openWithBetterSqlite3];
883
+ for (const loader of loaders) {
884
+ try {
885
+ return await loader(dbPath);
886
+ } catch {
887
+ continue;
888
+ }
889
+ }
890
+ return null;
891
+ }
892
+ function driverLoader(driver) {
893
+ switch (driver) {
894
+ case "bun":
895
+ case "bun:sqlite":
896
+ return openWithBunSqlite;
897
+ case "node":
898
+ case "node:sqlite":
899
+ return openWithNodeSqlite;
900
+ case "better-sqlite3":
901
+ return openWithBetterSqlite3;
902
+ default:
903
+ return null;
904
+ }
905
+ }
906
+ function isBunRuntime() {
907
+ return Boolean(process.versions["bun"]);
908
+ }
909
+ async function openWithBunSqlite(dbPath) {
910
+ const specifier = "bun:sqlite";
911
+ const sqlite = await import(specifier);
912
+ const db = new sqlite.Database(dbPath, { readonly: true, create: false });
913
+ return {
914
+ prepare(sql) {
915
+ const prepare = db.prepare ?? db.query;
916
+ return prepare.call(db, sql);
917
+ },
918
+ close() {
919
+ db.close();
920
+ }
921
+ };
922
+ }
923
+ async function openWithNodeSqlite(dbPath) {
924
+ const sqlite = await importNodeSqlite();
925
+ return new sqlite.DatabaseSync(dbPath, { open: true, readOnly: true, timeout: 5e3 });
926
+ }
927
+ async function openWithBetterSqlite3(dbPath) {
928
+ const BetterSqlite3 = await import("better-sqlite3");
929
+ const Ctor = BetterSqlite3.default ?? BetterSqlite3;
930
+ return new Ctor(dbPath, {
931
+ readonly: true,
932
+ fileMustExist: true
933
+ });
934
+ }
935
+ async function importNodeSqlite() {
936
+ const originalEmitWarning = process.emitWarning;
937
+ process.emitWarning = ((warning, ...args) => {
938
+ const message = typeof warning === "string" ? warning : warning.message;
939
+ const type = typeof args[0] === "string" ? args[0] : void 0;
940
+ if (message === "SQLite is an experimental feature and might change at any time" && type === "ExperimentalWarning") {
941
+ return;
942
+ }
943
+ originalEmitWarning(warning, ...args);
944
+ });
945
+ try {
946
+ const specifier = "node:sqlite";
947
+ const sqlite = await import(specifier);
948
+ if (typeof sqlite.DatabaseSync !== "function") {
949
+ throw new Error("node:sqlite DatabaseSync is unavailable");
950
+ }
951
+ return sqlite;
952
+ } finally {
953
+ process.emitWarning = originalEmitWarning;
954
+ }
955
+ }
956
+
957
+ // src/adapters/cursor.ts
828
958
  var CANDIDATE_KEY_PREFIXES = [
829
959
  "bubbleId:",
830
960
  "composerData:",
@@ -978,16 +1108,7 @@ async function* parseCursorUsageStore(store, options) {
978
1108
  }
979
1109
  }
980
1110
  async function openCursorDb(dbPath) {
981
- try {
982
- const BetterSqlite3 = await import("better-sqlite3");
983
- const Ctor = BetterSqlite3.default ?? BetterSqlite3;
984
- return new Ctor(
985
- dbPath,
986
- { readonly: true }
987
- );
988
- } catch {
989
- return null;
990
- }
1111
+ return openReadonlySqliteDatabase(dbPath);
991
1112
  }
992
1113
  function readStateRows(db) {
993
1114
  const rows = [];
@@ -1048,6 +1169,12 @@ function decodeStateValue(value) {
1048
1169
  if (Buffer.isBuffer(value)) {
1049
1170
  return value.toString("utf-8");
1050
1171
  }
1172
+ if (value instanceof Uint8Array) {
1173
+ return Buffer.from(value).toString("utf-8");
1174
+ }
1175
+ if (ArrayBuffer.isView(value)) {
1176
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString("utf-8");
1177
+ }
1051
1178
  return null;
1052
1179
  }
1053
1180
  function extractCursorMessages(root, rowKey) {
@@ -1410,17 +1537,11 @@ async function openOpencodeDb() {
1410
1537
  if (!dbPath) {
1411
1538
  return null;
1412
1539
  }
1413
- try {
1414
- const BetterSqlite3 = await import("better-sqlite3");
1415
- const Ctor = BetterSqlite3.default ?? BetterSqlite3;
1416
- return new Ctor(
1417
- dbPath,
1418
- { readonly: true }
1419
- );
1420
- } catch {
1421
- console.warn("devrage: better-sqlite3 not available, skipping OpenCode sessions");
1422
- return null;
1540
+ const db = await openReadonlySqliteDatabase(dbPath);
1541
+ if (!db) {
1542
+ console.warn("devrage: SQLite support not available, skipping OpenCode sessions");
1423
1543
  }
1544
+ return db;
1424
1545
  }
1425
1546
  function* queryUserMessages(db, options) {
1426
1547
  let query = `
@@ -1763,16 +1884,7 @@ function resolveHomePath(value) {
1763
1884
  return isAbsolute(value) ? value : resolve(value);
1764
1885
  }
1765
1886
  async function openT3Db(dbPath) {
1766
- try {
1767
- const BetterSqlite3 = await import("better-sqlite3");
1768
- const Ctor = BetterSqlite3.default ?? BetterSqlite3;
1769
- return new Ctor(
1770
- dbPath,
1771
- { readonly: true, fileMustExist: true }
1772
- );
1773
- } catch {
1774
- return null;
1775
- }
1887
+ return openReadonlySqliteDatabase(dbPath);
1776
1888
  }
1777
1889
  function* queryUserMessages2(db, location, options) {
1778
1890
  if (!hasColumns(db, "projection_thread_messages", ["thread_id", "role", "text", "created_at"])) {
@@ -2190,21 +2302,10 @@ async function* parseAgentThreads(dbDir, _options) {
2190
2302
  if (dbFiles.length === 0) {
2191
2303
  return;
2192
2304
  }
2193
- let Database;
2194
- try {
2195
- const mod = await import("better-sqlite3");
2196
- Database = mod.default ?? mod;
2197
- } catch {
2198
- return;
2199
- }
2200
2305
  for (const dbFile of dbFiles) {
2201
2306
  const dbPath = join9(dbDir, dbFile);
2202
- let db;
2203
- try {
2204
- db = new Database(dbPath, {
2205
- readonly: true
2206
- });
2207
- } catch {
2307
+ const db = await openReadonlySqliteDatabase(dbPath);
2308
+ if (!db) {
2208
2309
  continue;
2209
2310
  }
2210
2311
  try {
@@ -2214,14 +2315,12 @@ async function* parseAgentThreads(dbDir, _options) {
2214
2315
  (t) => t === "messages" || t === "thread_messages" || t.includes("message")
2215
2316
  );
2216
2317
  if (!msgTable) {
2217
- db.close();
2218
2318
  continue;
2219
2319
  }
2220
2320
  const columns = db.prepare(`PRAGMA table_info("${msgTable}")`).all();
2221
2321
  const colNames = columns.map((c2) => c2.name);
2222
2322
  const hasRole = colNames.includes("role");
2223
2323
  if (!hasRole) {
2224
- db.close();
2225
2324
  continue;
2226
2325
  }
2227
2326
  const contentCol = colNames.includes("content") ? "content" : colNames.includes("body") ? "body" : "text";
@@ -3667,7 +3766,7 @@ async function main() {
3667
3766
  process.exit(0);
3668
3767
  }
3669
3768
  if (command === "--version") {
3670
- console.log("0.5.6");
3769
+ console.log("0.5.8");
3671
3770
  process.exit(0);
3672
3771
  }
3673
3772
  const parsed = parseCommand(args);