cognova 0.2.9 → 0.2.11

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 (194) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/{pHQcHl8n.js → 0NJ3PaRM.js} +1 -1
  3. package/.output/public/_nuxt/{CGgZuPnb.js → 0yk-pS3R.js} +2 -2
  4. package/.output/public/_nuxt/{C8gtQaKB.js → 10_wwHSz.js} +1 -1
  5. package/.output/public/_nuxt/{BaYtugUf.js → 1zUTf4AP.js} +1 -1
  6. package/.output/public/_nuxt/{B63LIwsW.js → 2dNDtTiv.js} +3 -3
  7. package/.output/public/_nuxt/{Gy6_ehml.js → 2nOqGUPr.js} +1 -1
  8. package/.output/public/_nuxt/{C6UTTaCP.js → 3jQMk_5H.js} +1 -1
  9. package/.output/public/_nuxt/{BX6vcvkp.js → 4K03TkGA.js} +1 -1
  10. package/.output/public/_nuxt/{DjmFlCHK.js → 6boMs_nF.js} +1 -1
  11. package/.output/public/_nuxt/{CybU4a95.js → 7oCGSglN.js} +1 -1
  12. package/.output/public/_nuxt/{CfO_d5gC.js → 8q5NepGW.js} +1 -1
  13. package/.output/public/_nuxt/{DdXVAzIg.js → B93pdGAW.js} +1 -1
  14. package/.output/public/_nuxt/{D1t0272g.js → BA_pRWwX.js} +1 -1
  15. package/.output/public/_nuxt/{Be5L93GM.js → BDyn4ApB.js} +3 -3
  16. package/.output/public/_nuxt/{DHScQdvh.js → BGgwYWjH.js} +1 -1
  17. package/.output/public/_nuxt/{FYOLNuMV.js → BH0QDWEm.js} +1 -1
  18. package/.output/public/_nuxt/{BVc_fOF1.js → BHtY0l0l.js} +1 -1
  19. package/.output/public/_nuxt/{C6HEirAm.js → BIXrSYwR.js} +1 -1
  20. package/.output/public/_nuxt/BK8S2ony.js +1 -0
  21. package/.output/public/_nuxt/{DTu0K_pX.js → BKXg-alD.js} +1 -1
  22. package/.output/public/_nuxt/{Pa936BQP.js → BLnYhy_t.js} +1 -1
  23. package/.output/public/_nuxt/{wgX1ldgZ.js → BR8bKz8h.js} +1 -1
  24. package/.output/public/_nuxt/{B-513wJW.js → BS2ZNXI1.js} +1 -1
  25. package/.output/public/_nuxt/{a9_LvIZ4.js → BUghTae1.js} +1 -1
  26. package/.output/public/_nuxt/{B8Ou5yh2.js → BXuWCWsJ.js} +1 -1
  27. package/.output/public/_nuxt/{D_tCgCFX.js → BYHCP8x7.js} +1 -1
  28. package/.output/public/_nuxt/{CY3x8iy3.js → BZrSPX0S.js} +1 -1
  29. package/.output/public/_nuxt/{BNBGEa-U.js → B_wilgcd.js} +1 -1
  30. package/.output/public/_nuxt/BaZwjF8o.js +1 -0
  31. package/.output/public/_nuxt/{BwdxQyyo.js → Bciqk4dX.js} +1 -1
  32. package/.output/public/_nuxt/{BhnONkIm.js → Beom-INj.js} +1 -1
  33. package/.output/public/_nuxt/{CzurfTnt.js → BfcLZ_fC.js} +1 -1
  34. package/.output/public/_nuxt/{DwAJIRGg.js → Bk6JUtIo.js} +1 -1
  35. package/.output/public/_nuxt/BkfI94R8.js +1 -0
  36. package/.output/public/_nuxt/{Cs_udj6A.js → BmOtR1wX.js} +1 -1
  37. package/.output/public/_nuxt/{D7iYVmH6.js → BmVLxnDU.js} +1 -1
  38. package/.output/public/_nuxt/{6lIxuL6n.js → BtHQ1T0f.js} +1 -1
  39. package/.output/public/_nuxt/{RRbnMIVe.js → ByYh0uRg.js} +1 -1
  40. package/.output/public/_nuxt/{DBuW01Fx.js → C27WoGNZ.js} +1 -1
  41. package/.output/public/_nuxt/{DJ4pqxg-.js → C2FxZbO_.js} +1 -1
  42. package/.output/public/_nuxt/{myNwwtfj.js → C2vq6Te8.js} +1 -1
  43. package/.output/public/_nuxt/{C3JOLqW1.js → C39dQ88F.js} +1 -1
  44. package/.output/public/_nuxt/{4nN41Tyd.js → C3NST0BU.js} +1 -1
  45. package/.output/public/_nuxt/{BltasKce.js → C4mwL7SE.js} +1 -1
  46. package/.output/public/_nuxt/{B1vDCixB.js → C5R5QaCA.js} +1 -1
  47. package/.output/public/_nuxt/{CwOUYOe5.js → C6qKcgOY.js} +1 -1
  48. package/.output/public/_nuxt/{DGJKfutb.js → C71_a1IX.js} +1 -1
  49. package/.output/public/_nuxt/{CrjNndEO.js → C9WIgRRL.js} +1 -1
  50. package/.output/public/_nuxt/{B5JDxHxC.js → CBTkrk2M.js} +1 -1
  51. package/.output/public/_nuxt/{UbZP7wjm.js → CEnSeCqn.js} +1 -1
  52. package/.output/public/_nuxt/{Cn83lLUb.js → CKxSHyww.js} +1 -1
  53. package/.output/public/_nuxt/{CbTu13wM.js → CLfF6dSn.js} +1 -1
  54. package/.output/public/_nuxt/{Bsdcw5gr.js → CPutXj8l.js} +1 -1
  55. package/.output/public/_nuxt/{CrW6y2-s.js → CQqCBrXj.js} +1 -1
  56. package/.output/public/_nuxt/{OrcbFmB1.js → CSKJ-Ahh.js} +1 -1
  57. package/.output/public/_nuxt/{XJYoLoTi.js → CTCcEJU3.js} +1 -1
  58. package/.output/public/_nuxt/{Bk9-HxOg.js → CVFwOzbl.js} +1 -1
  59. package/.output/public/_nuxt/{CqMgz85Z.js → CVdCqaby.js} +1 -1
  60. package/.output/public/_nuxt/{Cmj-BR0q.js → CVqlefTY.js} +1 -1
  61. package/.output/public/_nuxt/CWyMCJQH.js +1 -0
  62. package/.output/public/_nuxt/{xrh9hzzH.js → CY-QVcA5.js} +2 -2
  63. package/.output/public/_nuxt/{BQ1T6zWA.js → CY-cjAwJ.js} +1 -1
  64. package/.output/public/_nuxt/{CziltI1u.js → CYPO5o_C.js} +1 -1
  65. package/.output/public/_nuxt/{C1FhgWcI.js → CZRnNmU8.js} +1 -1
  66. package/.output/public/_nuxt/{BES6FzF5.js → CZVzFlpI.js} +1 -1
  67. package/.output/public/_nuxt/{CsbHYpNi.js → CZoEPC_Q.js} +3 -3
  68. package/.output/public/_nuxt/{DvATBMPl.js → Cc-2ziaZ.js} +1 -1
  69. package/.output/public/_nuxt/{D3RROe4s.js → ChcO9s3o.js} +1 -1
  70. package/.output/public/_nuxt/{CIUZAp6g.js → Cl-LOSDV.js} +1 -1
  71. package/.output/public/_nuxt/{BwJVN9k0.js → CobqYwkp.js} +1 -1
  72. package/.output/public/_nuxt/{Cmdr5BNz.js → CqNwHCpo.js} +1 -1
  73. package/.output/public/_nuxt/{HPOXd4gt.js → CuxrHsu-.js} +1 -1
  74. package/.output/public/_nuxt/{BYIq3eP8.js → CwPdIZ9J.js} +1 -1
  75. package/.output/public/_nuxt/{D-O8wAju.js → D0ifH682.js} +1 -1
  76. package/.output/public/_nuxt/{BwVt3BzR.js → D3AZaldL.js} +1 -1
  77. package/.output/public/_nuxt/{BE_OHTBv.js → D4UJwDQJ.js} +1 -1
  78. package/.output/public/_nuxt/{hm3ShyaF.js → D5jZq8b3.js} +1 -1
  79. package/.output/public/_nuxt/{pNqWPbXW.js → D5q8SnqE.js} +1 -1
  80. package/.output/public/_nuxt/{BgG2k-zy.js → D8M722pn.js} +1 -1
  81. package/.output/public/_nuxt/{B2MVZlI-.js → D8lwrAYS.js} +1 -1
  82. package/.output/public/_nuxt/{DYu_NYj3.js → D9nmzBAw.js} +1 -1
  83. package/.output/public/_nuxt/{DBKxzQ3U.js → DC4idAGt.js} +1 -1
  84. package/.output/public/_nuxt/{qWWnsfRV.js → DEd2xVbS.js} +1 -1
  85. package/.output/public/_nuxt/{nTCVPCKg.js → DH4YkDAi.js} +1 -1
  86. package/.output/public/_nuxt/{DmmQUhEb.js → DJTCT0bl.js} +1 -1
  87. package/.output/public/_nuxt/{rRYEvKFu.js → DNP5E1bC.js} +1 -1
  88. package/.output/public/_nuxt/{DI1FIvaM.js → DO9SFIh1.js} +1 -1
  89. package/.output/public/_nuxt/DPklr_tJ.js +2 -0
  90. package/.output/public/_nuxt/{-JdScH3W.js → DQM4eBdt.js} +1 -1
  91. package/.output/public/_nuxt/{5MZBbx-H.js → DQh6I9z9.js} +1 -1
  92. package/.output/public/_nuxt/{DLuRYg2p.js → DS2wStH1.js} +1 -1
  93. package/.output/public/_nuxt/{0EY9Msdx.js → DTAStixR.js} +1 -1
  94. package/.output/public/_nuxt/{fVkHtgGF.js → DTGenhcA.js} +1 -1
  95. package/.output/public/_nuxt/{4Qy4OdWt.js → DUAAXJ6Q.js} +1 -1
  96. package/.output/public/_nuxt/{DTrFhdRO.js → DUVzIl3o.js} +1 -1
  97. package/.output/public/_nuxt/{Dj2opPDu.js → DXdwpJ-I.js} +1 -1
  98. package/.output/public/_nuxt/{C8tXHRIo.js → DY3uK7wM.js} +1 -1
  99. package/.output/public/_nuxt/{D76Dw8rw.js → DYbZBZet.js} +1 -1
  100. package/.output/public/_nuxt/{DSgubznn.js → D_4LPm1U.js} +1 -1
  101. package/.output/public/_nuxt/{D8gh62A0.js → Db4KMnt8.js} +1 -1
  102. package/.output/public/_nuxt/{JQV21WEV.js → DcJbYLTp.js} +1 -1
  103. package/.output/public/_nuxt/{D5G5UL_g.js → DfF81NlA.js} +1 -1
  104. package/.output/public/_nuxt/{HlbU_z9a.js → DhI5cA_n.js} +1 -1
  105. package/.output/public/_nuxt/{CaLO8sre.js → DhuOKJda.js} +1 -1
  106. package/.output/public/_nuxt/{CDradKPE.js → Di-Nc75e.js} +1 -1
  107. package/.output/public/_nuxt/{C2DZeD9G.js → Djs0Tlpa.js} +1 -1
  108. package/.output/public/_nuxt/DmmdPt_5.js +1 -0
  109. package/.output/public/_nuxt/{DSynhNOO.js → Dn5a-guE.js} +1 -1
  110. package/.output/public/_nuxt/{k16puyWi.js → DtjjnHnt.js} +1 -1
  111. package/.output/public/_nuxt/{CEJh-OAl.js → DuvzM-P1.js} +1 -1
  112. package/.output/public/_nuxt/{D7WbsJsa.js → DwY7rCd_.js} +1 -1
  113. package/.output/public/_nuxt/{REP2-rzf.js → Dya5oK8u.js} +1 -1
  114. package/.output/public/_nuxt/{CIJ2MJdc.js → DzA58_Lm.js} +1 -1
  115. package/.output/public/_nuxt/{Bp9kHdw6.js → DzGy77Vr.js} +1 -1
  116. package/.output/public/_nuxt/{DfIqFkyp.js → E3rXPwU8.js} +1 -1
  117. package/.output/public/_nuxt/{NusIKwmA.js → EgKnQnf-.js} +1 -1
  118. package/.output/public/_nuxt/{Clr3MHUr.js → GtEM7xVU.js} +1 -1
  119. package/.output/public/_nuxt/{DQd-GXKJ.js → H6JbrRBU.js} +1 -1
  120. package/.output/public/_nuxt/{BzTjmQCD.js → ILEvizzp.js} +1 -1
  121. package/.output/public/_nuxt/{DUDbJONU.js → JX1oqJI9.js} +1 -1
  122. package/.output/public/_nuxt/{BsnhHCOa.js → JbHa4oXq.js} +1 -1
  123. package/.output/public/_nuxt/{BE4Ndhrq.js → Kw0zy3FG.js} +1 -1
  124. package/.output/public/_nuxt/{BOGXUgGg.js → N5XtbYVD.js} +1 -1
  125. package/.output/public/_nuxt/{Oy4_RPEZ.js → PP_4ebzl.js} +1 -1
  126. package/.output/public/_nuxt/{DkhxRAb3.js → SrncdpaW.js} +1 -1
  127. package/.output/public/_nuxt/{C8wa4Wh4.js → U1MWjQMi.js} +1 -1
  128. package/.output/public/_nuxt/{CGfVTQUF.js → Um1vPiAz.js} +1 -1
  129. package/.output/public/_nuxt/{CH8kNq6W.js → XCjS70z4.js} +1 -1
  130. package/.output/public/_nuxt/{BZqdzrSw.js → YX8avsvq.js} +2 -2
  131. package/.output/public/_nuxt/{1zIQX9mh.js → _cy8R3nk.js} +1 -1
  132. package/.output/public/_nuxt/{DJoRBCVm.js → apYB9dr5.js} +1 -1
  133. package/.output/public/_nuxt/builds/latest.json +1 -1
  134. package/.output/public/_nuxt/builds/meta/515be38f-7222-4e4b-80cb-cef7594b1834.json +1 -0
  135. package/.output/public/_nuxt/entry.NKPfH2kE.css +1 -0
  136. package/.output/public/_nuxt/{B6bnHNWY.js → fbyIeNkc.js} +1 -1
  137. package/.output/public/_nuxt/{BjI5wb_q.js → g5MjDvm5.js} +1 -1
  138. package/.output/public/_nuxt/{BfECQayu.js → gTrVszwd.js} +1 -1
  139. package/.output/public/_nuxt/{TGVM5w2K.js → ixlNW2So.js} +1 -1
  140. package/.output/public/_nuxt/{CXBOHJ2W.js → nnQqD5pb.js} +1 -1
  141. package/.output/public/_nuxt/{C4ScCUjy.js → rfGRTJJW.js} +1 -1
  142. package/.output/public/_nuxt/{DCL9gWFw.js → t8aDAkZ5.js} +1 -1
  143. package/.output/public/_nuxt/{BJ8jbKuq.js → vIOxcXKR.js} +1 -1
  144. package/.output/public/_nuxt/{DA2RowFe.js → vScW1Zgm.js} +1 -1
  145. package/.output/public/_nuxt/{B6wYZD_K.js → wO6z2ugJ.js} +1 -1
  146. package/.output/public/_nuxt/{CwKe2KkK.js → x6FRJ5ac.js} +1 -1
  147. package/.output/public/_nuxt/{D7DTZ0iS.js → zq-a1TeT.js} +1 -1
  148. package/.output/server/chunks/build/{chat-CZMiB68R.mjs → chat-CR3JIVEq.mjs} +65 -11
  149. package/.output/server/chunks/build/chat-CR3JIVEq.mjs.map +1 -0
  150. package/.output/server/chunks/build/client.precomputed.mjs +1 -1
  151. package/.output/server/chunks/build/server.mjs +2 -2
  152. package/.output/server/chunks/build/styles.mjs +2 -2
  153. package/.output/server/chunks/build/{usage-CSrBh4Or.mjs → usage-BHdQZbfI.mjs} +9 -5
  154. package/.output/server/chunks/build/{usage-CSrBh4Or.mjs.map → usage-BHdQZbfI.mjs.map} +1 -1
  155. package/.output/server/chunks/nitro/nitro.mjs +1094 -863
  156. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  157. package/.output/server/chunks/routes/_ws/chat.mjs +2 -1
  158. package/.output/server/chunks/routes/_ws/chat.mjs.map +1 -1
  159. package/.output/server/chunks/routes/api/conversations/_id_.delete.mjs +4 -1
  160. package/.output/server/chunks/routes/api/conversations/_id_.delete.mjs.map +1 -1
  161. package/.output/server/chunks/routes/api/index.get3.mjs +1 -1
  162. package/.output/server/chunks/routes/api/index.get3.mjs.map +1 -1
  163. package/.output/server/chunks/routes/api/usage/stats.get.mjs +2 -0
  164. package/.output/server/chunks/routes/api/usage/stats.get.mjs.map +1 -1
  165. package/.output/server/package.json +1 -1
  166. package/Claude/hooks/session-start.py +23 -0
  167. package/app/components/chat/ConversationList.vue +7 -1
  168. package/app/components/chat/MessageBubble.vue +45 -1
  169. package/app/components/usage/UsageRecordsTable.vue +4 -2
  170. package/app/components/usage/UsageSourceDonut.client.vue +4 -2
  171. package/app/components/usage/UsageTopConsumers.vue +4 -2
  172. package/app/composables/useChat.ts +3 -0
  173. package/package.json +1 -1
  174. package/server/api/conversations/[id].delete.ts +9 -0
  175. package/server/api/conversations/index.get.ts +2 -1
  176. package/server/api/usage/stats.get.ts +2 -0
  177. package/server/bridge/responder.ts +276 -0
  178. package/server/bridge/router.ts +6 -4
  179. package/server/db/schema.ts +3 -1
  180. package/server/drizzle/migrations/0015_great_mephistopheles.sql +2 -0
  181. package/server/drizzle/migrations/meta/0015_snapshot.json +1972 -0
  182. package/server/drizzle/migrations/meta/_journal.json +7 -0
  183. package/server/routes/_ws/chat.ts +2 -1
  184. package/server/utils/log-token-usage.ts +1 -1
  185. package/shared/types/index.ts +6 -0
  186. package/.output/public/_nuxt/1lOC4Si0.js +0 -1
  187. package/.output/public/_nuxt/AUdD1uDD.js +0 -1
  188. package/.output/public/_nuxt/DMy2sxuC.js +0 -1
  189. package/.output/public/_nuxt/DlSJ4TF_.js +0 -1
  190. package/.output/public/_nuxt/PzQHm02e.js +0 -1
  191. package/.output/public/_nuxt/builds/meta/613faa5d-8ace-45e0-8274-b611cc4fd1ad.json +0 -1
  192. package/.output/public/_nuxt/entry.DkvuF_CR.css +0 -1
  193. package/.output/public/_nuxt/svWfwR0T.js +0 -2
  194. package/.output/server/chunks/build/chat-CZMiB68R.mjs.map +0 -1
