@rudderhq/server 0.2.7-canary.2 → 0.2.7-canary.20

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 (135) hide show
  1. package/dist/bundled-plugins/plugin-linear/dist/worker.js +22 -0
  2. package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +2 -2
  3. package/dist/config.d.ts +1 -0
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +26 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/database-backup-scheduler.d.ts +37 -0
  8. package/dist/database-backup-scheduler.d.ts.map +1 -0
  9. package/dist/database-backup-scheduler.js +50 -0
  10. package/dist/database-backup-scheduler.js.map +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +5 -10
  13. package/dist/index.js.map +1 -1
  14. package/dist/routes/calendar.js +1 -1
  15. package/dist/routes/calendar.js.map +1 -1
  16. package/dist/routes/chats.d.ts.map +1 -1
  17. package/dist/routes/chats.js +129 -3
  18. package/dist/routes/chats.js.map +1 -1
  19. package/dist/routes/sidebar-badges.d.ts.map +1 -1
  20. package/dist/routes/sidebar-badges.js +12 -25
  21. package/dist/routes/sidebar-badges.js.map +1 -1
  22. package/dist/services/automation-chat-output.d.ts.map +1 -1
  23. package/dist/services/automation-chat-output.js +1 -11
  24. package/dist/services/automation-chat-output.js.map +1 -1
  25. package/dist/services/automations.d.ts.map +1 -1
  26. package/dist/services/automations.js +12 -40
  27. package/dist/services/automations.js.map +1 -1
  28. package/dist/services/chat-assistant.helpers.d.ts +1 -1
  29. package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
  30. package/dist/services/chat-assistant.helpers.js +27 -3
  31. package/dist/services/chat-assistant.helpers.js.map +1 -1
  32. package/dist/services/chats.d.ts +25 -0
  33. package/dist/services/chats.d.ts.map +1 -1
  34. package/dist/services/chats.js +45 -0
  35. package/dist/services/chats.js.map +1 -1
  36. package/dist/services/costs.d.ts.map +1 -1
  37. package/dist/services/costs.js +163 -44
  38. package/dist/services/costs.js.map +1 -1
  39. package/dist/services/messenger.d.ts +2 -16
  40. package/dist/services/messenger.d.ts.map +1 -1
  41. package/dist/services/messenger.js +261 -50
  42. package/dist/services/messenger.js.map +1 -1
  43. package/dist/services/orgs.d.ts.map +1 -1
  44. package/dist/services/orgs.js +2 -1
  45. package/dist/services/orgs.js.map +1 -1
  46. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  47. package/dist/services/runtime-kernel/heartbeat.js +53 -2
  48. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  49. package/dist/services/runtime-kernel/heartbeat.release.d.ts.map +1 -1
  50. package/dist/services/runtime-kernel/heartbeat.release.js +79 -9
  51. package/dist/services/runtime-kernel/heartbeat.release.js.map +1 -1
  52. package/dist/services/sidebar-badges.d.ts +20 -6
  53. package/dist/services/sidebar-badges.d.ts.map +1 -1
  54. package/dist/services/sidebar-badges.js +172 -34
  55. package/dist/services/sidebar-badges.js.map +1 -1
  56. package/package.json +13 -13
  57. package/ui-dist/assets/{_basePickBy-Dt5BZkFR.js → _basePickBy-Z-nVVgZf.js} +1 -1
  58. package/ui-dist/assets/{_baseUniq-BV6xkV-q.js → _baseUniq-Dh-6NL8P.js} +1 -1
  59. package/ui-dist/assets/{arc-BavI3Cpg.js → arc-Dx0oQadP.js} +1 -1
  60. package/ui-dist/assets/{architectureDiagram-2XIMDMQ5-D084Ft-t.js → architectureDiagram-2XIMDMQ5-CWuo8Dj6.js} +1 -1
  61. package/ui-dist/assets/{blockDiagram-WCTKOSBZ-B11F-ZeY.js → blockDiagram-WCTKOSBZ-CbcNpbB_.js} +1 -1
  62. package/ui-dist/assets/{c4Diagram-IC4MRINW-CU79tVS8.js → c4Diagram-IC4MRINW-Dsqi1Fq9.js} +1 -1
  63. package/ui-dist/assets/channel-C0eHQJ77.js +1 -0
  64. package/ui-dist/assets/{chunk-4BX2VUAB-Bg9R5H9K.js → chunk-4BX2VUAB-CRY-15XU.js} +1 -1
  65. package/ui-dist/assets/{chunk-55IACEB6-Jn5G2tt5.js → chunk-55IACEB6-duEUUC1_.js} +1 -1
  66. package/ui-dist/assets/{chunk-FMBD7UC4-JXfQE2rg.js → chunk-FMBD7UC4-DIXiuDXe.js} +1 -1
  67. package/ui-dist/assets/{chunk-JSJVCQXG-BAeArUQT.js → chunk-JSJVCQXG-CBw9Zve6.js} +1 -1
  68. package/ui-dist/assets/{chunk-KX2RTZJC-CrZqiCTV.js → chunk-KX2RTZJC-Cbcp4AQs.js} +1 -1
  69. package/ui-dist/assets/{chunk-NQ4KR5QH-BqWqHlDy.js → chunk-NQ4KR5QH-C7bmgFmt.js} +1 -1
  70. package/ui-dist/assets/{chunk-QZHKN3VN-BqwOeAXJ.js → chunk-QZHKN3VN-DX9ha7sr.js} +1 -1
  71. package/ui-dist/assets/{chunk-WL4C6EOR-CnekGXD1.js → chunk-WL4C6EOR-Ds_jtgg-.js} +1 -1
  72. package/ui-dist/assets/classDiagram-VBA2DB6C-BYwRa3N4.js +1 -0
  73. package/ui-dist/assets/classDiagram-v2-RAHNMMFH-BYwRa3N4.js +1 -0
  74. package/ui-dist/assets/clone-BoMzR_MV.js +1 -0
  75. package/ui-dist/assets/{cose-bilkent-S5V4N54A-Bb0oormu.js → cose-bilkent-S5V4N54A-BTZXqhQM.js} +1 -1
  76. package/ui-dist/assets/{dagre-KLK3FWXG-Di0mSC7I.js → dagre-KLK3FWXG-DoagFvkM.js} +1 -1
  77. package/ui-dist/assets/{diagram-E7M64L7V-BmboA6j9.js → diagram-E7M64L7V-CB3LSeL7.js} +1 -1
  78. package/ui-dist/assets/{diagram-IFDJBPK2-kZhtKkiD.js → diagram-IFDJBPK2-DYl5A0v_.js} +1 -1
  79. package/ui-dist/assets/{diagram-P4PSJMXO-XW7Rz4Bf.js → diagram-P4PSJMXO-B75eqyOE.js} +1 -1
  80. package/ui-dist/assets/{erDiagram-INFDFZHY-BNczf0Sk.js → erDiagram-INFDFZHY-DQr-k6Ob.js} +1 -1
  81. package/ui-dist/assets/{flowDiagram-PKNHOUZH-Ch_czQvO.js → flowDiagram-PKNHOUZH-Ocns_Qv9.js} +1 -1
  82. package/ui-dist/assets/{ganttDiagram-A5KZAMGK-C1URfdUK.js → ganttDiagram-A5KZAMGK-BjE__MFt.js} +1 -1
  83. package/ui-dist/assets/{gitGraphDiagram-K3NZZRJ6-CcxWHXyd.js → gitGraphDiagram-K3NZZRJ6-C2kH5giJ.js} +1 -1
  84. package/ui-dist/assets/{graph-V1OMZqHH.js → graph-DCiMHpHb.js} +1 -1
  85. package/ui-dist/assets/{index-D_kInGws.js → index-BC5uZF8E.js} +1 -1
  86. package/ui-dist/assets/{index-D6HKN84d.js → index-BFfolhzq.js} +1 -1
  87. package/ui-dist/assets/{index-ryg6l2z9.js → index-BI02BXrX.js} +1 -1
  88. package/ui-dist/assets/{index-DzAtWBqd.js → index-BM2f3uhO.js} +1 -1
  89. package/ui-dist/assets/{index-DB47pZF6.js → index-BRhp1mgy.js} +1 -1
  90. package/ui-dist/assets/{index-CL_6t72P.js → index-BRjIyHuE.js} +1 -1
  91. package/ui-dist/assets/{index-TVEu67wj.js → index-BfivROpG.js} +1 -1
  92. package/ui-dist/assets/{index-S47mKW1y.js → index-Bqp_LUjM.js} +1 -1
  93. package/ui-dist/assets/{index-BeN23vsN.js → index-C6S5PhGB.js} +1 -1
  94. package/ui-dist/assets/{index-C09tSW5o.js → index-C8YCz15P.js} +1 -1
  95. package/ui-dist/assets/index-CEnH7dl6.css +1 -0
  96. package/ui-dist/assets/{index-BB4cyXDK.js → index-CGZps8RJ.js} +1 -1
  97. package/ui-dist/assets/{index-QBpzNJ_D.js → index-CL6ddeTd.js} +1 -1
  98. package/ui-dist/assets/{index-Cdz1D2gL.js → index-ClUvF5DZ.js} +1 -1
  99. package/ui-dist/assets/{index-CECy-vrC.js → index-CwWteDDT.js} +1 -1
  100. package/ui-dist/assets/{index-D5aBaXzj.js → index-D4FPw6Z-.js} +1 -1
  101. package/ui-dist/assets/{index-DYEitDv9.js → index-DE8Or43O.js} +1 -1
  102. package/ui-dist/assets/{index-DkmRKRUV.js → index-DTEf2nnT.js} +1 -1
  103. package/ui-dist/assets/{index-BlRJUkpp.js → index-DYK0Hv0u.js} +1 -1
  104. package/ui-dist/assets/{index-DdJ5VFjZ.js → index-DqZcHxj3.js} +1 -1
  105. package/ui-dist/assets/{index-BV8o7Fq3.js → index-FeiA9wfj.js} +1 -1
  106. package/ui-dist/assets/{index-CVEsvjFN.js → index-SOxEvpVg.js} +1 -1
  107. package/ui-dist/assets/{index-DY84bnqq.js → index-jnmcBz-Q.js} +1 -1
  108. package/ui-dist/assets/index-tihUm8g4.js +1528 -0
  109. package/ui-dist/assets/{infoDiagram-LFFYTUFH-iClsiP3Z.js → infoDiagram-LFFYTUFH-EYWd-pp8.js} +1 -1
  110. package/ui-dist/assets/{ishikawaDiagram-PHBUUO56-BttLWbR-.js → ishikawaDiagram-PHBUUO56-CXBLaizm.js} +1 -1
  111. package/ui-dist/assets/{journeyDiagram-4ABVD52K-DtuvfDLZ.js → journeyDiagram-4ABVD52K-AZ1urW_B.js} +1 -1
  112. package/ui-dist/assets/{kanban-definition-K7BYSVSG-DInRSYDg.js → kanban-definition-K7BYSVSG-C0wbmei0.js} +1 -1
  113. package/ui-dist/assets/{layout-_3fh9W3_.js → layout-D1zBnRfr.js} +1 -1
  114. package/ui-dist/assets/{linear-BPKSoGxr.js → linear-DotDRzvv.js} +1 -1
  115. package/ui-dist/assets/{mermaid.core-4SnxubJe.js → mermaid.core-CkITwr03.js} +4 -4
  116. package/ui-dist/assets/{mindmap-definition-YRQLILUH-CEFlhL8P.js → mindmap-definition-YRQLILUH-Dfd2b6xG.js} +1 -1
  117. package/ui-dist/assets/{pieDiagram-SKSYHLDU-B52KJjTW.js → pieDiagram-SKSYHLDU-C9UKudln.js} +1 -1
  118. package/ui-dist/assets/{quadrantDiagram-337W2JSQ-CM8WWsTV.js → quadrantDiagram-337W2JSQ-bOy85C9o.js} +1 -1
  119. package/ui-dist/assets/{requirementDiagram-Z7DCOOCP-CqLV4dlq.js → requirementDiagram-Z7DCOOCP-NJgBrRFH.js} +1 -1
  120. package/ui-dist/assets/{sankeyDiagram-WA2Y5GQK-BWciQ_Qk.js → sankeyDiagram-WA2Y5GQK-CjKs8Ww5.js} +1 -1
  121. package/ui-dist/assets/{sequenceDiagram-2WXFIKYE-DqPHsdhr.js → sequenceDiagram-2WXFIKYE-BeBTWv23.js} +1 -1
  122. package/ui-dist/assets/{stateDiagram-RAJIS63D-BFd1hpKm.js → stateDiagram-RAJIS63D-BpGOOwbe.js} +1 -1
  123. package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-Cpt3cnOp.js +1 -0
  124. package/ui-dist/assets/{timeline-definition-YZTLITO2-CVDJHxkz.js → timeline-definition-YZTLITO2-XKaBljll.js} +1 -1
  125. package/ui-dist/assets/{treemap-KZPCXAKY-BSISEEpV.js → treemap-KZPCXAKY-kWJ2O44d.js} +1 -1
  126. package/ui-dist/assets/{vennDiagram-LZ73GAT5-BkeCtczy.js → vennDiagram-LZ73GAT5-BUqICYfS.js} +1 -1
  127. package/ui-dist/assets/{xychartDiagram-JWTSCODW-CcXKs2fL.js → xychartDiagram-JWTSCODW-C7kF7s-X.js} +1 -1
  128. package/ui-dist/index.html +2 -2
  129. package/ui-dist/assets/channel-DMQmi8-S.js +0 -1
  130. package/ui-dist/assets/classDiagram-VBA2DB6C-CMV-ujRZ.js +0 -1
  131. package/ui-dist/assets/classDiagram-v2-RAHNMMFH-CMV-ujRZ.js +0 -1
  132. package/ui-dist/assets/clone-BWKwW-sv.js +0 -1
  133. package/ui-dist/assets/index-B137l9Mb.js +0 -1511
  134. package/ui-dist/assets/index-CpjJLMdb.css +0 -1
  135. package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-By51CxHn.js +0 -1
