@geminixiang/mikan 0.2.2 → 0.3.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +1 -1
  3. package/dist/adapters/slack/bot.d.ts +1 -1
  4. package/dist/adapters/slack/bot.d.ts.map +1 -1
  5. package/dist/adapters/slack/bot.js +13 -10
  6. package/dist/adapters/slack/bot.js.map +1 -1
  7. package/dist/adapters/slack/session.d.ts +5 -5
  8. package/dist/adapters/slack/session.d.ts.map +1 -1
  9. package/dist/adapters/slack/session.js +7 -9
  10. package/dist/adapters/slack/session.js.map +1 -1
  11. package/dist/adapters/slack/thread-manager.d.ts +20 -0
  12. package/dist/adapters/slack/thread-manager.d.ts.map +1 -0
  13. package/dist/adapters/slack/thread-manager.js +14 -0
  14. package/dist/adapters/slack/thread-manager.js.map +1 -0
  15. package/dist/agent.d.ts +1 -0
  16. package/dist/agent.d.ts.map +1 -1
  17. package/dist/agent.js +16 -4
  18. package/dist/agent.js.map +1 -1
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/login/portal.d.ts +1 -1
  24. package/dist/login/portal.d.ts.map +1 -1
  25. package/dist/login/portal.js.map +1 -1
  26. package/dist/login/{session.d.ts → store.d.ts} +1 -1
  27. package/dist/login/store.d.ts.map +1 -0
  28. package/dist/login/{session.js → store.js} +1 -1
  29. package/dist/login/store.js.map +1 -0
  30. package/dist/main.d.ts.map +1 -1
  31. package/dist/main.js +1 -1
  32. package/dist/main.js.map +1 -1
  33. package/dist/provisioner.d.ts +2 -0
  34. package/dist/provisioner.d.ts.map +1 -1
  35. package/dist/provisioner.js +16 -0
  36. package/dist/provisioner.js.map +1 -1
  37. package/dist/runtime/conversation-orchestrator.d.ts +5 -1
  38. package/dist/runtime/conversation-orchestrator.d.ts.map +1 -1
  39. package/dist/runtime/conversation-orchestrator.js +9 -10
  40. package/dist/runtime/conversation-orchestrator.js.map +1 -1
  41. package/dist/runtime/session-runtime.d.ts +0 -1
  42. package/dist/runtime/session-runtime.d.ts.map +1 -1
  43. package/dist/runtime/session-runtime.js +14 -15
  44. package/dist/runtime/session-runtime.js.map +1 -1
  45. package/dist/session-view/portal.d.ts.map +1 -1
  46. package/dist/session-view/portal.js +15 -15
  47. package/dist/session-view/portal.js.map +1 -1
  48. package/dist/session-view/service.d.ts +3 -3
  49. package/dist/session-view/service.d.ts.map +1 -1
  50. package/dist/session-view/service.js +128 -28
  51. package/dist/session-view/service.js.map +1 -1
  52. package/dist/sessions/chat-session-manager.d.ts +62 -0
  53. package/dist/sessions/chat-session-manager.d.ts.map +1 -0
  54. package/dist/sessions/chat-session-manager.js +439 -0
  55. package/dist/sessions/chat-session-manager.js.map +1 -0
  56. package/dist/sessions/store.d.ts +2 -22
  57. package/dist/sessions/store.d.ts.map +1 -1
  58. package/dist/sessions/store.js +31 -158
  59. package/dist/sessions/store.js.map +1 -1
  60. package/dist/tools/index.d.ts +10 -2
  61. package/dist/tools/index.d.ts.map +1 -1
  62. package/dist/tools/index.js +5 -1
  63. package/dist/tools/index.js.map +1 -1
  64. package/dist/tools/sandbox.d.ts +22 -0
  65. package/dist/tools/sandbox.d.ts.map +1 -0
  66. package/dist/tools/sandbox.js +73 -0
  67. package/dist/tools/sandbox.js.map +1 -0
  68. package/package.json +1 -1
  69. package/dist/adapters/slack/branch-manager.d.ts +0 -28
  70. package/dist/adapters/slack/branch-manager.d.ts.map +0 -1
  71. package/dist/adapters/slack/branch-manager.js +0 -117
  72. package/dist/adapters/slack/branch-manager.js.map +0 -1
  73. package/dist/conversation-history.d.ts +0 -16
  74. package/dist/conversation-history.d.ts.map +0 -1
  75. package/dist/conversation-history.js +0 -144
  76. package/dist/conversation-history.js.map +0 -1
  77. package/dist/login/session.d.ts.map +0 -1
  78. package/dist/login/session.js.map +0 -1
