@geminixiang/mama 0.2.0-beta.23 → 0.2.0-beta.24

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.
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-history.d.ts","sourceRoot":"","sources":["../src/conversation-history.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAG3D,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAKD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,iCAAiC,GAAG;IAAE,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAClF,MAAM,GAAG,IAAI,CAKf;AAED,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,iCAAiC,GACzC,MAAM,GAAG,IAAI,CAqCf;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,iCAAiC,EAAE,YAAY,GAAG,KAAK,CAAC,GACrE,MAAM,GAAG,IAAI,CAMf;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,IAAI,CAAC,iCAAiC,EAAE,YAAY,GAAG,KAAK,CAAC,GACrE,sBAAsB,EAAE,CAiC1B","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { mkdirSync, statSync } from \"fs\";\nimport { join } from \"path\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"./file-guards.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\nimport type { ConversationLogMessage } from \"./context.js\";\nimport * as log from \"./log.js\";\n\nexport interface MaterializeTopLevelHistoryOptions {\n conversationDir: string;\n sessionDir: string;\n cwd: string;\n recentDays?: number;\n maxMessages?: number;\n now?: Date;\n}\n\nconst DEFAULT_RECENT_DAYS = 14;\nconst DEFAULT_MAX_MESSAGES = 200;\n\nexport function resolveUsableTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): string | null {\n if (options.existingSessionFile && isSessionFreshForTopLevelHistory(options)) {\n return options.existingSessionFile;\n }\n return materializeTopLevelHistorySession(options);\n}\n\nexport function materializeTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions,\n): string | null {\n const messages = readTopLevelHistoryMessages(options);\n if (messages.length === 0) return null;\n\n mkdirSync(options.sessionDir, { recursive: true });\n const now = options.now ?? new Date();\n const sessionId = randomUUID();\n const filename = `${now.toISOString().replace(/[:.]/g, \"-\")}_${sessionId.slice(0, 8)}_history.jsonl`;\n const sessionFile = join(options.sessionDir, filename);\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: now.toISOString(),\n cwd: options.cwd,\n source: {\n kind: \"platform-history\",\n file: \"log.jsonl\",\n recentDays: options.recentDays ?? DEFAULT_RECENT_DAYS,\n },\n };\n const entries = messages.map((message) => ({\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date(message.date ?? now.toISOString()).toISOString(),\n message: {\n role: message.isBot ? \"assistant\" : \"user\",\n content: [{ type: \"text\", text: formatHistoryMessage(message) }],\n ...(message.date ? { timestamp: new Date(message.date).getTime() } : {}),\n },\n }));\n\n const content = [header, ...entries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(sessionFile, `${content}\\n`);\n atomicWritePrivateFile(join(options.sessionDir, \"current\"), filename);\n return sessionFile;\n}\n\nexport function latestTopLevelHistoryTime(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): number | null {\n const messages = readTopLevelHistoryMessages({ ...options, maxMessages: 1 });\n const latest = messages.at(-1);\n if (!latest?.date) return null;\n const ms = new Date(latest.date).getTime();\n return Number.isFinite(ms) ? ms : null;\n}\n\nexport function readTopLevelHistoryMessages(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): ConversationLogMessage[] {\n const logFile = join(options.conversationDir, \"log.jsonl\");\n const raw = readTextFileIfExists(logFile);\n if (raw === undefined) return [];\n\n const nowMs = (options.now ?? new Date()).getTime();\n const sinceMs = nowMs - (options.recentDays ?? DEFAULT_RECENT_DAYS) * 24 * 60 * 60 * 1000;\n const messages: ConversationLogMessage[] = [];\n const lines = raw.trim().split(\"\\n\").filter(Boolean);\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = parseJsonValue(\n lines[i],\n (value): value is ConversationLogMessage => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n if (entry.threadTs) continue;\n if (!entry.text?.trim()) continue;\n if (entry.date) {\n const dateMs = new Date(entry.date).getTime();\n if (Number.isFinite(dateMs) && dateMs < sinceMs) continue;\n }\n messages.push(entry);\n } catch (err) {\n log.logWarning(\n `Skipping malformed log entry at ${logFile}:${i + 1}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n return messages.slice(-(options.maxMessages ?? DEFAULT_MAX_MESSAGES));\n}\n\nfunction isSessionFreshForTopLevelHistory(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): boolean {\n if (!options.existingSessionFile) return false;\n const latestHistoryMs = latestTopLevelHistoryTime(options);\n if (latestHistoryMs === null) return true;\n\n try {\n return statSync(options.existingSessionFile).mtimeMs >= latestHistoryMs;\n } catch {\n return false;\n }\n}\n\nfunction formatHistoryMessage(message: ConversationLogMessage): string {\n const text = message.text?.trim() ?? \"\";\n if (message.isBot) return text;\n const userLabel = message.userName || message.user || \"unknown\";\n const timestamp = message.date ? formatLocalTimestamp(new Date(message.date)) : null;\n return timestamp ? `[${timestamp}] [${userLabel}]: ${text}` : `[${userLabel}]: ${text}`;\n}\n\nfunction formatLocalTimestamp(date: Date): string {\n const offset = -date.getTimezoneOffset();\n const sign = offset >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offset);\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +\n `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`\n );\n}\n\nfunction pad(n: number): string {\n return n.toString().padStart(2, \"0\");\n}\n"]}
1
+ {"version":3,"file":"conversation-history.d.ts","sourceRoot":"","sources":["../src/conversation-history.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAG3D,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAKD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,iCAAiC,GAAG;IAAE,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAClF,MAAM,GAAG,IAAI,CAKf;AAED,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,iCAAiC,GACzC,MAAM,GAAG,IAAI,CAiCf;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,iCAAiC,EAAE,YAAY,GAAG,KAAK,CAAC,GACrE,MAAM,GAAG,IAAI,CAMf;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,IAAI,CAAC,iCAAiC,EAAE,YAAY,GAAG,KAAK,CAAC,GACrE,sBAAsB,EAAE,CAiC1B","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { mkdirSync, statSync } from \"fs\";\nimport { join } from \"path\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"./file-guards.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\nimport type { ConversationLogMessage } from \"./context.js\";\nimport * as log from \"./log.js\";\n\nexport interface MaterializeTopLevelHistoryOptions {\n conversationDir: string;\n sessionDir: string;\n cwd: string;\n recentDays?: number;\n maxMessages?: number;\n now?: Date;\n}\n\nconst DEFAULT_RECENT_DAYS = 14;\nconst DEFAULT_MAX_MESSAGES = 200;\n\nexport function resolveUsableTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): string | null {\n if (options.existingSessionFile && isSessionFreshForTopLevelHistory(options)) {\n return options.existingSessionFile;\n }\n return materializeTopLevelHistorySession(options);\n}\n\nexport function materializeTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions,\n): string | null {\n const messages = readTopLevelHistoryMessages(options);\n if (messages.length === 0) return null;\n\n mkdirSync(options.sessionDir, { recursive: true });\n const now = options.now ?? new Date();\n const sessionId = randomUUID();\n const filename = `${now.toISOString().replace(/[:.]/g, \"-\")}_${sessionId.slice(0, 8)}_history.jsonl`;\n const sessionFile = join(options.sessionDir, filename);\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: now.toISOString(),\n cwd: options.cwd,\n source: {\n kind: \"platform-history\",\n file: \"log.jsonl\",\n recentDays: options.recentDays ?? DEFAULT_RECENT_DAYS,\n },\n };\n const entries = messages.map((message) => ({\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date(message.date ?? now.toISOString()).toISOString(),\n message: buildHistorySessionMessage(message),\n }));\n\n const content = [header, ...entries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(sessionFile, `${content}\\n`);\n atomicWritePrivateFile(join(options.sessionDir, \"current\"), filename);\n return sessionFile;\n}\n\nexport function latestTopLevelHistoryTime(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): number | null {\n const messages = readTopLevelHistoryMessages({ ...options, maxMessages: 1 });\n const latest = messages.at(-1);\n if (!latest?.date) return null;\n const ms = new Date(latest.date).getTime();\n return Number.isFinite(ms) ? ms : null;\n}\n\nexport function readTopLevelHistoryMessages(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): ConversationLogMessage[] {\n const logFile = join(options.conversationDir, \"log.jsonl\");\n const raw = readTextFileIfExists(logFile);\n if (raw === undefined) return [];\n\n const nowMs = (options.now ?? new Date()).getTime();\n const sinceMs = nowMs - (options.recentDays ?? DEFAULT_RECENT_DAYS) * 24 * 60 * 60 * 1000;\n const messages: ConversationLogMessage[] = [];\n const lines = raw.trim().split(\"\\n\").filter(Boolean);\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = parseJsonValue(\n lines[i],\n (value): value is ConversationLogMessage => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n if (entry.threadTs) continue;\n if (!entry.text?.trim()) continue;\n if (entry.date) {\n const dateMs = new Date(entry.date).getTime();\n if (Number.isFinite(dateMs) && dateMs < sinceMs) continue;\n }\n messages.push(entry);\n } catch (err) {\n log.logWarning(\n `Skipping malformed log entry at ${logFile}:${i + 1}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n return messages.slice(-(options.maxMessages ?? DEFAULT_MAX_MESSAGES));\n}\n\nfunction isSessionFreshForTopLevelHistory(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): boolean {\n if (!options.existingSessionFile) return false;\n const latestHistoryMs = latestTopLevelHistoryTime(options);\n if (latestHistoryMs === null) return true;\n\n try {\n return statSync(options.existingSessionFile).mtimeMs >= latestHistoryMs;\n } catch {\n return false;\n }\n}\n\nfunction buildHistorySessionMessage(message: ConversationLogMessage): object {\n const base = {\n role: message.isBot ? \"assistant\" : \"user\",\n content: [{ type: \"text\", text: formatHistoryMessage(message) }],\n ...(message.date ? { timestamp: new Date(message.date).getTime() } : {}),\n };\n if (!message.isBot) return base;\n\n return {\n ...base,\n api: \"platform-history\",\n provider: \"platform-history\",\n model: \"platform-history\",\n usage: zeroUsage(),\n stopReason: \"stop\",\n };\n}\n\nfunction zeroUsage(): object {\n return {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n };\n}\n\nfunction formatHistoryMessage(message: ConversationLogMessage): string {\n const text = message.text?.trim() ?? \"\";\n if (message.isBot) return text;\n const userLabel = message.userName || message.user || \"unknown\";\n const timestamp = message.date ? formatLocalTimestamp(new Date(message.date)) : null;\n return timestamp ? `[${timestamp}] [${userLabel}]: ${text}` : `[${userLabel}]: ${text}`;\n}\n\nfunction formatLocalTimestamp(date: Date): string {\n const offset = -date.getTimezoneOffset();\n const sign = offset >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offset);\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +\n `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`\n );\n}\n\nfunction pad(n: number): string {\n return n.toString().padStart(2, \"0\");\n}\n"]}
@@ -38,11 +38,7 @@ export function materializeTopLevelHistorySession(options) {
38
38
  id: randomUUID().slice(0, 8),
39
39
  parentId: null,
40
40
  timestamp: new Date(message.date ?? now.toISOString()).toISOString(),
41
- message: {
42
- role: message.isBot ? "assistant" : "user",
43
- content: [{ type: "text", text: formatHistoryMessage(message) }],
44
- ...(message.date ? { timestamp: new Date(message.date).getTime() } : {}),
45
- },
41
+ message: buildHistorySessionMessage(message),
46
42
  }));
