ultracontext 1.4.12 → 1.5.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.
@@ -308,18 +308,18 @@ async function checkForUpdate() {
308
308
  if (latest && isNewer(latest, current)) printUpdateNotice(current, latest);
309
309
  }
310
310
  async function launchSyncDaemon() {
311
- const { launchDaemon } = await import("../launcher-BMMjzr5k.mjs");
311
+ const { launchDaemon } = await import("../launcher-374b809z.mjs");
312
312
  await launchDaemon({
313
313
  entryPath: fileURLToPath(new URL("./sdk-sync.mjs", import.meta.url)),
314
314
  diagnosticsHint: "DAEMON_VERBOSE=1 ultracontext sync"
315
315
  });
316
316
  }
317
317
  async function runCtlSDK() {
318
- const { runCtl } = await import("../ctl-CXfNEPN8.mjs");
318
+ const { runCtl } = await import("../ctl-_C5oTsoT.mjs");
319
319
  await runCtl();
320
320
  }
321
321
  async function launchTui() {
322
- const { tuiBoot } = await import("../tui-C04sD2eJ.mjs");
322
+ const { tuiBoot } = await import("../tui-zO1IZH_S.mjs");
323
323
  await tuiBoot({ assetsRoot: path.resolve(__dirname, "..", "..") });
324
324
  }