@@ -1,5 +1,5 @@
1
1
  import type { Db } from "@rudderhq/db";
2
- import { type MessengerApprovalThreadItem, type MessengerBudgetThreadItem, type MessengerHeartbeatRunThreadItem, type MessengerIssueThreadItem, type MessengerJoinRequestThreadItem, type MessengerSystemThreadKind, type MessengerThreadSummary } from "@rudderhq/shared";
2
+ import { type MessengerApprovalThreadItem, type MessengerBudgetThreadItem, type MessengerHeartbeatRunThreadItem, type MessengerIssueThreadItem, type MessengerJoinRequestThreadItem, type MessengerSystemThreadKind, type MessengerThreadDetail, type MessengerThreadSummary } from "@rudderhq/shared";
3
3
  import { chatService } from "./chats.js";
4
4
  type ThreadReadState = {
5
5
  lastReadAt: Date;
@@ -14,21 +14,7 @@ export declare function messengerService(db: Db): {
14
14
  } | null>;
15
15
  getIssuesThread: (orgId: string, userId: string) => Promise<{
16
16
  summary: MessengerThreadSummary;
17
- detail: {
18
- threadKey: string;
19
- kind: "issues";
20
- title: string;
21
- subtitle: string;
22
- preview: string | null;
23
- latestActivityAt: Date | null;
24
- lastReadAt: Date | null;
25
- unreadCount: number;
26
- needsAttention: boolean;
27
- isPinned: false;
28
- href: string;
29
- description: string;
30
- items: MessengerIssueThreadItem[];
31
- };
17
+ detail: MessengerThreadDetail<MessengerIssueThreadItem>;
32
18
  }>;
33
19
  getApprovalsThread: (orgId: string, userId: string) => Promise<{
34
20
  summary: MessengerThreadSummary;
@@ -1 +1 @@
1
- {"version":3,"file":"messenger.d.ts","sourceRoot":"","sources":["../../src/services/messenger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAcvC,OAAO,EASL,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAE9B,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAG9B,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA8BzC,KAAK,eAAe,GAAG;IACrB,UAAU,EAAE,IAAI,CAAC;CAClB,CAAC;AAuHF,KAAK,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAC/F,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AA0hBlG,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE;iCAmcK,MAAM,UAAU,MAAM;oCAmDnB,MAAM,UAAU,MAAM;sBAKjC,mBAAmB;kBAC3B,cAAc,EAAE;;6BA3BJ,MAAM,UAAU,MAAM;;;;;;;;;;;;;;;;;;gCAInB,MAAM,UAAU,MAAM;;;;;;;;;;;;;;;;;;6BAIzB,MAAM,UAAU,MAAM,cAAc,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAuB9D,MAAM,UAAU,MAAM,aAAa,MAAM;;;;;;;;;4BAQzC,MAAM,UAAU,MAAM,aAAa,MAAM;2BAAzC,MAAM,UAAU,MAAM,aAAa,MAAM;EA+C/E"}
1
+ {"version":3,"file":"messenger.d.ts","sourceRoot":"","sources":["../../src/services/messenger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAcvC,OAAO,EASL,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAE9B,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAE9B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA8BzC,KAAK,eAAe,GAAG;IACrB,UAAU,EAAE,IAAI,CAAC;CAClB,CAAC;AAgIF,KAAK,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAK/F,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAqiBlG,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE;iCA2rBK,MAAM,UAAU,MAAM;oCA0DnB,MAAM,UAAU,MAAM;sBAKjC,mBAAmB;kBAC3B,cAAc,EAAE;;6BA3BJ,MAAM,UAAU,MAAM;;;;gCAInB,MAAM,UAAU,MAAM;;;;;;;;;;;;;;;;;;6BAIzB,MAAM,UAAU,MAAM,cAAc,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAuB9D,MAAM,UAAU,MAAM,aAAa,MAAM;;;;;;;;;4BAQzC,MAAM,UAAU,MAAM,aAAa,MAAM;2BAAzC,MAAM,UAAU,MAAM,aAAa,MAAM;EA+C/E"}
@@ -1,4 +1,4 @@
1
- import { and, desc, eq, inArray, isNull, or } from "drizzle-orm";
1
+ import { and, desc, eq, gt, inArray, isNull, or, sql } from "drizzle-orm";
2
2
  import { activityLog, approvalComments, approvals, agents, authUsers, heartbeatRuns, issueComments, issues, joinRequests, messengerThreadUserStates, } from "@rudderhq/db";
3
3
  import { formatMessengerPreview, formatMessengerTitle, } from "@rudderhq/shared";
4
4
  import { issueService } from "./issues.js";
@@ -496,6 +496,10 @@ async function loadThreadStates(db, orgId, userId, threadKeys) {
496
496
  .where(and(eq(messengerThreadUserStates.orgId, orgId), eq(messengerThreadUserStates.userId, userId), inArray(messengerThreadUserStates.threadKey, threadKeys)));
497
497
  return new Map(rows.map((row) => [row.threadKey, row]));
498
498
  }
499
+ async function lastReadAtForThread(db, orgId, userId, threadKey, threadStates) {
500
+ const states = threadStates ?? loadThreadStates(db, orgId, userId, [threadKey]);
501
+ return (await states).get(threadKey)?.lastReadAt ?? null;
502
+ }
499
503
  export function messengerService(db) {
500
504
  const issuesSvc = issueService(db);
501
505
  const chatsSvc = chatService(db);
@@ -538,30 +542,45 @@ export function messengerService(db) {
538
542
  }
539
543
  return Array.from(universe.values());
540
544
  }
541
- async function loadIssueSummaryData(orgId, userId) {
545
+ async function loadIssueData(orgId, userId, threadStates, options) {
546
+ const lastReadAtPromise = lastReadAtForThread(db, orgId, userId, "issues", threadStates);
542
547
  const issuesUniverse = await loadIssueUniverse(orgId, userId);
543
548
  const issueIds = issuesUniverse.map((row) => row.id);
544
- const threadStates = await loadThreadStates(db, orgId, userId, ["issues"]);
545
- const lastReadAt = threadStates.get("issues")?.lastReadAt ?? null;
549
+ const lastReadAt = await lastReadAtPromise;
546
550
  const [commentRows, activityRows] = await Promise.all([
547
551
  issueIds.length === 0
548
552
  ? Promise.resolve([])
549
- : db
550
- .select({
551
- id: issueComments.id,
552
- issueId: issueComments.issueId,
553
- body: issueComments.body,
554
- authorAgentId: issueComments.authorAgentId,
555
- authorUserId: issueComments.authorUserId,
556
- authorAgentName: agents.name,
557
- authorUserName: authUsers.name,
558
- createdAt: issueComments.createdAt,
559
- })
560
- .from(issueComments)
561
- .leftJoin(agents, eq(issueComments.authorAgentId, agents.id))
562
- .leftJoin(authUsers, eq(issueComments.authorUserId, authUsers.id))
563
- .where(and(eq(issueComments.orgId, orgId), inArray(issueComments.issueId, issueIds)))
564
- .orderBy(desc(issueComments.createdAt)),
553
+ : options.includeDetail
554
+ ? db
555
+ .select({
556
+ id: issueComments.id,
557
+ issueId: issueComments.issueId,
558
+ body: issueComments.body,
559
+ authorAgentId: issueComments.authorAgentId,
560
+ authorUserId: issueComments.authorUserId,
561
+ authorAgentName: agents.name,
562
+ authorUserName: authUsers.name,
563
+ createdAt: issueComments.createdAt,
564
+ })
565
+ .from(issueComments)
566
+ .leftJoin(agents, eq(issueComments.authorAgentId, agents.id))
567
+ .leftJoin(authUsers, eq(issueComments.authorUserId, authUsers.id))
568
+ .where(and(eq(issueComments.orgId, orgId), inArray(issueComments.issueId, issueIds)))
569
+ .orderBy(desc(issueComments.createdAt))
570
+ : db
571
+ .select({
572
+ id: issueComments.id,
573
+ issueId: issueComments.issueId,
574
+ body: issueComments.body,
575
+ authorAgentId: issueComments.authorAgentId,
576
+ authorUserId: issueComments.authorUserId,
577
+ authorAgentName: sql `null`,
578
+ authorUserName: sql `null`,
579
+ createdAt: issueComments.createdAt,
580
+ })
581
+ .from(issueComments)
582
+ .where(and(eq(issueComments.orgId, orgId), inArray(issueComments.issueId, issueIds), sql `(${issueComments.authorUserId} is null or ${issueComments.authorUserId} <> ${userId})`))
583
+ .orderBy(desc(issueComments.createdAt)),
565
584
  issueIds.length === 0
566
585
  ? Promise.resolve([])
567
586
  : db
@@ -582,7 +601,7 @@ export function messengerService(db) {
582
601
  const latestCommentByIssue = new Map();
583
602
  const latestExternalCommentByIssue = new Map();
584
603
  for (const row of commentRows) {
585
- if (!latestCommentByIssue.has(row.issueId)) {
604
+ if (options.includeDetail && !latestCommentByIssue.has(row.issueId)) {
586
605
  latestCommentByIssue.set(row.issueId, row);
587
606
  }
588
607
  if (!isSelfAuthoredComment(row, userId) && !latestExternalCommentByIssue.has(row.issueId)) {
@@ -648,20 +667,26 @@ export function messengerService(db) {
648
667
  : null;
649
668
  const summaryPreview = attentionActivityAt ? issueThreadPreview(issue, attentionPreview) : null;
650
669
  return {
651
- item: issueCard(issue, userId, issue.followed, latestPreview, latestActivityAt ?? issue.updatedAt, latestSourceIsComment ? latestVisibleComment : null, statusChangeActivity),
670
+ issue,
671
+ item: options.includeDetail
672
+ ? issueCard(issue, userId, issue.followed, latestPreview, latestActivityAt ?? issue.updatedAt, latestSourceIsComment ? latestVisibleComment : null, statusChangeActivity)
673
+ : null,
652
674
  attentionActivityAt,
653
675
  attentionPreview: summaryPreview,
654
676
  };
655
677
  });
656
- const unsortedItems = unsortedEntries.map((entry) => entry.item);
657
678
  const latestFirstEntries = [...unsortedEntries].sort((a, b) => {
658
679
  const aTime = a.attentionActivityAt?.getTime() ?? Number.NEGATIVE_INFINITY;
659
680
  const bTime = b.attentionActivityAt?.getTime() ?? Number.NEGATIVE_INFINITY;
660
681
  if (aTime !== bTime)
661
682
  return bTime - aTime;
662
- return a.item.title.localeCompare(b.item.title);
683
+ return issueDisplayLabel(a.issue).localeCompare(issueDisplayLabel(b.issue));
663
684
  });
664
- const chronologicalItems = [...unsortedItems].sort(compareChronologicalActivity);
685
+ const chronologicalItems = options.includeDetail
686
+ ? unsortedEntries
687
+ .flatMap((entry) => entry.item ? [entry.item] : [])
688
+ .sort(compareChronologicalActivity)
689
+ : [];
665
690
  const latestAttentionEntry = latestFirstEntries.find((entry) => entry.attentionActivityAt);
666
691
  const latestActivityAt = latestAttentionEntry?.attentionActivityAt ?? null;
667
692
  const unreadCount = unsortedEntries.filter((entry) => {
@@ -672,9 +697,12 @@ export function messengerService(db) {
672
697
  return true;
673
698
  return itemActivity.getTime() > lastReadAt.getTime();
674
699
  }).length;
675
- return {
700
+ const data = {
676
701
  summary: issueSummary(issuesUniverse.length, latestActivityAt, unreadCount, lastReadAt, latestAttentionEntry?.attentionPreview ?? null),
677
- detail: {
702
+ itemCount: issuesUniverse.length,
703
+ };
704
+ if (options.includeDetail) {
705
+ data.detail = {
678
706
  threadKey: "issues",
679
707
  kind: "issues",
680
708
  title: "Issues",
@@ -688,12 +716,22 @@ export function messengerService(db) {
688
716
  href: "/messenger/issues",
689
717
  description: "Followed issues, issues I created, issues assigned to me, and issues ready for my review",
690
718
  items: chronologicalItems,
691
- },
719
+ };
720
+ }
721
+ return data;
722
+ }
723
+ async function loadIssueSummaryData(orgId, userId, threadStates) {
724
+ const data = await loadIssueData(orgId, userId, threadStates, { includeDetail: true });
725
+ return {
726
+ summary: data.summary,
727
+ detail: data.detail,
692
728
  };
693
729
  }
694
- async function loadApprovalSummaryData(orgId, userId) {
695
- const threadStates = await loadThreadStates(db, orgId, userId, ["approvals"]);
696
- const lastReadAt = threadStates.get("approvals")?.lastReadAt ?? null;
730
+ async function loadIssueThreadSummaryData(orgId, userId, threadStates) {
731
+ return loadIssueData(orgId, userId, threadStates, { includeDetail: false });
732
+ }
733
+ async function loadApprovalSummaryData(orgId, userId, threadStates) {
734
+ const lastReadAtPromise = lastReadAtForThread(db, orgId, userId, "approvals", threadStates);
697
735
  const [approvalRows, latestComments] = await Promise.all([
698
736
  db
699
737
  .select()
@@ -711,6 +749,7 @@ export function messengerService(db) {
711
749
  .where(eq(approvals.orgId, orgId))
712
750
  .orderBy(desc(approvalComments.createdAt)),
713
751
  ]);
752
+ const lastReadAt = await lastReadAtPromise;
714
753
  const latestCommentByApproval = new Map();
715
754
  for (const row of latestComments) {
716
755
  if (!latestCommentByApproval.has(row.approvalId)) {
@@ -754,9 +793,95 @@ export function messengerService(db) {
754
793
  },
755
794
  };
756
795
  }
757
- async function loadFailedRunData(orgId, userId) {
758
- const threadStates = await loadThreadStates(db, orgId, userId, ["failed-runs"]);
759
- const lastReadAt = threadStates.get("failed-runs")?.lastReadAt ?? null;
796
+ async function loadApprovalThreadSummaryData(orgId, userId, threadStates) {
797
+ const lastReadAt = await lastReadAtForThread(db, orgId, userId, "approvals", threadStates);
798
+ const approvalPredicate = eq(approvals.orgId, orgId);
799
+ const pendingApprovalPredicate = and(eq(approvals.orgId, orgId), eq(approvals.status, "pending"));
800
+ const [summaryRows, latestApprovalRows, latestCommentRows, unreadRows] = await Promise.all([
801
+ db
802
+ .select({
803
+ itemCount: sql `count(*)::int`,
804
+ })
805
+ .from(approvals)
806
+ .where(approvalPredicate),
807
+ db
808
+ .select()
809
+ .from(approvals)
810
+ .where(approvalPredicate)
811
+ .orderBy(desc(approvals.updatedAt), desc(approvals.createdAt))
812
+ .limit(1),
813
+ db
814
+ .execute(sql `
815
+ select
816
+ latest_comment.approval_id as "approvalId",
817
+ latest_comment.body as "body",
818
+ latest_comment.created_at as "createdAt"
819
+ from ${approvals}
820
+ inner join lateral (
821
+ select
822
+ ${approvalComments.approvalId},
823
+ ${approvalComments.body},
824
+ ${approvalComments.createdAt}
825
+ from ${approvalComments}
826
+ where ${approvalComments.orgId} = ${orgId}
827
+ and ${approvalComments.approvalId} = ${approvals.id}
828
+ order by ${approvalComments.createdAt} desc
829
+ limit 1
830
+ ) latest_comment on true
831
+ where ${approvals.orgId} = ${orgId}
832
+ order by latest_comment.created_at desc
833
+ limit 1
834
+ `),
835
+ db
836
+ .select({
837
+ unreadCount: sql `count(*)::int`,
838
+ })
839
+ .from(approvals)
840
+ .where(lastReadAt ? and(pendingApprovalPredicate, gt(approvals.updatedAt, lastReadAt)) : pendingApprovalPredicate),
841
+ ]);
842
+ const latestApproval = (latestApprovalRows[0] ?? null);
843
+ const latestApprovalCommentRows = latestApproval
844
+ ? await db
845
+ .select({
846
+ approvalId: approvalComments.approvalId,
847
+ body: approvalComments.body,
848
+ createdAt: approvalComments.createdAt,
849
+ })
850
+ .from(approvalComments)
851
+ .where(eq(approvalComments.approvalId, latestApproval.id))
852
+ .orderBy(desc(approvalComments.createdAt))
853
+ .limit(1)
854
+ : [];
855
+ const latestCommentRow = (latestCommentRows[0] ?? null);
856
+ const latestCommentApprovalRows = latestCommentRow
857
+ ? await db
858
+ .select()
859
+ .from(approvals)
860
+ .where(and(eq(approvals.id, latestCommentRow.approvalId), approvalPredicate))
861
+ .limit(1)
862
+ : [];
863
+ const candidateItems = [];
864
+ if (latestApproval) {
865
+ const latestComment = (latestApprovalCommentRows[0] ?? null);
866
+ const latestActivityAt = maxDate(latestApproval.updatedAt, latestComment?.createdAt) ?? latestApproval.updatedAt;
867
+ candidateItems.push(approvalCard(latestApproval, latestComment, userId, latestActivityAt));
868
+ }
869
+ if (latestCommentRow) {
870
+ const approval = (latestCommentApprovalRows[0] ?? null);
871
+ if (approval) {
872
+ const latestActivityAt = maxDate(approval.updatedAt, latestCommentRow.createdAt) ?? approval.updatedAt;
873
+ candidateItems.push(approvalCard(approval, latestCommentRow, userId, latestActivityAt));
874
+ }
875
+ }
876
+ const latestItem = candidateItems.sort(compareLatestActivity)[0] ?? null;
877
+ const itemCount = Number(summaryRows[0]?.itemCount ?? 0);
878
+ return {
879
+ itemCount,
880
+ summary: approvalSummary(itemCount, latestItem?.latestActivityAt ?? null, Number(unreadRows[0]?.unreadCount ?? 0), lastReadAt, latestItem?.preview ?? null),
881
+ };
882
+ }
883
+ async function loadFailedRunData(orgId, userId, threadStates) {
884
+ const lastReadAtPromise = lastReadAtForThread(db, orgId, userId, "failed-runs", threadStates);
760
885
  const [runRows, agentRows] = await Promise.all([
761
886
  db
762
887
  .select({
@@ -782,6 +907,7 @@ export function messengerService(db) {
782
907
  .from(agents)
783
908
  .where(eq(agents.orgId, orgId)),
784
909
  ]);
910
+ const lastReadAt = await lastReadAtPromise;
785
911
  const agentNames = new Map(agentRows.map((row) => [row.id, row.name]));
786
912
  const items = runRows.map((run) => failedRunCard(run, agentNames.get(run.agentId) ?? null));
787
913
  const latestFirstItems = [...items].sort(compareLatestActivity);
@@ -807,10 +933,49 @@ export function messengerService(db) {
807
933
  },
808
934
  };
809
935
  }
810
- async function loadBudgetAlertData(orgId, userId) {
811
- const threadStates = await loadThreadStates(db, orgId, userId, ["budget-alerts"]);
812
- const lastReadAt = threadStates.get("budget-alerts")?.lastReadAt ?? null;
936
+ async function loadFailedRunSummaryData(orgId, userId, threadStates) {
937
+ const lastReadAt = await lastReadAtForThread(db, orgId, userId, "failed-runs", threadStates);
938
+ const latestActivitySql = sql `max(coalesce(${heartbeatRuns.updatedAt}, ${heartbeatRuns.createdAt}))`;
939
+ const failedRunPredicate = and(eq(heartbeatRuns.orgId, orgId), eq(heartbeatRuns.status, "failed"));
940
+ const [summaryRows, latestRows, unreadRows] = await Promise.all([
941
+ db
942
+ .select({
943
+ itemCount: sql `count(*)::int`,
944
+ latestActivityAt: latestActivitySql,
945
+ })
946
+ .from(heartbeatRuns)
947
+ .where(failedRunPredicate),
948
+ db
949
+ .select({
950
+ error: heartbeatRuns.error,
951
+ stderrExcerpt: heartbeatRuns.stderrExcerpt,
952
+ })
953
+ .from(heartbeatRuns)
954
+ .where(failedRunPredicate)
955
+ .orderBy(desc(heartbeatRuns.updatedAt), desc(heartbeatRuns.createdAt))
956
+ .limit(1),
957
+ lastReadAt
958
+ ? db
959
+ .select({
960
+ unreadCount: sql `count(*)::int`,
961
+ })
962
+ .from(heartbeatRuns)
963
+ .where(and(failedRunPredicate, gt(heartbeatRuns.updatedAt, lastReadAt)))
964
+ : Promise.resolve([]),
965
+ ]);
966
+ const summaryRow = summaryRows[0];
967
+ const latestRun = latestRows[0] ?? null;
968
+ const itemCount = Number(summaryRow?.itemCount ?? 0);
969
+ const unreadCount = lastReadAt ? Number(unreadRows[0]?.unreadCount ?? 0) : itemCount;
970
+ return {
971
+ itemCount,
972
+ summary: systemSummary("failed-runs", "Failed runs", itemCount, normalizeDate(summaryRow?.latestActivityAt ?? null), unreadCount, lastReadAt, "No failed runs yet", truncate(latestRun?.error) ?? truncate(latestRun?.stderrExcerpt)),
973
+ };
974
+ }
975
+ async function loadBudgetAlertData(orgId, userId, threadStates) {
976
+ const lastReadAtPromise = lastReadAtForThread(db, orgId, userId, "budget-alerts", threadStates);
813
977
  const incidents = ((await budgetsSvc.overview(orgId)).activeIncidents ?? []);
978
+ const lastReadAt = await lastReadAtPromise;
814
979
  const items = incidents.map((incident) => budgetCard(incident));
815
980
  const latestActivityAt = items[0]?.latestActivityAt ?? null;
816
981
  const unreadCount = systemUnreadCountSince(incidents, lastReadAt);
@@ -833,14 +998,14 @@ export function messengerService(db) {
833
998
  },
834
999
  };
835
1000
  }
836
- async function loadJoinRequestData(orgId, userId) {
837
- const threadStates = await loadThreadStates(db, orgId, userId, ["join-requests"]);
838
- const lastReadAt = threadStates.get("join-requests")?.lastReadAt ?? null;
1001
+ async function loadJoinRequestData(orgId, userId, threadStates) {
1002
+ const lastReadAtPromise = lastReadAtForThread(db, orgId, userId, "join-requests", threadStates);
839
1003
  const rows = (await db
840
1004
  .select()
841
1005
  .from(joinRequests)
842
1006
  .where(and(eq(joinRequests.orgId, orgId), eq(joinRequests.status, "pending_approval")))
843
1007
  .orderBy(desc(joinRequests.updatedAt), desc(joinRequests.createdAt)));
1008
+ const lastReadAt = await lastReadAtPromise;
844
1009
  const items = rows.map((row) => joinRequestCard(row));
845
1010
  const latestActivityAt = items[0]?.latestActivityAt ?? null;
846
1011
  const unreadCount = systemUnreadCountSince(rows, lastReadAt);
@@ -863,25 +1028,71 @@ export function messengerService(db) {
863
1028
  },
864
1029
  };
865
1030
  }
1031
+ async function loadJoinRequestSummaryData(orgId, userId, threadStates) {
1032
+ const lastReadAt = await lastReadAtForThread(db, orgId, userId, "join-requests", threadStates);
1033
+ const latestActivitySql = sql `max(coalesce(${joinRequests.updatedAt}, ${joinRequests.createdAt}))`;
1034
+ const joinRequestPredicate = and(eq(joinRequests.orgId, orgId), eq(joinRequests.status, "pending_approval"));
1035
+ const [summaryRows, latestRows, unreadRows] = await Promise.all([
1036
+ db
1037
+ .select({
1038
+ itemCount: sql `count(*)::int`,
1039
+ latestActivityAt: latestActivitySql,
1040
+ })
1041
+ .from(joinRequests)
1042
+ .where(joinRequestPredicate),
1043
+ db
1044
+ .select({
1045
+ capabilities: joinRequests.capabilities,
1046
+ requestEmailSnapshot: joinRequests.requestEmailSnapshot,
1047
+ })
1048
+ .from(joinRequests)
1049
+ .where(joinRequestPredicate)
1050
+ .orderBy(desc(joinRequests.updatedAt), desc(joinRequests.createdAt))
1051
+ .limit(1),
1052
+ lastReadAt
1053
+ ? db
1054
+ .select({
1055
+ unreadCount: sql `count(*)::int`,
1056
+ })
1057
+ .from(joinRequests)
1058
+ .where(and(joinRequestPredicate, gt(joinRequests.updatedAt, lastReadAt)))
1059
+ : Promise.resolve([]),
1060
+ ]);
1061
+ const summaryRow = summaryRows[0];
1062
+ const latestRequest = latestRows[0] ?? null;
1063
+ const itemCount = Number(summaryRow?.itemCount ?? 0);
1064
+ const unreadCount = lastReadAt ? Number(unreadRows[0]?.unreadCount ?? 0) : itemCount;
1065
+ return {
1066
+ itemCount,
1067
+ summary: systemSummary("join-requests", "Join requests", itemCount, normalizeDate(summaryRow?.latestActivityAt ?? null), unreadCount, lastReadAt, "No pending join requests", latestRequest?.capabilities ?? latestRequest?.requestEmailSnapshot ?? null),
1068
+ };
1069
+ }
866
1070
  async function listThreadSummaries(orgId, userId) {
1071
+ const syntheticThreadStates = loadThreadStates(db, orgId, userId, [
1072
+ "issues",
1073
+ "approvals",
1074
+ "failed-runs",
1075
+ "budget-alerts",
1076
+ "join-requests",
1077
+ ]);
867
1078
  const [chats, issueData, approvalData, failedRunData, budgetData, joinRequestData] = await Promise.all([
868
- chatsSvc.list(orgId, { status: "active" }, userId),
869
- loadIssueSummaryData(orgId, userId),
870
- loadApprovalSummaryData(orgId, userId),
871
- loadFailedRunData(orgId, userId),
872
- loadBudgetAlertData(orgId, userId),
873
- loadJoinRequestData(orgId, userId),
1079
+ chatsSvc.listSummaries(orgId, { status: "active" }, userId),
1080
+ loadIssueThreadSummaryData(orgId, userId, syntheticThreadStates),
1081
+ loadApprovalThreadSummaryData(orgId, userId, syntheticThreadStates),
1082
+ loadFailedRunSummaryData(orgId, userId, syntheticThreadStates),
1083
+ loadBudgetAlertData(orgId, userId, syntheticThreadStates),
1084
+ loadJoinRequestSummaryData(orgId, userId, syntheticThreadStates),
874
1085
  ]);
875
1086
  const syntheticSummaries = [];
876
- if (issueData.detail.items.length > 0)
1087
+ if (issueData.itemCount > 0)
877
1088
  syntheticSummaries.push(issueData.summary);
878
- if (approvalData.detail.items.length > 0)
1089
+ if (approvalData.itemCount > 0)
879
1090
  syntheticSummaries.push(approvalData.summary);
880
- if (failedRunData.detail.items.length > 0)
1091
+ if (failedRunData.itemCount > 0)
881
1092
  syntheticSummaries.push(failedRunData.summary);
882
1093
  if (budgetData.detail.items.length > 0)
883
1094
  syntheticSummaries.push(budgetData.summary);
884
- if (joinRequestData.detail.items.length > 0)
1095
+ if (joinRequestData.itemCount > 0)
885
1096
  syntheticSummaries.push(joinRequestData.summary);
886
1097
  const threadSummaries = [
887
1098
  ...chats.map(chatSummary),