@@ -0,0 +1,276 @@
1
+ import { randomUUID } from 'crypto'
2
+ import { query } from '@anthropic-ai/claude-agent-sdk'
3
+ import { desc, eq } from 'drizzle-orm'
4
+ import { getDb, schema } from '~~/server/db'
5
+ import { logTokenUsage } from '~~/server/utils/log-token-usage'
6
+ import { sendOutboundMessage } from './router'
7
+ import type { NormalizedMessage } from './types'
8
+
9
+ /**
10
+ * Get or create the Main Chat conversation.
11
+ * There's exactly one conversation with isMain=true.
12
+ */
13
+ async function getOrCreateMainChat() {
14
+ const db = getDb()
15
+
16
+ const [existing] = await db.select()
17
+ .from(schema.conversations)
18
+ .where(eq(schema.conversations.isMain, true))
19
+ .limit(1)
20
+
21
+ if (existing) return existing
22
+
23
+ const [created] = await db.insert(schema.conversations)
24
+ .values({
25
+ sessionId: `main-chat-${randomUUID()}`,
26
+ title: 'Main Chat',
27
+ isMain: true,
28
+ status: 'idle',
29
+ messageCount: 0,
30
+ totalCostUsd: 0
31
+ })
32
+ .returning()
33
+
34
+ console.log('[bridge] Created Main Chat conversation')
35
+ return created!
36
+ }
37
+
38
+ /**
39
+ * Load memory context directly from DB so the bridge prompt
40
+ * includes user preferences/facts even if the session-start hook
41
+ * can't reach the API (avoids false onboarding).
42
+ */
43
+ async function loadMemoryContext(): Promise<string> {
44
+ try {
45
+ const db = getDb()
46
+ const memories = await db.select()
47
+ .from(schema.memoryChunks)
48
+ .orderBy(desc(schema.memoryChunks.relevanceScore), desc(schema.memoryChunks.createdAt))
49
+ .limit(10)
50
+
51
+ if (memories.length === 0) return ''
52
+
53
+ const lines = ['[Memory context from previous sessions]']
54
+ for (const m of memories)
55
+ lines.push(`- ${m.content}`)
56
+ return lines.join('\n')
57
+ } catch {
58
+ return ''
59
+ }
60
+ }
61
+
62
+ // SDK result shape (same as agent-executor.ts)
63
+ interface SDKResult {
64
+ subtype: string
65
+ total_cost_usd: number
66
+ num_turns: number
67
+ duration_ms: number
68
+ result?: string
69
+ errors?: string[]
70
+ session_id?: string
71
+ usage: { input_tokens: number, output_tokens: number }
72
+ }
73
+
74
+ /**
75
+ * Generate a response to a bridge message using the Claude Agent SDK.
76
+ * Routes through the unified Main Chat conversation.
77
+ */
78
+ export async function generateBridgeResponse(
79
+ bridgeId: string,
80
+ message: NormalizedMessage,
81
+ bridgeMessageId: string
82
+ ): Promise<void> {
83
+ const db = getDb()
84
+ const mainChat = await getOrCreateMainChat()
85
+
86
+ // Handle bridge commands
87
+ if (message.text.trim() === '/new') {
88
+ await db.update(schema.conversations)
89
+ .set({ sdkSessionId: null, summary: null })
90
+ .where(eq(schema.conversations.id, mainChat.id))
91
+
92
+ await sendOutboundMessage({
93
+ bridgeId,
94
+ platform: message.platform,
95
+ recipient: message.channelId || message.sender,
96
+ text: 'Conversation reset. Send me a message to start fresh.'
97
+ })
98
+ return
99
+ }
100
+
101
+ // Link inbound bridge message to main chat
102
+ await db.update(schema.bridgeMessages)
103
+ .set({ conversationId: mainChat.id })
104
+ .where(eq(schema.bridgeMessages.id, bridgeMessageId))
105
+
106
+ // Persist user message in the conversation
107
+ await db.insert(schema.conversationMessages).values({
108
+ conversationId: mainChat.id,
109
+ role: 'user',
110
+ content: JSON.stringify([{ type: 'text', text: message.text }]),
111
+ source: message.platform
112
+ })
113
+
114
+ // Build the prompt with memory context + platform context
115
+ const memoryContext = await loadMemoryContext()
116
+ const senderLabel = message.senderName || message.sender
117
+ const parts: string[] = []
118
+ if (memoryContext) parts.push(memoryContext)
119
+ parts.push(`[${message.platform} message from ${senderLabel}]: ${message.text}`)
120
+ const prompt = parts.join('\n\n')
121
+
122
+ // Update status
123
+ await db.update(schema.conversations)
124
+ .set({ status: 'streaming' })
125
+ .where(eq(schema.conversations.id, mainChat.id))
126
+
127
+ let responseText = ''
128
+ let sdkSessionId: string | undefined
129
+ let costUsd = 0
130
+ let durationMs = 0
131
+ let inputTokens = 0
132
+ let outputTokens = 0
133
+ let numTurns = 0
134
+
135
+ try {
136
+ const result = await runQuery(prompt, mainChat.sdkSessionId || undefined)
137
+ responseText = result.text
138
+ sdkSessionId = result.sdkSessionId
139
+ costUsd = result.costUsd
140
+ durationMs = result.durationMs
141
+ inputTokens = result.inputTokens
142
+ outputTokens = result.outputTokens
143
+ numTurns = result.numTurns
144
+ } catch (error) {
145
+ // If resume fails, retry without resume
146
+ if (mainChat.sdkSessionId) {
147
+ console.warn('[bridge] SDK resume failed, retrying fresh:', error instanceof Error ? error.message : error)
148
+ try {
149
+ const result = await runQuery(prompt, undefined)
150
+ responseText = result.text
151
+ sdkSessionId = result.sdkSessionId
152
+ costUsd = result.costUsd
153
+ durationMs = result.durationMs
154
+ inputTokens = result.inputTokens
155
+ outputTokens = result.outputTokens
156
+ numTurns = result.numTurns
157
+ } catch (retryError) {
158
+ console.error('[bridge] SDK query failed:', retryError)
159
+ responseText = 'Sorry, I ran into an issue processing your message. Please try again.'
160
+ }
161
+ } else {
162
+ console.error('[bridge] SDK query failed:', error)
163
+ responseText = 'Sorry, I ran into an issue processing your message. Please try again.'
164
+ }
165
+ }
166
+
167
+ if (!responseText)
168
+ responseText = '(No response generated)'
169
+
170
+ // Update conversation metadata
171
+ const msgs = await db.select()
172
+ .from(schema.conversationMessages)
173
+ .where(eq(schema.conversationMessages.conversationId, mainChat.id))
174
+
175
+ await db.update(schema.conversations)
176
+ .set({
177
+ status: 'idle',
178
+ sdkSessionId: sdkSessionId || mainChat.sdkSessionId,
179
+ messageCount: msgs.length + 1, // +1 for the assistant message we're about to add
180
+ totalCostUsd: (mainChat.totalCostUsd || 0) + costUsd,
181
+ endedAt: new Date()
182
+ })
183
+ .where(eq(schema.conversations.id, mainChat.id))
184
+
185
+ // Persist assistant message
186
+ await db.insert(schema.conversationMessages).values({
187
+ conversationId: mainChat.id,
188
+ role: 'assistant',
189
+ content: JSON.stringify([{ type: 'text', text: responseText }]),
190
+ costUsd,
191
+ durationMs
192
+ })
193
+
194
+ // Log token usage
195
+ if (costUsd > 0 || inputTokens > 0) {
196
+ logTokenUsage({
197
+ source: 'bridge',
198
+ sourceId: mainChat.id,
199
+ sourceName: `Bridge: ${message.platform}`,
200
+ inputTokens,
201
+ outputTokens,
202
+ costUsd,
203
+ durationMs,
204
+ numTurns
205
+ })
206
+ }
207
+
208
+ // Send response back through the bridge adapter
209
+ await sendOutboundMessage({
210
+ bridgeId,
211
+ platform: message.platform,
212
+ recipient: message.channelId || message.sender,
213
+ text: responseText
214
+ })
215
+ }
216
+
217
+ /**
218
+ * Run a Claude Agent SDK query and extract the response.
219
+ */
220
+ interface QueryResult {
221
+ text: string
222
+ sdkSessionId?: string
223
+ costUsd: number
224
+ durationMs: number
225
+ inputTokens: number
226
+ outputTokens: number
227
+ numTurns: number
228
+ }
229
+
230
+ async function runQuery(
231
+ prompt: string,
232
+ resumeSessionId?: string
233
+ ): Promise<QueryResult> {
234
+ const projectDir = process.env.COGNOVA_PROJECT_DIR || process.cwd()
235
+
236
+ const conversation = query({
237
+ prompt,
238
+ options: {
239
+ cwd: projectDir,
240
+ settingSources: ['user', 'project'],
241
+ permissionMode: 'bypassPermissions',
242
+ allowDangerouslySkipPermissions: true,
243
+ maxTurns: 50,
244
+ ...(resumeSessionId ? { resume: resumeSessionId } : {})
245
+ }
246
+ })
247
+
248
+ let text = ''
249
+ let sdkSessionId: string | undefined
250
+ let costUsd = 0
251
+ let durationMs = 0
252
+ let inputTokens = 0
253
+ let outputTokens = 0
254
+ let numTurns = 0
255
+
256
+ for await (const message of conversation) {
257
+ if (message.type === 'system' && (message as { subtype?: string }).subtype === 'init') {
258
+ sdkSessionId = (message as { session_id?: string }).session_id
259
+ } else if (message.type === 'result') {
260
+ const msg = message as unknown as SDKResult
261
+ if (msg.subtype === 'success' && msg.result)
262
+ text = msg.result
263
+ else if (msg.errors?.length)
264
+ text = msg.errors.join('\n')
265
+
266
+ sdkSessionId = msg.session_id || sdkSessionId
267
+ costUsd = msg.total_cost_usd || 0
268
+ durationMs = msg.duration_ms || 0
269
+ inputTokens = msg.usage?.input_tokens || 0
270
+ outputTokens = msg.usage?.output_tokens || 0
271
+ numTurns = msg.num_turns || 0
272
+ }
273
+ }
274
+
275
+ return { text, sdkSessionId, costUsd, durationMs, inputTokens, outputTokens, numTurns }
276
+ }
@@ -1,6 +1,7 @@
1
1
  import { eq } from 'drizzle-orm'