325
325
  async function run() {
@@ -1,6 +1,6 @@
1
- import { a as asIso, c as extractSessionIdFromPath, d as normalizeWhitespace, f as preserveText, h as truncateString, i as toInt, l as firstMessageTimestamp, m as toMessage, n as extractProjectPathFromFile, o as coerceMessageText, p as safeJsonParse, r as sha256, s as expandHome, t as boolFromEnv, u as normalizeRole } from "../utils-CmuIYHtm.mjs";
1
+ import { a as asIso, c as extractSessionIdFromPath, d as normalizeWhitespace, f as preserveText, g as truncateString, h as toMessage, i as toInt, l as firstMessageTimestamp, m as stripIDEContextTags, n as extractProjectPathFromFile, o as coerceMessageText, p as safeJsonParse, r as sha256, s as expandHome, t as boolFromEnv, u as normalizeRole } from "../utils-BiZUI99i.mjs";
2
2
  import { n as normalizeBootstrapMode, t as createBootstrapStateKey } from "../protocol-BI9ficcl.mjs";
3
- import { n as resolveLockPath, t as acquireFileLock } from "../lock-5aJnda81.mjs";
3
+ import { n as resolveLockPath, t as acquireFileLock } from "../lock-DtnsoNoW.mjs";
4
4
  import process$1 from "node:process";
5
5
  import path from "node:path";
6
6
  import fs from "node:fs";
@@ -785,6 +785,124 @@ function parseOpenClawLine({ line, filePath }) {
785
785
  return null;
786
786
  }
787
787
 
788
+ //#endregion
789
+ //#region ../../packages/parsers/src/agents/cursor.mjs
790
+ function parseCursorLine({ line, filePath }) {
791
+ const parsed = safeJsonParse(line);
792
+ if (!parsed || typeof parsed !== "object") return null;
793
+ if (!parsed.type && parsed.role) parsed.type = parsed.role;
794
+ const result = parseClaudeCodeLine({
795
+ line: JSON.stringify(parsed),
796
+ filePath
797
+ });
798
+ if (!result) return null;
799
+ result.eventType = result.eventType.replace(/^claude\./, "cursor.");
800
+ if (result.kind === "user" && result.message) result.message = stripIDEContextTags(result.message);
801
+ return result;
802
+ }
803
+
804
+ //#endregion
805
+ //#region ../../packages/parsers/src/agents/gemini.mjs
806
+ const FILE_MOD_TOOLS = [
807
+ "write_file",
808
+ "edit_file",
809
+ "save_file",
810
+ "replace"
811
+ ];
812
+ const FILE_PATH_KEYS = [
813
+ "file_path",
814
+ "path",
815
+ "filePath",
816
+ "filename"
817
+ ];
818
+ function extractGeminiTextContent(content) {
819
+ if (!content) return "";
820
+ if (typeof content === "string") return preserveText(content);
821
+ if (Array.isArray(content)) {
822
+ const parts = [];
823
+ for (const item of content) {
824
+ if (!item || typeof item !== "object") continue;
825
+ if (typeof item.text === "string") {
826
+ const chunk = preserveText(item.text);
827
+ if (chunk) parts.push(chunk);
828
+ }
829
+ }
830
+ return parts.join("\n");
831
+ }
832
+ if (typeof content === "object" && typeof content.text === "string") return preserveText(content.text);
833
+ return "";
834
+ }
835
+ function formatToolCall(tc) {
836
+ const name = tc.name ?? "unknown";
837
+ const args = tc.args ?? {};
838
+ let filePath = "";
839
+ for (const key of FILE_PATH_KEYS) if (typeof args[key] === "string" && args[key]) {
840
+ filePath = args[key];
841
+ break;
842
+ }
843
+ if (FILE_MOD_TOOLS.includes(name)) {
844
+ const content = preserveText(args.content ?? args.file_text ?? args.new_content ?? "");
845
+ if (content) return `[${name}] ${filePath}\n${truncateString(content, 500)}`;
846
+ return `[${name}] ${filePath}`;
847
+ }
848
+ const compact = JSON.stringify(args);
849
+ const detail = compact.length > 500 ? compact.slice(0, 500) + "..." : compact;
850
+ return `[${name}]${filePath ? ` ${filePath}` : ""} ${detail}`;
851
+ }
852
+ function extractGeminiSessionId(filePath) {
853
+ const base = path.basename(filePath, ".json");
854
+ const match = base.match(/session-[\d-]+-(.+)$/);
855
+ if (match) return match[1];
856
+ return base || "unknown-session";
857
+ }
858
+ function extractGeminiTimestamp(filePath) {
859
+ const match = path.basename(filePath, ".json").match(/session-(\d{4})(\d{2})(\d{2})/);
860
+ if (match) return `${match[1]}-${match[2]}-${match[3]}T00:00:00.000Z`;
861
+ return (/* @__PURE__ */ new Date()).toISOString();
862
+ }
863
+ function parseGeminiFile({ fileContents, filePath }) {
864
+ const parsed = safeJsonParse(fileContents);
865
+ if (!parsed || typeof parsed !== "object") return [];
866
+ const messages = parsed.messages;
867
+ if (!Array.isArray(messages) || messages.length === 0) return [];
868
+ const sessionId = extractGeminiSessionId(filePath);
869
+ const fileTimestamp = extractGeminiTimestamp(filePath);
870
+ const events = [];
871
+ for (let i = 0; i < messages.length; i++) {
872
+ const msg = messages[i];
873
+ if (!msg || typeof msg !== "object") continue;
874
+ const type = String(msg.type ?? "").toLowerCase();
875
+ const isUser = type === "user";
876
+ if (!isUser && !(type === "gemini")) continue;
877
+ const text = extractGeminiTextContent(msg.content);
878
+ const toolCallTexts = [];
879
+ if (Array.isArray(msg.toolCalls)) for (const tc of msg.toolCalls) {
880
+ if (!tc || typeof tc !== "object") continue;
881
+ toolCallTexts.push(formatToolCall(tc));
882
+ }
883
+ const parts = [];
884
+ if (text) parts.push(text);
885
+ if (toolCallTexts.length) parts.push(toolCallTexts.join("\n\n"));
886
+ const message = parts.join("\n\n");
887
+ if (!message) continue;
888
+ events.push({
889
+ sessionId,
890
+ eventType: isUser ? "gemini.user" : "gemini.assistant",
891
+ kind: isUser ? "user" : "assistant",
892
+ timestamp: msg.timestamp ?? fileTimestamp,
893
+ message: toMessage(message),
894
+ raw: {
895
+ type: msg.type,
896
+ id: msg.id,
897
+ index: i,
898
+ hasToolCalls: toolCallTexts.length > 0,
899
+ toolCallCount: toolCallTexts.length
900
+ }
901
+ });
902
+ }
903
+ return events;
904
+ }
905
+
788
906
  //#endregion
789
907
  //#region ../../packages/parsers/src/gstack.mjs
790
908
  function extractGstackProjectSlug(filePath) {
@@ -896,6 +1014,9 @@ async function hasLocalClaudeSession(sessionId, cwd = "", baseDir) {
896
1014
  followSymbolicLinks: false
897
1015
  })).some((filePath) => path.basename(filePath, ".jsonl") === id);
