@geminixiang/mama 0.2.0-beta.0 → 0.2.0-beta.10

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 (273) hide show
  1. package/README.md +171 -334
  2. package/dist/adapter.d.ts +36 -10
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +10 -5
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +349 -114
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/discord/context.d.ts +1 -1
  10. package/dist/adapters/discord/context.d.ts.map +1 -1
  11. package/dist/adapters/discord/context.js +102 -31
  12. package/dist/adapters/discord/context.js.map +1 -1
  13. package/dist/adapters/shared.d.ts +71 -0
  14. package/dist/adapters/shared.d.ts.map +1 -0
  15. package/dist/adapters/shared.js +168 -0
  16. package/dist/adapters/shared.js.map +1 -0
  17. package/dist/adapters/slack/bot.d.ts +29 -22
  18. package/dist/adapters/slack/bot.d.ts.map +1 -1
  19. package/dist/adapters/slack/bot.js +620 -186
  20. package/dist/adapters/slack/bot.js.map +1 -1
  21. package/dist/adapters/slack/branch-manager.d.ts +22 -0
  22. package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
  23. package/dist/adapters/slack/branch-manager.js +97 -0
  24. package/dist/adapters/slack/branch-manager.js.map +1 -0
  25. package/dist/adapters/slack/context.d.ts +1 -1
  26. package/dist/adapters/slack/context.d.ts.map +1 -1
  27. package/dist/adapters/slack/context.js +136 -71
  28. package/dist/adapters/slack/context.js.map +1 -1
  29. package/dist/adapters/slack/session.d.ts +3 -0
  30. package/dist/adapters/slack/session.d.ts.map +1 -0
  31. package/dist/adapters/slack/session.js +16 -0
  32. package/dist/adapters/slack/session.js.map +1 -0
  33. package/dist/adapters/slack/tools/attach.d.ts +1 -1
  34. package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
  35. package/dist/adapters/slack/tools/attach.js.map +1 -1
  36. package/dist/adapters/telegram/bot.d.ts +2 -0
  37. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  38. package/dist/adapters/telegram/bot.js +190 -123
  39. package/dist/adapters/telegram/bot.js.map +1 -1
  40. package/dist/adapters/telegram/context.d.ts.map +1 -1
  41. package/dist/adapters/telegram/context.js +57 -59
  42. package/dist/adapters/telegram/context.js.map +1 -1
  43. package/dist/adapters/telegram/html.d.ts +3 -0
  44. package/dist/adapters/telegram/html.d.ts.map +1 -0
  45. package/dist/adapters/telegram/html.js +98 -0
  46. package/dist/adapters/telegram/html.js.map +1 -0
  47. package/dist/agent.d.ts +9 -10
  48. package/dist/agent.d.ts.map +1 -1
  49. package/dist/agent.js +645 -555
  50. package/dist/agent.js.map +1 -1
  51. package/dist/commands/auto-reply.d.ts +16 -0
  52. package/dist/commands/auto-reply.d.ts.map +1 -0
  53. package/dist/commands/auto-reply.js +69 -0
  54. package/dist/commands/auto-reply.js.map +1 -0
  55. package/dist/commands/index.d.ts +5 -0
  56. package/dist/commands/index.d.ts.map +1 -0
  57. package/dist/commands/index.js +19 -0
  58. package/dist/commands/index.js.map +1 -0
  59. package/dist/commands/login.d.ts +5 -0
  60. package/dist/commands/login.d.ts.map +1 -0
  61. package/dist/commands/login.js +76 -0
  62. package/dist/commands/login.js.map +1 -0
  63. package/dist/commands/model.d.ts +14 -0
  64. package/dist/commands/model.d.ts.map +1 -0
  65. package/dist/commands/model.js +112 -0
  66. package/dist/commands/model.js.map +1 -0
  67. package/dist/commands/new.d.ts +9 -0
  68. package/dist/commands/new.d.ts.map +1 -0
  69. package/dist/commands/new.js +28 -0
  70. package/dist/commands/new.js.map +1 -0
  71. package/dist/commands/registry.d.ts +7 -0
  72. package/dist/commands/registry.d.ts.map +1 -0
  73. package/dist/commands/registry.js +14 -0
  74. package/dist/commands/registry.js.map +1 -0
  75. package/dist/commands/sandbox.d.ts +10 -0
  76. package/dist/commands/sandbox.d.ts.map +1 -0
  77. package/dist/commands/sandbox.js +88 -0
  78. package/dist/commands/sandbox.js.map +1 -0
  79. package/dist/commands/session-view.d.ts +5 -0
  80. package/dist/commands/session-view.d.ts.map +1 -0
  81. package/dist/commands/session-view.js +62 -0
  82. package/dist/commands/session-view.js.map +1 -0
  83. package/dist/commands/types.d.ts +41 -0
  84. package/dist/commands/types.d.ts.map +1 -0
  85. package/dist/commands/types.js +2 -0
  86. package/dist/commands/types.js.map +1 -0
  87. package/dist/commands/utils.d.ts +8 -0
  88. package/dist/commands/utils.d.ts.map +1 -0
  89. package/dist/commands/utils.js +14 -0
  90. package/dist/commands/utils.js.map +1 -0
  91. package/dist/config.d.ts +53 -7
  92. package/dist/config.d.ts.map +1 -1
  93. package/dist/config.js +320 -55
  94. package/dist/config.js.map +1 -1
  95. package/dist/context.d.ts +10 -42
  96. package/dist/context.d.ts.map +1 -1
  97. package/dist/context.js +15 -128
  98. package/dist/context.js.map +1 -1
  99. package/dist/events.d.ts +16 -5
  100. package/dist/events.d.ts.map +1 -1
  101. package/dist/events.js +127 -58
  102. package/dist/events.js.map +1 -1
  103. package/dist/execution-resolver.d.ts +24 -0
  104. package/dist/execution-resolver.d.ts.map +1 -0
  105. package/dist/execution-resolver.js +115 -0
  106. package/dist/execution-resolver.js.map +1 -0
  107. package/dist/file-guards.d.ts +6 -0
  108. package/dist/file-guards.d.ts.map +1 -0
  109. package/dist/file-guards.js +48 -0
  110. package/dist/file-guards.js.map +1 -0
  111. package/dist/fs-atomic.d.ts +10 -0
  112. package/dist/fs-atomic.d.ts.map +1 -0
  113. package/dist/fs-atomic.js +45 -0
  114. package/dist/fs-atomic.js.map +1 -0
  115. package/dist/index.d.ts +7 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +4 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/instrument.d.ts.map +1 -1
  120. package/dist/instrument.js +3 -3
  121. package/dist/instrument.js.map +1 -1
  122. package/dist/log.d.ts +3 -7
  123. package/dist/log.d.ts.map +1 -1
  124. package/dist/log.js +20 -45
  125. package/dist/log.js.map +1 -1
  126. package/dist/login/index.d.ts +41 -0
  127. package/dist/login/index.d.ts.map +1 -0
  128. package/dist/login/index.js +202 -0
  129. package/dist/login/index.js.map +1 -0
  130. package/dist/login/portal.d.ts +19 -0
  131. package/dist/login/portal.d.ts.map +1 -0
  132. package/dist/login/portal.js +1453 -0
  133. package/dist/login/portal.js.map +1 -0
  134. package/dist/login/session.d.ts +33 -0
  135. package/dist/login/session.d.ts.map +1 -0
  136. package/dist/login/session.js +68 -0
  137. package/dist/login/session.js.map +1 -0
  138. package/dist/main.d.ts.map +1 -1
  139. package/dist/main.js +229 -264
  140. package/dist/main.js.map +1 -1
  141. package/dist/provisioner.d.ts +79 -0
  142. package/dist/provisioner.d.ts.map +1 -0
  143. package/dist/provisioner.js +437 -0
  144. package/dist/provisioner.js.map +1 -0
  145. package/dist/runtime/conversation-orchestrator.d.ts +42 -0
  146. package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
  147. package/dist/runtime/conversation-orchestrator.js +150 -0
  148. package/dist/runtime/conversation-orchestrator.js.map +1 -0
  149. package/dist/runtime/index.d.ts +2 -0
  150. package/dist/runtime/index.d.ts.map +1 -0
  151. package/dist/runtime/index.js +2 -0
  152. package/dist/runtime/index.js.map +1 -0
  153. package/dist/runtime/session-runtime.d.ts +27 -0
  154. package/dist/runtime/session-runtime.d.ts.map +1 -0
  155. package/dist/runtime/session-runtime.js +211 -0
  156. package/dist/runtime/session-runtime.js.map +1 -0
  157. package/dist/sandbox/cloudflare.d.ts +15 -0
  158. package/dist/sandbox/cloudflare.d.ts.map +1 -0
  159. package/dist/sandbox/cloudflare.js +137 -0
  160. package/dist/sandbox/cloudflare.js.map +1 -0
  161. package/dist/sandbox/container.d.ts +16 -0
  162. package/dist/sandbox/container.d.ts.map +1 -0
  163. package/dist/sandbox/container.js +126 -0
  164. package/dist/sandbox/container.js.map +1 -0
  165. package/dist/sandbox/errors.d.ts +6 -0
  166. package/dist/sandbox/errors.d.ts.map +1 -0
  167. package/dist/sandbox/errors.js +11 -0
  168. package/dist/sandbox/errors.js.map +1 -0
  169. package/dist/sandbox/firecracker.d.ts +17 -0
  170. package/dist/sandbox/firecracker.d.ts.map +1 -0
  171. package/dist/sandbox/firecracker.js +212 -0
  172. package/dist/sandbox/firecracker.js.map +1 -0
  173. package/dist/sandbox/host.d.ts +11 -0
  174. package/dist/sandbox/host.d.ts.map +1 -0
  175. package/dist/sandbox/host.js +89 -0
  176. package/dist/sandbox/host.js.map +1 -0
  177. package/dist/sandbox/image.d.ts +5 -0
  178. package/dist/sandbox/image.d.ts.map +1 -0
  179. package/dist/sandbox/image.js +30 -0
  180. package/dist/sandbox/image.js.map +1 -0
  181. package/dist/sandbox/index.d.ts +22 -0
  182. package/dist/sandbox/index.d.ts.map +1 -0
  183. package/dist/sandbox/index.js +54 -0
  184. package/dist/sandbox/index.js.map +1 -0
  185. package/dist/sandbox/path-context.d.ts +4 -0
  186. package/dist/sandbox/path-context.d.ts.map +1 -0
  187. package/dist/sandbox/path-context.js +20 -0
  188. package/dist/sandbox/path-context.js.map +1 -0
  189. package/dist/sandbox/types.d.ts +67 -0
  190. package/dist/sandbox/types.d.ts.map +1 -0
  191. package/dist/sandbox/types.js +2 -0
  192. package/dist/sandbox/types.js.map +1 -0
  193. package/dist/sandbox/utils.d.ts +4 -0
  194. package/dist/sandbox/utils.d.ts.map +1 -0
  195. package/dist/sandbox/utils.js +51 -0
  196. package/dist/sandbox/utils.js.map +1 -0
  197. package/dist/sandbox.d.ts +1 -39
  198. package/dist/sandbox.d.ts.map +1 -1
  199. package/dist/sandbox.js +1 -286
  200. package/dist/sandbox.js.map +1 -1
  201. package/dist/sentry.d.ts +2 -2
  202. package/dist/sentry.d.ts.map +1 -1
  203. package/dist/sentry.js +6 -4
  204. package/dist/sentry.js.map +1 -1
  205. package/dist/session-policy.d.ts +13 -0
  206. package/dist/session-policy.d.ts.map +1 -0
  207. package/dist/session-policy.js +23 -0
  208. package/dist/session-policy.js.map +1 -0
  209. package/dist/session-store.d.ts +35 -8
  210. package/dist/session-store.d.ts.map +1 -1
  211. package/dist/session-store.js +182 -23
  212. package/dist/session-store.js.map +1 -1
  213. package/dist/session-view/command.d.ts +5 -0
  214. package/dist/session-view/command.d.ts.map +1 -0
  215. package/dist/session-view/command.js +11 -0
  216. package/dist/session-view/command.js.map +1 -0
  217. package/dist/session-view/portal.d.ts +16 -0
  218. package/dist/session-view/portal.d.ts.map +1 -0
  219. package/dist/session-view/portal.js +1742 -0
  220. package/dist/session-view/portal.js.map +1 -0
  221. package/dist/session-view/service.d.ts +34 -0
  222. package/dist/session-view/service.d.ts.map +1 -0
  223. package/dist/session-view/service.js +427 -0
  224. package/dist/session-view/service.js.map +1 -0
  225. package/dist/session-view/store.d.ts +18 -0
  226. package/dist/session-view/store.d.ts.map +1 -0
  227. package/dist/session-view/store.js +39 -0
  228. package/dist/session-view/store.js.map +1 -0
  229. package/dist/store.d.ts +4 -7
  230. package/dist/store.d.ts.map +1 -1
  231. package/dist/store.js +26 -52
  232. package/dist/store.js.map +1 -1
  233. package/dist/tool-diagnostics.d.ts +2 -0
  234. package/dist/tool-diagnostics.d.ts.map +1 -0
  235. package/dist/tool-diagnostics.js +7 -0
  236. package/dist/tool-diagnostics.js.map +1 -0
  237. package/dist/tools/bash.d.ts +1 -1
  238. package/dist/tools/bash.d.ts.map +1 -1
  239. package/dist/tools/bash.js.map +1 -1
  240. package/dist/tools/edit.d.ts +1 -1
  241. package/dist/tools/edit.d.ts.map +1 -1
  242. package/dist/tools/edit.js.map +1 -1
  243. package/dist/tools/event.d.ts +62 -0
  244. package/dist/tools/event.d.ts.map +1 -0
  245. package/dist/tools/event.js +138 -0
  246. package/dist/tools/event.js.map +1 -0
  247. package/dist/tools/index.d.ts +8 -2
  248. package/dist/tools/index.d.ts.map +1 -1
  249. package/dist/tools/index.js +5 -1
  250. package/dist/tools/index.js.map +1 -1
  251. package/dist/tools/read.d.ts +1 -1
  252. package/dist/tools/read.d.ts.map +1 -1
  253. package/dist/tools/read.js.map +1 -1
  254. package/dist/tools/write.d.ts +1 -1
  255. package/dist/tools/write.d.ts.map +1 -1
  256. package/dist/tools/write.js.map +1 -1
  257. package/dist/trigger.d.ts +31 -0
  258. package/dist/trigger.d.ts.map +1 -0
  259. package/dist/trigger.js +98 -0
  260. package/dist/trigger.js.map +1 -0
  261. package/dist/ui-copy.d.ts +12 -0
  262. package/dist/ui-copy.d.ts.map +1 -0
  263. package/dist/ui-copy.js +36 -0
  264. package/dist/ui-copy.js.map +1 -0
  265. package/dist/vault-routing.d.ts +4 -0
  266. package/dist/vault-routing.d.ts.map +1 -0
  267. package/dist/vault-routing.js +16 -0
  268. package/dist/vault-routing.js.map +1 -0
  269. package/dist/vault.d.ts +72 -0
  270. package/dist/vault.d.ts.map +1 -0
  271. package/dist/vault.js +264 -0
  272. package/dist/vault.js.map +1 -0
  273. package/package.json +16 -13