47
43
  const content = [header, ...entries].map((entry) => JSON.stringify(entry)).join("\n");
48
44
  atomicWritePrivateFile(sessionFile, `${content}\n`);
@@ -99,6 +95,33 @@ function isSessionFreshForTopLevelHistory(options) {
99
95
  return false;
100
96
  }
101
97
  }
98
+ function buildHistorySessionMessage(message) {
99
+ const base = {
100
+ role: message.isBot ? "assistant" : "user",
101
+ content: [{ type: "text", text: formatHistoryMessage(message) }],
102
+ ...(message.date ? { timestamp: new Date(message.date).getTime() } : {}),
103
+ };
104
+ if (!message.isBot)
105
+ return base;
106
+ return {
107
+ ...base,
108
+ api: "platform-history",
109
+ provider: "platform-history",
110
+ model: "platform-history",
111
+ usage: zeroUsage(),
112
+ stopReason: "stop",
113
+ };
114
+ }
115
+ function zeroUsage() {
116
+ return {
117
+ input: 0,
118
+ output: 0,
119
+ cacheRead: 0,
120
+ cacheWrite: 0,
121
+ totalTokens: 0,
122
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
123
+ };
124
+ }
102
125
  function formatHistoryMessage(message) {
103
126
  const text = message.text?.trim() ?? "";
104
127
  if (message.isBot)
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-history.js","sourceRoot":"","sources":["../src/conversation-history.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { mkdirSync, statSync } from \"fs\";\nimport { join } from \"path\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"./file-guards.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\nimport type { ConversationLogMessage } from \"./context.js\";\nimport * as log from \"./log.js\";\n\nexport interface MaterializeTopLevelHistoryOptions {\n conversationDir: string;\n sessionDir: string;\n cwd: string;\n recentDays?: number;\n maxMessages?: number;\n now?: Date;\n}\n\nconst DEFAULT_RECENT_DAYS = 14;\nconst DEFAULT_MAX_MESSAGES = 200;\n\nexport function resolveUsableTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): string | null {\n if (options.existingSessionFile && isSessionFreshForTopLevelHistory(options)) {\n return options.existingSessionFile;\n }\n return materializeTopLevelHistorySession(options);\n}\n\nexport function materializeTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions,\n): string | null {\n const messages = readTopLevelHistoryMessages(options);\n if (messages.length === 0) return null;\n\n mkdirSync(options.sessionDir, { recursive: true });\n const now = options.now ?? new Date();\n const sessionId = randomUUID();\n const filename = `${now.toISOString().replace(/[:.]/g, \"-\")}_${sessionId.slice(0, 8)}_history.jsonl`;\n const sessionFile = join(options.sessionDir, filename);\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: now.toISOString(),\n cwd: options.cwd,\n source: {\n kind: \"platform-history\",\n file: \"log.jsonl\",\n recentDays: options.recentDays ?? DEFAULT_RECENT_DAYS,\n },\n };\n const entries = messages.map((message) => ({\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date(message.date ?? now.toISOString()).toISOString(),\n message: {\n role: message.isBot ? \"assistant\" : \"user\",\n content: [{ type: \"text\", text: formatHistoryMessage(message) }],\n ...(message.date ? { timestamp: new Date(message.date).getTime() } : {}),\n },\n }));\n\n const content = [header, ...entries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(sessionFile, `${content}\\n`);\n atomicWritePrivateFile(join(options.sessionDir, \"current\"), filename);\n return sessionFile;\n}\n\nexport function latestTopLevelHistoryTime(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): number | null {\n const messages = readTopLevelHistoryMessages({ ...options, maxMessages: 1 });\n const latest = messages.at(-1);\n if (!latest?.date) return null;\n const ms = new Date(latest.date).getTime();\n return Number.isFinite(ms) ? ms : null;\n}\n\nexport function readTopLevelHistoryMessages(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): ConversationLogMessage[] {\n const logFile = join(options.conversationDir, \"log.jsonl\");\n const raw = readTextFileIfExists(logFile);\n if (raw === undefined) return [];\n\n const nowMs = (options.now ?? new Date()).getTime();\n const sinceMs = nowMs - (options.recentDays ?? DEFAULT_RECENT_DAYS) * 24 * 60 * 60 * 1000;\n const messages: ConversationLogMessage[] = [];\n const lines = raw.trim().split(\"\\n\").filter(Boolean);\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = parseJsonValue(\n lines[i],\n (value): value is ConversationLogMessage => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n if (entry.threadTs) continue;\n if (!entry.text?.trim()) continue;\n if (entry.date) {\n const dateMs = new Date(entry.date).getTime();\n if (Number.isFinite(dateMs) && dateMs < sinceMs) continue;\n }\n messages.push(entry);\n } catch (err) {\n log.logWarning(\n `Skipping malformed log entry at ${logFile}:${i + 1}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n return messages.slice(-(options.maxMessages ?? DEFAULT_MAX_MESSAGES));\n}\n\nfunction isSessionFreshForTopLevelHistory(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): boolean {\n if (!options.existingSessionFile) return false;\n const latestHistoryMs = latestTopLevelHistoryTime(options);\n if (latestHistoryMs === null) return true;\n\n try {\n return statSync(options.existingSessionFile).mtimeMs >= latestHistoryMs;\n } catch {\n return false;\n }\n}\n\nfunction formatHistoryMessage(message: ConversationLogMessage): string {\n const text = message.text?.trim() ?? \"\";\n if (message.isBot) return text;\n const userLabel = message.userName || message.user || \"unknown\";\n const timestamp = message.date ? formatLocalTimestamp(new Date(message.date)) : null;\n return timestamp ? `[${timestamp}] [${userLabel}]: ${text}` : `[${userLabel}]: ${text}`;\n}\n\nfunction formatLocalTimestamp(date: Date): string {\n const offset = -date.getTimezoneOffset();\n const sign = offset >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offset);\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +\n `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`\n );\n}\n\nfunction pad(n: number): string {\n return n.toString().padStart(2, \"0\");\n}\n"]}
1
+ {"version":3,"file":"conversation-history.js","sourceRoot":"","sources":["../src/conversation-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAWhC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC/B,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAEjC,MAAM,UAAU,mCAAmC,CACjD,OAAmF;IAEnF,IAAI,OAAO,CAAC,mBAAmB,IAAI,gCAAgC,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7E,OAAO,OAAO,CAAC,mBAAmB,CAAC;IACrC,CAAC;IACD,OAAO,iCAAiC,CAAC,OAAO,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,iCAAiC,CAC/C,OAA0C;IAE1C,MAAM,QAAQ,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvC,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IACtC,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,gBAAgB,CAAC;IACrG,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG;QACb,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,CAAC;QACV,EAAE,EAAE,SAAS;QACb,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE;QAC5B,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,MAAM,EAAE;YACN,IAAI,EAAE,kBAAkB;YACxB,IAAI,EAAE,WAAW;YACjB,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,mBAAmB;SACtD;KACF,CAAC;IACF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACzC,IAAI,EAAE,SAAS;QACf,EAAE,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5B,QAAQ,EAAE,IAAI;QACd,SAAS,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE;QACpE,OAAO,EAAE,0BAA0B,CAAC,OAAO,CAAC;KAC7C,CAAC,CAAC,CAAC;IAEJ,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtF,sBAAsB,CAAC,WAAW,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;IACpD,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;IACtE,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAsE;IAEtE,MAAM,QAAQ,GAAG,2BAA2B,CAAC,EAAE,GAAG,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,EAAE,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3C,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,OAAsE;IAEtE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IAEjC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;IACpD,MAAM,OAAO,GAAG,KAAK,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC1F,MAAM,QAAQ,GAA6B,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAErD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,cAAc,CAC1B,KAAK,CAAC,CAAC,CAAC,EACR,CAAC,KAAK,EAAmC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAC3D,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,uBAAuB,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,MAAM,CAAC,CACrF,CAAC;YACF,IAAI,KAAK,CAAC,QAAQ;gBAAE,SAAS;YAC7B,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE;gBAAE,SAAS;YAClC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;gBAC9C,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,OAAO;oBAAE,SAAS;YAC5D,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,UAAU,CACZ,mCAAmC,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,EACrD,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACjD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,IAAI,oBAAoB,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,gCAAgC,CACvC,OAAmF;IAEnF,IAAI,CAAC,OAAO,CAAC,mBAAmB;QAAE,OAAO,KAAK,CAAC;IAC/C,MAAM,eAAe,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC3D,IAAI,eAAe,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAE1C,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,OAAO,IAAI,eAAe,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B,CAAC,OAA+B;IACjE,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM;QAC1C,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzE,CAAC;IACF,IAAI,CAAC,OAAO,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAEhC,OAAO;QACL,GAAG,IAAI;QACP,GAAG,EAAE,kBAAkB;QACvB,QAAQ,EAAE,kBAAkB;QAC5B,KAAK,EAAE,kBAAkB;QACzB,KAAK,EAAE,SAAS,EAAE;QAClB,UAAU,EAAE,MAAM;KACnB,CAAC;AACJ,CAAC;AAED,SAAS,SAAS;IAChB,OAAO;QACL,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,SAAS,EAAE,CAAC;QACZ,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,CAAC;QACd,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;KACrE,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,OAA+B;IAC3D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxC,IAAI,OAAO,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,IAAI,SAAS,CAAC;IAChE,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,oBAAoB,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrF,OAAO,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,MAAM,SAAS,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,SAAS,MAAM,IAAI,EAAE,CAAC;AAC1F,CAAC;AAED,SAAS,oBAAoB,CAAC,IAAU;IACtC,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,CACL,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG;QAC3E,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE;QAC7E,GAAG,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,CACvD,CAAC;AACJ,CAAC;AAED,SAAS,GAAG,CAAC,CAAS;IACpB,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACvC,CAAC","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { mkdirSync, statSync } from \"fs\";\nimport { join } from \"path\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"./file-guards.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\nimport type { ConversationLogMessage } from \"./context.js\";\nimport * as log from \"./log.js\";\n\nexport interface MaterializeTopLevelHistoryOptions {\n conversationDir: string;\n sessionDir: string;\n cwd: string;\n recentDays?: number;\n maxMessages?: number;\n now?: Date;\n}\n\nconst DEFAULT_RECENT_DAYS = 14;\nconst DEFAULT_MAX_MESSAGES = 200;\n\nexport function resolveUsableTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): string | null {\n if (options.existingSessionFile && isSessionFreshForTopLevelHistory(options)) {\n return options.existingSessionFile;\n }\n return materializeTopLevelHistorySession(options);\n}\n\nexport function materializeTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions,\n): string | null {\n const messages = readTopLevelHistoryMessages(options);\n if (messages.length === 0) return null;\n\n mkdirSync(options.sessionDir, { recursive: true });\n const now = options.now ?? new Date();\n const sessionId = randomUUID();\n const filename = `${now.toISOString().replace(/[:.]/g, \"-\")}_${sessionId.slice(0, 8)}_history.jsonl`;\n const sessionFile = join(options.sessionDir, filename);\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: now.toISOString(),\n cwd: options.cwd,\n source: {\n kind: \"platform-history\",\n file: \"log.jsonl\",\n recentDays: options.recentDays ?? DEFAULT_RECENT_DAYS,\n },\n };\n const entries = messages.map((message) => ({\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date(message.date ?? now.toISOString()).toISOString(),\n message: buildHistorySessionMessage(message),\n }));\n\n const content = [header, ...entries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(sessionFile, `${content}\\n`);\n atomicWritePrivateFile(join(options.sessionDir, \"current\"), filename);\n return sessionFile;\n}\n\nexport function latestTopLevelHistoryTime(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): number | null {\n const messages = readTopLevelHistoryMessages({ ...options, maxMessages: 1 });\n const latest = messages.at(-1);\n if (!latest?.date) return null;\n const ms = new Date(latest.date).getTime();\n return Number.isFinite(ms) ? ms : null;\n}\n\nexport function readTopLevelHistoryMessages(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): ConversationLogMessage[] {\n const logFile = join(options.conversationDir, \"log.jsonl\");\n const raw = readTextFileIfExists(logFile);\n if (raw === undefined) return [];\n\n const nowMs = (options.now ?? new Date()).getTime();\n const sinceMs = nowMs - (options.recentDays ?? DEFAULT_RECENT_DAYS) * 24 * 60 * 60 * 1000;\n const messages: ConversationLogMessage[] = [];\n const lines = raw.trim().split(\"\\n\").filter(Boolean);\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = parseJsonValue(\n lines[i],\n (value): value is ConversationLogMessage => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n if (entry.threadTs) continue;\n if (!entry.text?.trim()) continue;\n if (entry.date) {\n const dateMs = new Date(entry.date).getTime();\n if (Number.isFinite(dateMs) && dateMs < sinceMs) continue;\n }\n messages.push(entry);\n } catch (err) {\n log.logWarning(\n `Skipping malformed log entry at ${logFile}:${i + 1}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n return messages.slice(-(options.maxMessages ?? DEFAULT_MAX_MESSAGES));\n}\n\nfunction isSessionFreshForTopLevelHistory(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): boolean {\n if (!options.existingSessionFile) return false;\n const latestHistoryMs = latestTopLevelHistoryTime(options);\n if (latestHistoryMs === null) return true;\n\n try {\n return statSync(options.existingSessionFile).mtimeMs >= latestHistoryMs;\n } catch {\n return false;\n }\n}\n\nfunction buildHistorySessionMessage(message: ConversationLogMessage): object {\n const base = {\n role: message.isBot ? \"assistant\" : \"user\",\n content: [{ type: \"text\", text: formatHistoryMessage(message) }],\n ...(message.date ? { timestamp: new Date(message.date).getTime() } : {}),\n };\n if (!message.isBot) return base;\n\n return {\n ...base,\n api: \"platform-history\",\n provider: \"platform-history\",\n model: \"platform-history\",\n usage: zeroUsage(),\n stopReason: \"stop\",\n };\n}\n\nfunction zeroUsage(): object {\n return {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n };\n}\n\nfunction formatHistoryMessage(message: ConversationLogMessage): string {\n const text = message.text?.trim() ?? \"\";\n if (message.isBot) return text;\n const userLabel = message.userName || message.user || \"unknown\";\n const timestamp = message.date ? formatLocalTimestamp(new Date(message.date)) : null;\n return timestamp ? `[${timestamp}] [${userLabel}]: ${text}` : `[${userLabel}]: ${text}`;\n}\n\nfunction formatLocalTimestamp(date: Date): string {\n const offset = -date.getTimezoneOffset();\n const sign = offset >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offset);\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +\n `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`\n );\n}\n\nfunction pad(n: number): string {\n return n.toString().padStart(2, \"0\");\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminixiang/mama",
3
- "version": "0.2.0-beta.23",
3
+ "version": "0.2.0-beta.24",
4
4
  "description": "Multi-Agent Mischief Assistant for Slack, Telegram, and Discord",
5
5
  "keywords": [
6
6
  "agent",