898
1016
  }
1017
+ function isNativeClaudeEntry(raw) {
1018
+ return raw && typeof raw === "object" && "type" in raw && ("sessionId" in raw || "uuid" in raw);
1019
+ }
899
1020
  async function writeClaudeSession({ sessionId, cwd, messages, baseDir }) {
900
1021
  const runCwd = String(cwd || process.cwd());
901
1022
  const resolvedSessionId = normalizeSessionUuid(sessionId);
@@ -913,11 +1034,21 @@ async function writeClaudeSession({ sessionId, cwd, messages, baseDir }) {
913
1034
  let parentUuid = null;
914
1035
  for (let i = 0; i < (messages?.length ?? 0); i += 1) {
915
1036
  const message = messages[i];
1037
+ const raw = message?.content?.raw;
1038
+ if (isNativeClaudeEntry(raw)) {
1039
+ const entryUuid = raw.uuid || randomUUID();
1040
+ lines.push(JSON.stringify({
1041
+ ...raw,
1042
+ parentUuid,
1043
+ sessionId: resolvedSessionId
1044
+ }));
1045
+ parentUuid = entryUuid;
1046
+ continue;
1047
+ }
916
1048
  const normalizedRole = normalizeRole(message?.role);
917
1049
  const role = normalizedRole === "assistant" ? "assistant" : normalizedRole === "user" ? "user" : "assistant";
918
- const rawText = coerceMessageText(message).trim();
919
- if (!rawText) continue;
920
- const text = normalizedRole === "system" ? `[system] ${rawText}` : rawText;
1050
+ const text = extractMessageText(message, normalizedRole);
1051
+ if (!text) continue;
921
1052
  const ts = asIso(message?.content?.timestamp ?? message?.metadata?.timestamp ?? new Date(new Date(firstTs).getTime() + i * 1e3).toISOString());
922
1053
  const entryUuid = randomUUID();
923
1054
  lines.push(JSON.stringify({
@@ -931,32 +1062,35 @@ async function writeClaudeSession({ sessionId, cwd, messages, baseDir }) {
931
1062
  type: role,
932
1063
  message: {
933
1064
  role,
934
- content: text
1065
+ content: [{
1066
+ type: "text",
1067
+ text
1068
+ }]
935
1069
  },
936
1070
  timestamp: ts,
937
1071
  uuid: entryUuid
938
1072
  }));
939
1073
  parentUuid = entryUuid;
940
1074
  }
941
- if (lines.length === 0) {
942
- const entryUuid = randomUUID();
943
- lines.push(JSON.stringify({
944
- parentUuid: null,
945
- isSidechain: false,
946
- userType: "external",
947
- cwd: runCwd,
948
- sessionId: resolvedSessionId,
949
- version: "adapter",
950
- gitBranch: "",
951
- type: "assistant",
952
- message: {
953
- role: "assistant",
954
- content: "[system] Session restored from UltraContext with no user/assistant messages."
955
- },
956
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
957
- uuid: entryUuid
958
- }));
959
- }
1075
+ if (lines.length === 0) lines.push(JSON.stringify({
1076
+ parentUuid: null,
1077
+ isSidechain: false,
1078
+ userType: "external",
1079
+ cwd: runCwd,
1080
+ sessionId: resolvedSessionId,
1081
+ version: "adapter",
1082
+ gitBranch: "",
1083
+ type: "assistant",
1084
+ message: {
1085
+ role: "assistant",
1086
+ content: [{
1087
+ type: "text",
1088
+ text: "[system] Session restored from UltraContext with no user/assistant messages."
1089
+ }]
1090
+ },
1091
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1092
+ uuid: randomUUID()
1093
+ }));
960
1094
  await fs$1.writeFile(filePath, `${lines.join("\n")}\n`, "utf8");
961
1095
  return {
962
1096
  written: true,
@@ -974,6 +1108,17 @@ async function writeClaudeSession({ sessionId, cwd, messages, baseDir }) {
974
1108
  };
975
1109
  }
976
1110
  }
1111
+ function extractMessageText(message, normalizedRole) {
1112
+ const raw = message?.content?.raw;
1113
+ if (raw?.type === "response_item" && raw?.payload?.type === "message") {
1114
+ const text = (raw.payload.content ?? []).map((c) => c.text ?? "").filter(Boolean).join("\n");
1115
+ if (text) return normalizedRole === "system" ? `[system] ${text}` : text;
1116
+ }
1117
+ if (raw?.type === "event_msg" && raw?.payload?.type === "user_message") return raw.payload.message ?? "";
1118
+ const msg = message?.content?.message ?? "";
1119
+ if (!msg) return "";
1120
+ return normalizedRole === "system" ? `[system] ${msg}` : msg;
1121
+ }
977
1122
 
978
1123
  //#endregion
979
1124
  //#region ../../packages/parsers/src/writers/codex.mjs
@@ -1350,7 +1495,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1350
1495
  return "";
1351
1496
  }
1352
1497
  function pushRecentLog(level, message, data) {
1353
- let line = String(message ?? "");
1498
+ let line = String(message ?? "").replace(/[\r\n\t\v\f\x00-\x1f]+/g, " ").replace(/\s{2,}/g, " ");
1354
1499
  if (line.startsWith("Appended event to session context")) line = "context append";
1355
1500
  if (line.startsWith("Context created")) line = "Context created";
1356
1501
  if (line.startsWith("Context created without metadata fallback")) line = "Context created (fallback)";
@@ -1561,6 +1706,20 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1561
1706
  globs: [openclawGlob],
1562
1707
  parseLine: parseOpenClawLine
1563
1708
  });