2
2
  import { getDb, schema } from '~~/server/db'
3
3
  import { notifyResourceChange } from '~~/server/utils/notify-resource'
4
+ import { generateBridgeResponse } from './responder'
4
5
  import { getAdapter } from './registry'
5
6
  import type { NormalizedMessage, OutboundMessage, DeliveryResult } from './types'
6
7
 
@@ -38,10 +39,11 @@ export async function handleInboundMessage(
38
39
  meta: { platform: message.platform, sender: message.sender, direction: 'inbound' }
39
40
  })
40
41
 
41
- // TODO Phase 2+: Route to Claude agent for processing
42
- // This will create/resume a conversation and stream the agent response,
43
- // then send the response back through the originating adapter.
44
- console.log(`[bridge] Inbound message from ${message.sender} via ${message.platform}: ${message.text.substring(0, 100)}`)
42
+ // Route to Claude agent for response (fire-and-forget)
43
+ console.log(`[bridge] Inbound from ${message.sender} via ${message.platform}: ${message.text.substring(0, 100)}`)
44
+ void generateBridgeResponse(bridgeId, message, stored!.id).catch((error) => {
45
+ console.error('[bridge] Failed to generate response:', error)
46
+ })
45
47
  }
46
48
 
47
49
  /**
@@ -131,6 +131,7 @@ export const conversations = pgTable('conversations', {
131
131
  enum: ['idle', 'streaming', 'interrupted', 'error']
132
132
  }).default('idle').notNull(),
133
133
  totalCostUsd: real('total_cost_usd').default(0).notNull(),
134
+ isMain: boolean('is_main').default(false).notNull(),
134
135
  startedAt: timestamp('started_at', { withTimezone: true }).defaultNow().notNull(),
135
136
  endedAt: timestamp('ended_at', { withTimezone: true }),
136
137
  messageCount: integer('message_count').default(0).notNull()
@@ -143,6 +144,7 @@ export const conversationMessages = pgTable('conversation_messages', {
143
144
  .references(() => conversations.id, { onDelete: 'cascade' }),
144
145
  role: text('role', { enum: ['user', 'assistant'] }).notNull(),
145
146
  content: text('content').notNull(), // JSON string of ChatContentBlock[]
147
+ source: text('source', { enum: ['web', 'telegram', 'discord', 'imessage', 'email', 'google'] }),
146
148
  costUsd: real('cost_usd'),
147
149
  durationMs: integer('duration_ms'),
148
150
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
@@ -383,7 +385,7 @@ export const skillsCatalog = pgTable('skills_catalog', {
383
385
 
384
386
  export const tokenUsage = pgTable('token_usage', {
385
387
  id: uuid('id').primaryKey().defaultRandom(),
386
- source: text('source', { enum: ['chat', 'agent', 'memory_extraction'] }).notNull(),
388
+ source: text('source', { enum: ['chat', 'agent', 'memory_extraction', 'bridge'] }).notNull(),
387
389
  sourceId: text('source_id'),
388
390
  sourceName: text('source_name'),
389
391
  inputTokens: integer('input_tokens').default(0).notNull(),
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "conversation_messages" ADD COLUMN "source" text;--> statement-breakpoint
2
+ ALTER TABLE "conversations" ADD COLUMN "is_main" boolean DEFAULT false NOT NULL;