@@ -22,28 +22,28 @@ export function loadSessionViewModel(sessionFile) {
22
22
  const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;
23
23
  const parent = header.parentSession
24
24
  ? buildSessionRelation(resolve(header.parentSession), "parent")
25
- : undefined;
26
- const forks = listRelatedSessionFiles(resolvedFile)
25
+ : buildInferredThreadParentRelation(resolvedFile);
26
+ const threads = listRelatedSessionFiles(resolvedFile)
27
27
  .filter((candidate) => candidate !== resolvedFile)
28
- .map((candidate) => buildSessionRelation(candidate, "fork", resolvedFile))
28
+ .map((candidate) => buildSessionRelation(candidate, "thread", resolvedFile))
29
29
  .filter((relation) => relation !== null)
30
30
  .toSorted((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));
31
- const forksByEntryId = new Map();
32
- for (const fork of forks) {
33
- if (!fork.anchorEntryId)
31
+ const threadsByEntryId = new Map();
32
+ for (const thread of threads) {
33
+ if (!thread.anchorEntryId)
34
34
  continue;
35
- const bucket = forksByEntryId.get(fork.anchorEntryId) ?? [];
36
- bucket.push(fork);
37
- forksByEntryId.set(fork.anchorEntryId, bucket);
35
+ const bucket = threadsByEntryId.get(thread.anchorEntryId) ?? [];
36
+ bucket.push(thread);
37
+ threadsByEntryId.set(thread.anchorEntryId, bucket);
38
38
  }
39
39
  const items = entries.flatMap((entry) => {
40
40
  const item = mapEntryToItem(entry);
41
41
  if (!item)
42
42
  return [];
43
43
  if (item.entryId) {
44
- const anchoredForks = forksByEntryId.get(item.entryId);
45
- if (anchoredForks) {
46
- item.forks = anchoredForks;
44
+ const anchoredThreads = threadsByEntryId.get(item.entryId);
45
+ if (anchoredThreads) {
46
+ item.threads = anchoredThreads;
47
47
  }
48
48
  }
49
49
  return [item];
@@ -57,7 +57,7 @@ export function loadSessionViewModel(sessionFile) {
57
57
  entryCount: entries.length,
58
58
  items,
59
59
  parent: parent ?? undefined,
60
- forks,
60
+ threads,
61
61
  };
62
62
  }
63
63
  export function resolveRequestedSessionFile(baseSessionFile, requestedFileName) {
@@ -107,19 +107,15 @@ function buildSessionRelation(sessionFile, kind, expectedParent) {
107
107
  log.logWarning(`Skipping session file with missing header while building ${kind} relation: ${sessionFile}`);
108
108
  return null;
109
109
  }
110
- if (kind === "fork") {
111
- const parentSession = resolve(header.parentSession ?? "");
112
- if (parentSession !== expectedParent &&
113
- !(expectedParent &&
114
- isPlatformHistorySession(expectedParent) &&
115
- isPlatformHistorySession(parentSession))) {
116
- return null;
117
- }
110
+ if (kind === "thread" &&
111
+ !isChildThreadSession(sessionFile, header.parentSession, expectedParent)) {
112
+ return null;
118
113
  }
119
114
  const entries = sm.getEntries();
120
115
  const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;
121
- const anchorEntryId = kind === "fork" && expectedParent
122
- ? findForkAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries)
116
+ const threadId = kind === "thread" ? getFixedThreadSessionId(sessionFile) : null;
117
+ const anchorEntryId = kind === "thread" && expectedParent
118
+ ? findThreadAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries, threadId)
123
119
  : undefined;
124
120
  return {
125
121
  kind,
@@ -132,7 +128,44 @@ function buildSessionRelation(sessionFile, kind, expectedParent) {
132
128
  anchorEntryId,
133
129
  };
134
130
  }
135
- function findForkAnchorEntryId(parentEntries, childEntries) {
131
+ function buildInferredThreadParentRelation(sessionFile) {
132
+ if (!getFixedThreadSessionId(sessionFile))
133
+ return undefined;
134
+ const parentSession = resolveCurrentChannelSessionForSessionFile(sessionFile);
135
+ if (!parentSession || parentSession === sessionFile)
136
+ return undefined;
137
+ return buildSessionRelation(parentSession, "parent") ?? undefined;
138
+ }
139
+ function isChildThreadSession(sessionFile, parentSession, expectedParent) {
140
+ if (!expectedParent)
141
+ return false;
142
+ if (parentSession) {
143
+ const resolvedParent = resolve(parentSession);
144
+ return (resolvedParent === expectedParent ||
145
+ (isPlatformHistorySession(expectedParent) && isPlatformHistorySession(resolvedParent)));
146
+ }
147
+ if (!getFixedThreadSessionId(sessionFile))
148
+ return false;
149
+ return resolveCurrentChannelSessionForSessionFile(sessionFile) === expectedParent;
150
+ }
151
+ function resolveCurrentChannelSessionForSessionFile(sessionFile) {
152
+ return resolveChannelSessionFile(dirname(dirname(sessionFile)));
153
+ }
154
+ function getFixedThreadSessionId(sessionFile) {
155
+ const fileName = basename(sessionFile);
156
+ if (!fileName.endsWith(".jsonl"))
157
+ return null;
158
+ if (/^\d{4}-\d{2}-\d{2}T.+_[0-9a-f]{8}\.jsonl$/i.test(fileName))
159
+ return null;
160
+ return fileName.slice(0, -".jsonl".length);
161
+ }
162
+ function findThreadAnchorEntryId(parentEntries, childEntries, threadId) {
163
+ const threadTimestamp = parseThreadTimestamp(threadId);
164
+ if (threadTimestamp !== undefined) {
165
+ const timestampAnchor = findEntryIdByMessageTimestamp(parentEntries, threadTimestamp);
166
+ if (timestampAnchor)
167
+ return timestampAnchor;
168
+ }
136
169
  let sharedCount = 0;
137
170
  while (sharedCount < parentEntries.length &&
138
171
  sharedCount < childEntries.length &&
@@ -142,11 +175,78 @@ function findForkAnchorEntryId(parentEntries, childEntries) {
142
175
  if (sharedCount > 0) {
143
176
  return parentEntries[sharedCount - 1]?.id;
144
177
  }
178
+ const contentAnchor = findSharedContentAnchorEntryId(parentEntries, childEntries);
179
+ if (contentAnchor)
180
+ return contentAnchor;
145
181
  const childRoot = findComparableUserMessage(childEntries);
146
182
  if (!childRoot)
147
183
  return undefined;
148
184
  return findParentAnchorByRootMessage(parentEntries, childRoot);
149
185
  }
186
+ function parseThreadTimestamp(threadId) {
187
+ if (!threadId)
188
+ return undefined;
189
+ const timestamp = Number(threadId) * 1000;
190
+ return Number.isFinite(timestamp) ? timestamp : undefined;
191
+ }
192
+ function findEntryIdByMessageTimestamp(entries, timestamp) {
193
+ for (const entry of entries) {
194
+ if (entry.type !== "message")
195
+ continue;
196
+ const messageTimestamp = entry.message.timestamp;
197
+ if (messageTimestamp === timestamp)
198
+ return entry.id;
199
+ }
200
+ return undefined;
201
+ }
202
+ function findSharedContentAnchorEntryId(parentEntries, childEntries) {
203
+ const parentMessages = parentEntries.flatMap((entry) => {
204
+ const comparable = getComparableSessionMessage(entry);
205
+ return comparable ? [comparable] : [];
206
+ });
207
+ const childMessages = childEntries.flatMap((entry) => {
208
+ const comparable = getComparableSessionMessage(entry);
209
+ return comparable ? [comparable] : [];
210
+ });
211
+ if (parentMessages.length === 0 || childMessages.length === 0)
212
+ return undefined;
213
+ let bestParentEnd = -1;
214
+ let bestLength = 0;
215
+ for (let parentStart = 0; parentStart < parentMessages.length; parentStart++) {
216
+ let length = 0;
217
+ while (parentStart + length < parentMessages.length &&
218
+ length < childMessages.length &&
219
+ comparableSessionMessagesMatch(parentMessages[parentStart + length], childMessages[length])) {
220
+ length += 1;
221
+ }
222
+ if (length > bestLength) {
223
+ bestLength = length;
224
+ bestParentEnd = parentStart + length - 1;
225
+ }
226
+ }
227
+ return bestLength > 0 ? parentMessages[bestParentEnd]?.entryId : undefined;
228
+ }
229
+ function getComparableSessionMessage(entry) {
230
+ if (entry.type !== "message")
231
+ return null;
232
+ const role = entry.message.role;
233
+ if (role !== "user" && role !== "assistant")
234
+ return null;
235
+ const body = role === "user"
236
+ ? contentToText(entry.message.content)
237
+ : assistantContentToText(entry.message.content);
238
+ const normalizedText = normalizeComparableSessionText(body, role);
239
+ if (!normalizedText)
240
+ return null;
241
+ return { entryId: entry.id, role, normalizedText };
242
+ }
243
+ function comparableSessionMessagesMatch(parent, child) {
244
+ return parent.role === child.role && parent.normalizedText === child.normalizedText;
245
+ }
246
+ function normalizeComparableSessionText(text, role) {
247
+ const normalized = role === "user" ? normalizeComparableUserText(text) : text.trim();
248
+ return normalized.replace(/\s+/g, " ").trim();
249
+ }
150
250
  function findParentAnchorByRootMessage(parentEntries, childRoot) {
151
251
  let textMatchId;
152
252
  for (const entry of parentEntries) {
@@ -227,7 +327,7 @@ function mapEntryToItem(entry) {
227
327
  case "compaction":
228
328
  return mapCompactionEntry(entry);
229
329
  case "branch_summary":
230
- return mapBranchSummaryEntry(entry);
330
+ return mapSessionSummaryEntry(entry);
231
331
  case "custom_message":
232
332
  return {
233
333
  kind: "system",
@@ -327,7 +427,7 @@ function mapMessageEntry(entry) {
327
427
  case "branchSummary":
328
428
  return {
329
429
  kind: "system",
330
- title: "Branch summary",
430
+ title: "Session summary",
331
431
  body: String(message.summary ?? ""),
332
432
  meta: entry.timestamp,
333
433
  tone: "muted",
@@ -362,10 +462,10 @@ function mapCompactionEntry(entry) {
362
462
  tone: "muted",
363
463
  };
364
464
  }
365
- function mapBranchSummaryEntry(entry) {
465
+ function mapSessionSummaryEntry(entry) {
366
466
  return {
367
467
  kind: "system",
368
- title: "Branch summary",
468
+ title: "Session summary",
369
469
  body: entry.summary,
370
470
  meta: entry.timestamp,
371
471
  tone: "muted",
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/session-view/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, dirname, join, resolve } from \"path\";\nimport { existsSync, readdirSync } from \"fs\";\nimport {\n SessionManager,\n type BranchSummaryEntry,\n type CompactionEntry,\n type SessionEntry,\n type SessionMessageEntry,\n} from \"@earendil-works/pi-coding-agent\";\nimport {\n getThreadSessionFile,\n resolveChannelSessionFile,\n tryResolveThreadSession,\n} from \"../sessions/store.js\";\nimport { isPlatformHistorySession } from \"../sessions/metadata.js\";\nimport * as log from \"../log.js\";\n\nexport interface SessionViewItem {\n kind: \"user\" | \"assistant\" | \"tool\" | \"system\";\n title: string;\n body?: string;\n meta?: string;\n tone?: \"default\" | \"ok\" | \"err\" | \"muted\";\n entryId?: string;\n forks?: SessionViewRelation[];\n}\n\nexport interface SessionViewRelation {\n kind: \"parent\" | \"fork\";\n fileName: string;\n sessionId: string;\n title: string;\n updatedAt: string;\n entryCount: number;\n summary?: string;\n anchorEntryId?: string;\n}\n\nexport interface SessionViewModel {\n sessionId: string;\n fileName: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n entryCount: number;\n items: SessionViewItem[];\n parent?: SessionViewRelation;\n forks: SessionViewRelation[];\n}\n\nexport function resolveExistingSessionFile(\n workingDir: string,\n conversationId: string,\n sessionKey: string,\n): string | null {\n const conversationDir = join(workingDir, conversationId);\n if (sessionKey.includes(\":\")) {\n return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey));\n }\n return resolveChannelSessionFile(conversationDir);\n}\n\nexport function loadSessionViewModel(sessionFile: string): SessionViewModel {\n const resolvedFile = resolve(sessionFile);\n const sm = SessionManager.open(resolvedFile);\n const header = sm.getHeader();\n if (!header) throw new Error(`No valid session found: ${sessionFile}`);\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;\n\n const parent = header.parentSession\n ? buildSessionRelation(resolve(header.parentSession), \"parent\")\n : undefined;\n const forks = listRelatedSessionFiles(resolvedFile)\n .filter((candidate) => candidate !== resolvedFile)\n .map((candidate) => buildSessionRelation(candidate, \"fork\", resolvedFile))\n .filter((relation): relation is SessionViewRelation => relation !== null)\n .toSorted((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));\n\n const forksByEntryId = new Map<string, SessionViewRelation[]>();\n for (const fork of forks) {\n if (!fork.anchorEntryId) continue;\n const bucket = forksByEntryId.get(fork.anchorEntryId) ?? [];\n bucket.push(fork);\n forksByEntryId.set(fork.anchorEntryId, bucket);\n }\n\n const items = entries.flatMap((entry) => {\n const item = mapEntryToItem(entry);\n if (!item) return [];\n if (item.entryId) {\n const anchoredForks = forksByEntryId.get(item.entryId);\n if (anchoredForks) {\n item.forks = anchoredForks;\n }\n }\n return [item];\n });\n\n return {\n sessionId: header.id,\n fileName: basename(resolvedFile),\n title,\n createdAt: header.timestamp,\n updatedAt,\n entryCount: entries.length,\n items,\n parent: parent ?? undefined,\n forks,\n };\n}\n\nexport function resolveRequestedSessionFile(\n baseSessionFile: string,\n requestedFileName?: string | null,\n): string | null {\n const resolvedBase = resolve(baseSessionFile);\n if (!requestedFileName) return resolvedBase;\n\n const trimmed = requestedFileName.trim();\n if (!trimmed) return resolvedBase;\n\n const fileName = basename(trimmed);\n if (fileName !== trimmed || !fileName.endsWith(\".jsonl\")) return null;\n\n const candidate = join(dirname(resolvedBase), fileName);\n if (!existsSync(candidate)) return null;\n\n let sm: SessionManager;\n try {\n sm = SessionManager.open(candidate);\n } catch (err) {\n throw new Error(\n `Session file is corrupted: ${candidate}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n { cause: err },\n );\n }\n if (!sm.getHeader()) {\n throw new Error(`Session file is missing a valid header: ${candidate}`);\n }\n return candidate;\n}\n\nfunction listRelatedSessionFiles(sessionFile: string): string[] {\n const dir = dirname(sessionFile);\n if (!existsSync(dir)) return [];\n\n return readdirSync(dir)\n .filter((name) => name.endsWith(\".jsonl\"))\n .map((fileName) => join(dir, fileName));\n}\n\nfunction buildSessionRelation(\n sessionFile: string,\n kind: \"parent\" | \"fork\",\n expectedParent?: string,\n): SessionViewRelation | null {\n let sm: SessionManager;\n try {\n sm = SessionManager.open(sessionFile);\n } catch (err) {\n log.logWarning(\n `Skipping corrupted session file while building ${kind} relation: ${sessionFile}`,\n err instanceof Error ? err.message : String(err),\n );\n return null;\n }\n const header = sm.getHeader();\n if (!header) {\n log.logWarning(\n `Skipping session file with missing header while building ${kind} relation: ${sessionFile}`,\n );\n return null;\n }\n if (kind === \"fork\") {\n const parentSession = resolve(header.parentSession ?? \"\");\n if (\n parentSession !== expectedParent &&\n !(\n expectedParent &&\n isPlatformHistorySession(expectedParent) &&\n isPlatformHistorySession(parentSession)\n )\n ) {\n return null;\n }\n }\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const anchorEntryId =\n kind === \"fork\" && expectedParent\n ? findForkAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries)\n : undefined;\n return {\n kind,\n fileName: basename(sessionFile),\n sessionId: header.id,\n title: sm.getSessionName() || `Session ${header.id.slice(0, 8)}`,\n updatedAt,\n entryCount: entries.length,\n summary: extractSessionSummary(entries),\n anchorEntryId,\n };\n}\n\nfunction findForkAnchorEntryId(\n parentEntries: SessionEntry[],\n childEntries: SessionEntry[],\n): string | undefined {\n let sharedCount = 0;\n while (\n sharedCount < parentEntries.length &&\n sharedCount < childEntries.length &&\n parentEntries[sharedCount]?.id === childEntries[sharedCount]?.id\n ) {\n sharedCount += 1;\n }\n\n if (sharedCount > 0) {\n return parentEntries[sharedCount - 1]?.id;\n }\n\n const childRoot = findComparableUserMessage(childEntries);\n if (!childRoot) return undefined;\n\n return findParentAnchorByRootMessage(parentEntries, childRoot);\n}\n\nfunction findParentAnchorByRootMessage(\n parentEntries: SessionEntry[],\n childRoot: ComparableUserMessage,\n): string | undefined {\n let textMatchId: string | undefined;\n\n for (const entry of parentEntries) {\n const comparable = getComparableUserMessage(entry);\n if (!comparable) continue;\n if (comparable.normalizedText !== childRoot.normalizedText) continue;\n if (\n childRoot.messageTimestamp !== undefined &&\n comparable.messageTimestamp !== undefined &&\n comparable.messageTimestamp === childRoot.messageTimestamp\n ) {\n return entry.id;\n }\n textMatchId ??= entry.id;\n }\n\n return textMatchId;\n}\n\ninterface ComparableUserMessage {\n normalizedText: string;\n messageTimestamp?: number;\n}\n\nfunction findComparableUserMessage(entries: SessionEntry[]): ComparableUserMessage | null {\n for (const entry of entries) {\n const comparable = getComparableUserMessage(entry);\n if (comparable) return comparable;\n }\n return null;\n}\n\nfunction getComparableUserMessage(entry: SessionEntry): ComparableUserMessage | null {\n if (entry.type !== \"message\" || entry.message.role !== \"user\") return null;\n\n const body = contentToText(entry.message.content);\n const normalizedText = normalizeComparableUserText(body);\n if (!normalizedText) return null;\n\n const messageTimestamp =\n typeof entry.message.timestamp === \"number\" ? entry.message.timestamp : undefined;\n return { normalizedText, messageTimestamp };\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction extractSessionSummary(entries: SessionEntry[]): string | undefined {\n for (const entry of entries) {\n if (entry.type !== \"message\") continue;\n const item = mapEntryToItem(entry);\n if (!item?.body) continue;\n return collapseSummary(item.body);\n }\n return undefined;\n}\n\nfunction collapseSummary(text: string): string {\n const singleLine = text.replace(/\\s+/g, \" \").trim();\n return singleLine.length > 96 ? `${singleLine.slice(0, 93)}…` : singleLine;\n}\n\nfunction mapEntryToItem(entry: SessionEntry): SessionViewItem | null {\n switch (entry.type) {\n case \"message\":\n return mapMessageEntry(entry);\n case \"model_change\":\n return {\n kind: \"system\",\n title: \"Model changed\",\n body: `${entry.provider} / ${entry.modelId}`,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"thinking_level_change\":\n return {\n kind: \"system\",\n title: \"Thinking level changed\",\n body: entry.thinkingLevel,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"compaction\":\n return mapCompactionEntry(entry);\n case \"branch_summary\":\n return mapBranchSummaryEntry(entry);\n case \"custom_message\":\n return {\n kind: \"system\",\n title: `Custom message · ${entry.customType}`,\n body: contentToText(entry.content),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom data · ${entry.customType}`,\n body: entry.data === undefined ? \"(no data)\" : JSON.stringify(entry.data, null, 2),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"label\":\n return {\n kind: \"system\",\n title: \"Label updated\",\n body: entry.label || \"(cleared)\",\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"session_info\":\n return entry.name\n ? {\n kind: \"system\",\n title: \"Session renamed\",\n body: entry.name,\n meta: entry.timestamp,\n tone: \"muted\",\n }\n : null;\n default:\n return null;\n }\n}\n\nfunction mapMessageEntry(entry: SessionMessageEntry): SessionViewItem {\n const message = entry.message as unknown as Record<string, unknown> & {\n role?: string;\n content?: unknown;\n provider?: string;\n model?: string;\n toolName?: string;\n isError?: boolean;\n command?: string;\n output?: string;\n exitCode?: number;\n cancelled?: boolean;\n truncated?: boolean;\n stopReason?: string;\n errorMessage?: string;\n customType?: string;\n summary?: string;\n };\n\n switch (message.role) {\n case \"user\":\n return {\n kind: \"user\",\n title: \"User\",\n body: contentToText(message.content),\n meta: entry.timestamp,\n entryId: entry.id,\n };\n case \"assistant\": {\n const assistantBody =\n assistantContentToText(message.content) ||\n (message.errorMessage ? `_${message.errorMessage}_` : \"\");\n const metaParts = [message.provider, message.model, message.stopReason].filter(Boolean);\n return {\n kind: \"assistant\",\n title: \"Assistant\",\n body: assistantBody,\n meta:\n metaParts.length > 0 ? `${entry.timestamp} · ${metaParts.join(\" · \")}` : entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"toolResult\":\n return {\n kind: \"tool\",\n title: `Tool result · ${String(message.toolName ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: message.isError ? \"err\" : \"ok\",\n entryId: entry.id,\n };\n case \"bashExecution\": {\n const command = String(message.command ?? \"\").trim();\n const output = String(message.output ?? \"\").trim();\n const details = [\n typeof message.exitCode === \"number\" ? `[exitCode] ${message.exitCode}` : \"\",\n message.cancelled ? `[cancelled] true` : \"\",\n message.truncated ? `[truncated] true` : \"\",\n ].filter(Boolean);\n const body = [command ? `$ ${command}` : \"\", output, ...details].filter(Boolean).join(\"\\n\\n\");\n return {\n kind: \"tool\",\n title: \"Bash execution\",\n body: body || \"(no output)\",\n meta: entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom message · ${String(message.customType ?? \"custom\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"branchSummary\":\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"compactionSummary\":\n return {\n kind: \"system\",\n title: \"Compaction summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n default:\n return {\n kind: \"system\",\n title: `Message · ${String(message.role ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n }\n}\n\nfunction mapCompactionEntry(entry: CompactionEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Context compacted\",\n body: entry.summary,\n meta: `${entry.timestamp} · ${entry.tokensBefore} tokens before compaction`,\n tone: \"muted\",\n };\n}\n\nfunction mapBranchSummaryEntry(entry: BranchSummaryEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: entry.summary,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n}\n\nfunction assistantContentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n\nfunction contentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n"]}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/session-view/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, dirname, join, resolve } from \"path\";\nimport { existsSync, readdirSync } from \"fs\";\nimport {\n SessionManager,\n type BranchSummaryEntry as SessionBranchSummaryEntry,\n type CompactionEntry,\n type SessionEntry,\n type SessionMessageEntry,\n} from \"@earendil-works/pi-coding-agent\";\nimport {\n getThreadSessionFile,\n resolveChannelSessionFile,\n tryResolveThreadSession,\n} from \"../sessions/store.js\";\nimport { isPlatformHistorySession } from \"../sessions/metadata.js\";\nimport * as log from \"../log.js\";\n\nexport interface SessionViewItem {\n kind: \"user\" | \"assistant\" | \"tool\" | \"system\";\n title: string;\n body?: string;\n meta?: string;\n tone?: \"default\" | \"ok\" | \"err\" | \"muted\";\n entryId?: string;\n threads?: SessionViewRelation[];\n}\n\nexport interface SessionViewRelation {\n kind: \"parent\" | \"thread\";\n fileName: string;\n sessionId: string;\n title: string;\n updatedAt: string;\n entryCount: number;\n summary?: string;\n anchorEntryId?: string;\n}\n\nexport interface SessionViewModel {\n sessionId: string;\n fileName: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n entryCount: number;\n items: SessionViewItem[];\n parent?: SessionViewRelation;\n threads: SessionViewRelation[];\n}\n\nexport function resolveExistingSessionFile(\n workingDir: string,\n conversationId: string,\n sessionKey: string,\n): string | null {\n const conversationDir = join(workingDir, conversationId);\n if (sessionKey.includes(\":\")) {\n return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey));\n }\n return resolveChannelSessionFile(conversationDir);\n}\n\nexport function loadSessionViewModel(sessionFile: string): SessionViewModel {\n const resolvedFile = resolve(sessionFile);\n const sm = SessionManager.open(resolvedFile);\n const header = sm.getHeader();\n if (!header) throw new Error(`No valid session found: ${sessionFile}`);\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;\n\n const parent = header.parentSession\n ? buildSessionRelation(resolve(header.parentSession), \"parent\")\n : buildInferredThreadParentRelation(resolvedFile);\n const threads = listRelatedSessionFiles(resolvedFile)\n .filter((candidate) => candidate !== resolvedFile)\n .map((candidate) => buildSessionRelation(candidate, \"thread\", resolvedFile))\n .filter((relation): relation is SessionViewRelation => relation !== null)\n .toSorted((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));\n\n const threadsByEntryId = new Map<string, SessionViewRelation[]>();\n for (const thread of threads) {\n if (!thread.anchorEntryId) continue;\n const bucket = threadsByEntryId.get(thread.anchorEntryId) ?? [];\n bucket.push(thread);\n threadsByEntryId.set(thread.anchorEntryId, bucket);\n }\n\n const items = entries.flatMap((entry) => {\n const item = mapEntryToItem(entry);\n if (!item) return [];\n if (item.entryId) {\n const anchoredThreads = threadsByEntryId.get(item.entryId);\n if (anchoredThreads) {\n item.threads = anchoredThreads;\n }\n }\n return [item];\n });\n\n return {\n sessionId: header.id,\n fileName: basename(resolvedFile),\n title,\n createdAt: header.timestamp,\n updatedAt,\n entryCount: entries.length,\n items,\n parent: parent ?? undefined,\n threads,\n };\n}\n\nexport function resolveRequestedSessionFile(\n baseSessionFile: string,\n requestedFileName?: string | null,\n): string | null {\n const resolvedBase = resolve(baseSessionFile);\n if (!requestedFileName) return resolvedBase;\n\n const trimmed = requestedFileName.trim();\n if (!trimmed) return resolvedBase;\n\n const fileName = basename(trimmed);\n if (fileName !== trimmed || !fileName.endsWith(\".jsonl\")) return null;\n\n const candidate = join(dirname(resolvedBase), fileName);\n if (!existsSync(candidate)) return null;\n\n let sm: SessionManager;\n try {\n sm = SessionManager.open(candidate);\n } catch (err) {\n throw new Error(\n `Session file is corrupted: ${candidate}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n { cause: err },\n );\n }\n if (!sm.getHeader()) {\n throw new Error(`Session file is missing a valid header: ${candidate}`);\n }\n return candidate;\n}\n\nfunction listRelatedSessionFiles(sessionFile: string): string[] {\n const dir = dirname(sessionFile);\n if (!existsSync(dir)) return [];\n\n return readdirSync(dir)\n .filter((name) => name.endsWith(\".jsonl\"))\n .map((fileName) => join(dir, fileName));\n}\n\nfunction buildSessionRelation(\n sessionFile: string,\n kind: \"parent\" | \"thread\",\n expectedParent?: string,\n): SessionViewRelation | null {\n let sm: SessionManager;\n try {\n sm = SessionManager.open(sessionFile);\n } catch (err) {\n log.logWarning(\n `Skipping corrupted session file while building ${kind} relation: ${sessionFile}`,\n err instanceof Error ? err.message : String(err),\n );\n return null;\n }\n const header = sm.getHeader();\n if (!header) {\n log.logWarning(\n `Skipping session file with missing header while building ${kind} relation: ${sessionFile}`,\n );\n return null;\n }\n if (\n kind === \"thread\" &&\n !isChildThreadSession(sessionFile, header.parentSession, expectedParent)\n ) {\n return null;\n }\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const threadId = kind === \"thread\" ? getFixedThreadSessionId(sessionFile) : null;\n const anchorEntryId =\n kind === \"thread\" && expectedParent\n ? findThreadAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries, threadId)\n : undefined;\n return {\n kind,\n fileName: basename(sessionFile),\n sessionId: header.id,\n title: sm.getSessionName() || `Session ${header.id.slice(0, 8)}`,\n updatedAt,\n entryCount: entries.length,\n summary: extractSessionSummary(entries),\n anchorEntryId,\n };\n}\n\nfunction buildInferredThreadParentRelation(sessionFile: string): SessionViewRelation | undefined {\n if (!getFixedThreadSessionId(sessionFile)) return undefined;\n\n const parentSession = resolveCurrentChannelSessionForSessionFile(sessionFile);\n if (!parentSession || parentSession === sessionFile) return undefined;\n\n return buildSessionRelation(parentSession, \"parent\") ?? undefined;\n}\n\nfunction isChildThreadSession(\n sessionFile: string,\n parentSession: string | undefined,\n expectedParent: string | undefined,\n): boolean {\n if (!expectedParent) return false;\n\n if (parentSession) {\n const resolvedParent = resolve(parentSession);\n return (\n resolvedParent === expectedParent ||\n (isPlatformHistorySession(expectedParent) && isPlatformHistorySession(resolvedParent))\n );\n }\n\n if (!getFixedThreadSessionId(sessionFile)) return false;\n return resolveCurrentChannelSessionForSessionFile(sessionFile) === expectedParent;\n}\n\nfunction resolveCurrentChannelSessionForSessionFile(sessionFile: string): string | null {\n return resolveChannelSessionFile(dirname(dirname(sessionFile)));\n}\n\nfunction getFixedThreadSessionId(sessionFile: string): string | null {\n const fileName = basename(sessionFile);\n if (!fileName.endsWith(\".jsonl\")) return null;\n if (/^\\d{4}-\\d{2}-\\d{2}T.+_[0-9a-f]{8}\\.jsonl$/i.test(fileName)) return null;\n return fileName.slice(0, -\".jsonl\".length);\n}\n\nfunction findThreadAnchorEntryId(\n parentEntries: SessionEntry[],\n childEntries: SessionEntry[],\n threadId: string | null,\n): string | undefined {\n const threadTimestamp = parseThreadTimestamp(threadId);\n if (threadTimestamp !== undefined) {\n const timestampAnchor = findEntryIdByMessageTimestamp(parentEntries, threadTimestamp);\n if (timestampAnchor) return timestampAnchor;\n }\n let sharedCount = 0;\n while (\n sharedCount < parentEntries.length &&\n sharedCount < childEntries.length &&\n parentEntries[sharedCount]?.id === childEntries[sharedCount]?.id\n ) {\n sharedCount += 1;\n }\n\n if (sharedCount > 0) {\n return parentEntries[sharedCount - 1]?.id;\n }\n\n const contentAnchor = findSharedContentAnchorEntryId(parentEntries, childEntries);\n if (contentAnchor) return contentAnchor;\n\n const childRoot = findComparableUserMessage(childEntries);\n if (!childRoot) return undefined;\n\n return findParentAnchorByRootMessage(parentEntries, childRoot);\n}\n\nfunction parseThreadTimestamp(threadId: string | null): number | undefined {\n if (!threadId) return undefined;\n const timestamp = Number(threadId) * 1000;\n return Number.isFinite(timestamp) ? timestamp : undefined;\n}\n\nfunction findEntryIdByMessageTimestamp(\n entries: SessionEntry[],\n timestamp: number,\n): string | undefined {\n for (const entry of entries) {\n if (entry.type !== \"message\") continue;\n const messageTimestamp = (entry.message as { timestamp?: unknown }).timestamp;\n if (messageTimestamp === timestamp) return entry.id;\n }\n return undefined;\n}\n\nfunction findSharedContentAnchorEntryId(\n parentEntries: SessionEntry[],\n childEntries: SessionEntry[],\n): string | undefined {\n const parentMessages = parentEntries.flatMap((entry) => {\n const comparable = getComparableSessionMessage(entry);\n return comparable ? [comparable] : [];\n });\n const childMessages = childEntries.flatMap((entry) => {\n const comparable = getComparableSessionMessage(entry);\n return comparable ? [comparable] : [];\n });\n if (parentMessages.length === 0 || childMessages.length === 0) return undefined;\n\n let bestParentEnd = -1;\n let bestLength = 0;\n for (let parentStart = 0; parentStart < parentMessages.length; parentStart++) {\n let length = 0;\n while (\n parentStart + length < parentMessages.length &&\n length < childMessages.length &&\n comparableSessionMessagesMatch(parentMessages[parentStart + length], childMessages[length])\n ) {\n length += 1;\n }\n if (length > bestLength) {\n bestLength = length;\n bestParentEnd = parentStart + length - 1;\n }\n }\n\n return bestLength > 0 ? parentMessages[bestParentEnd]?.entryId : undefined;\n}\n\ninterface ComparableSessionMessage {\n entryId: string;\n role: \"user\" | \"assistant\";\n normalizedText: string;\n}\n\nfunction getComparableSessionMessage(entry: SessionEntry): ComparableSessionMessage | null {\n if (entry.type !== \"message\") return null;\n const role = entry.message.role;\n if (role !== \"user\" && role !== \"assistant\") return null;\n\n const body =\n role === \"user\"\n ? contentToText(entry.message.content)\n : assistantContentToText(entry.message.content);\n const normalizedText = normalizeComparableSessionText(body, role);\n if (!normalizedText) return null;\n\n return { entryId: entry.id, role, normalizedText };\n}\n\nfunction comparableSessionMessagesMatch(\n parent: ComparableSessionMessage,\n child: ComparableSessionMessage,\n): boolean {\n return parent.role === child.role && parent.normalizedText === child.normalizedText;\n}\n\nfunction normalizeComparableSessionText(text: string, role: \"user\" | \"assistant\"): string {\n const normalized = role === \"user\" ? normalizeComparableUserText(text) : text.trim();\n return normalized.replace(/\\s+/g, \" \").trim();\n}\n\nfunction findParentAnchorByRootMessage(\n parentEntries: SessionEntry[],\n childRoot: ComparableUserMessage,\n): string | undefined {\n let textMatchId: string | undefined;\n\n for (const entry of parentEntries) {\n const comparable = getComparableUserMessage(entry);\n if (!comparable) continue;\n if (comparable.normalizedText !== childRoot.normalizedText) continue;\n if (\n childRoot.messageTimestamp !== undefined &&\n comparable.messageTimestamp !== undefined &&\n comparable.messageTimestamp === childRoot.messageTimestamp\n ) {\n return entry.id;\n }\n textMatchId ??= entry.id;\n }\n\n return textMatchId;\n}\n\ninterface ComparableUserMessage {\n normalizedText: string;\n messageTimestamp?: number;\n}\n\nfunction findComparableUserMessage(entries: SessionEntry[]): ComparableUserMessage | null {\n for (const entry of entries) {\n const comparable = getComparableUserMessage(entry);\n if (comparable) return comparable;\n }\n return null;\n}\n\nfunction getComparableUserMessage(entry: SessionEntry): ComparableUserMessage | null {\n if (entry.type !== \"message\" || entry.message.role !== \"user\") return null;\n\n const body = contentToText(entry.message.content);\n const normalizedText = normalizeComparableUserText(body);\n if (!normalizedText) return null;\n\n const messageTimestamp =\n typeof entry.message.timestamp === \"number\" ? entry.message.timestamp : undefined;\n return { normalizedText, messageTimestamp };\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction extractSessionSummary(entries: SessionEntry[]): string | undefined {\n for (const entry of entries) {\n if (entry.type !== \"message\") continue;\n const item = mapEntryToItem(entry);\n if (!item?.body) continue;\n return collapseSummary(item.body);\n }\n return undefined;\n}\n\nfunction collapseSummary(text: string): string {\n const singleLine = text.replace(/\\s+/g, \" \").trim();\n return singleLine.length > 96 ? `${singleLine.slice(0, 93)}…` : singleLine;\n}\n\nfunction mapEntryToItem(entry: SessionEntry): SessionViewItem | null {\n switch (entry.type) {\n case \"message\":\n return mapMessageEntry(entry);\n case \"model_change\":\n return {\n kind: \"system\",\n title: \"Model changed\",\n body: `${entry.provider} / ${entry.modelId}`,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"thinking_level_change\":\n return {\n kind: \"system\",\n title: \"Thinking level changed\",\n body: entry.thinkingLevel,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"compaction\":\n return mapCompactionEntry(entry);\n case \"branch_summary\":\n return mapSessionSummaryEntry(entry);\n case \"custom_message\":\n return {\n kind: \"system\",\n title: `Custom message · ${entry.customType}`,\n body: contentToText(entry.content),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom data · ${entry.customType}`,\n body: entry.data === undefined ? \"(no data)\" : JSON.stringify(entry.data, null, 2),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"label\":\n return {\n kind: \"system\",\n title: \"Label updated\",\n body: entry.label || \"(cleared)\",\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"session_info\":\n return entry.name\n ? {\n kind: \"system\",\n title: \"Session renamed\",\n body: entry.name,\n meta: entry.timestamp,\n tone: \"muted\",\n }\n : null;\n default:\n return null;\n }\n}\n\nfunction mapMessageEntry(entry: SessionMessageEntry): SessionViewItem {\n const message = entry.message as unknown as Record<string, unknown> & {\n role?: string;\n content?: unknown;\n provider?: string;\n model?: string;\n toolName?: string;\n isError?: boolean;\n command?: string;\n output?: string;\n exitCode?: number;\n cancelled?: boolean;\n truncated?: boolean;\n stopReason?: string;\n errorMessage?: string;\n customType?: string;\n summary?: string;\n };\n\n switch (message.role) {\n case \"user\":\n return {\n kind: \"user\",\n title: \"User\",\n body: contentToText(message.content),\n meta: entry.timestamp,\n entryId: entry.id,\n };\n case \"assistant\": {\n const assistantBody =\n assistantContentToText(message.content) ||\n (message.errorMessage ? `_${message.errorMessage}_` : \"\");\n const metaParts = [message.provider, message.model, message.stopReason].filter(Boolean);\n return {\n kind: \"assistant\",\n title: \"Assistant\",\n body: assistantBody,\n meta:\n metaParts.length > 0 ? `${entry.timestamp} · ${metaParts.join(\" · \")}` : entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"toolResult\":\n return {\n kind: \"tool\",\n title: `Tool result · ${String(message.toolName ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: message.isError ? \"err\" : \"ok\",\n entryId: entry.id,\n };\n case \"bashExecution\": {\n const command = String(message.command ?? \"\").trim();\n const output = String(message.output ?? \"\").trim();\n const details = [\n typeof message.exitCode === \"number\" ? `[exitCode] ${message.exitCode}` : \"\",\n message.cancelled ? `[cancelled] true` : \"\",\n message.truncated ? `[truncated] true` : \"\",\n ].filter(Boolean);\n const body = [command ? `$ ${command}` : \"\", output, ...details].filter(Boolean).join(\"\\n\\n\");\n return {\n kind: \"tool\",\n title: \"Bash execution\",\n body: body || \"(no output)\",\n meta: entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom message · ${String(message.customType ?? \"custom\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"branchSummary\":\n return {\n kind: \"system\",\n title: \"Session summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"compactionSummary\":\n return {\n kind: \"system\",\n title: \"Compaction summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n default:\n return {\n kind: \"system\",\n title: `Message · ${String(message.role ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n }\n}\n\nfunction mapCompactionEntry(entry: CompactionEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Context compacted\",\n body: entry.summary,\n meta: `${entry.timestamp} · ${entry.tokensBefore} tokens before compaction`,\n tone: \"muted\",\n };\n}\n\nfunction mapSessionSummaryEntry(entry: SessionBranchSummaryEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Session summary\",\n body: entry.summary,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n}\n\nfunction assistantContentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n\nfunction contentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n"]}
@@ -0,0 +1,62 @@
1
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
2
+ import { type ResolvedSessionScope } from "./store.js";
3
+ export interface ChatSessionManagerOptions {
4
+ recentDays?: number;
5
+ maxTopLevelMessages?: number;
6
+ now?: () => Date;
7
+ }
8
+ export interface ResolveChatSessionScopeOptions {
9
+ conversationDir: string;
10
+ sessionKey: string;
11
+ cwd?: string;
12
+ /** The triggering platform message ID. Excluded from bootstrap to avoid duplicate user turns. */
13
+ currentMessageId?: string;
14
+ }
15
+ export interface SyncChatSessionManagerOptions {
16
+ conversationDir: string;
17
+ sessionKey: string;
18
+ sessionManager: SessionManager;
19
+ /** The triggering platform message ID. Excluded from sync to avoid duplicate user turns. */
20
+ currentMessageId?: string;
21
+ }
22
+ export interface ResetChatSessionOptions {
23
+ conversationDir: string;
24
+ sessionKey: string;
25
+ cwd?: string;
26
+ }
27
+ export interface RegisterThreadSessionOptions {
28
+ conversationDir: string;
29
+ sessionKey: string;
30
+ cwd?: string;
31
+ }
32
+ export interface HasMaterializedSessionOptions {
33
+ conversationDir: string;
34
+ sessionKey: string;
35
+ }
36
+ export interface ThreadBootstrapWaitOptions {
37
+ parentSessionKey: string;
38
+ sessionKey: string;
39
+ hasThreadSession: () => boolean;
40
+ isParentRunning: () => boolean;
41
+ sleep?: (ms: number) => Promise<void>;
42
+ pollMs?: number;
43
+ }
44
+ export declare function isThreadSessionKey(sessionKey: string): boolean;
45
+ export declare function extractThreadId(sessionKey: string): string;
46
+ export declare function hasMaterializedChatSession(options: HasMaterializedSessionOptions): boolean;
47
+ export declare function registerThreadSession(options: RegisterThreadSessionOptions): string | null;
48
+ export declare function waitForThreadSessionBootstrap(options: ThreadBootstrapWaitOptions): Promise<boolean>;
49
+ export declare class ChatSessionManager {
50
+ private readonly recentDays;
51
+ private readonly maxTopLevelMessages;
52
+ private readonly now;
53
+ constructor(options?: ChatSessionManagerOptions);
54
+ resolveSessionScope(options: ResolveChatSessionScopeOptions): Promise<ResolvedSessionScope>;
55
+ syncSessionManager(options: SyncChatSessionManagerOptions): void;
56
+ resetSession(options: ResetChatSessionOptions): string;
57
+ registerThreadSession(options: RegisterThreadSessionOptions): string | null;
58
+ hasMaterializedSession(options: HasMaterializedSessionOptions): boolean;
59
+ private resolveTopLevelSessionFile;
60
+ private resolveThreadSessionScope;
61
+ }
62
+ //# sourceMappingURL=chat-session-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-session-manager.d.ts","sourceRoot":"","sources":["../../src/sessions/chat-session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAqB,MAAM,iCAAiC,CAAC;AAOpF,OAAO,EAUL,KAAK,oBAAoB,EAE1B,MAAM,YAAY,CAAC;AAapB,MAAM,WAAW,yBAAyB;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iGAAiG;IACjG,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,6BAA6B;IAC5C,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,cAAc,CAAC;IAC/B,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,6BAA6B;IAC5C,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC,eAAe,EAAE,MAAM,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAQ1F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM,GAAG,IAAI,CAQ1F;AAED,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,OAAO,CAAC,CAqBlB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IAEjC,YAAY,OAAO,GAAE,yBAA8B,EAIlD;IAEK,mBAAmB,CACvB,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,oBAAoB,CAAC,CAqB/B;IAED,kBAAkB,CAAC,OAAO,EAAE,6BAA6B,GAAG,IAAI,CAS/D;IAED,YAAY,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAUrD;IAED,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM,GAAG,IAAI,CAE1E;IAED,sBAAsB,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAEtE;IAED,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,yBAAyB;CAoClC","sourcesContent":["import { SessionManager, type SessionEntry } from \"@earendil-works/pi-coding-agent\";\nimport { join } from \"path\";\nimport type { ConversationLogMessage } from \"../context.js\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"../file-guards.js\";\nimport { atomicWritePrivateFile } from \"../fs-atomic.js\";\nimport * as log from \"../log.js\";\nimport { isPlatformHistorySession } from \"./metadata.js\";\nimport {\n createManagedSessionFile,\n createManagedSessionFileAtPath,\n extractSessionSuffix,\n getChannelSessionDir,\n getThreadSessionFile,\n openManagedSession,\n resolveChannelSessionFile,\n tryResolveCurrentSession,\n tryResolveThreadSession,\n type ResolvedSessionScope,\n type ThreadRootMessage,\n} from \"./store.js\";\n\nconst DEFAULT_RECENT_DAYS = 14;\nconst DEFAULT_MAX_TOP_LEVEL_MESSAGES = 200;\nconst CHAT_SYNC_CUSTOM_TYPE = \"mikan.chat_sync\";\n\ntype SessionAppendMessage = Parameters<SessionManager[\"appendMessage\"]>[0];\n\ninterface LogRecord {\n message: ConversationLogMessage;\n index: number;\n}\n\nexport interface ChatSessionManagerOptions {\n recentDays?: number;\n maxTopLevelMessages?: number;\n now?: () => Date;\n}\n\nexport interface ResolveChatSessionScopeOptions {\n conversationDir: string;\n sessionKey: string;\n cwd?: string;\n /** The triggering platform message ID. Excluded from bootstrap to avoid duplicate user turns. */\n currentMessageId?: string;\n}\n\nexport interface SyncChatSessionManagerOptions {\n conversationDir: string;\n sessionKey: string;\n sessionManager: SessionManager;\n /** The triggering platform message ID. Excluded from sync to avoid duplicate user turns. */\n currentMessageId?: string;\n}\n\nexport interface ResetChatSessionOptions {\n conversationDir: string;\n sessionKey: string;\n cwd?: string;\n}\n\nexport interface RegisterThreadSessionOptions {\n conversationDir: string;\n sessionKey: string;\n cwd?: string;\n}\n\nexport interface HasMaterializedSessionOptions {\n conversationDir: string;\n sessionKey: string;\n}\n\nexport interface ThreadBootstrapWaitOptions {\n parentSessionKey: string;\n sessionKey: string;\n hasThreadSession: () => boolean;\n isParentRunning: () => boolean;\n sleep?: (ms: number) => Promise<void>;\n pollMs?: number;\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function isThreadSessionKey(sessionKey: string): boolean {\n return sessionKey.includes(\":\");\n}\n\nexport function extractThreadId(sessionKey: string): string {\n return extractSessionSuffix(sessionKey);\n}\n\nexport function hasMaterializedChatSession(options: HasMaterializedSessionOptions): boolean {\n if (!isThreadSessionKey(options.sessionKey)) {\n return resolveChannelSessionFile(options.conversationDir) !== null;\n }\n return (\n tryResolveThreadSession(getThreadSessionFile(options.conversationDir, options.sessionKey)) !==\n null\n );\n}\n\nexport function registerThreadSession(options: RegisterThreadSessionOptions): string | null {\n if (!isThreadSessionKey(options.sessionKey)) return null;\n\n const threadFile = getThreadSessionFile(options.conversationDir, options.sessionKey);\n return (\n tryResolveThreadSession(threadFile) ??\n createManagedSessionFileAtPath(threadFile, options.cwd ?? options.conversationDir)\n );\n}\n\nexport async function waitForThreadSessionBootstrap(\n options: ThreadBootstrapWaitOptions,\n): Promise<boolean> {\n const {\n parentSessionKey,\n sessionKey,\n hasThreadSession,\n isParentRunning,\n sleep = defaultSleep,\n pollMs = 100,\n } = options;\n\n if (!isThreadSessionKey(sessionKey)) return false;\n if (sessionKey === parentSessionKey) return false;\n if (hasThreadSession()) return false;\n\n let waited = false;\n while (isParentRunning() && !hasThreadSession()) {\n waited = true;\n await sleep(pollMs);\n }\n\n return waited;\n}\n\nexport class ChatSessionManager {\n private readonly recentDays: number;\n private readonly maxTopLevelMessages: number;\n private readonly now: () => Date;\n\n constructor(options: ChatSessionManagerOptions = {}) {\n this.recentDays = options.recentDays ?? DEFAULT_RECENT_DAYS;\n this.maxTopLevelMessages = options.maxTopLevelMessages ?? DEFAULT_MAX_TOP_LEVEL_MESSAGES;\n this.now = options.now ?? (() => new Date());\n }\n\n async resolveSessionScope(\n options: ResolveChatSessionScopeOptions,\n ): Promise<ResolvedSessionScope> {\n const cwd = options.cwd ?? options.conversationDir;\n const sessionDir = getChannelSessionDir(options.conversationDir);\n\n if (!isThreadSessionKey(options.sessionKey)) {\n const contextFile = this.resolveTopLevelSessionFile({\n conversationDir: options.conversationDir,\n sessionDir,\n cwd,\n currentMessageId: options.currentMessageId,\n });\n return { sessionDir, contextFile, threadRootMessage: null };\n }\n\n return this.resolveThreadSessionScope({\n conversationDir: options.conversationDir,\n sessionDir,\n sessionKey: options.sessionKey,\n cwd,\n currentMessageId: options.currentMessageId,\n });\n }\n\n syncSessionManager(options: SyncChatSessionManagerOptions): void {\n const records = readConversationLog(options.conversationDir);\n syncSessionManagerFromLog(\n options.sessionManager,\n selectExistingSessionSyncMessages(records, {\n sessionKey: isThreadSessionKey(options.sessionKey) ? options.sessionKey : null,\n excludeMessageId: options.currentMessageId,\n }),\n );\n }\n\n resetSession(options: ResetChatSessionOptions): string {\n const cwd = options.cwd ?? options.conversationDir;\n if (isThreadSessionKey(options.sessionKey)) {\n return createManagedSessionFileAtPath(\n getThreadSessionFile(options.conversationDir, options.sessionKey),\n cwd,\n );\n }\n\n return createManagedSessionFile(getChannelSessionDir(options.conversationDir), cwd);\n }\n\n registerThreadSession(options: RegisterThreadSessionOptions): string | null {\n return registerThreadSession(options);\n }\n\n hasMaterializedSession(options: HasMaterializedSessionOptions): boolean {\n return hasMaterializedChatSession(options);\n }\n\n private resolveTopLevelSessionFile(options: {\n conversationDir: string;\n sessionDir: string;\n cwd: string;\n currentMessageId?: string;\n }): string {\n const records = readConversationLog(options.conversationDir);\n const existing = tryResolveCurrentSession(options.sessionDir);\n if (existing && !isPlatformHistorySession(existing)) {\n syncSessionFromLog(\n existing,\n options.sessionDir,\n options.cwd,\n selectExistingSessionSyncMessages(records, {\n sessionKey: null,\n excludeMessageId: options.currentMessageId,\n }),\n );\n return existing;\n }\n\n const sessionFile = createManagedSessionFile(options.sessionDir, options.cwd);\n const bootstrapRecords = selectRecentTopLevelMessages(records, {\n recentDays: this.recentDays,\n maxMessages: this.maxTopLevelMessages,\n now: this.now(),\n excludeMessageId: options.currentMessageId,\n });\n bootstrapSessionFromLog(sessionFile, options.sessionDir, options.cwd, bootstrapRecords);\n return sessionFile;\n }\n\n private resolveThreadSessionScope(options: {\n conversationDir: string;\n sessionDir: string;\n sessionKey: string;\n cwd: string;\n currentMessageId?: string;\n }): ResolvedSessionScope {\n const threadFile = getThreadSessionFile(options.conversationDir, options.sessionKey);\n const threadId = extractThreadId(options.sessionKey);\n const records = readConversationLog(options.conversationDir);\n const threadRootMessage = buildThreadRootSeed(findLogRecordById(records, threadId)?.message);\n const existing = tryResolveThreadSession(threadFile);\n if (existing) {\n syncSessionFromLog(\n existing,\n options.sessionDir,\n options.cwd,\n selectExistingSessionSyncMessages(records, {\n sessionKey: options.sessionKey,\n excludeMessageId: options.currentMessageId,\n }),\n );\n return { sessionDir: options.sessionDir, contextFile: existing, threadRootMessage };\n }\n\n createManagedSessionFileAtPath(threadFile, options.cwd);\n const bootstrapRecords = selectThreadBootstrapMessages(records, threadId, {\n recentDays: this.recentDays,\n maxTopLevelMessages: this.maxTopLevelMessages,\n now: this.now(),\n excludeMessageId: options.currentMessageId,\n });\n bootstrapSessionFromLog(threadFile, options.sessionDir, options.cwd, bootstrapRecords);\n\n return { sessionDir: options.sessionDir, contextFile: threadFile, threadRootMessage };\n }\n}\n\nfunction readConversationLog(conversationDir: string): LogRecord[] {\n const logFile = join(conversationDir, \"log.jsonl\");\n const raw = readTextFileIfExists(logFile);\n if (raw === undefined) return [];\n\n const lines = raw.trim().split(\"\\n\").filter(Boolean);\n const records: LogRecord[] = [];\n for (let i = 0; i < lines.length; i++) {\n try {\n const message = parseJsonValue(\n lines[i],\n (value): value is ConversationLogMessage => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n records.push({ message, index: i });\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 return records;\n}\n\nfunction findLogRecordById(records: LogRecord[], messageId: string): LogRecord | undefined {\n for (let i = records.length - 1; i >= 0; i--) {\n if (records[i].message.ts === messageId) return records[i];\n }\n return undefined;\n}\n\nfunction selectRecentTopLevelMessages(\n records: LogRecord[],\n options: {\n recentDays: number;\n maxMessages: number;\n now: Date;\n excludeMessageId?: string;\n },\n): LogRecord[] {\n const sinceMs = options.now.getTime() - options.recentDays * 24 * 60 * 60 * 1000;\n return records\n .filter((record) => isTopLevelHistoryMessage(record.message, sinceMs, options.excludeMessageId))\n .slice(-options.maxMessages);\n}\n\nfunction selectThreadBootstrapMessages(\n records: LogRecord[],\n threadId: string,\n options: {\n recentDays: number;\n maxTopLevelMessages: number;\n now: Date;\n excludeMessageId?: string;\n },\n): LogRecord[] {\n const rootRecord = findLogRecordById(records, threadId);\n const topLevelSource = rootRecord\n ? records.filter((record) => record.index <= rootRecord.index)\n : records;\n const topLevelRecords = selectRecentTopLevelMessages(topLevelSource, {\n recentDays: options.recentDays,\n maxMessages: options.maxTopLevelMessages,\n now: options.now,\n excludeMessageId: options.excludeMessageId,\n });\n const threadRecords = records.filter(\n (record) =>\n isRenderableChatMessage(record.message, options.excludeMessageId) &&\n (record.message.ts === threadId || record.message.threadTs === threadId),\n );\n\n return dedupeAndSortRecords([...topLevelRecords, ...threadRecords]);\n}\n\nfunction isTopLevelHistoryMessage(\n message: ConversationLogMessage,\n sinceMs: number,\n excludeMessageId?: string,\n): boolean {\n if (!isRenderableChatMessage(message, excludeMessageId)) return false;\n if (message.threadTs) return false;\n if (!message.date) return true;\n\n const dateMs = new Date(message.date).getTime();\n return !Number.isFinite(dateMs) || dateMs >= sinceMs;\n}\n\nfunction selectExistingSessionSyncMessages(\n records: LogRecord[],\n options: { sessionKey: string | null; excludeMessageId?: string },\n): LogRecord[] {\n const threadId = options.sessionKey ? extractThreadId(options.sessionKey) : null;\n return dedupeAndSortRecords(\n records.filter((record) => {\n if (!isRenderableChatMessage(record.message, options.excludeMessageId)) return false;\n if (!threadId) return !record.message.threadTs;\n return record.message.ts === threadId || record.message.threadTs === threadId;\n }),\n );\n}\n\nfunction isRenderableChatMessage(\n message: ConversationLogMessage,\n excludeMessageId?: string,\n): boolean {\n if (excludeMessageId && message.ts === excludeMessageId) return false;\n if (isChatCommandMessage(message)) return false;\n return !!message.text?.trim();\n}\n\nfunction isChatCommandMessage(message: ConversationLogMessage): boolean {\n const text = message.text?.trim() ?? \"\";\n return (\n !message.isBot &&\n /^\\/(?:pi-[\\w-]+|login|session|new|stop|model|sandbox|admin|auto-reply)(?:@\\w+)?(?:\\s|$)/i.test(\n text,\n )\n );\n}\n\nfunction dedupeAndSortRecords(records: LogRecord[]): LogRecord[] {\n const byKey = new Map<string, LogRecord>();\n for (const record of records) {\n byKey.set(record.message.ts ?? `line:${record.index}`, record);\n }\n\n return Array.from(byKey.values()).toSorted((a, b) => {\n const aTime = sortTime(a);\n const bTime = sortTime(b);\n if (aTime !== bTime) return aTime - bTime;\n return a.index - b.index;\n });\n}\n\nfunction sortTime(record: LogRecord): number {\n if (record.message.date) {\n const dateMs = new Date(record.message.date).getTime();\n if (Number.isFinite(dateMs)) return dateMs;\n }\n\n if (record.message.ts) {\n const tsMs = Number(record.message.ts) * 1000;\n if (Number.isFinite(tsMs)) return tsMs;\n }\n\n return record.index;\n}\n\nfunction bootstrapSessionFromLog(\n sessionFile: string,\n sessionDir: string,\n cwd: string,\n records: LogRecord[],\n): void {\n if (records.length === 0) return;\n\n const sessionManager = openManagedSession(sessionFile, sessionDir, cwd);\n appendLogRecordsToSession(sessionManager, records);\n sessionManager.appendCustomEntry(CHAT_SYNC_CUSTOM_TYPE, {\n source: \"log.jsonl\",\n messageCount: records.length,\n lastMessageId: records.at(-1)?.message.ts,\n });\n forceRewriteSession(sessionManager, sessionFile);\n}\n\nfunction syncSessionFromLog(\n sessionFile: string,\n sessionDir: string,\n cwd: string,\n records: LogRecord[],\n): void {\n if (records.length === 0) return;\n syncSessionManagerFromLog(openManagedSession(sessionFile, sessionDir, cwd), records);\n}\n\nfunction syncSessionManagerFromLog(sessionManager: SessionManager, records: LogRecord[]): void {\n if (records.length === 0) return;\n\n const existingEntries = sessionManager.getEntries();\n const lastSyncedMessageId = getLatestChatSyncMessageId(existingEntries);\n const startIndex = lastSyncedMessageId\n ? records.findIndex((record) => record.message.ts === lastSyncedMessageId) + 1\n : 0;\n const syncCandidates = records.slice(Math.max(startIndex, 0));\n if (syncCandidates.length === 0) return;\n\n const represented = buildRepresentedMessageCounts(existingEntries);\n const newRecords = syncCandidates.filter(\n (record) => !consumeRepresentedLogMessage(record, represented),\n );\n if (newRecords.length === 0) return;\n\n appendLogRecordsToSession(sessionManager, newRecords);\n sessionManager.appendCustomEntry(CHAT_SYNC_CUSTOM_TYPE, {\n source: \"log.jsonl\",\n messageCount: newRecords.length,\n lastMessageId: syncCandidates.at(-1)?.message.ts,\n });\n}\n\nfunction appendLogRecordsToSession(sessionManager: SessionManager, records: LogRecord[]): void {\n for (const record of records) {\n const message = buildHistorySessionMessage(record.message);\n if (message) sessionManager.appendMessage(message);\n }\n}\n\nfunction forceRewriteSession(sessionManager: SessionManager, sessionFile: string): void {\n const header = sessionManager.getHeader();\n if (!header) return;\n\n const content = [header, ...sessionManager.getEntries()]\n .map((entry) => JSON.stringify(entry))\n .join(\"\\n\");\n atomicWritePrivateFile(sessionFile, `${content}\\n`);\n}\n\nfunction getLatestChatSyncMessageId(entries: SessionEntry[]): string | undefined {\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n if (entry.type !== \"custom\" || entry.customType !== CHAT_SYNC_CUSTOM_TYPE) continue;\n if (!isRecord(entry.data)) return undefined;\n return typeof entry.data.lastMessageId === \"string\" ? entry.data.lastMessageId : undefined;\n }\n return undefined;\n}\n\nfunction buildRepresentedMessageCounts(entries: SessionEntry[]): Map<string, number> {\n const counts = new Map<string, number>();\n for (const entry of entries) {\n const comparable = comparableSessionMessage(entry);\n if (!comparable) continue;\n counts.set(comparable, (counts.get(comparable) ?? 0) + 1);\n }\n return counts;\n}\n\nfunction consumeRepresentedLogMessage(record: LogRecord, counts: Map<string, number>): boolean {\n const comparable = comparableLogMessage(record.message);\n if (!comparable) return false;\n\n const count = counts.get(comparable) ?? 0;\n if (count <= 0) return false;\n counts.set(comparable, count - 1);\n return true;\n}\n\nfunction comparableSessionMessage(entry: SessionEntry): string | null {\n if (entry.type !== \"message\") return null;\n const role = entry.message.role;\n if (role !== \"user\" && role !== \"assistant\") return null;\n\n const text = normalizeComparableText(getSessionMessageText(entry));\n if (!text) return null;\n return `${role}:${text}`;\n}\n\nfunction comparableLogMessage(message: ConversationLogMessage): string | null {\n const text = message.text?.trim();\n if (!text) return null;\n return `${message.isBot ? \"assistant\" : \"user\"}:${normalizeComparableText(text)}`;\n}\n\nfunction getSessionMessageText(entry: SessionEntry): string {\n if (entry.type !== \"message\" || !(\"content\" in entry.message)) return \"\";\n const content = entry.message.content;\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n return content\n .map((part) => (part.type === \"text\" && \"text\" in part ? part.text : \"\"))\n .join(\"\\n\");\n}\n\nfunction normalizeComparableText(text: string): string {\n return text\n .replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s*/,\n \"\",\n )\n .trim();\n}\n\nfunction buildHistorySessionMessage(message: ConversationLogMessage): SessionAppendMessage | null {\n const text = message.text?.trim();\n if (!text) return null;\n\n const timestamp = parseMessageTimestamp(message);\n if (!message.isBot) {\n return {\n role: \"user\",\n content: [{ type: \"text\", text: formatHistoryMessage(message) }],\n ...(timestamp !== undefined ? { timestamp } : {}),\n } as SessionAppendMessage;\n }\n\n return {\n role: \"assistant\",\n content: [{ type: \"text\", text }],\n api: \"platform-history\",\n provider: \"platform-history\",\n model: \"platform-history\",\n usage: zeroUsage(),\n stopReason: \"stop\",\n ...(timestamp !== undefined ? { timestamp } : {}),\n } as SessionAppendMessage;\n}\n\nfunction buildThreadRootSeed(\n message: ConversationLogMessage | undefined,\n): ThreadRootMessage | null {\n if (!message) return null;\n return {\n text: message.text,\n userName: message.userName,\n user: message.user,\n loggedAt: message.date ? new Date(message.date).getTime() : undefined,\n isBot: message.isBot,\n };\n}\n\nfunction parseMessageTimestamp(message: ConversationLogMessage): number | undefined {\n if (message.date) {\n const dateMs = new Date(message.date).getTime();\n if (Number.isFinite(dateMs)) return dateMs;\n }\n\n if (message.ts) {\n const tsMs = Number(message.ts) * 1000;\n if (Number.isFinite(tsMs)) return tsMs;\n }\n\n return undefined;\n}\n\nfunction formatHistoryMessage(message: ConversationLogMessage): string {\n const text = message.text?.trim() ?? \"\";\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 | null {\n const time = date.getTime();\n if (!Number.isFinite(time)) return null;\n\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\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"]}