@@ -0,0 +1,427 @@
1
+ import { basename, dirname, join, resolve } from "path";
2
+ import { existsSync, readdirSync } from "fs";
3
+ import { SessionManager, } from "@earendil-works/pi-coding-agent";
4
+ import { getThreadSessionFile, resolveChannelSessionFile, tryResolveThreadSession, } from "../session-store.js";
5
+ import * as log from "../log.js";
6
+ export function resolveExistingSessionFile(workingDir, conversationId, sessionKey) {
7
+ const conversationDir = join(workingDir, conversationId);
8
+ if (sessionKey.includes(":")) {
9
+ return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey));
10
+ }
11
+ return resolveChannelSessionFile(conversationDir);
12
+ }
13
+ export function loadSessionViewModel(sessionFile) {
14
+ const resolvedFile = resolve(sessionFile);
15
+ const sm = SessionManager.open(resolvedFile);
16
+ const header = sm.getHeader();
17
+ if (!header)
18
+ throw new Error(`No valid session found: ${sessionFile}`);
19
+ const entries = sm.getEntries();
20
+ const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;
21
+ const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;
22
+ const parent = header.parentSession
23
+ ? buildSessionRelation(resolve(header.parentSession), "parent")
24
+ : undefined;
25
+ const forks = listRelatedSessionFiles(resolvedFile)
26
+ .filter((candidate) => candidate !== resolvedFile)
27
+ .map((candidate) => buildSessionRelation(candidate, "fork", resolvedFile))
28
+ .filter((relation) => relation !== null)
29
+ .toSorted((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));
30
+ const forksByEntryId = new Map();
31
+ for (const fork of forks) {
32
+ if (!fork.anchorEntryId)
33
+ continue;
34
+ const bucket = forksByEntryId.get(fork.anchorEntryId) ?? [];
35
+ bucket.push(fork);
36
+ forksByEntryId.set(fork.anchorEntryId, bucket);
37
+ }
38
+ const items = entries.flatMap((entry) => {
39
+ const item = mapEntryToItem(entry);
40
+ if (!item)
41
+ return [];
42
+ if (item.entryId) {
43
+ const anchoredForks = forksByEntryId.get(item.entryId);
44
+ if (anchoredForks) {
45
+ item.forks = anchoredForks;
46
+ }
47
+ }
48
+ return [item];
49
+ });
50
+ return {
51
+ sessionId: header.id,
52
+ fileName: basename(resolvedFile),
53
+ title,
54
+ createdAt: header.timestamp,
55
+ updatedAt,
56
+ entryCount: entries.length,
57
+ items,
58
+ parent: parent ?? undefined,
59
+ forks,
60
+ };
61
+ }
62
+ export function resolveRequestedSessionFile(baseSessionFile, requestedFileName) {
63
+ const resolvedBase = resolve(baseSessionFile);
64
+ if (!requestedFileName)
65
+ return resolvedBase;
66
+ const trimmed = requestedFileName.trim();
67
+ if (!trimmed)
68
+ return resolvedBase;
69
+ const fileName = basename(trimmed);
70
+ if (fileName !== trimmed || !fileName.endsWith(".jsonl"))
71
+ return null;
72
+ const candidate = join(dirname(resolvedBase), fileName);
73
+ if (!existsSync(candidate))
74
+ return null;
75
+ let sm;
76
+ try {
77
+ sm = SessionManager.open(candidate);
78
+ }
79
+ catch (err) {
80
+ throw new Error(`Session file is corrupted: ${candidate}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
81
+ }
82
+ if (!sm.getHeader()) {
83
+ throw new Error(`Session file is missing a valid header: ${candidate}`);
84
+ }
85
+ return candidate;
86
+ }
87
+ function listRelatedSessionFiles(sessionFile) {
88
+ const dir = dirname(sessionFile);
89
+ if (!existsSync(dir))
90
+ return [];
91
+ return readdirSync(dir)
92
+ .filter((name) => name.endsWith(".jsonl"))
93
+ .map((fileName) => join(dir, fileName));
94
+ }
95
+ function buildSessionRelation(sessionFile, kind, expectedParent) {
96
+ let sm;
97
+ try {
98
+ sm = SessionManager.open(sessionFile);
99
+ }
100
+ catch (err) {
101
+ log.logWarning(`Skipping corrupted session file while building ${kind} relation: ${sessionFile}`, err instanceof Error ? err.message : String(err));
102
+ return null;
103
+ }
104
+ const header = sm.getHeader();
105
+ if (!header) {
106
+ log.logWarning(`Skipping session file with missing header while building ${kind} relation: ${sessionFile}`);
107
+ return null;
108
+ }
109
+ if (kind === "fork" && resolve(header.parentSession ?? "") !== expectedParent) {
110
+ return null;
111
+ }
112
+ const entries = sm.getEntries();
113
+ const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;
114
+ const anchorEntryId = kind === "fork" && expectedParent
115
+ ? findForkAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries)
116
+ : undefined;
117
+ return {
118
+ kind,
119
+ fileName: basename(sessionFile),
120
+ sessionId: header.id,
121
+ title: sm.getSessionName() || `Session ${header.id.slice(0, 8)}`,
122
+ updatedAt,
123
+ entryCount: entries.length,
124
+ summary: extractSessionSummary(entries),
125
+ anchorEntryId,
126
+ };
127
+ }
128
+ function findForkAnchorEntryId(parentEntries, childEntries) {
129
+ let sharedCount = 0;
130
+ while (sharedCount < parentEntries.length &&
131
+ sharedCount < childEntries.length &&
132
+ parentEntries[sharedCount]?.id === childEntries[sharedCount]?.id) {
133
+ sharedCount += 1;
134
+ }
135
+ if (sharedCount > 0) {
136
+ return parentEntries[sharedCount - 1]?.id;
137
+ }
138
+ const childRoot = findComparableUserMessage(childEntries);
139
+ if (!childRoot)
140
+ return undefined;
141
+ return findParentAnchorByRootMessage(parentEntries, childRoot);
142
+ }
143
+ function findParentAnchorByRootMessage(parentEntries, childRoot) {
144
+ let textMatchId;
145
+ for (const entry of parentEntries) {
146
+ const comparable = getComparableUserMessage(entry);
147
+ if (!comparable)
148
+ continue;
149
+ if (comparable.normalizedText !== childRoot.normalizedText)
150
+ continue;
151
+ if (childRoot.messageTimestamp !== undefined &&
152
+ comparable.messageTimestamp !== undefined &&
153
+ comparable.messageTimestamp === childRoot.messageTimestamp) {
154
+ return entry.id;
155
+ }
156
+ textMatchId ??= entry.id;
157
+ }
158
+ return textMatchId;
159
+ }
160
+ function findComparableUserMessage(entries) {
161
+ for (const entry of entries) {
162
+ const comparable = getComparableUserMessage(entry);
163
+ if (comparable)
164
+ return comparable;
165
+ }
166
+ return null;
167
+ }
168
+ function getComparableUserMessage(entry) {
169
+ if (entry.type !== "message" || entry.message.role !== "user")
170
+ return null;
171
+ const body = contentToText(entry.message.content);
172
+ const normalizedText = normalizeComparableUserText(body);
173
+ if (!normalizedText)
174
+ return null;
175
+ const messageTimestamp = typeof entry.message.timestamp === "number" ? entry.message.timestamp : undefined;
176
+ return { normalizedText, messageTimestamp };
177
+ }
178
+ function normalizeComparableUserText(text) {
179
+ const withoutTimestamp = text.replace(/^\[[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)/, "");
180
+ return stripSlackAttachmentBlock(withoutTimestamp).trim();
181
+ }
182
+ function stripSlackAttachmentBlock(text) {
183
+ return text.replace(/\n*<slack_attachments>\n[\s\S]*?\n<\/slack_attachments>\s*$/g, "");
184
+ }
185
+ function extractSessionSummary(entries) {
186
+ for (const entry of entries) {
187
+ if (entry.type !== "message")
188
+ continue;
189
+ const item = mapEntryToItem(entry);
190
+ if (!item?.body)
191
+ continue;
192
+ return collapseSummary(item.body);
193
+ }
194
+ return undefined;
195
+ }
196
+ function collapseSummary(text) {
197
+ const singleLine = text.replace(/\s+/g, " ").trim();
198
+ return singleLine.length > 96 ? `${singleLine.slice(0, 93)}…` : singleLine;
199
+ }
200
+ function mapEntryToItem(entry) {
201
+ switch (entry.type) {
202
+ case "message":
203
+ return mapMessageEntry(entry);
204
+ case "model_change":
205
+ return {
206
+ kind: "system",
207
+ title: "Model changed",
208
+ body: `${entry.provider} / ${entry.modelId}`,
209
+ meta: entry.timestamp,
210
+ tone: "muted",
211
+ };
212
+ case "thinking_level_change":
213
+ return {
214
+ kind: "system",
215
+ title: "Thinking level changed",
216
+ body: entry.thinkingLevel,
217
+ meta: entry.timestamp,
218
+ tone: "muted",
219
+ };
220
+ case "compaction":
221
+ return mapCompactionEntry(entry);
222
+ case "branch_summary":
223
+ return mapBranchSummaryEntry(entry);
224
+ case "custom_message":
225
+ return {
226
+ kind: "system",
227
+ title: `Custom message · ${entry.customType}`,
228
+ body: contentToText(entry.content),
229
+ meta: entry.timestamp,
230
+ tone: "muted",
231
+ };
232
+ case "custom":
233
+ return {
234
+ kind: "system",
235
+ title: `Custom data · ${entry.customType}`,
236
+ body: entry.data === undefined ? "(no data)" : JSON.stringify(entry.data, null, 2),
237
+ meta: entry.timestamp,
238
+ tone: "muted",
239
+ };
240
+ case "label":
241
+ return {
242
+ kind: "system",
243
+ title: "Label updated",
244
+ body: entry.label || "(cleared)",
245
+ meta: entry.timestamp,
246
+ tone: "muted",
247
+ };
248
+ case "session_info":
249
+ return entry.name
250
+ ? {
251
+ kind: "system",
252
+ title: "Session renamed",
253
+ body: entry.name,
254
+ meta: entry.timestamp,
255
+ tone: "muted",
256
+ }
257
+ : null;
258
+ default:
259
+ return null;
260
+ }
261
+ }
262
+ function mapMessageEntry(entry) {
263
+ const message = entry.message;
264
+ switch (message.role) {
265
+ case "user":
266
+ return {
267
+ kind: "user",
268
+ title: "User",
269
+ body: contentToText(message.content),
270
+ meta: entry.timestamp,
271
+ entryId: entry.id,
272
+ };
273
+ case "assistant": {
274
+ const assistantBody = assistantContentToText(message.content) ||
275
+ (message.errorMessage ? `_${message.errorMessage}_` : "");
276
+ const metaParts = [message.provider, message.model, message.stopReason].filter(Boolean);
277
+ return {
278
+ kind: "assistant",
279
+ title: "Assistant",
280
+ body: assistantBody,
281
+ meta: metaParts.length > 0 ? `${entry.timestamp} · ${metaParts.join(" · ")}` : entry.timestamp,
282
+ entryId: entry.id,
283
+ };
284
+ }
285
+ case "toolResult":
286
+ return {
287
+ kind: "tool",
288
+ title: `Tool result · ${String(message.toolName ?? "unknown")}`,
289
+ body: contentToText(message.content),
290
+ meta: entry.timestamp,
291
+ tone: message.isError ? "err" : "ok",
292
+ entryId: entry.id,
293
+ };
294
+ case "bashExecution": {
295
+ const command = String(message.command ?? "").trim();
296
+ const output = String(message.output ?? "").trim();
297
+ const details = [
298
+ typeof message.exitCode === "number" ? `[exitCode] ${message.exitCode}` : "",
299
+ message.cancelled ? `[cancelled] true` : "",
300
+ message.truncated ? `[truncated] true` : "",
301
+ ].filter(Boolean);
302
+ const body = [command ? `$ ${command}` : "", output, ...details].filter(Boolean).join("\n\n");
303
+ return {
304
+ kind: "tool",
305
+ title: "Bash execution",
306
+ body: body || "(no output)",
307
+ meta: entry.timestamp,
308
+ entryId: entry.id,
309
+ };
310
+ }
311
+ case "custom":
312
+ return {
313
+ kind: "system",
314
+ title: `Custom message · ${String(message.customType ?? "custom")}`,
315
+ body: contentToText(message.content),
316
+ meta: entry.timestamp,
317
+ tone: "muted",
318
+ entryId: entry.id,
319
+ };
320
+ case "branchSummary":
321
+ return {
322
+ kind: "system",
323
+ title: "Branch summary",
324
+ body: String(message.summary ?? ""),
325
+ meta: entry.timestamp,
326
+ tone: "muted",
327
+ entryId: entry.id,
328
+ };
329
+ case "compactionSummary":
330
+ return {
331
+ kind: "system",
332
+ title: "Compaction summary",
333
+ body: String(message.summary ?? ""),
334
+ meta: entry.timestamp,
335
+ tone: "muted",
336
+ entryId: entry.id,
337
+ };
338
+ default:
339
+ return {
340
+ kind: "system",
341
+ title: `Message · ${String(message.role ?? "unknown")}`,
342
+ body: contentToText(message.content),
343
+ meta: entry.timestamp,
344
+ tone: "muted",
345
+ entryId: entry.id,
346
+ };
347
+ }
348
+ }
349
+ function mapCompactionEntry(entry) {
350
+ return {
351
+ kind: "system",
352
+ title: "Context compacted",
353
+ body: entry.summary,
354
+ meta: `${entry.timestamp} · ${entry.tokensBefore} tokens before compaction`,
355
+ tone: "muted",
356
+ };
357
+ }
358
+ function mapBranchSummaryEntry(entry) {
359
+ return {
360
+ kind: "system",
361
+ title: "Branch summary",
362
+ body: entry.summary,
363
+ meta: entry.timestamp,
364
+ tone: "muted",
365
+ };
366
+ }
367
+ function assistantContentToText(content) {
368
+ if (typeof content === "string")
369
+ return content;
370
+ if (!Array.isArray(content))
371
+ return "";
372
+ const lines = [];
373
+ for (const block of content) {
374
+ if (!block || typeof block !== "object")
375
+ continue;
376
+ const value = block;
377
+ if (value.type === "text" && typeof value.text === "string") {
378
+ lines.push(value.text);
379
+ continue;
380
+ }
381
+ if (value.type === "thinking" && typeof value.thinking === "string") {
382
+ lines.push(`[thinking]\n${value.thinking}`);
383
+ continue;
384
+ }
385
+ if (value.type === "toolCall") {
386
+ const name = typeof value.name === "string" ? value.name : "tool";
387
+ const args = value.arguments === undefined ? "" : JSON.stringify(value.arguments, null, 2);
388
+ lines.push([`[toolCall] ${name}`, args].filter(Boolean).join("\n"));
389
+ continue;
390
+ }
391
+ if (value.type === "image") {
392
+ lines.push(`[image ${String(value.mimeType ?? "unknown")}]`);
393
+ }
394
+ }
395
+ return lines.join("\n\n");
396
+ }
397
+ function contentToText(content) {
398
+ if (typeof content === "string")
399
+ return content;
400
+ if (!Array.isArray(content))
401
+ return "";
402
+ const lines = [];
403
+ for (const block of content) {
404
+ if (!block || typeof block !== "object")
405
+ continue;
406
+ const value = block;
407
+ if (value.type === "text" && typeof value.text === "string") {
408
+ lines.push(value.text);
409
+ continue;
410
+ }
411
+ if (value.type === "thinking" && typeof value.thinking === "string") {
412
+ lines.push(`[thinking]\n${value.thinking}`);
413
+ continue;
414
+ }
415
+ if (value.type === "toolCall") {
416
+ const name = typeof value.name === "string" ? value.name : "tool";
417
+ const args = value.arguments === undefined ? "" : JSON.stringify(value.arguments, null, 2);
418
+ lines.push([`[toolCall] ${name}`, args].filter(Boolean).join("\n"));
419
+ continue;
420
+ }
421
+ if (value.type === "image") {
422
+ lines.push(`[image ${String(value.mimeType ?? "unknown")}]`);
423
+ }
424
+ }
425
+ return lines.join("\n\n");
426
+ }
427
+ //# sourceMappingURL=service.js.map
@@ -0,0 +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 \"../session-store.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\" && resolve(header.parentSession ?? \"\") !== expectedParent) {\n return null;\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"]}
@@ -0,0 +1,18 @@
1
+ import type { PlatformName } from "../adapter.js";
2
+ export interface SessionViewToken {
3
+ token: string;
4
+ platform: PlatformName;
5
+ platformUserId: string;
6
+ platformUserName?: string;
7
+ conversationId: string;
8
+ sessionKey: string;
9
+ sessionFile: string;
10
+ expiresAt: number;
11
+ }
12
+ export declare class InMemorySessionViewTokenStore {
13
+ private tokens;
14
+ create(platform: PlatformName, platformUserId: string, conversationId: string, sessionKey: string, sessionFile: string, platformUserName?: string): SessionViewToken;
15
+ peek(rawToken: string): SessionViewToken | undefined;
16
+ purge(): void;
17
+ }
18
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/session-view/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,qBAAa,6BAA6B;IACxC,OAAO,CAAC,MAAM,CAAuC;IAErD,MAAM,CACJ,QAAQ,EAAE,YAAY,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,gBAAgB,CAAC,EAAE,MAAM,GACxB,gBAAgB,CAalB;IAED,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAOnD;IAED,KAAK,IAAI,IAAI,CAOZ;CACF","sourcesContent":["import { randomBytes } from \"crypto\";\nimport type { PlatformName } from \"../adapter.js\";\n\nexport interface SessionViewToken {\n token: string;\n platform: PlatformName;\n platformUserId: string;\n platformUserName?: string;\n conversationId: string;\n sessionKey: string;\n sessionFile: string;\n expiresAt: number;\n}\n\nconst TTL_MS = 24 * 60 * 60 * 1000;\n\nexport class InMemorySessionViewTokenStore {\n private tokens = new Map<string, SessionViewToken>();\n\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n sessionKey: string,\n sessionFile: string,\n platformUserName?: string,\n ): SessionViewToken {\n const token: SessionViewToken = {\n token: randomBytes(16).toString(\"hex\"),\n platform,\n platformUserId,\n ...(platformUserName ? { platformUserName } : {}),\n conversationId,\n sessionKey,\n sessionFile,\n expiresAt: Date.now() + TTL_MS,\n };\n this.tokens.set(token.token, token);\n return token;\n }\n\n peek(rawToken: string): SessionViewToken | undefined {\n const entry = this.tokens.get(rawToken);\n if (!entry || Date.now() > entry.expiresAt) {\n if (entry) this.tokens.delete(rawToken);\n return undefined;\n }\n return entry;\n }\n\n purge(): void {\n const now = Date.now();\n for (const [key, value] of this.tokens) {\n if (now > value.expiresAt) {\n this.tokens.delete(key);\n }\n }\n }\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import { randomBytes } from "crypto";
2
+ const TTL_MS = 24 * 60 * 60 * 1000;
3
+ export class InMemorySessionViewTokenStore {
4
+ constructor() {
5
+ this.tokens = new Map();
6
+ }
7
+ create(platform, platformUserId, conversationId, sessionKey, sessionFile, platformUserName) {
8
+ const token = {
9
+ token: randomBytes(16).toString("hex"),
10
+ platform,
11
+ platformUserId,
12
+ ...(platformUserName ? { platformUserName } : {}),
13
+ conversationId,
14
+ sessionKey,
15
+ sessionFile,
16
+ expiresAt: Date.now() + TTL_MS,
17
+ };
18
+ this.tokens.set(token.token, token);
19
+ return token;
20
+ }
21
+ peek(rawToken) {
22
+ const entry = this.tokens.get(rawToken);
23
+ if (!entry || Date.now() > entry.expiresAt) {
24
+ if (entry)
25
+ this.tokens.delete(rawToken);
26
+ return undefined;
27
+ }
28
+ return entry;
29
+ }
30
+ purge() {
31
+ const now = Date.now();
32
+ for (const [key, value] of this.tokens) {
33
+ if (now > value.expiresAt) {
34
+ this.tokens.delete(key);
35
+ }
36
+ }
37
+ }
38
+ }
39
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/session-view/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAcrC,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEnC,MAAM,OAAO,6BAA6B;IAA1C;QACU,WAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IAyCvD,CAAC;IAvCC,MAAM,CACJ,QAAsB,EACtB,cAAsB,EACtB,cAAsB,EACtB,UAAkB,EAClB,WAAmB,EACnB,gBAAyB;QAEzB,MAAM,KAAK,GAAqB;YAC9B,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;YACtC,QAAQ;YACR,cAAc;YACd,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,cAAc;YACd,UAAU;YACV,WAAW;YACX,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM;SAC/B,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACpC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC,QAAgB;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAC3C,IAAI,KAAK;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACxC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACvC,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;gBAC1B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;CACF","sourcesContent":["import { randomBytes } from \"crypto\";\nimport type { PlatformName } from \"../adapter.js\";\n\nexport interface SessionViewToken {\n token: string;\n platform: PlatformName;\n platformUserId: string;\n platformUserName?: string;\n conversationId: string;\n sessionKey: string;\n sessionFile: string;\n expiresAt: number;\n}\n\nconst TTL_MS = 24 * 60 * 60 * 1000;\n\nexport class InMemorySessionViewTokenStore {\n private tokens = new Map<string, SessionViewToken>();\n\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n sessionKey: string,\n sessionFile: string,\n platformUserName?: string,\n ): SessionViewToken {\n const token: SessionViewToken = {\n token: randomBytes(16).toString(\"hex\"),\n platform,\n platformUserId,\n ...(platformUserName ? { platformUserName } : {}),\n conversationId,\n sessionKey,\n sessionFile,\n expiresAt: Date.now() + TTL_MS,\n };\n this.tokens.set(token.token, token);\n return token;\n }\n\n peek(rawToken: string): SessionViewToken | undefined {\n const entry = this.tokens.get(rawToken);\n if (!entry || Date.now() > entry.expiresAt) {\n if (entry) this.tokens.delete(rawToken);\n return undefined;\n }\n return entry;\n }\n\n purge(): void {\n const now = Date.now();\n for (const [key, value] of this.tokens) {\n if (now > value.expiresAt) {\n this.tokens.delete(key);\n }\n }\n }\n}\n"]}
package/dist/store.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export interface Attachment {
2
2
  original: string;
3
- local: string;
3
+ localPath: string;
4
4
  }
5
5
  export interface LoggedMessage {
6
6
  date: string;
@@ -20,8 +20,6 @@ export interface ChannelStoreConfig {
20
20
  export declare class ChannelStore {
21
21
  private workingDir;
22
22
  private botToken;
23
- private pendingDownloads;
24
- private isDownloading;
25
23
  private recentlyLogged;
26
24
  constructor(config: ChannelStoreConfig);
27
25
  /**
@@ -33,14 +31,14 @@ export declare class ChannelStore {
33
31
  */
34
32
  generateLocalFilename(originalName: string, timestamp: string): string;
35
33
  /**
36
- * Process attachments from a Slack message event
37
- * Returns attachment metadata and queues downloads
34
+ * Process attachments from a Slack message event.
35
+ * Downloads files before returning so callers only receive readable paths.
38
36
  */
39
37
  processAttachments(channelId: string, files: Array<{
40
38
  name?: string;
41
39
  url_private_download?: string;
42
40
  url_private?: string;
43
- }>, timestamp: string): Attachment[];
41
+ }>, timestamp: string): Promise<Attachment[]>;
44
42
  /**
45
43
  * Log a message to the channel's log.jsonl
46
44
  * Returns false if message was already logged (duplicate)
@@ -55,7 +53,6 @@ export declare class ChannelStore {
55
53
  * Returns null if no log exists
56
54
  */
57
55
  getLastTimestamp(channelId: string): string | null;
58
- private processDownloadQueue;
59
56
  private downloadAttachment;
60
57
  }
61
58
  //# sourceMappingURL=store.d.ts.map