1709
+ const cursorGlob = expandHome(process$1.env.CURSOR_GLOB ?? "~/.cursor/projects/**/*.jsonl");
1710
+ if (boolFromEnv(process$1.env.INGEST_CURSOR, true)) sources.push({
1711
+ name: "cursor",
1712
+ enabled: true,
1713
+ globs: [cursorGlob],
1714
+ parseLine: parseCursorLine
1715
+ });
1716
+ const geminiGlob = expandHome(process$1.env.GEMINI_GLOB ?? "~/.gemini/tmp/*/chats/session-*.json");
1717
+ if (boolFromEnv(process$1.env.INGEST_GEMINI, true)) sources.push({
1718
+ name: "gemini",
1719
+ enabled: true,
1720
+ globs: [geminiGlob],
1721
+ parseFile: parseGeminiFile
1722
+ });
1564
1723
  const gstackGlob = expandHome(process$1.env.GSTACK_GLOB ?? "~/.gstack/projects/**/*.jsonl");
1565
1724
  if (boolFromEnv(process$1.env.INGEST_GSTACK, true)) sources.push({
1566
1725
  name: "gstack",
@@ -1612,7 +1771,11 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1612
1771
  if (shouldStop()) break;
1613
1772
  try {
1614
1773
  const stat = await fs$1.stat(filePath);
1615
- store.setOffset(offsetStoreKey(source.name, `${stat.dev}:${stat.ino}`), stat.size);
1774
+ const fileId = `${stat.dev}:${stat.ino}`;
1775
+ if (source.parseFile) {
1776
+ const contents = await fs$1.readFile(filePath, "utf8");
1777
+ store.setOffset(offsetStoreKey(source.name, fileId), sha256(contents));
1778
+ } else store.setOffset(offsetStoreKey(source.name, fileId), stat.size);
1616
1779
  } catch {}
1617
1780
  }
1618
1781
  }
@@ -1783,6 +1946,18 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1783
1946
  started_at: sessionEvents[0].normalized.timestamp
1784
1947
  };
1785
1948
  if (projectPath) contextMeta.project_path = projectPath;
1949
+ const isRealUserEvent = (ev) => {
1950
+ if (ev.normalized.kind !== "user") return false;
1951
+ const et = ev.normalized.eventType ?? "";
1952
+ const msg = ev.normalized.message ?? "";
1953
+ if (et === "response_item.message") return false;
1954
+ if (msg.startsWith("A new session was started")) return false;
1955
+ if (msg.startsWith("[result]")) return false;
1956
+ if (msg.startsWith("<")) return false;
1957
+ return true;
1958
+ };
1959
+ const firstUserEvent = sessionEvents.find(isRealUserEvent) ?? sessionEvents.find((ev) => ev.normalized.kind === "user");
1960
+ if (firstUserEvent?.normalized?.message) contextMeta.title = firstUserEvent.normalized.message.replace(/[\r\n\t\v\f\x00-\x1f]+/g, " ").replace(/\s{2,}/g, " ").trim().slice(0, 120);
1786
1961
  const ctxId = await getOrCreateContext(store, uc, sessionContextStoreKey(sourceName, sessionId), contextMeta, sourceName);
