@rubytech/create-maxy-code 0.1.379 → 0.1.383

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 (68) hide show
  1. package/dist/__tests__/plugin-install.test.js +18 -1
  2. package/dist/index.js +22 -8
  3. package/dist/lib/plugin-install.js +22 -0
  4. package/package.json +1 -1
  5. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +23 -8
  6. package/payload/platform/plugins/docs/references/admin-ui.md +11 -6
  7. package/payload/platform/plugins/docs/references/attachments.md +5 -1
  8. package/payload/platform/plugins/docs/references/internals.md +4 -0
  9. package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
  10. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.d.ts +2 -0
  11. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.d.ts.map +1 -0
  12. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js +23 -0
  13. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/d1-command.test.js.map +1 -0
  14. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts +12 -0
  15. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.d.ts.map +1 -0
  16. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js +14 -0
  17. package/payload/platform/plugins/scheduling/mcp/dist/lib/d1-command.js.map +1 -0
  18. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +3 -1
  19. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  20. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +1 -1
  21. package/payload/platform/services/claude-session-manager/dist/account-title-stores.d.ts +21 -0
  22. package/payload/platform/services/claude-session-manager/dist/account-title-stores.d.ts.map +1 -0
  23. package/payload/platform/services/claude-session-manager/dist/account-title-stores.js +71 -0
  24. package/payload/platform/services/claude-session-manager/dist/account-title-stores.js.map +1 -0
  25. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +2 -2
  26. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  27. package/payload/platform/services/claude-session-manager/dist/http-server.js +46 -103
  28. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  29. package/payload/platform/services/claude-session-manager/dist/index.js +45 -8
  30. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  31. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.d.ts +51 -0
  32. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.d.ts.map +1 -0
  33. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.js +163 -0
  34. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.js.map +1 -0
  35. package/payload/server/package.json +2 -1
  36. package/payload/server/public/assets/{AdminLoginScreens-BHo8w_9J.js → AdminLoginScreens-C32Kg8HN.js} +1 -1
  37. package/payload/server/public/assets/AdminShell-DizHgGBB.js +1 -0
  38. package/payload/server/public/assets/{Checkbox-LmjNFccS.js → Checkbox-C-dvkjKb.js} +1 -1
  39. package/payload/server/public/assets/{admin-BBWILfl8.js → admin-CSBsndZ4.js} +1 -1
  40. package/payload/server/public/assets/admin-types-hioowVct.js +1 -0
  41. package/payload/server/public/assets/{browser-DtLvD_ES.js → browser-BUuA2PPl.js} +1 -1
  42. package/payload/server/public/assets/{calendar-D3i4IqOV.js → calendar-CGlyx54B.js} +1 -1
  43. package/payload/server/public/assets/chat-DT1PbRkD.js +1 -0
  44. package/payload/server/public/assets/data-BC4KTIX4.js +1 -0
  45. package/payload/server/public/assets/{graph-ClQeGEH4.js → graph-CvQZVxwZ.js} +1 -1
  46. package/payload/server/public/assets/{graph-labels-OaIKHgMg.js → graph-labels-C7bJMcXs.js} +1 -1
  47. package/payload/server/public/assets/{operator-Dxqfta5U.js → operator-4skjNtEa.js} +1 -1
  48. package/payload/server/public/assets/{page-Dv_YEOUW.js → page-B0zvkWaG.js} +1 -1
  49. package/payload/server/public/assets/page-BYlBTwqJ.js +32 -0
  50. package/payload/server/public/assets/{public-C_mM8sSZ.js → public-ClYyeEA1.js} +1 -1
  51. package/payload/server/public/assets/{rotate-ccw-VF0b6NTt.js → rotate-ccw-DPCP-Lmz.js} +1 -1
  52. package/payload/server/public/assets/useSubAccountSwitcher-B8FEcQNM.js +9 -0
  53. package/payload/server/public/assets/{useSubAccountSwitcher-VzvzZC7h.css → useSubAccountSwitcher-DqxG4I5C.css} +1 -1
  54. package/payload/server/public/browser.html +4 -4
  55. package/payload/server/public/calendar.html +4 -4
  56. package/payload/server/public/chat.html +7 -7
  57. package/payload/server/public/data.html +5 -5
  58. package/payload/server/public/graph.html +7 -7
  59. package/payload/server/public/index.html +9 -9
  60. package/payload/server/public/operator.html +9 -9
  61. package/payload/server/public/public.html +7 -7
  62. package/payload/server/server.js +301 -82
  63. package/payload/server/public/assets/AdminShell-DbTijFVA.js +0 -1
  64. package/payload/server/public/assets/admin-types-DJoj6VJv.js +0 -1
  65. package/payload/server/public/assets/chat-DU-T6blN.js +0 -1
  66. package/payload/server/public/assets/data-B_vDxQrO.js +0 -1
  67. package/payload/server/public/assets/page-BkIb7CHh.js +0 -32
  68. package/payload/server/public/assets/useSubAccountSwitcher-CQbgG-FH.js +0 -9
@@ -1886,6 +1886,14 @@ function credsCensus(authDir) {
1886
1886
  return { credsPresent: true, registered: false, hasAccountSignature: false, e164: null };
1887
1887
  }
1888
1888
  }
