@geminixiang/mikan 1.0.0-beta.38 → 1.0.0-beta.39

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 (193) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +1 -1
  3. package/dist/adapter.js +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +3 -6
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +16 -39
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/github/bot.d.ts +2 -0
  10. package/dist/adapters/github/bot.d.ts.map +1 -1
  11. package/dist/adapters/github/bot.js +15 -11
  12. package/dist/adapters/github/bot.js.map +1 -1
  13. package/dist/adapters/github/github-ops.d.ts +1 -1
  14. package/dist/adapters/github/github-ops.d.ts.map +1 -1
  15. package/dist/adapters/github/github-ops.js +2 -1
  16. package/dist/adapters/github/github-ops.js.map +1 -1
  17. package/dist/adapters/github/repo.d.ts +2 -1
  18. package/dist/adapters/github/repo.d.ts.map +1 -1
  19. package/dist/adapters/github/repo.js +2 -3
  20. package/dist/adapters/github/repo.js.map +1 -1
  21. package/dist/adapters/github/types.d.ts +1 -1
  22. package/dist/adapters/github/types.d.ts.map +1 -1
  23. package/dist/adapters/github/types.js.map +1 -1
  24. package/dist/adapters/intake.js +1 -1
  25. package/dist/adapters/intake.js.map +1 -1
  26. package/dist/adapters/shared.d.ts +17 -5
  27. package/dist/adapters/shared.d.ts.map +1 -1
  28. package/dist/adapters/shared.js +44 -8
  29. package/dist/adapters/shared.js.map +1 -1
  30. package/dist/adapters/slack/blocks.d.ts +14 -0
  31. package/dist/adapters/slack/blocks.d.ts.map +1 -1
  32. package/dist/adapters/slack/blocks.js +34 -0
  33. package/dist/adapters/slack/blocks.js.map +1 -1
  34. package/dist/adapters/slack/bot.d.ts +19 -6
  35. package/dist/adapters/slack/bot.d.ts.map +1 -1
  36. package/dist/adapters/slack/bot.js +88 -21
  37. package/dist/adapters/slack/bot.js.map +1 -1
  38. package/dist/adapters/slack/types.d.ts +1 -1
  39. package/dist/adapters/slack/types.js.map +1 -1
  40. package/dist/adapters/telegram/bot.d.ts +5 -6
  41. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  42. package/dist/adapters/telegram/bot.js +29 -51
  43. package/dist/adapters/telegram/bot.js.map +1 -1
  44. package/dist/adapters/types.d.ts +19 -1
  45. package/dist/adapters/types.d.ts.map +1 -1
  46. package/dist/adapters/types.js.map +1 -1
  47. package/dist/agent.d.ts.map +1 -1
  48. package/dist/agent.js +55 -54
  49. package/dist/agent.js.map +1 -1
  50. package/dist/cli/ext-dev.d.ts.map +1 -1
  51. package/dist/cli/ext-dev.js +8 -8
  52. package/dist/cli/ext-dev.js.map +1 -1
  53. package/dist/cli/ext.js +3 -3
  54. package/dist/cli/ext.js.map +1 -1
  55. package/dist/cli/office.js +2 -2
  56. package/dist/cli/office.js.map +1 -1
  57. package/dist/commands/auto-reply.d.ts.map +1 -1
  58. package/dist/commands/auto-reply.js +1 -2
  59. package/dist/commands/auto-reply.js.map +1 -1
  60. package/dist/commands/login.js +1 -1
  61. package/dist/commands/login.js.map +1 -1
  62. package/dist/commands/manifest.d.ts.map +1 -1
  63. package/dist/commands/manifest.js +3 -1
  64. package/dist/commands/manifest.js.map +1 -1
  65. package/dist/commands/model.d.ts.map +1 -1
  66. package/dist/commands/model.js +3 -7
  67. package/dist/commands/model.js.map +1 -1
  68. package/dist/commands/sandbox.d.ts.map +1 -1
  69. package/dist/commands/sandbox.js +7 -7
  70. package/dist/commands/sandbox.js.map +1 -1
  71. package/dist/commands/session-view.d.ts.map +1 -1
  72. package/dist/commands/session-view.js +1 -2
  73. package/dist/commands/session-view.js.map +1 -1
  74. package/dist/commands/types.d.ts +2 -1
  75. package/dist/commands/types.d.ts.map +1 -1
  76. package/dist/commands/types.js.map +1 -1
  77. package/dist/config.d.ts +10 -18
  78. package/dist/config.d.ts.map +1 -1
  79. package/dist/config.js +16 -15
  80. package/dist/config.js.map +1 -1
  81. package/dist/execution-resolver.d.ts +4 -6
  82. package/dist/execution-resolver.d.ts.map +1 -1
  83. package/dist/execution-resolver.js +10 -19
  84. package/dist/execution-resolver.js.map +1 -1
  85. package/dist/harness/extensions/loader.js +1 -1
  86. package/dist/harness/extensions/loader.js.map +1 -1
  87. package/dist/index.d.ts +2 -0
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +1 -0
  90. package/dist/index.js.map +1 -1
  91. package/dist/main.js +16 -16
  92. package/dist/main.js.map +1 -1
  93. package/dist/{office-address.d.ts → office/address.d.ts} +13 -14
  94. package/dist/office/address.d.ts.map +1 -0
  95. package/dist/{office-address.js → office/address.js} +13 -18
  96. package/dist/office/address.js.map +1 -0
  97. package/dist/office/index.d.ts +11 -0
  98. package/dist/office/index.d.ts.map +1 -0
  99. package/dist/office/index.js +11 -0
  100. package/dist/office/index.js.map +1 -0
  101. package/dist/office/layout.d.ts +20 -0
  102. package/dist/office/layout.d.ts.map +1 -0
  103. package/dist/office/layout.js +88 -0
  104. package/dist/office/layout.js.map +1 -0
  105. package/dist/{office-migration.d.ts → office/migration.d.ts} +3 -3
  106. package/dist/office/migration.d.ts.map +1 -0
  107. package/dist/{office-migration.js → office/migration.js} +8 -12
  108. package/dist/office/migration.js.map +1 -0
  109. package/dist/{office-registry.d.ts → office/registry.d.ts} +18 -24
  110. package/dist/office/registry.d.ts.map +1 -0
  111. package/dist/{office-registry.js → office/registry.js} +22 -83
  112. package/dist/office/registry.js.map +1 -0
  113. package/dist/office/types.d.ts +71 -0
  114. package/dist/office/types.d.ts.map +1 -0
  115. package/dist/office/types.js +2 -0
  116. package/dist/office/types.js.map +1 -0
  117. package/dist/packages/admin.js +5 -5
  118. package/dist/packages/admin.js.map +1 -1
  119. package/dist/packages/inspect.d.ts.map +1 -1
  120. package/dist/packages/inspect.js +3 -8
  121. package/dist/packages/inspect.js.map +1 -1
  122. package/dist/packages/materialize.js +1 -1
  123. package/dist/packages/materialize.js.map +1 -1
  124. package/dist/packages/resolve.js +5 -8
  125. package/dist/packages/resolve.js.map +1 -1
  126. package/dist/packages/types.d.ts +2 -5
  127. package/dist/packages/types.d.ts.map +1 -1
  128. package/dist/packages/types.js.map +1 -1
  129. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  130. package/dist/runtime/conversation-runtime.js +13 -15
  131. package/dist/runtime/conversation-runtime.js.map +1 -1
  132. package/dist/runtime/session-lifecycle.js +1 -1
  133. package/dist/runtime/session-lifecycle.js.map +1 -1
  134. package/dist/runtime/types.d.ts +3 -2
  135. package/dist/runtime/types.d.ts.map +1 -1
  136. package/dist/runtime/types.js.map +1 -1
  137. package/dist/sandbox/identity.js +1 -1
  138. package/dist/sandbox/identity.js.map +1 -1
  139. package/dist/settings-mutation.d.ts +3 -2
  140. package/dist/settings-mutation.d.ts.map +1 -1
  141. package/dist/settings-mutation.js +6 -7
  142. package/dist/settings-mutation.js.map +1 -1
  143. package/dist/tools/generate-image.d.ts +8 -2
  144. package/dist/tools/generate-image.d.ts.map +1 -1
  145. package/dist/tools/generate-image.js +5 -3
  146. package/dist/tools/generate-image.js.map +1 -1
  147. package/dist/tools/index.d.ts +5 -0
  148. package/dist/tools/index.d.ts.map +1 -1
  149. package/dist/tools/index.js +3 -5
  150. package/dist/tools/index.js.map +1 -1
  151. package/dist/trigger.d.ts +1 -1
  152. package/dist/trigger.d.ts.map +1 -1
  153. package/dist/trigger.js +5 -10
  154. package/dist/trigger.js.map +1 -1
  155. package/dist/types.d.ts +3 -22
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js.map +1 -1
  158. package/dist/vault/index.js +1 -1
  159. package/dist/vault/index.js.map +1 -1
  160. package/dist/vault/injection.d.ts +3 -1
  161. package/dist/vault/injection.d.ts.map +1 -1
  162. package/dist/vault/injection.js +2 -2
  163. package/dist/vault/injection.js.map +1 -1
  164. package/dist/web/admin/portal.d.ts.map +1 -1
  165. package/dist/web/admin/portal.js +67 -80
  166. package/dist/web/admin/portal.js.map +1 -1
  167. package/dist/web/admin/types.d.ts +2 -1
  168. package/dist/web/admin/types.d.ts.map +1 -1
  169. package/dist/web/admin/types.js.map +1 -1
  170. package/dist/web/server.d.ts +1 -1
  171. package/dist/web/server.d.ts.map +1 -1
  172. package/dist/web/server.js +3 -3
  173. package/dist/web/server.js.map +1 -1
  174. package/dist/web/session-view/portal.js +1 -1
  175. package/dist/web/session-view/portal.js.map +1 -1
  176. package/dist/workspace-projection/index.d.ts +2 -4
  177. package/dist/workspace-projection/index.d.ts.map +1 -1
  178. package/dist/workspace-projection/index.js +21 -56
  179. package/dist/workspace-projection/index.js.map +1 -1
  180. package/dist/workspace-projection/types.d.ts +0 -2
  181. package/dist/workspace-projection/types.d.ts.map +1 -1
  182. package/dist/workspace-projection/types.js.map +1 -1
  183. package/package.json +41 -16
  184. package/dist/office-address.d.ts.map +0 -1
  185. package/dist/office-address.js.map +0 -1
  186. package/dist/office-migration.d.ts.map +0 -1
  187. package/dist/office-migration.js.map +0 -1
  188. package/dist/office-registry.d.ts.map +0 -1
  189. package/dist/office-registry.js.map +0 -1
  190. package/dist/store.d.ts +0 -51
  191. package/dist/store.d.ts.map +0 -1
  192. package/dist/store.js +0 -185
  193. package/dist/store.js.map +0 -1