1787
1962
  contextIds.set(sessionId, ctxId);
1788
1963
  }
@@ -1821,12 +1996,13 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1821
1996
  lastSessionId: sessionId,
1822
1997
  lastAt: Date.now()
1823
1998
  });
1824
- if (cfg.logAppends) log("info", "Bulk appended events to session context", {
1825
- source: sourceName,
1826
- session_id: sessionId,
1827
- context_id: sessionContextId,
1828
- count: sessionEvents.length
1829
- });
1999
+ if (cfg.logAppends) for (const { normalized } of sessionEvents) {
2000
+ const msg = (normalized.message ?? "").replace(/[\r\n\t\v\f\x00-\x1f]+/g, " ").replace(/\s{2,}/g, " ").trim().slice(0, 80);
2001
+ log("info", `[${normalized.eventType}] ${msg}`, {
2002
+ source: sourceName,
2003
+ session_id: sessionId
2004
+ });
2005
+ }
1830
2006
  });
1831
2007
  }
1832
2008
  async function readNewLines(filePath, offset) {
@@ -1881,6 +2057,56 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1881
2057
  bumpSourceStat(source.name, "filesScanned");
1882
2058
  const fileId = `${stat.dev}:${stat.ino}`;
1883
2059
  const offsetKey = offsetStoreKey(source.name, fileId);
2060
+ if (source.parseFile) {
2061
+ const fileContents = await fs$1.readFile(filePath, "utf8");
2062
+ const contentHash = sha256(fileContents);
2063
+ if (store.getOffset(offsetKey) === contentHash) return;
2064
+ noteSourceActivity(source.name, {
2065
+ lastFile: filePath,
2066
+ lastAt: Date.now()
2067
+ });
2068
+ const allEvents = source.parseFile({
2069
+ fileContents,
2070
+ filePath
2071
+ });
2072
+ if (!Array.isArray(allEvents) || allEvents.length === 0) return;
2073
+ bumpStat("linesRead", allEvents.length);
2074
+ bumpSourceStat(source.name, "linesRead", allEvents.length);
2075
+ const pendingEvents = [];
2076
+ for (let i = 0; i < allEvents.length; i++) {
2077
+ if (shouldStop()) break;
2078
+ const normalized = allEvents[i];
2079
+ if (!normalized || !normalized.sessionId) continue;
2080
+ if (ingestMode === "last_24h" && !isWithinLast24h(normalized.timestamp)) continue;
2081
+ bumpStat("parsedEvents");
2082
+ bumpSourceStat(source.name, "parsedEvents");
2083
+ noteSourceActivity(source.name, {
2084
+ lastEventType: normalized.eventType,
2085
+ lastSessionId: normalized.sessionId,
2086
+ lastAt: Date.now()
2087
+ });
2088
+ const eventId = sha256(`${source.name}|${cfg.host}|${cfg.userId}|${normalized.sessionId}|${fileId}|${i}`);
2089
+ if (!markEventSeen(store, source.name, eventId)) {
2090
+ bumpStat("deduped");
2091
+ bumpSourceStat(source.name, "deduped");
2092
+ continue;
2093
+ }
2094
+ pendingEvents.push({
2095
+ normalized,
2096
+ eventId,
2097
+ lineOffset: i
2098
+ });
2099
+ }
2100
+ if (pendingEvents.length > 0) await appendBulkToUltraContext({
2101
+ store,
2102
+ uc,
2103
+ sourceName: source.name,
2104
+ events: pendingEvents,
2105
+ filePath
2106
+ });
2107
+ store.setOffset(offsetKey, contentHash);
2108
+ return;
2109
+ }
1884
2110
  const { lines, nextOffset } = await readNewLines(filePath, toInt(store.getOffset(offsetKey), 0));
1885
2111
  bumpStat("linesRead", lines.length);
1886
2112
  bumpSourceStat(source.name, "linesRead", lines.length);