cognova 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) 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/def74b99-d70c-4f30-aa29-70248cbeac7d.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/{usage-CSrBh4Or.mjs → usage-BHdQZbfI.mjs} +9 -5
  153. package/.output/server/chunks/build/{usage-CSrBh4Or.mjs.map → usage-BHdQZbfI.mjs.map} +1 -1
  154. package/.output/server/chunks/nitro/nitro.mjs +1076 -862
  155. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  156. package/.output/server/chunks/routes/_ws/chat.mjs +2 -1
  157. package/.output/server/chunks/routes/_ws/chat.mjs.map +1 -1
  158. package/.output/server/chunks/routes/api/conversations/_id_.delete.mjs +4 -1
  159. package/.output/server/chunks/routes/api/conversations/_id_.delete.mjs.map +1 -1
  160. package/.output/server/chunks/routes/api/index.get3.mjs +1 -1
  161. package/.output/server/chunks/routes/api/index.get3.mjs.map +1 -1
  162. package/.output/server/chunks/routes/api/usage/stats.get.mjs +2 -0
  163. package/.output/server/chunks/routes/api/usage/stats.get.mjs.map +1 -1
  164. package/.output/server/package.json +1 -1
  165. package/app/components/chat/ConversationList.vue +7 -1
  166. package/app/components/chat/MessageBubble.vue +45 -1
  167. package/app/components/usage/UsageRecordsTable.vue +4 -2
  168. package/app/components/usage/UsageSourceDonut.client.vue +4 -2
  169. package/app/components/usage/UsageTopConsumers.vue +4 -2
  170. package/app/composables/useChat.ts +3 -0
  171. package/package.json +1 -1
  172. package/server/api/conversations/[id].delete.ts +9 -0
  173. package/server/api/conversations/index.get.ts +2 -1
  174. package/server/api/usage/stats.get.ts +2 -0
  175. package/server/bridge/responder.ts +248 -0
  176. package/server/bridge/router.ts +6 -4
  177. package/server/db/schema.ts +3 -1
  178. package/server/drizzle/migrations/0015_great_mephistopheles.sql +2 -0
  179. package/server/drizzle/migrations/meta/0015_snapshot.json +1972 -0
  180. package/server/drizzle/migrations/meta/_journal.json +7 -0
  181. package/server/routes/_ws/chat.ts +2 -1
  182. package/server/utils/log-token-usage.ts +1 -1
  183. package/shared/types/index.ts +6 -0
  184. package/.output/public/_nuxt/1lOC4Si0.js +0 -1
  185. package/.output/public/_nuxt/AUdD1uDD.js +0 -1
  186. package/.output/public/_nuxt/DMy2sxuC.js +0 -1
  187. package/.output/public/_nuxt/DlSJ4TF_.js +0 -1
  188. package/.output/public/_nuxt/PzQHm02e.js +0 -1
  189. package/.output/public/_nuxt/builds/meta/613faa5d-8ace-45e0-8274-b611cc4fd1ad.json +0 -1
  190. package/.output/public/_nuxt/entry.DkvuF_CR.css +0 -1
  191. package/.output/public/_nuxt/svWfwR0T.js +0 -2
  192. package/.output/server/chunks/build/chat-CZMiB68R.mjs.map +0 -1