package/dist/store.js DELETED
@@ -1,185 +0,0 @@
1
- import { appendFile, writeFile } from "fs/promises";
2
- import { dirname, join } from "path";
3
- import { ensureDirExists, isRecord, parseJsonValue, readTextFileIfExists, } from "./utils/file-guards.js";
4
- import { withRetry } from "./adapters/shared.js";
5
- import { conversationOfficeDir, createOfficeAddress, officeDirName } from "./office-address.js";
6
- import { ensureOfficeDir } from "./office-registry.js";
7
- class AttachmentDownloadHttpError extends Error {
8
- constructor(message, status) {
9
- super(message);
10
- this.status = status;
11
- }
12
- }
13
- function isRetryableAttachmentDownloadError(error) {
14
- if (!(error instanceof AttachmentDownloadHttpError))
15
- return true;
16
- return error.status === 408 || error.status === 429 || error.status >= 500;
17
- }
18
- /**
19
- * Slack office store: message logs and attachment downloads for one Slack
20
- * workspace (the bot token is a Slack credential). Channel ids are therefore
21
- * Slack conversation ids; other platforms log through their adapters.
22
- */
23
- export class ChannelStore {
24
- constructor(config) {
25
- // Track recently logged message timestamps to prevent duplicates
26
- // Key: "channelId:ts", automatically cleaned up after 60 seconds
27
- this.recentlyLogged = new Map();
28
- this.loggingMessages = new Set();
29
- this.workingDir = config.workingDir;
30
- this.botToken = config.botToken;
31
- // Ensure working directory exists
32
- ensureDirExists(this.workingDir);
33
- }
34
- /**
35
- * Get or create the directory for a channel/DM
36
- */
37
- getChannelDir(channelId) {
38
- return ensureOfficeDir(this.workingDir, createOfficeAddress("slack", channelId));
39
- }
40
- /**
41
- * Generate a unique local filename for an attachment
42
- */
43
- generateLocalFilename(originalName, timestamp) {
44
- // Convert slack timestamp (1234567890.123456) to milliseconds
45
- const ts = Math.floor(parseFloat(timestamp) * 1000);
46
- // Sanitize original name (remove problematic characters)
47
- const sanitized = originalName.replace(/[^a-zA-Z0-9._-]/g, "_");
48
- return `${ts}_${sanitized}`;
49
- }
50
- /**
51
- * Process attachments from a Slack message event.
52
- * Downloads files before returning so callers only receive readable paths.
53
- */
54
- async processAttachments(channelId, files, timestamp) {
55
- // Attachment downloads can be the office's first write; materialize (and
56
- // register) it before composing office-relative attachment paths.
57
- this.getChannelDir(channelId);
58
- const downloads = [];
59
- for (const file of files) {
60
- const url = file.url_private_download || file.url_private;
61
- if (!url)
62
- continue;
63
- if (!file.name) {
64
- throw new Error(`Attachment missing name for URL: ${url}`);
65
- }
66
- const filename = this.generateLocalFilename(file.name, timestamp);
67
- const localPath = `${officeDirName(createOfficeAddress("slack", channelId))}/attachments/${filename}`;
68
- const attachment = {
69
- original: file.name,
70
- localPath,
71
- };
72
- downloads.push(this.downloadAttachmentWithRetry(localPath, url)
73
- .then(() => attachment)
74
- .catch((error) => {
75
- const errorMsg = error instanceof Error ? error.message : String(error);
76
- throw new Error(`Failed to download attachment ${localPath}: ${errorMsg}`, {
77
- cause: error,
78
- });
79
- }));
80
- }
81
- return Promise.all(downloads);
82
- }
83
- /**
84
- * Log a message to the channel's log.jsonl
85
- * Returns false if message was already logged (duplicate)
86
- */
87
- async logMessage(channelId, message) {
88
- // Check for duplicate (same channel + timestamp)
89
- const dedupeKey = `${channelId}:${message.ts}`;
90
- if (this.recentlyLogged.has(dedupeKey) || this.loggingMessages.has(dedupeKey)) {
91
- return false; // Already logged or being logged
92
- }
93
- this.loggingMessages.add(dedupeKey);
94
- const logPath = join(this.getChannelDir(channelId), "log.jsonl");
95
- // Ensure message has a date field
96
- if (!message.date) {
97
- // Parse timestamp to get date
98
- let date;
99
- if (message.ts.includes(".")) {
100
- // Slack timestamp format (1234567890.123456)
101
- date = new Date(parseFloat(message.ts) * 1000);
102
- }
103
- else {
104
- // Epoch milliseconds
105
- date = new Date(parseInt(message.ts, 10));
106
- }
107
- message.date = date.toISOString();
108
- }
109
- try {
110
- const line = `${JSON.stringify(message)}\n`;
111
- await appendFile(logPath, line, "utf-8");
112
- // Mark as logged only after the append succeeds. Otherwise a transient
113
- // write failure can make retries look like duplicates and drop messages.
114
- this.recentlyLogged.set(dedupeKey, Date.now());
115
- setTimeout(() => this.recentlyLogged.delete(dedupeKey), 60000);
116
- return true;
117
- }
118
- finally {
119
- this.loggingMessages.delete(dedupeKey);
120
- }
121
- }
122
- /**
123
- * Log a bot response
124
- */
125
- async logBotResponse(channelId, text, ts) {
126
- await this.logMessage(channelId, {
127
- date: new Date().toISOString(),
128
- ts,
129
- user: "bot",
130
- text,
131
- attachments: [],
132
- isMessagingBot: true,
133
- });
134
- }
135
- /**
136
- * Get the timestamp of the last logged message for a channel
137
- * Returns null if no log exists
138
- */
139
- getLastTimestamp(channelId) {
140
- const logPath = join(conversationOfficeDir(this.workingDir, createOfficeAddress("slack", channelId)), "log.jsonl");
141
- const content = readTextFileIfExists(logPath);
142
- if (content === undefined) {
143
- return null;
144
- }
145
- try {
146
- const lines = content.trim().split("\n");
147
- if (lines.length === 0 || lines[0] === "") {
148
- return null;
149
- }
150
- const lastLine = lines[lines.length - 1];
151
- const message = parseJsonValue(lastLine, (value) => isRecord(value) && typeof value.ts === "string", (detail) => (detail === "unexpected JSON shape" ? "log entry missing timestamp" : detail));
152
- return message.ts;
153
- }
154
- catch {
155
- return null;
156
- }
157
- }
158
- /**
159
- * Download a single attachment
160
- */
161
- async downloadAttachmentWithRetry(localPath, url) {
162
- await withRetry(() => this.downloadAttachment(localPath, url), {
163
- maxAttempts: 3,
164
- baseDelayMs: 250,
165
- isRateLimited: isRetryableAttachmentDownloadError,
166
- });
167
- }
168
- async downloadAttachment(localPath, url) {
169
- const filePath = join(this.workingDir, localPath);
170
- // Ensure directory exists
171
- const parentDir = join(this.workingDir, dirname(localPath));
172
- ensureDirExists(parentDir);
173
- const response = await fetch(url, {
174
- headers: {
175
- Authorization: `Bearer ${this.botToken}`,
176
- },
177
- });
178
- if (!response.ok) {
179
- throw new AttachmentDownloadHttpError(`HTTP ${response.status}: ${response.statusText}`, response.status);
180
- }
181
- const buffer = await response.arrayBuffer();
182
- await writeFile(filePath, Buffer.from(buffer));
183
- }
184
- }
185
- //# sourceMappingURL=store.js.map
package/dist/store.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFile, writeFile } from \"fs/promises\";\nimport { dirname, join } from \"path\";\nimport {\n ensureDirExists,\n isRecord,\n parseJsonValue,\n readTextFileIfExists,\n} from \"./utils/file-guards.js\";\nimport { withRetry } from \"./adapters/shared.js\";\n\nexport type { Attachment, ChannelStoreConfig, LoggedMessage } from \"./types.js\";\nimport type { Attachment, ChannelStoreConfig, LoggedMessage } from \"./types.js\";\nimport { conversationOfficeDir, createOfficeAddress, officeDirName } from \"./office-address.js\";\nimport { ensureOfficeDir } from \"./office-registry.js\";\n\nclass AttachmentDownloadHttpError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message);\n }\n}\n\nfunction isRetryableAttachmentDownloadError(error: unknown): boolean {\n if (!(error instanceof AttachmentDownloadHttpError)) return true;\n return error.status === 408 || error.status === 429 || error.status >= 500;\n}\n\n/**\n * Slack office store: message logs and attachment downloads for one Slack\n * workspace (the bot token is a Slack credential). Channel ids are therefore\n * Slack conversation ids; other platforms log through their adapters.\n */\nexport class ChannelStore {\n private workingDir: string;\n private botToken: string;\n // Track recently logged message timestamps to prevent duplicates\n // Key: \"channelId:ts\", automatically cleaned up after 60 seconds\n private recentlyLogged = new Map<string, number>();\n private loggingMessages = new Set<string>();\n\n constructor(config: ChannelStoreConfig) {\n this.workingDir = config.workingDir;\n this.botToken = config.botToken;\n\n // Ensure working directory exists\n ensureDirExists(this.workingDir);\n }\n\n /**\n * Get or create the directory for a channel/DM\n */\n getChannelDir(channelId: string): string {\n return ensureOfficeDir(this.workingDir, createOfficeAddress(\"slack\", channelId));\n }\n\n /**\n * Generate a unique local filename for an attachment\n */\n generateLocalFilename(originalName: string, timestamp: string): string {\n // Convert slack timestamp (1234567890.123456) to milliseconds\n const ts = Math.floor(parseFloat(timestamp) * 1000);\n // Sanitize original name (remove problematic characters)\n const sanitized = originalName.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n return `${ts}_${sanitized}`;\n }\n\n /**\n * Process attachments from a Slack message event.\n * Downloads files before returning so callers only receive readable paths.\n */\n async processAttachments(\n channelId: string,\n files: Array<{ name?: string; url_private_download?: string; url_private?: string }>,\n timestamp: string,\n ): Promise<Attachment[]> {\n // Attachment downloads can be the office's first write; materialize (and\n // register) it before composing office-relative attachment paths.\n this.getChannelDir(channelId);\n const downloads: Array<Promise<Attachment>> = [];\n\n for (const file of files) {\n const url = file.url_private_download || file.url_private;\n if (!url) continue;\n if (!file.name) {\n throw new Error(`Attachment missing name for URL: ${url}`);\n }\n\n const filename = this.generateLocalFilename(file.name, timestamp);\n const localPath = `${officeDirName(createOfficeAddress(\"slack\", channelId))}/attachments/${filename}`;\n const attachment: Attachment = {\n original: file.name,\n localPath,\n };\n\n downloads.push(\n this.downloadAttachmentWithRetry(localPath, url)\n .then(() => attachment)\n .catch((error) => {\n const errorMsg = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to download attachment ${localPath}: ${errorMsg}`, {\n cause: error,\n });\n }),\n );\n }\n\n return Promise.all(downloads);\n }\n\n /**\n * Log a message to the channel's log.jsonl\n * Returns false if message was already logged (duplicate)\n */\n async logMessage(channelId: string, message: LoggedMessage): Promise<boolean> {\n // Check for duplicate (same channel + timestamp)\n const dedupeKey = `${channelId}:${message.ts}`;\n if (this.recentlyLogged.has(dedupeKey) || this.loggingMessages.has(dedupeKey)) {\n return false; // Already logged or being logged\n }\n this.loggingMessages.add(dedupeKey);\n\n const logPath = join(this.getChannelDir(channelId), \"log.jsonl\");\n\n // Ensure message has a date field\n if (!message.date) {\n // Parse timestamp to get date\n let date: Date;\n if (message.ts.includes(\".\")) {\n // Slack timestamp format (1234567890.123456)\n date = new Date(parseFloat(message.ts) * 1000);\n } else {\n // Epoch milliseconds\n date = new Date(parseInt(message.ts, 10));\n }\n message.date = date.toISOString();\n }\n\n try {\n const line = `${JSON.stringify(message)}\\n`;\n await appendFile(logPath, line, \"utf-8\");\n\n // Mark as logged only after the append succeeds. Otherwise a transient\n // write failure can make retries look like duplicates and drop messages.\n this.recentlyLogged.set(dedupeKey, Date.now());\n setTimeout(() => this.recentlyLogged.delete(dedupeKey), 60000);\n return true;\n } finally {\n this.loggingMessages.delete(dedupeKey);\n }\n }\n\n /**\n * Log a bot response\n */\n async logBotResponse(channelId: string, text: string, ts: string): Promise<void> {\n await this.logMessage(channelId, {\n date: new Date().toISOString(),\n ts,\n user: \"bot\",\n text,\n attachments: [],\n isMessagingBot: true,\n });\n }\n\n /**\n * Get the timestamp of the last logged message for a channel\n * Returns null if no log exists\n */\n getLastTimestamp(channelId: string): string | null {\n const logPath = join(\n conversationOfficeDir(this.workingDir, createOfficeAddress(\"slack\", channelId)),\n \"log.jsonl\",\n );\n const content = readTextFileIfExists(logPath);\n if (content === undefined) {\n return null;\n }\n\n try {\n const lines = content.trim().split(\"\\n\");\n if (lines.length === 0 || lines[0] === \"\") {\n return null;\n }\n const lastLine = lines[lines.length - 1];\n const message = parseJsonValue(\n lastLine,\n (value): value is LoggedMessage => isRecord(value) && typeof value.ts === \"string\",\n (detail) => (detail === \"unexpected JSON shape\" ? \"log entry missing timestamp\" : detail),\n );\n return message.ts;\n } catch {\n return null;\n }\n }\n\n /**\n * Download a single attachment\n */\n private async downloadAttachmentWithRetry(localPath: string, url: string): Promise<void> {\n await withRetry(() => this.downloadAttachment(localPath, url), {\n maxAttempts: 3,\n baseDelayMs: 250,\n isRateLimited: isRetryableAttachmentDownloadError,\n });\n }\n\n private async downloadAttachment(localPath: string, url: string): Promise<void> {\n const filePath = join(this.workingDir, localPath);\n\n // Ensure directory exists\n const parentDir = join(this.workingDir, dirname(localPath));\n ensureDirExists(parentDir);\n\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.botToken}`,\n },\n });\n\n if (!response.ok) {\n throw new AttachmentDownloadHttpError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n );\n }\n\n const buffer = await response.arrayBuffer();\n await writeFile(filePath, Buffer.from(buffer));\n }\n}\n"]}