1889
+ function getAuthAgeMs(authDir) {
1890
+ try {
1891
+ const stats = fsSync.statSync(resolveCredsPath(authDir));
1892
+ return Date.now() - stats.mtimeMs;
1893
+ } catch {
1894
+ return null;
1895
+ }
1896
+ }
1889
1897
  async function clearAuth(authDir) {
1890
1898
  try {
1891
1899
  await fs.access(authDir);
@@ -1897,6 +1905,17 @@ async function clearAuth(authDir) {
1897
1905
  console.error(`${TAG} cleared credentials at ${authDir}`);
1898
1906
  return true;
1899
1907
  }
1908
+ function discardAuthDirSync(authDir) {
1909
+ try {
1910
+ if (!fsSync.existsSync(authDir)) return false;
1911
+ fsSync.rmSync(authDir, { recursive: true, force: true });
1912
+ console.error(`${TAG} discarded credential dir ${authDir}`);
1913
+ return true;
1914
+ } catch (err) {
1915
+ console.warn(`${TAG} discardAuthDirSync failed authDir=${authDir}: ${String(err)}`);
1916
+ return false;
1917
+ }
1918
+ }
1900
1919
  function listCredentialAccountIds(configDir2) {
1901
1920
  const credsRoot = path.join(os.homedir(), configDir2, "credentials", "whatsapp");
1902
1921
  try {
@@ -3154,7 +3173,7 @@ async function sendMediaMessage(sock, to, media, opts) {
3154
3173
  }
3155
3174
 
3156
3175
  // app/lib/whatsapp/message-store.ts
3157
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, appendFileSync, readdirSync } from "fs";
3176
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, appendFileSync, readdirSync, statSync as statSync2 } from "fs";
3158
3177
  import { dirname, join as join3 } from "path";
3159
3178
  var STORE_ROOT = () => join3(process.env.MAXY_DIR_OVERRIDE ?? MAXY_DIR, "data", "whatsapp-messages");
3160
3179
  function conversationFile(platformAccountId, remoteJid) {
@@ -3175,6 +3194,7 @@ function parseLines(raw) {
3175
3194
  }
3176
3195
  return out;
3177
3196
  }
3197
+ var byDateSentAsc = (a, b) => a.dateSent.localeCompare(b.dateSent);
3178
3198
  function appendMessage(platformAccountId, record) {
3179
3199
  const file = conversationFile(platformAccountId, record.remoteJid);
3180
3200
  try {
@@ -3209,13 +3229,79 @@ function readConversation(platformAccountId, remoteJid) {
3209
3229
  const file = conversationFile(platformAccountId, remoteJid);
3210
3230
  try {
3211
3231
  if (!existsSync3(file)) return [];
3212
- return parseLines(readFileSync4(file, "utf8")).sort((a, b) => a.dateSent.localeCompare(b.dateSent));
3232
+ return parseLines(readFileSync4(file, "utf8")).sort(byDateSentAsc);
3213
3233
  } catch (err) {
3214
3234
  const reason = err instanceof Error ? err.message : String(err);
3215
3235
  console.error(`[whatsapp-message-store] FAIL op=read reason=${reason.slice(0, 100)} remoteJid=${remoteJid}`);
3216
3236
  return [];
3217
3237
  }
3218
3238
  }
3239
+ var summaryCache = /* @__PURE__ */ new Map();
3240
+ function summariseRecords(records) {
3241
+ if (records.length === 0) return null;
3242
+ const sorted = records.slice().sort(byDateSentAsc);
3243
+ return {
3244
+ first: sorted[0],
3245
+ last: sorted[sorted.length - 1],
3246
+ firstNamedSender: sorted.find((r) => r.senderName)?.senderName ?? null
3247
+ };
3248
+ }
3249
+ function readConversationSummaries(platformAccountId) {
3250
+ const dir = join3(STORE_ROOT(), platformAccountId);
3251
+ const out = /* @__PURE__ */ new Map();
3252
+ const seen = /* @__PURE__ */ new Set();
3253
+ let fullReads = 0;
3254
+ let cacheHits = 0;
3255
+ let bytesRead = 0;
3256
+ try {
3257
+ if (!existsSync3(dir)) return out;
3258
+ for (const entry of readdirSync(dir)) {
3259
+ if (!entry.endsWith(".jsonl")) continue;
3260
+ const remoteJid = entry.slice(0, -".jsonl".length);
3261
+ const file = join3(dir, entry);
3262
+ seen.add(file);
3263
+ let size;
3264
+ let mtimeMs;
3265
+ try {
3266
+ const st = statSync2(file);
3267
+ size = st.size;
3268
+ mtimeMs = st.mtimeMs;
3269
+ } catch {
3270
+ continue;
3271
+ }
3272
+ const cached2 = summaryCache.get(file);
3273
+ if (cached2 && cached2.size === size && cached2.mtimeMs === mtimeMs) {
3274
+ cacheHits++;
3275
+ out.set(remoteJid, cached2.summary);
3276
+ continue;
3277
+ }
3278
+ let summary;
3279
+ try {
3280
+ const raw = readFileSync4(file, "utf8");
3281
+ bytesRead += Buffer.byteLength(raw);
3282
+ fullReads++;
3283
+ summary = summariseRecords(parseLines(raw));
3284
+ } catch (err) {
3285
+ const reason = err instanceof Error ? err.message : String(err);
3286
+ console.error(`[whatsapp-message-store] FAIL op=summarise reason=${reason.slice(0, 100)} remoteJid=${remoteJid}`);
3287
+ continue;
3288
+ }
3289
+ if (!summary) continue;
3290
+ summaryCache.set(file, { size, mtimeMs, summary });
3291
+ out.set(remoteJid, summary);
3292
+ }
3293
+ for (const key of summaryCache.keys()) {
3294
+ if (dirname(key) === dir && !seen.has(key)) summaryCache.delete(key);
3295
+ }
3296
+ } catch (err) {
3297
+ const reason = err instanceof Error ? err.message : String(err);
3298
+ console.error(`[whatsapp-message-store] FAIL op=summaries reason=${reason.slice(0, 100)} platformAccountId=${platformAccountId}`);
3299
+ }
3300
+ console.log(
3301
+ `[whatsapp-message-store] op=summaries accountId=${platformAccountId} conversations=${out.size} fullReads=${fullReads} cacheHits=${cacheHits} bytesRead=${bytesRead}`
3302
+ );
3303
+ return out;
3304
+ }
3219
3305
  function readAllConversations(platformAccountId) {
3220
3306
  const dir = join3(STORE_ROOT(), platformAccountId);
3221
3307
  const out = /* @__PURE__ */ new Map();
@@ -5186,6 +5272,92 @@ function resolveScratchpadPath(raw, scratchpadRootAbs) {
5186
5272
  return resolveUnderRoot(raw, scratchpadRootAbs, "scratchpad");
5187
5273
  }
5188
5274
 
5275
+ // app/lib/doc-to-text.ts
5276
+ import { unzipSync, strFromU8 } from "fflate";
5277
+ var DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
5278
+ var ODT = "application/vnd.oasis.opendocument.text";
5279
+ var CONVERTIBLE_DOC_MIME = /* @__PURE__ */ new Set([
5280
+ DOCX,
5281
+ ODT,
5282
+ "text/rtf",
5283
+ "application/rtf"
5284
+ ]);
5285
+ function bareMime(mimeType) {
5286
+ return mimeType.split(";")[0].trim().toLowerCase();
5287
+ }
5288
+ function unescapeXml(s) {
5289
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
5290
+ }
5291
+ function unzipEntry(buffer, entry) {
5292
+ const files = unzipSync(new Uint8Array(buffer));
5293
+ const bytes = files[entry];
5294
+ if (!bytes) throw new Error(`zip entry not found: ${entry}`);
5295
+ return strFromU8(bytes);
5296
+ }
5297
+ function docxToText(buffer) {
5298
+ const xml = unzipEntry(buffer, "word/document.xml");
5299
+ const paras = [];
5300
+ for (const p of xml.split("</w:p>")) {
5301
+ const withBreaks = p.replace(/<w:tab\b[^>]*\/?>/g, " ").replace(/<w:br\b[^>]*\/?>/g, "\n");
5302
+ let line = "";
5303
+ for (const m of withBreaks.matchAll(/<w:t\b[^>]*>([\s\S]*?)<\/w:t>/g)) {
5304
+ line += unescapeXml(m[1]);
5305
+ }
5306
+ if (line.length > 0) paras.push(line);
5307
+ }
5308
+ return paras.join("\n");
5309
+ }
5310
+ function odtToText(buffer) {
5311
+ const xml = unzipEntry(buffer, "content.xml");
5312
+ const body = xml.replace(/^[\s\S]*?<office:text\b[^>]*>/, "").replace(/<\/office:text>[\s\S]*$/, "");
5313
+ const withBreaks = body.replace(/<text:line-break\b[^>]*\/?>/g, "\n").replace(/<text:tab\b[^>]*\/?>/g, " ").replace(/<\/text:(?:p|h)>/g, "\n");
5314
+ const stripped = withBreaks.replace(/<[^>]+>/g, "");
5315
+ return unescapeXml(stripped).replace(/\n{3,}/g, "\n\n").trim();
5316
+ }
5317
+ var RTF_DROP_DEST = /^\\(?:\*|fonttbl|colortbl|stylesheet|listtable|info|pict)\b/;
5318
+ function stripRtfDestinations(s) {
5319
+ let out = "";
5320
+ for (let i = 0; i < s.length; i++) {
5321
+ const ch = s[i];
5322
+ if (ch === "\\" && i + 1 < s.length) {
5323
+ out += ch + s[i + 1];
5324
+ i++;
5325
+ continue;
5326
+ }
5327
+ if (ch === "{" && RTF_DROP_DEST.test(s.slice(i + 1))) {
5328
+ let depth = 1;
5329
+ let j = i + 1;
5330
+ for (; j < s.length && depth > 0; j++) {
5331
+ if (s[j] === "\\") {
5332
+ j++;
5333
+ continue;
5334
+ }
5335
+ if (s[j] === "{") depth++;
5336
+ else if (s[j] === "}") depth--;
5337
+ }
5338
+ i = j - 1;
5339
+ continue;
5340
+ }
5341
+ out += ch;
5342
+ }
5343
+ return out;
5344
+ }
5345
+ function rtfToText(rtf) {
5346
+ let s = stripRtfDestinations(rtf);
5347
+ s = s.replace(/\\par[d]?\b/g, "\n").replace(/\\line\b/g, "\n").replace(/\\tab\b/g, " ");
5348
+ s = s.replace(/\\'([0-9a-fA-F]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
5349
+ s = s.replace(/\\[a-zA-Z]+-?\d* ?/g, "");
5350
+ s = s.replace(/\\([{}\\])/g, "$1").replace(/[{}]/g, "");
5351
+ return s.replace(/[ \t]{2,}/g, " ").replace(/\n{3,}/g, "\n\n").trim();
5352
+ }
5353
+ function docToText(buffer, mimeType) {
5354
+ const mime = bareMime(mimeType);
5355
+ if (mime === DOCX) return docxToText(buffer);
5356
+ if (mime === ODT) return odtToText(buffer);
5357
+ if (mime === "text/rtf" || mime === "application/rtf") return rtfToText(buffer.toString("latin1"));
5358
+ return null;
5359
+ }
5360
+
5189
5361
  // app/lib/attachments.ts
5190
5362
  function uploadsDirFor(accountId, subPath, attachmentId) {
5191
5363
  return subPath ? resolve7(DATA_ROOT, "accounts", accountId, "uploads", subPath, attachmentId) : resolve7(DATA_ROOT, "accounts", accountId, "uploads", attachmentId);
@@ -5203,6 +5375,14 @@ var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
5203
5375
  "text/calendar",
5204
5376
  "application/zip",
5205
5377
  "application/x-zip-compressed",
5378
+ // Task 1347 — Word/OpenDocument/RTF text documents. The bytes are converted to
5379
+ // a readable-text sibling at ingestion (writeAttachment → doc-to-text.ts)
5380
+ // because the on-device Read tool cannot parse the binary containers. rtf is
5381
+ // reported by browsers as either text/rtf or application/rtf.
5382
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
5383
+ "application/vnd.oasis.opendocument.text",
5384
+ "text/rtf",
5385
+ "application/rtf",
5206
5386
  // Task 1240 — voice recordings persist as playable clips in the admin bubble.
5207
5387
  // These match AUDIO_MIME_TYPES in app/lib/stt/audio-mime.ts exactly.
5208
5388
  "audio/ogg",
@@ -5218,6 +5398,7 @@ var MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024;
5218
5398
  var MAX_FILES_PER_MESSAGE = 5;
5219
5399
  var MAX_DATA_UPLOAD_BYTES = 2 * 1024 * 1024 * 1024;
5220
5400
  var MAX_ZIP_UNCOMPRESSED_BYTES = 100 * 1024 * 1024;
5401
+ var EXTRACTED_TEXT_SUFFIX = ".extracted.txt";
5221
5402
  function assertSupportedMime(mimeType) {
5222
5403
  const bare = mimeType.split(";")[0].trim().toLowerCase();
5223
5404
  if (!SUPPORTED_MIME_TYPES.has(bare)) {
@@ -5243,7 +5424,15 @@ async function writeAttachment(accountId, subPath, filename, mimeType, sizeBytes
5243
5424
  };
5244
5425
  await writeFile2(storagePath, buffer);
5245
5426
  await writeFile2(metaPath, JSON.stringify(meta, null, 2));
5246
- return { attachmentId, filename, storagePath, metaPath, mimeType, sizeBytes };
5427
+ let readableTextPath;
5428
+ if (CONVERTIBLE_DOC_MIME.has(mimeType.split(";")[0].trim().toLowerCase())) {
5429
+ const text = docToText(buffer, mimeType);
5430
+ if (text) {
5431
+ readableTextPath = resolve7(dir, `${attachmentId}${EXTRACTED_TEXT_SUFFIX}`);
5432
+ await writeFile2(readableTextPath, text);
5433
+ }
5434
+ }
5435
+ return { attachmentId, filename, storagePath, metaPath, mimeType, sizeBytes, readableTextPath };
5247
5436
  }
5248
5437
  async function storeAttachment(accountId, file) {
5249
5438
  assertSupportedMime(file.type);
@@ -5284,6 +5473,8 @@ var MIME_BY_EXT = {
5284
5473
  ".zip": "application/zip",
5285
5474
  ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
5286
5475
  ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
5476
+ ".odt": "application/vnd.oasis.opendocument.text",
5477
+ ".rtf": "text/rtf",
5287
5478
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
5288
5479
  ".ics": "text/calendar"
5289
5480
  };
@@ -6137,6 +6328,15 @@ async function loginConnectionLoop(accountId, login) {
6137
6328
  } catch (err) {
6138
6329
  const current = activeLogins.get(accountId);
6139
6330
  if (current?.id !== login.id) return;
6331
+ await drainCredsSaveQueue();
6332
+ const preRetryCensus = credsCensus(login.authDir);
6333
+ if (!preRetryCensus.hasAccountSignature) {
6334
+ current.error = "WhatsApp accepted the code but did not finish linking. This usually means the number has reached its linked-device limit or has a stale linked device. On the phone, open WhatsApp, Linked Devices, remove existing devices, then ask for a new code.";
6335
+ console.error(
6336
+ `${TAG17} op=pairing-incomplete cid=${cidShort} closeKind=${closeKind(getStatusCode(err), false)} reason=no-account-signature`
6337
+ );
6338
+ return;
6339
+ }
6140
6340
  const classification = classifyDisconnect(err);
6141
6341
  if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
6142
6342
  if (attempt >= LOGIN_MAX_RECONNECTS) {
@@ -6349,6 +6549,13 @@ async function waitForLogin(opts) {
6349
6549
  console.error(`${TAG17} waitForLogin poll timeout account=${accountId} elapsed=${elapsed}s \u2014 login kept alive (pairing code still valid)`);
6350
6550
  return { connected: false, message: "Still waiting for you to enter the pairing code. The code is still valid \u2014 ask me to keep waiting, or request a new code.", configPersisted: false };
6351
6551
  }
6552
+ function listActiveLoginAccountIds() {
6553
+ const ids = [];
6554
+ for (const [accountId, login] of activeLogins) {
6555
+ if (isLoginFresh(login)) ids.push(accountId);
6556
+ }
6557
+ return ids;
6558
+ }
6352
6559
 
6353
6560
  // app/lib/whatsapp/outbound/send-document.ts
6354
6561
  import { realpathSync as realpathSync3 } from "fs";
@@ -6545,6 +6752,7 @@ function serializeWhatsAppSchema() {
6545
6752
  import { readdirSync as readdirSync3 } from "fs";
6546
6753
  import { join as join9 } from "path";
6547
6754
  var TAG19 = "[whatsapp:reconcile]";
6755
+ var HALF_REGISTERED_MIN_AGE_MS = 5 * 6e4;
6548
6756
  function listCredsAccountIds(credsRoot) {
6549
6757
  try {
6550
6758
  return readdirSync3(credsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && hasCredsSync(join9(credsRoot, e.name))).map((e) => e.name);
@@ -6558,14 +6766,28 @@ function isAccountConfigured(accountDir, accountId) {
6558
6766
  return !!accounts && Object.prototype.hasOwnProperty.call(accounts, accountId);
6559
6767
  }
6560
6768
  function reconcileCredsOnDisk(opts) {
6561
- const { credsRoot, accountDir, liveAccountIds } = opts;
6769
+ const { credsRoot, accountDir, liveAccountIds, activeLoginAccountIds } = opts;
6562
6770
  const entries = [];
6563
6771
  for (const accountId of listCredsAccountIds(credsRoot)) {
6564
6772
  if (liveAccountIds.has(accountId)) continue;
6565
6773
  const authDir = join9(credsRoot, accountId);
6566
6774
  const census = credsCensus(authDir);
6567
6775
  if (census.registered && !census.hasAccountSignature) {
6568
- console.error(`${TAG19} op=half-registered account=${accountId} registered=true hasAccount=false`);
6776
+ const inFlight = activeLoginAccountIds.has(accountId);
6777
+ const ageMs = getAuthAgeMs(authDir);
6778
+ const abandoned = !inFlight && ageMs !== null && ageMs >= HALF_REGISTERED_MIN_AGE_MS;
6779
+ if (!abandoned) {
6780
+ console.error(`${TAG19} op=half-registered account=${accountId} registered=true hasAccount=false inFlight=${inFlight} ageMs=${ageMs ?? "null"}`);
6781
+ entries.push({ accountId, connected: false, linkedUnconfigured: true });
6782
+ continue;
6783
+ }
6784
+ console.error(`${TAG19} op=discard-half-registered account=${accountId} registered=true hasAccount=false ageMs=${ageMs}`);
6785
+ const removed = discardAuthDirSync(authDir);
6786
+ if (!removed) {
6787
+ console.error(`${TAG19} op=discard-half-registered-failed account=${accountId} \u2014 residue persists`);
6788
+ entries.push({ accountId, connected: false, linkedUnconfigured: true });
6789
+ }
6790
+ continue;
6569
6791
  }
6570
6792
  const selfPhone = readSelfId(authDir).e164 ?? void 0;
6571
6793
  const configured = accountDir ? isAccountConfigured(accountDir, accountId) : false;
@@ -6593,9 +6815,10 @@ app2.get("/status", (c) => {
6593
6815
  try {
6594
6816
  const live = getStatus();
6595
6817
  const liveIds = new Set(live.map((a) => a.accountId));
6818
+ const activeLoginIds = new Set(listActiveLoginAccountIds());
6596
6819
  const credsRoot = join10(MAXY_DIR, "credentials", "whatsapp");
6597
6820
  const accountDir = resolveAccount()?.accountDir ?? null;
6598
- const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds });
6821
+ const reconciled = reconcileCredsOnDisk({ credsRoot, accountDir, liveAccountIds: liveIds, activeLoginAccountIds: activeLoginIds });
6599
6822
  const accounts = [...live, ...reconciled];
6600
6823
  const summary = accounts.map((a) => `${a.accountId}:${a.connected ? "up" : a.linkedUnconfigured ? "linked-unconfigured" : "down"}`).join(", ");
6601
6824
  console.error(`${TAG20} status accounts=${accounts.length} [${summary}]`);
@@ -7015,11 +7238,11 @@ app2.get("/group-info", async (c) => {
7015
7238
  var whatsapp_default = app2;
7016
7239
 
7017
7240
  // server/routes/whatsapp-reader.ts
7018
- import { readFileSync as readFileSync15, watch, statSync as statSync4, openSync, readSync, closeSync, existsSync as existsSync8, readdirSync as readdirSync8, realpathSync as realpathSync4 } from "fs";
7241
+ import { readFileSync as readFileSync15, watch, statSync as statSync5, openSync, readSync, closeSync, existsSync as existsSync8, readdirSync as readdirSync8, realpathSync as realpathSync4 } from "fs";
7019
7242
  import { basename as basename4, dirname as dirname3, isAbsolute, join as join14, relative as relative2, resolve as resolve12, sep as sep3 } from "path";
7020
7243
 
7021
7244
  // server/routes/admin/sidebar-sessions.ts
7022
- import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync2 } from "fs";
7245
+ import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
7023
7246
  import { basename as basename3, dirname as dirname2, join as join11, resolve as resolve11 } from "path";
7024
7247
 
7025
7248
  // app/lib/whatsapp-reader/select-sessions.ts
@@ -7043,25 +7266,22 @@ function selectReaderChannelSessions(rows) {
7043
7266
  source: "session"
7044
7267
  }));
7045
7268
  }
7046
- function buildStoreConversationRows(accountId, convos) {
7269
+ function buildStoreConversationRows(accountId, summaries) {
7047
7270
  const rows = [];
7048
- for (const [remoteJid, messages] of convos) {
7049
- if (messages.length === 0) continue;
7050
- const last = messages[messages.length - 1];
7271
+ for (const [remoteJid, { first, last, firstNamedSender }] of summaries) {
7051
7272
  const scope = last.scope;
7052
7273
  const isGroup = remoteJid.endsWith("@g.us");
7053
- const senderTelephone = isGroup ? null : messages[0].senderTelephone;
7054
- const senderName = messages.find((m) => m.senderName)?.senderName ?? null;
7274
+ const senderTelephone = isGroup ? null : first.senderTelephone;
7055
7275
  rows.push({
7056
7276
  sessionId: "",
7057
7277
  projectDir: "",
7058
7278
  title: remoteJid,
7059
7279
  senderId: senderTelephone,
7060
- startedAt: messages[0].dateSent,
7280
+ startedAt: first.dateSent,
7061
7281
  channel: "whatsapp",
7062
7282
  role: scope === "admin" ? "admin" : "public",
7063
7283
  operatorName: null,
7064
- whatsappName: senderName,
7284
+ whatsappName: firstNamedSender,
7065
7285
  lastMessageAt: last.dateSent,
7066
7286
  modelGated: false,
7067
7287
  model: null,
@@ -7312,26 +7532,10 @@ app3.get("/", requireAdminSession, async (c) => {
7312
7532
  const scopeAccountId = getAccountIdForSession(cacheKey) ?? null;
7313
7533
  const validAccounts = listValidAccounts();
7314
7534
  const multiAccount = validAccounts.length > 1;
7315
- const accounts = [];
7316
- for (const a of validAccounts) {
7317
- let businessName = null;
7318
- try {
7319
- const branding = await fetchBranding(a.accountId);
7320
- businessName = branding?.name || null;
7321
- } catch {
7322
- }
7323
- accounts.push({
7324
- accountId: a.accountId,
7325
- role: a.config.role ?? "",
7326
- businessName,
7327
- isHouse: a.config.role === "house"
7328
- });
7329
- }
7330
- console.log(`[admin-sessions-list] accounts=${accounts.length}`);
7331
7535
  const configDir2 = claudeConfigDir();
7332
7536
  if (!configDir2) {
7333
7537
  console.error("[admin-sessions-list] CLAUDE_CONFIG_DIR not set; returning empty list");
7334
- return c.json({ sessions: [], accountId: scopeAccountId, accounts });
7538
+ return c.json({ sessions: [], accountId: scopeAccountId });
7335
7539
  }
7336
7540
  const projectsRoot = join11(configDir2, "projects");
7337
7541
  const jsonls = enumerateJsonls(projectsRoot);
@@ -7362,7 +7566,7 @@ app3.get("/", requireAdminSession, async (c) => {
7362
7566
  let mtimeMs;
7363
7567
  try {
7364
7568
  body = readFileSync12(path2, "utf8");
7365
- mtimeMs = statSync2(path2).mtimeMs;
7569
+ mtimeMs = statSync3(path2).mtimeMs;
7366
7570
  } catch {
7367
7571
  continue;
7368
7572
  }
@@ -7440,13 +7644,13 @@ app3.get("/", requireAdminSession, async (c) => {
7440
7644
  if (multiAccount) {
7441
7645
  console.log(`[admin-sessions-list] untagged-accountId-rows=${untaggedCount}`);
7442
7646
  }
7443
- return c.json({ sessions: rows, accountId: scopeAccountId, accounts });
7647
+ return c.json({ sessions: rows, accountId: scopeAccountId });
7444
7648
  });
7445
7649
  var sidebar_sessions_default = app3;
7446
7650
 
7447
7651
  // app/lib/admin-identity/pin-validator.ts
7448
7652
  import { createHash } from "crypto";
7449
- import { existsSync as existsSync7, readFileSync as readFileSync13, readdirSync as readdirSync6, statSync as statSync3 } from "fs";
7653
+ import { existsSync as existsSync7, readFileSync as readFileSync13, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
7450
7654
  import { join as join12 } from "path";
7451
7655
  function hashPin(pin) {
7452
7656
  return createHash("sha256").update(pin).digest("hex");
@@ -7834,16 +8038,29 @@ function pickActivity(entries, grown, nowMs, concurrencyWindowMs) {
7834
8038
  // server/routes/whatsapp-reader.ts
7835
8039
  var app4 = new Hono();
7836
8040
  app4.get("/conversations", requireAdminSession, async (c) => {
8041
+ const cacheKey = c.var.cacheKey;
8042
+ const scopeAccountId = getAccountIdForSession(cacheKey) ?? null;
8043
+ const validAccounts = listValidAccounts();
8044
+ const multiAccount = validAccounts.length > 1;
8045
+ if (multiAccount && !scopeAccountId) {
8046
+ console.log(`[wa-reader] op=list-scope-unresolved cacheKey=${cacheKey.slice(0, 8)}`);
8047
+ return c.json({ conversations: [] });
8048
+ }
7837
8049
  const cfg = claudeConfigDir();
7838
8050
  if (!cfg) return c.json({ conversations: [] });
7839
8051
  const projectsRoot = join14(cfg, "projects");
7840
8052
  const userTitles = loadUserTitles(ACCOUNTS_DIR ?? null);
7841
8053
  const rows = [];
8054
+ let sessionRowsExcludedUntagged = 0;
7842
8055
  for (const { path: path2 } of enumerateJsonls(projectsRoot)) {
7843
8056
  const sessionId = basename4(path2).replace(/\.jsonl$/, "");
7844
8057
  const projectDir = dirname3(path2);
7845
8058
  const meta = readSidecarMeta(join14(projectDir, `${sessionId}.meta.json`));
7846
8059
  if (!isReaderChannelSession(meta.role, meta.channel)) continue;
8060
+ if (multiAccount) {
8061
+ if (meta.accountId === null) sessionRowsExcludedUntagged += 1;
8062
+ if (meta.accountId !== scopeAccountId) continue;
8063
+ }
7847
8064
  let body = "";
7848
8065
  try {
7849
8066
  body = readFileSync15(path2, "utf8");
@@ -7868,7 +8085,7 @@ app4.get("/conversations", requireAdminSession, async (c) => {
7868
8085
  });
7869
8086
  }
7870
8087
  const sessionRows = selectReaderChannelSessions(rows).filter((r) => r.channel !== "whatsapp");
7871
- const account = resolveAccount();
8088
+ const account = multiAccount ? validAccounts.find((a) => a.accountId === scopeAccountId) ?? null : resolveAccount();
7872
8089
  let users = [];
7873
8090
  if (account) {
7874
8091
  try {
@@ -7898,16 +8115,17 @@ app4.get("/conversations", requireAdminSession, async (c) => {
7898
8115
  ...r,
7899
8116
  operatorName: r.senderId ? nameBySender.get(r.senderId) ?? null : null
7900
8117
  }));
7901
- const storeRows = account ? buildStoreConversationRows(account.accountId, readAllConversations(account.accountId)) : [];
8118
+ const storeRows = account ? buildStoreConversationRows(account.accountId, readConversationSummaries(account.accountId)) : [];
7902
8119
  const adminScope = storeRows.filter((r) => r.scope === "admin").length;
7903
8120
  const publicScope = storeRows.length - adminScope;
8121
+ const sessionRowCount = sessionConversations.length;
7904
8122
  console.log(
7905
- `[wa-reader] op=list accountId=${account?.accountId ?? "null"} conversations=${storeRows.length} adminScope=${adminScope} publicScope=${publicScope}`
8123
+ `[wa-reader] op=list pinnedAccount=${scopeAccountId?.slice(0, 8) ?? "null"} resolvedAccount=${account?.accountId.slice(0, 8) ?? "null"} multiAccount=${multiAccount} conversations=${sessionRowCount + storeRows.length} sessionRows=${sessionRowCount} storeRows=${storeRows.length} adminScope=${adminScope} publicScope=${publicScope} sessionRowsExcludedUntagged=${sessionRowsExcludedUntagged}`
7906
8124
  );
7907
8125
  return c.json({ conversations: [...sessionConversations, ...storeRows] });
7908
8126
  });
7909
8127
  function readFrom(path2, from) {
7910
- const end = statSync4(path2).size;
8128
+ const end = statSync5(path2).size;
7911
8129
  if (end <= from) return { buf: Buffer.alloc(0), end: from };
7912
8130
  const fd = openSync(path2, "r");
7913
8131
  try {
@@ -8024,7 +8242,7 @@ function runActivityTick(c) {
8024
8242
  let size = 0;
8025
8243
  let mtimeMs = 0;
8026
8244
  try {
8027
- const st = statSync4(join14(c.subagentsDir, name));
8245
+ const st = statSync5(join14(c.subagentsDir, name));
8028
8246
  size = st.size;
8029
8247
  mtimeMs = st.mtimeMs;
8030
8248
  } catch {
@@ -8090,7 +8308,7 @@ data: ${JSON.stringify(turn)}
8090
8308
  start(controller) {
8091
8309
  const fileEnd0 = (() => {
8092
8310
  try {
8093
- return statSync4(jsonlPath).size;
8311
+ return statSync5(jsonlPath).size;
8094
8312
  } catch {
8095
8313
  return 0;
8096
8314
  }
@@ -8219,7 +8437,7 @@ app4.get("/directives", requireAdminSession, (c) => {
8219
8437
  const pid = Number(pidPart);
8220
8438
  let len = 0;
8221
8439
  try {
8222
- len = statSync4(join14(dir, name)).size;
8440
+ len = statSync5(join14(dir, name)).size;
8223
8441
  } catch {
8224
8442
  }
8225
8443
  return { name, secs, pid, ts: Number.isFinite(secs) ? new Date(secs * 1e3).toISOString() : null, len };
@@ -8284,7 +8502,7 @@ data: ${JSON.stringify(turn)}
8284
8502
  };
8285
8503
  const fileEnd0 = (() => {
8286
8504
  try {
8287
- return statSync4(file).size;
8505
+ return statSync5(file).size;
8288
8506
  } catch {
8289
8507
  return 0;
8290
8508
  }
@@ -8365,7 +8583,7 @@ data: ${JSON.stringify(turn)}
8365
8583
  var whatsapp_reader_default = app4;
8366
8584
 
8367
8585
  // server/routes/public-reader.ts
8368
- import { statSync as statSync5, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2, readFileSync as readFileSync16, readdirSync as readdirSync9 } from "fs";
8586
+ import { statSync as statSync6, openSync as openSync2, readSync as readSync2, closeSync as closeSync2, watch as watch2, readFileSync as readFileSync16, readdirSync as readdirSync9 } from "fs";
8369
8587
  import { basename as basename5, dirname as dirname4, join as join15, resolve as resolve13, sep as sep4 } from "path";
8370
8588
 
8371
8589
  // app/lib/whatsapp-reader/delivered-kinds.ts
@@ -8499,7 +8717,7 @@ function enrichPublicAttachments(turns, sessionAttachments, cursor) {
8499
8717
  }
8500
8718
  }
8501
8719
  function readFrom2(path2, from) {
8502
- const end = statSync5(path2).size;
8720
+ const end = statSync6(path2).size;
8503
8721
  if (end <= from) return { buf: Buffer.alloc(0), end: from };
8504
8722
  const fd = openSync2(path2, "r");
8505
8723
  try {
@@ -8640,7 +8858,7 @@ app5.get("/attachment/:attachmentId", (c) => {
8640
8858
  let dataFile;
8641
8859
  let buffer;
8642
8860
  try {
8643
- dataFile = readdirSync9(dir).find((f) => !f.endsWith(".meta.json"));
8861
+ dataFile = readdirSync9(dir).find((f) => !f.endsWith(".meta.json") && !f.endsWith(EXTRACTED_TEXT_SUFFIX));
8644
8862
  if (!dataFile) throw new Error("no data file");
8645
8863
  buffer = readFileSync16(resolve13(dir, dataFile));
8646
8864
  } catch {
@@ -8662,7 +8880,7 @@ var public_reader_default = app5;
8662
8880
  // server/routes/webchat.ts
8663
8881
  import { basename as basename6, dirname as dirname5 } from "path";
8664
8882
  import { join as join18 } from "path";
8665
- import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync19, renameSync as renameSync3, statSync as statSync6, writeFileSync as writeFileSync7 } from "fs";
8883
+ import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync19, renameSync as renameSync3, statSync as statSync7, writeFileSync as writeFileSync7 } from "fs";
8666
8884
 
8667
8885
  // server/canonical-webchat-override.ts
8668
8886
  import { readFileSync as readFileSync17, writeFileSync as writeFileSync5, renameSync } from "fs";
@@ -8932,7 +9150,7 @@ function sessionSidecarExists(sessionId) {
8932
9150
  function jsonlSizeBytes(projectDir, sessionId) {
8933
9151
  if (projectDir === null) return null;
8934
9152
  try {
8935
- return statSync6(join18(projectDir, `${sessionId}.jsonl`)).size;
9153
+ return statSync7(join18(projectDir, `${sessionId}.jsonl`)).size;
8936
9154
  } catch {
8937
9155
  return null;
8938
9156
  }
@@ -9076,7 +9294,7 @@ function latestAdminWebchatSessionId(requesterUserId, primaryUserId, scopeAccoun
9076
9294
  if (multiAccount && meta.accountId !== scopeAccountId) continue;
9077
9295
  let mtimeMs;
9078
9296
  try {
9079
- mtimeMs = statSync6(path2).mtimeMs;
9297
+ mtimeMs = statSync7(path2).mtimeMs;
9080
9298
  } catch {
9081
9299
  continue;
9082
9300
  }
@@ -9269,8 +9487,13 @@ ${result.result.text}` : result.result.text;
9269
9487
  console.error(`[webchat:inbound] op=send-refused kind=files reason=store-failed detail=${detail}`);
9270
9488
  return c.json({ error: "failed to store attachment" }, 500);
9271
9489
  }
9490
+ for (const s of stored) {
9491
+ const dot = s.filename.lastIndexOf(".");
9492
+ const ext = dot >= 0 ? s.filename.slice(dot).toLowerCase() : "";
9493
+ console.log(`[attach-ingest] mime=${s.mimeType} ext=${ext} converted=${!!s.readableTextPath} path=${s.storagePath}`);
9494
+ }
9272
9495
  if (stored.length > 0) {
9273
- const listing = stored.map((s) => `${s.filename} (${s.storagePath})`).join(", ");
9496
+ const listing = stored.map((s) => `${s.filename} (${s.readableTextPath ?? s.storagePath})`).join(", ");
9274
9497
  const note = `[The operator uploaded ${stored.length} file(s): ${listing}]`;
9275
9498
  text = text.length > 0 ? `${text}
9276
9499
 
@@ -9908,7 +10131,7 @@ import { openSync as openSync3, closeSync as closeSync3, writeFileSync as writeF
9908
10131
  import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
9909
10132
 
9910
10133
  // ../lib/admins-write/src/index.ts
9911
- import { existsSync as existsSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync9, renameSync as renameSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync13, statSync as statSync7, appendFileSync as appendFileSync2 } from "fs";
10134
+ import { existsSync as existsSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync9, renameSync as renameSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync13, statSync as statSync8, appendFileSync as appendFileSync2 } from "fs";
9912
10135
  import { dirname as dirname6, join as join22 } from "path";
9913
10136
  function id8(value) {
9914
10137
  return value.slice(0, 8);
@@ -10031,7 +10254,7 @@ function computeAdminStoreDivergence(input) {
10031
10254
  if (entry.startsWith(".")) continue;
10032
10255
  const accountDir = join22(input.accountsDir, entry);
10033
10256
  try {
10034
- if (!statSync7(accountDir).isDirectory()) continue;
10257
+ if (!statSync8(accountDir).isDirectory()) continue;
10035
10258
  } catch {
10036
10259
  continue;
10037
10260
  }
@@ -10431,7 +10654,7 @@ ${body.newPin}
10431
10654
  var onboarding_default = app9;
10432
10655
 
10433
10656
  // server/routes/client-error.ts
10434
- import { appendFileSync as appendFileSync3, existsSync as existsSync16, renameSync as renameSync6, statSync as statSync8 } from "fs";
10657
+ import { appendFileSync as appendFileSync3, existsSync as existsSync16, renameSync as renameSync6, statSync as statSync9 } from "fs";
10435
10658
  import { join as join23 } from "path";
10436
10659
  var CLIENT_ERRORS_LOG = join23(LOG_DIR, "client-errors.log");
10437
10660
  var MAX_LOG_SIZE = 10 * 1024 * 1024;
@@ -10482,7 +10705,7 @@ function stackHead(stack) {
10482
10705
  function rotateIfNeeded() {
10483
10706
  try {
10484
10707
  if (!existsSync16(CLIENT_ERRORS_LOG)) return;
10485
- const stats = statSync8(CLIENT_ERRORS_LOG);
10708
+ const stats = statSync9(CLIENT_ERRORS_LOG);
10486
10709
  if (stats.size < MAX_LOG_SIZE) return;
10487
10710
  renameSync6(CLIENT_ERRORS_LOG, CLIENT_ERRORS_LOG + ".1");
10488
10711
  } catch (err) {
@@ -10788,7 +11011,7 @@ app12.get("/", requireAdminSession, async (c) => {
10788
11011
  var accounts_default = app12;
10789
11012
 
10790
11013
  // server/routes/admin/logs.ts
10791
- import { existsSync as existsSync18, readdirSync as readdirSync14, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
11014
+ import { existsSync as existsSync18, readdirSync as readdirSync14, readFileSync as readFileSync25, statSync as statSync10 } from "fs";
10792
11015
  import { resolve as resolve16, basename as basename7 } from "path";
10793
11016
 
10794
11017
  // app/lib/logs-read-resolve.ts
@@ -10828,7 +11051,7 @@ app13.get("/", async (c) => {
10828
11051
  searched.push(filePath);
10829
11052
  try {
10830
11053
  const buffer = readFileSync25(filePath);
10831
- const onDiskBytes = statSync9(filePath).size;
11054
+ const onDiskBytes = statSync10(filePath).size;
10832
11055
  const headers = {
10833
11056
  "Content-Type": "text/plain; charset=utf-8",
10834
11057
  "Content-Length": String(buffer.byteLength)
@@ -10893,7 +11116,7 @@ app13.get("/", async (c) => {
10893
11116
  try {
10894
11117
  const filename = basename7(hit.path);
10895
11118
  const buffer = readFileSync25(hit.path);
10896
- const onDiskBytes = statSync9(hit.path).size;
11119
+ const onDiskBytes = statSync10(hit.path).size;
10897
11120
  const headers = {
10898
11121
  "Content-Type": "text/plain; charset=utf-8",
10899
11122
  "Content-Length": String(buffer.byteLength)
@@ -10936,7 +11159,7 @@ app13.get("/", async (c) => {
10936
11159
  console.warn(`[admin/logs] readdir-fail dir=${dir} reason=${reason}`);
10937
11160
  continue;
10938
11161
  }
10939
- files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync9(resolve16(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
11162
+ files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync10(resolve16(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
10940
11163
  seen.add(name);
10941
11164
  try {
10942
11165
  const content = readFileSync25(resolve16(dir, name));
@@ -11028,7 +11251,7 @@ app15.get("/:attachmentId", requireAdminSession, async (c) => {
11028
11251
  return new Response("Not found", { status: 404 });
11029
11252
  }
11030
11253
  const entries = await readdir(dir);
11031
- const dataFile = entries.find((f) => !f.endsWith(".meta.json"));
11254
+ const dataFile = entries.find((f) => !f.endsWith(".meta.json") && !f.endsWith(EXTRACTED_TEXT_SUFFIX));
11032
11255
  if (!dataFile) {
11033
11256
  return new Response("Not found", { status: 404 });
11034
11257
  }
@@ -11821,7 +12044,7 @@ app17.put("/:id/label", requireAdminSession, async (c) => {
11821
12044
  var sessions_default = app17;
11822
12045
 
11823
12046
  // app/lib/claude-agent/spawn-context.ts
11824
- import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync16, statSync as statSync10 } from "fs";
12047
+ import { existsSync as existsSync22, readFileSync as readFileSync27, readdirSync as readdirSync16, statSync as statSync11 } from "fs";
11825
12048
  import { dirname as dirname7, resolve as resolve19, join as join25 } from "path";
11826
12049
  async function resolveOwnerProfileBlock(accountId, userId) {
11827
12050
  if (!userId) return { ok: false, reason: "missing-user-id" };
@@ -11924,7 +12147,7 @@ function listPluginDirs() {
11924
12147
  for (const name of readdirSync16(platformPlugins)) {
11925
12148
  const dir = join25(platformPlugins, name);
11926
12149
  try {
11927
- if (statSync10(dir).isDirectory()) out.set(name, dir);
12150
+ if (statSync11(dir).isDirectory()) out.set(name, dir);
11928
12151
  } catch {
11929
12152
  }
11930
12153
  }
@@ -11938,7 +12161,7 @@ function listPluginDirs() {
11938
12161
  for (const name of readdirSync16(bundlePlugins)) {
11939
12162
  const dir = join25(bundlePlugins, name);
11940
12163
  try {
11941
- if (statSync10(dir).isDirectory()) out.set(name, dir);
12164
+ if (statSync11(dir).isDirectory()) out.set(name, dir);
11942
12165
  } catch {
11943
12166
  }
11944
12167
  }
@@ -13508,11 +13731,11 @@ app21.get("/download", requireAdminSession, async (c) => {
13508
13731
  const nodeStream = createReadStream2(absolute);
13509
13732
  const webStream = Readable2.toWeb(nodeStream);
13510
13733
  if (disposition === "inline") {
13511
- if (!mimeType.startsWith("image/")) {
13512
- console.error(`[data] op=thumb-refused path="${relPath}" mime="${mimeType}" reason=not-image`);
13513
- return c.json({ error: "Inline serve is image-only" }, 400);
13734
+ if (!mimeType.startsWith("image/") && mimeType !== "application/pdf") {
13735
+ console.error(`[data] op=inline-refused path="${relPath}" mime="${mimeType}" reason=not-image-or-pdf`);
13736
+ return c.json({ error: "Inline serve is image- or PDF-only" }, 400);
13514
13737
  }
13515
- console.error(`[data] op=thumb-serve path="${relPath}" mime="${mimeType}" size=${info.size} status=200`);
13738
+ console.error(`[data] op=inline-serve path="${relPath}" mime="${mimeType}" size=${info.size} status=200`);
13516
13739
  return new Response(webStream, {
13517
13740
  status: 200,
13518
13741
  headers: {
@@ -16456,10 +16679,8 @@ app29.post("/", requireAdminSession, async (c) => {
16456
16679
  return c.json({ error: "sessionId must be a v4 UUID" }, 400);
16457
16680
  }
16458
16681
  const base = managerBase("session-delete:wrapper");
16459
- const accountId = getAccountIdForSession(c.var.cacheKey);
16460
- const acctQuery = accountId ? `?accountId=${encodeURIComponent(accountId)}` : "";
16461
16682
  try {
16462
- const stop = await fetch(`${base}/${sessionId}/stop${acctQuery}`, { method: "POST" });
16683
+ const stop = await fetch(`${base}/${sessionId}/stop`, { method: "POST" });
16463
16684
  if (stop.status !== 204 && stop.status !== 200 && stop.status !== 404) {
16464
16685
  console.error(`[session-delete] sessionId=${sessionId} stop-status=${stop.status}`);
16465
16686
  return c.json({ error: `stop-failed-${stop.status}` }, 502);
@@ -16470,7 +16691,7 @@ app29.post("/", requireAdminSession, async (c) => {
16470
16691
  }
16471
16692
  let del;
16472
16693
  try {
16473
- del = await fetch(`${base}/${sessionId}${acctQuery}`, { method: "DELETE" });
16694
+ del = await fetch(`${base}/${sessionId}`, { method: "DELETE" });
16474
16695
  } catch (err) {
16475
16696
  console.error(`[session-delete] sessionId=${sessionId} delete-unreachable err=${err instanceof Error ? err.message : String(err)}`);
16476
16697
  return c.json({ error: "manager-unreachable" }, 502);
@@ -16499,11 +16720,9 @@ app30.post("/", requireAdminSession, async (c) => {
16499
16720
  if (!SESSION_ID_RE2.test(sessionId)) {
16500
16721
  return c.json({ error: "sessionId must be a v4 UUID" }, 400);
16501
16722
  }
16502
- const accountId = getAccountIdForSession(c.var.cacheKey);
16503
- const acctQuery = accountId ? `?accountId=${encodeURIComponent(accountId)}` : "";
16504
16723
  let res;
16505
16724
  try {
16506
- res = await fetch(`${managerBase("session-stop:wrapper")}/${sessionId}/stop${acctQuery}`, { method: "POST" });
16725
+ res = await fetch(`${managerBase("session-stop:wrapper")}/${sessionId}/stop`, { method: "POST" });
16507
16726
  } catch (err) {
16508
16727
  console.error(`[session-stop] sessionId=${sessionId} manager-unreachable err=${err instanceof Error ? err.message : String(err)}`);
16509
16728
  return c.json({ error: "manager-unreachable" }, 502);
@@ -18525,7 +18744,7 @@ app48.route("/request-magic-link", request_magic_link_default);
18525
18744
  var access_default = app48;
18526
18745
 
18527
18746
  // server/routes/sites.ts
18528
- import { existsSync as existsSync28, readFileSync as readFileSync31, realpathSync as realpathSync6, statSync as statSync11 } from "fs";
18747
+ import { existsSync as existsSync28, readFileSync as readFileSync31, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
18529
18748
  import { resolve as resolve26 } from "path";
18530
18749
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
18531
18750
  var MIME = {
@@ -18591,7 +18810,7 @@ app49.get("/:rel{.*}", (c) => {
18591
18810
  }
18592
18811
  let stat8;
18593
18812
  try {
18594
- stat8 = existsSync28(filePath) ? statSync11(filePath) : null;
18813
+ stat8 = existsSync28(filePath) ? statSync12(filePath) : null;
18595
18814
  } catch {
18596
18815
  stat8 = null;
18597
18816
  }
@@ -22287,7 +22506,7 @@ function broadcastAdminShutdown(reason) {
22287
22506
 
22288
22507
  // ../lib/entitlement/src/index.ts
22289
22508
  import { createPublicKey, createHash as createHash5, verify as cryptoVerify } from "crypto";
22290
- import { existsSync as existsSync32, readFileSync as readFileSync34, statSync as statSync12 } from "fs";
22509
+ import { existsSync as existsSync32, readFileSync as readFileSync34, statSync as statSync13 } from "fs";
22291
22510
  import { resolve as resolve32 } from "path";
22292
22511
 
22293
22512
  // ../lib/entitlement/src/canonicalize.ts
@@ -22339,7 +22558,7 @@ function resolveEntitlement(brand, account) {
22339
22558
  if (!existsSync32(entitlementPath)) {
22340
22559
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
22341
22560
  }
22342
- const stat8 = statSync12(entitlementPath);
22561
+ const stat8 = statSync13(entitlementPath);
22343
22562
  const key = memoKey(stat8.mtimeMs, account);
22344
22563
  if (memo && memo.key === key) {
22345
22564
  return memo.result;
@@ -22766,14 +22985,14 @@ var chatRoutes = createChatRoutes({
22766
22985
  awaitReply: (key, timeoutMs) => webchatGateway.awaitReply(key, timeoutMs)
22767
22986
  });
22768
22987
  app55.use("*", clientIpMiddleware);
22988
+ function allowsSameOriginFraming(path2) {
22989
+ return path2 === "/vnc-viewer.html" || path2.startsWith("/api/admin/attachment/") || path2.startsWith("/api/public-reader/attachment/") || path2 === "/api/admin/files/download";
22990
+ }
22769
22991
  app55.use("*", async (c, next) => {
22770
22992
  await next();
22771
22993
  c.header("X-Content-Type-Options", "nosniff");
22772
22994
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
22773
- c.header(
22774
- "X-Frame-Options",
22775
- c.req.path === "/vnc-viewer.html" ? "SAMEORIGIN" : "DENY"
22776
- );
22995
+ c.header("X-Frame-Options", allowsSameOriginFraming(c.req.path) ? "SAMEORIGIN" : "DENY");
22777
22996
  });
22778
22997
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
22779
22998
  app55.use("*", async (c, next) => {