@@ -0,0 +1,248 @@
1
+ import { randomUUID } from 'crypto'
2
+ import { query } from '@anthropic-ai/claude-agent-sdk'
3
+ import { 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
+ // SDK result shape (same as agent-executor.ts)
39
+ interface SDKResult {
40
+ subtype: string
41
+ total_cost_usd: number
42
+ num_turns: number
43
+ duration_ms: number
44
+ result?: string
45
+ errors?: string[]
46
+ session_id?: string
47
+ usage: { input_tokens: number, output_tokens: number }
48
+ }
49
+
50
+ /**
51
+ * Generate a response to a bridge message using the Claude Agent SDK.
52
+ * Routes through the unified Main Chat conversation.
53
+ */
54
+ export async function generateBridgeResponse(
55
+ bridgeId: string,
56
+ message: NormalizedMessage,
57
+ bridgeMessageId: string
58
+ ): Promise<void> {
59
+ const db = getDb()
60
+ const mainChat = await getOrCreateMainChat()
61
+
62
+ // Handle bridge commands
63
+ if (message.text.trim() === '/new') {
64
+ await db.update(schema.conversations)
65
+ .set({ sdkSessionId: null, summary: null })
66
+ .where(eq(schema.conversations.id, mainChat.id))
67
+
68
+ await sendOutboundMessage({
69
+ bridgeId,
70
+ platform: message.platform,
71
+ recipient: message.channelId || message.sender,
72
+ text: 'Conversation reset. Send me a message to start fresh.'
73
+ })
74
+ return
75
+ }
76
+
77
+ // Link inbound bridge message to main chat
78
+ await db.update(schema.bridgeMessages)
79
+ .set({ conversationId: mainChat.id })
80
+ .where(eq(schema.bridgeMessages.id, bridgeMessageId))
81
+
82
+ // Persist user message in the conversation
83
+ await db.insert(schema.conversationMessages).values({
84
+ conversationId: mainChat.id,
85
+ role: 'user',
86
+ content: JSON.stringify([{ type: 'text', text: message.text }]),
87
+ source: message.platform
88
+ })
89
+
90
+ // Build the prompt with platform context
91
+ const senderLabel = message.senderName || message.sender
92
+ const prompt = `[${message.platform} message from ${senderLabel}]: ${message.text}`
93
+
94
+ // Update status
95
+ await db.update(schema.conversations)
96
+ .set({ status: 'streaming' })
97
+ .where(eq(schema.conversations.id, mainChat.id))
98
+
99
+ let responseText = ''
100
+ let sdkSessionId: string | undefined
101
+ let costUsd = 0
102
+ let durationMs = 0
103
+ let inputTokens = 0
104
+ let outputTokens = 0
105
+ let numTurns = 0
106
+
107
+ try {
108
+ const result = await runQuery(prompt, mainChat.sdkSessionId || undefined)
109
+ responseText = result.text
110
+ sdkSessionId = result.sdkSessionId
111
+ costUsd = result.costUsd
112
+ durationMs = result.durationMs
113
+ inputTokens = result.inputTokens
114
+ outputTokens = result.outputTokens
115
+ numTurns = result.numTurns
116
+ } catch (error) {
117
+ // If resume fails, retry without resume
118
+ if (mainChat.sdkSessionId) {
119
+ console.warn('[bridge] SDK resume failed, retrying fresh:', error instanceof Error ? error.message : error)
120
+ try {
121
+ const result = await runQuery(prompt, undefined)
122
+ responseText = result.text
123
+ sdkSessionId = result.sdkSessionId
124
+ costUsd = result.costUsd
125
+ durationMs = result.durationMs
126
+ inputTokens = result.inputTokens
127
+ outputTokens = result.outputTokens
128
+ numTurns = result.numTurns
129
+ } catch (retryError) {
130
+ console.error('[bridge] SDK query failed:', retryError)
131
+ responseText = 'Sorry, I ran into an issue processing your message. Please try again.'
132
+ }
133
+ } else {
134
+ console.error('[bridge] SDK query failed:', error)
135
+ responseText = 'Sorry, I ran into an issue processing your message. Please try again.'
136
+ }
137
+ }
138
+
139
+ if (!responseText)
140
+ responseText = '(No response generated)'
141
+
142
+ // Update conversation metadata
143
+ const msgs = await db.select()
144
+ .from(schema.conversationMessages)
145
+ .where(eq(schema.conversationMessages.conversationId, mainChat.id))
146
+
147
+ await db.update(schema.conversations)
148
+ .set({
149
+ status: 'idle',
150
+ sdkSessionId: sdkSessionId || mainChat.sdkSessionId,
151
+ messageCount: msgs.length + 1, // +1 for the assistant message we're about to add
152
+ totalCostUsd: (mainChat.totalCostUsd || 0) + costUsd,
153
+ endedAt: new Date()
154
+ })
155
+ .where(eq(schema.conversations.id, mainChat.id))
156
+
157
+ // Persist assistant message
158
+ await db.insert(schema.conversationMessages).values({
159
+ conversationId: mainChat.id,
160
+ role: 'assistant',
161
+ content: JSON.stringify([{ type: 'text', text: responseText }]),
162
+ costUsd,
163
+ durationMs
164
+ })
165
+
166
+ // Log token usage
167
+ if (costUsd > 0 || inputTokens > 0) {
168
+ logTokenUsage({
169
+ source: 'bridge',
170
+ sourceId: mainChat.id,
171
+ sourceName: `Bridge: ${message.platform}`,
172
+ inputTokens,
173
+ outputTokens,
174
+ costUsd,
175
+ durationMs,
176
+ numTurns
177
+ })
178
+ }
179
+
180
+ // Send response back through the bridge adapter
181
+ await sendOutboundMessage({
182
+ bridgeId,
183
+ platform: message.platform,
184
+ recipient: message.channelId || message.sender,
185
+ text: responseText
186
+ })
187
+ }
188
+
189
+ /**
190
+ * Run a Claude Agent SDK query and extract the response.
191
+ */
192
+ interface QueryResult {
193
+ text: string
194
+ sdkSessionId?: string
195
+ costUsd: number
196
+ durationMs: number
197
+ inputTokens: number
198
+ outputTokens: number
199
+ numTurns: number
200
+ }
201
+
202
+ async function runQuery(
203
+ prompt: string,
204
+ resumeSessionId?: string
205
+ ): Promise<QueryResult> {
206
+ const projectDir = process.env.COGNOVA_PROJECT_DIR || process.cwd()
207
+
208
+ const conversation = query({
209
+ prompt,
210
+ options: {
211
+ cwd: projectDir,
212
+ settingSources: ['user', 'project'],
213
+ permissionMode: 'bypassPermissions',
214
+ allowDangerouslySkipPermissions: true,
215
+ maxTurns: 50,
216
+ ...(resumeSessionId ? { resume: resumeSessionId } : {})
217
+ }
218
+ })
219
+
220
+ let text = ''
221
+ let sdkSessionId: string | undefined
222
+ let costUsd = 0
223
+ let durationMs = 0
224
+ let inputTokens = 0
225
+ let outputTokens = 0
226
+ let numTurns = 0
227
+
228
+ for await (const message of conversation) {
229
+ if (message.type === 'system' && (message as { subtype?: string }).subtype === 'init') {
230
+ sdkSessionId = (message as { session_id?: string }).session_id
231
+ } else if (message.type === 'result') {
232
+ const msg = message as unknown as SDKResult
233
+ if (msg.subtype === 'success' && msg.result)
234
+ text = msg.result
235
+ else if (msg.errors?.length)
236
+ text = msg.errors.join('\n')
237
+
238
+ sdkSessionId = msg.session_id || sdkSessionId
239
+ costUsd = msg.total_cost_usd || 0
240
+ durationMs = msg.duration_ms || 0
241
+ inputTokens = msg.usage?.input_tokens || 0
242
+ outputTokens = msg.usage?.output_tokens || 0
243
+ numTurns = msg.num_turns || 0
244
+ }
245
+ }
246
+
247
+ return { text, sdkSessionId, costUsd, durationMs, inputTokens, outputTokens, numTurns }
248
+ }
@@ -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;