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.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{pHQcHl8n.js → 0NJ3PaRM.js} +1 -1
- package/.output/public/_nuxt/{CGgZuPnb.js → 0yk-pS3R.js} +2 -2
- package/.output/public/_nuxt/{C8gtQaKB.js → 10_wwHSz.js} +1 -1
- package/.output/public/_nuxt/{BaYtugUf.js → 1zUTf4AP.js} +1 -1
- package/.output/public/_nuxt/{B63LIwsW.js → 2dNDtTiv.js} +3 -3
- package/.output/public/_nuxt/{Gy6_ehml.js → 2nOqGUPr.js} +1 -1
- package/.output/public/_nuxt/{C6UTTaCP.js → 3jQMk_5H.js} +1 -1
- package/.output/public/_nuxt/{BX6vcvkp.js → 4K03TkGA.js} +1 -1
- package/.output/public/_nuxt/{DjmFlCHK.js → 6boMs_nF.js} +1 -1
- package/.output/public/_nuxt/{CybU4a95.js → 7oCGSglN.js} +1 -1
- package/.output/public/_nuxt/{CfO_d5gC.js → 8q5NepGW.js} +1 -1
- package/.output/public/_nuxt/{DdXVAzIg.js → B93pdGAW.js} +1 -1
- package/.output/public/_nuxt/{D1t0272g.js → BA_pRWwX.js} +1 -1
- package/.output/public/_nuxt/{Be5L93GM.js → BDyn4ApB.js} +3 -3
- package/.output/public/_nuxt/{DHScQdvh.js → BGgwYWjH.js} +1 -1
- package/.output/public/_nuxt/{FYOLNuMV.js → BH0QDWEm.js} +1 -1
- package/.output/public/_nuxt/{BVc_fOF1.js → BHtY0l0l.js} +1 -1
- package/.output/public/_nuxt/{C6HEirAm.js → BIXrSYwR.js} +1 -1
- package/.output/public/_nuxt/BK8S2ony.js +1 -0
- package/.output/public/_nuxt/{DTu0K_pX.js → BKXg-alD.js} +1 -1
- package/.output/public/_nuxt/{Pa936BQP.js → BLnYhy_t.js} +1 -1
- package/.output/public/_nuxt/{wgX1ldgZ.js → BR8bKz8h.js} +1 -1
- package/.output/public/_nuxt/{B-513wJW.js → BS2ZNXI1.js} +1 -1
- package/.output/public/_nuxt/{a9_LvIZ4.js → BUghTae1.js} +1 -1
- package/.output/public/_nuxt/{B8Ou5yh2.js → BXuWCWsJ.js} +1 -1
- package/.output/public/_nuxt/{D_tCgCFX.js → BYHCP8x7.js} +1 -1
- package/.output/public/_nuxt/{CY3x8iy3.js → BZrSPX0S.js} +1 -1
- package/.output/public/_nuxt/{BNBGEa-U.js → B_wilgcd.js} +1 -1
- package/.output/public/_nuxt/BaZwjF8o.js +1 -0
- package/.output/public/_nuxt/{BwdxQyyo.js → Bciqk4dX.js} +1 -1
- package/.output/public/_nuxt/{BhnONkIm.js → Beom-INj.js} +1 -1
- package/.output/public/_nuxt/{CzurfTnt.js → BfcLZ_fC.js} +1 -1
- package/.output/public/_nuxt/{DwAJIRGg.js → Bk6JUtIo.js} +1 -1
- package/.output/public/_nuxt/BkfI94R8.js +1 -0
- package/.output/public/_nuxt/{Cs_udj6A.js → BmOtR1wX.js} +1 -1
- package/.output/public/_nuxt/{D7iYVmH6.js → BmVLxnDU.js} +1 -1
- package/.output/public/_nuxt/{6lIxuL6n.js → BtHQ1T0f.js} +1 -1
- package/.output/public/_nuxt/{RRbnMIVe.js → ByYh0uRg.js} +1 -1
- package/.output/public/_nuxt/{DBuW01Fx.js → C27WoGNZ.js} +1 -1
- package/.output/public/_nuxt/{DJ4pqxg-.js → C2FxZbO_.js} +1 -1
- package/.output/public/_nuxt/{myNwwtfj.js → C2vq6Te8.js} +1 -1
- package/.output/public/_nuxt/{C3JOLqW1.js → C39dQ88F.js} +1 -1
- package/.output/public/_nuxt/{4nN41Tyd.js → C3NST0BU.js} +1 -1
- package/.output/public/_nuxt/{BltasKce.js → C4mwL7SE.js} +1 -1
- package/.output/public/_nuxt/{B1vDCixB.js → C5R5QaCA.js} +1 -1
- package/.output/public/_nuxt/{CwOUYOe5.js → C6qKcgOY.js} +1 -1
- package/.output/public/_nuxt/{DGJKfutb.js → C71_a1IX.js} +1 -1
- package/.output/public/_nuxt/{CrjNndEO.js → C9WIgRRL.js} +1 -1
- package/.output/public/_nuxt/{B5JDxHxC.js → CBTkrk2M.js} +1 -1
- package/.output/public/_nuxt/{UbZP7wjm.js → CEnSeCqn.js} +1 -1
- package/.output/public/_nuxt/{Cn83lLUb.js → CKxSHyww.js} +1 -1
- package/.output/public/_nuxt/{CbTu13wM.js → CLfF6dSn.js} +1 -1
- package/.output/public/_nuxt/{Bsdcw5gr.js → CPutXj8l.js} +1 -1
- package/.output/public/_nuxt/{CrW6y2-s.js → CQqCBrXj.js} +1 -1
- package/.output/public/_nuxt/{OrcbFmB1.js → CSKJ-Ahh.js} +1 -1
- package/.output/public/_nuxt/{XJYoLoTi.js → CTCcEJU3.js} +1 -1
- package/.output/public/_nuxt/{Bk9-HxOg.js → CVFwOzbl.js} +1 -1
- package/.output/public/_nuxt/{CqMgz85Z.js → CVdCqaby.js} +1 -1
- package/.output/public/_nuxt/{Cmj-BR0q.js → CVqlefTY.js} +1 -1
- package/.output/public/_nuxt/CWyMCJQH.js +1 -0
- package/.output/public/_nuxt/{xrh9hzzH.js → CY-QVcA5.js} +2 -2
- package/.output/public/_nuxt/{BQ1T6zWA.js → CY-cjAwJ.js} +1 -1
- package/.output/public/_nuxt/{CziltI1u.js → CYPO5o_C.js} +1 -1
- package/.output/public/_nuxt/{C1FhgWcI.js → CZRnNmU8.js} +1 -1
- package/.output/public/_nuxt/{BES6FzF5.js → CZVzFlpI.js} +1 -1
- package/.output/public/_nuxt/{CsbHYpNi.js → CZoEPC_Q.js} +3 -3
- package/.output/public/_nuxt/{DvATBMPl.js → Cc-2ziaZ.js} +1 -1
- package/.output/public/_nuxt/{D3RROe4s.js → ChcO9s3o.js} +1 -1
- package/.output/public/_nuxt/{CIUZAp6g.js → Cl-LOSDV.js} +1 -1
- package/.output/public/_nuxt/{BwJVN9k0.js → CobqYwkp.js} +1 -1
- package/.output/public/_nuxt/{Cmdr5BNz.js → CqNwHCpo.js} +1 -1
- package/.output/public/_nuxt/{HPOXd4gt.js → CuxrHsu-.js} +1 -1
- package/.output/public/_nuxt/{BYIq3eP8.js → CwPdIZ9J.js} +1 -1
- package/.output/public/_nuxt/{D-O8wAju.js → D0ifH682.js} +1 -1
- package/.output/public/_nuxt/{BwVt3BzR.js → D3AZaldL.js} +1 -1
- package/.output/public/_nuxt/{BE_OHTBv.js → D4UJwDQJ.js} +1 -1
- package/.output/public/_nuxt/{hm3ShyaF.js → D5jZq8b3.js} +1 -1
- package/.output/public/_nuxt/{pNqWPbXW.js → D5q8SnqE.js} +1 -1
- package/.output/public/_nuxt/{BgG2k-zy.js → D8M722pn.js} +1 -1
- package/.output/public/_nuxt/{B2MVZlI-.js → D8lwrAYS.js} +1 -1
- package/.output/public/_nuxt/{DYu_NYj3.js → D9nmzBAw.js} +1 -1
- package/.output/public/_nuxt/{DBKxzQ3U.js → DC4idAGt.js} +1 -1
- package/.output/public/_nuxt/{qWWnsfRV.js → DEd2xVbS.js} +1 -1
- package/.output/public/_nuxt/{nTCVPCKg.js → DH4YkDAi.js} +1 -1
- package/.output/public/_nuxt/{DmmQUhEb.js → DJTCT0bl.js} +1 -1
- package/.output/public/_nuxt/{rRYEvKFu.js → DNP5E1bC.js} +1 -1
- package/.output/public/_nuxt/{DI1FIvaM.js → DO9SFIh1.js} +1 -1
- package/.output/public/_nuxt/DPklr_tJ.js +2 -0
- package/.output/public/_nuxt/{-JdScH3W.js → DQM4eBdt.js} +1 -1
- package/.output/public/_nuxt/{5MZBbx-H.js → DQh6I9z9.js} +1 -1
- package/.output/public/_nuxt/{DLuRYg2p.js → DS2wStH1.js} +1 -1
- package/.output/public/_nuxt/{0EY9Msdx.js → DTAStixR.js} +1 -1
- package/.output/public/_nuxt/{fVkHtgGF.js → DTGenhcA.js} +1 -1
- package/.output/public/_nuxt/{4Qy4OdWt.js → DUAAXJ6Q.js} +1 -1
- package/.output/public/_nuxt/{DTrFhdRO.js → DUVzIl3o.js} +1 -1
- package/.output/public/_nuxt/{Dj2opPDu.js → DXdwpJ-I.js} +1 -1
- package/.output/public/_nuxt/{C8tXHRIo.js → DY3uK7wM.js} +1 -1
- package/.output/public/_nuxt/{D76Dw8rw.js → DYbZBZet.js} +1 -1
- package/.output/public/_nuxt/{DSgubznn.js → D_4LPm1U.js} +1 -1
- package/.output/public/_nuxt/{D8gh62A0.js → Db4KMnt8.js} +1 -1
- package/.output/public/_nuxt/{JQV21WEV.js → DcJbYLTp.js} +1 -1
- package/.output/public/_nuxt/{D5G5UL_g.js → DfF81NlA.js} +1 -1
- package/.output/public/_nuxt/{HlbU_z9a.js → DhI5cA_n.js} +1 -1
- package/.output/public/_nuxt/{CaLO8sre.js → DhuOKJda.js} +1 -1
- package/.output/public/_nuxt/{CDradKPE.js → Di-Nc75e.js} +1 -1
- package/.output/public/_nuxt/{C2DZeD9G.js → Djs0Tlpa.js} +1 -1
- package/.output/public/_nuxt/DmmdPt_5.js +1 -0
- package/.output/public/_nuxt/{DSynhNOO.js → Dn5a-guE.js} +1 -1
- package/.output/public/_nuxt/{k16puyWi.js → DtjjnHnt.js} +1 -1
- package/.output/public/_nuxt/{CEJh-OAl.js → DuvzM-P1.js} +1 -1
- package/.output/public/_nuxt/{D7WbsJsa.js → DwY7rCd_.js} +1 -1
- package/.output/public/_nuxt/{REP2-rzf.js → Dya5oK8u.js} +1 -1
- package/.output/public/_nuxt/{CIJ2MJdc.js → DzA58_Lm.js} +1 -1
- package/.output/public/_nuxt/{Bp9kHdw6.js → DzGy77Vr.js} +1 -1
- package/.output/public/_nuxt/{DfIqFkyp.js → E3rXPwU8.js} +1 -1
- package/.output/public/_nuxt/{NusIKwmA.js → EgKnQnf-.js} +1 -1
- package/.output/public/_nuxt/{Clr3MHUr.js → GtEM7xVU.js} +1 -1
- package/.output/public/_nuxt/{DQd-GXKJ.js → H6JbrRBU.js} +1 -1
- package/.output/public/_nuxt/{BzTjmQCD.js → ILEvizzp.js} +1 -1
- package/.output/public/_nuxt/{DUDbJONU.js → JX1oqJI9.js} +1 -1
- package/.output/public/_nuxt/{BsnhHCOa.js → JbHa4oXq.js} +1 -1
- package/.output/public/_nuxt/{BE4Ndhrq.js → Kw0zy3FG.js} +1 -1
- package/.output/public/_nuxt/{BOGXUgGg.js → N5XtbYVD.js} +1 -1
- package/.output/public/_nuxt/{Oy4_RPEZ.js → PP_4ebzl.js} +1 -1
- package/.output/public/_nuxt/{DkhxRAb3.js → SrncdpaW.js} +1 -1
- package/.output/public/_nuxt/{C8wa4Wh4.js → U1MWjQMi.js} +1 -1
- package/.output/public/_nuxt/{CGfVTQUF.js → Um1vPiAz.js} +1 -1
- package/.output/public/_nuxt/{CH8kNq6W.js → XCjS70z4.js} +1 -1
- package/.output/public/_nuxt/{BZqdzrSw.js → YX8avsvq.js} +2 -2
- package/.output/public/_nuxt/{1zIQX9mh.js → _cy8R3nk.js} +1 -1
- package/.output/public/_nuxt/{DJoRBCVm.js → apYB9dr5.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/def74b99-d70c-4f30-aa29-70248cbeac7d.json +1 -0
- package/.output/public/_nuxt/entry.NKPfH2kE.css +1 -0
- package/.output/public/_nuxt/{B6bnHNWY.js → fbyIeNkc.js} +1 -1
- package/.output/public/_nuxt/{BjI5wb_q.js → g5MjDvm5.js} +1 -1
- package/.output/public/_nuxt/{BfECQayu.js → gTrVszwd.js} +1 -1
- package/.output/public/_nuxt/{TGVM5w2K.js → ixlNW2So.js} +1 -1
- package/.output/public/_nuxt/{CXBOHJ2W.js → nnQqD5pb.js} +1 -1
- package/.output/public/_nuxt/{C4ScCUjy.js → rfGRTJJW.js} +1 -1
- package/.output/public/_nuxt/{DCL9gWFw.js → t8aDAkZ5.js} +1 -1
- package/.output/public/_nuxt/{BJ8jbKuq.js → vIOxcXKR.js} +1 -1
- package/.output/public/_nuxt/{DA2RowFe.js → vScW1Zgm.js} +1 -1
- package/.output/public/_nuxt/{B6wYZD_K.js → wO6z2ugJ.js} +1 -1
- package/.output/public/_nuxt/{CwKe2KkK.js → x6FRJ5ac.js} +1 -1
- package/.output/public/_nuxt/{D7DTZ0iS.js → zq-a1TeT.js} +1 -1
- package/.output/server/chunks/build/{chat-CZMiB68R.mjs → chat-CR3JIVEq.mjs} +65 -11
- package/.output/server/chunks/build/chat-CR3JIVEq.mjs.map +1 -0
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/server.mjs +2 -2
- package/.output/server/chunks/build/{usage-CSrBh4Or.mjs → usage-BHdQZbfI.mjs} +9 -5
- package/.output/server/chunks/build/{usage-CSrBh4Or.mjs.map → usage-BHdQZbfI.mjs.map} +1 -1
- package/.output/server/chunks/nitro/nitro.mjs +1076 -862
- package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/_ws/chat.mjs +2 -1
- package/.output/server/chunks/routes/_ws/chat.mjs.map +1 -1
- package/.output/server/chunks/routes/api/conversations/_id_.delete.mjs +4 -1
- package/.output/server/chunks/routes/api/conversations/_id_.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/index.get3.mjs +1 -1
- package/.output/server/chunks/routes/api/index.get3.mjs.map +1 -1
- package/.output/server/chunks/routes/api/usage/stats.get.mjs +2 -0
- package/.output/server/chunks/routes/api/usage/stats.get.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/app/components/chat/ConversationList.vue +7 -1
- package/app/components/chat/MessageBubble.vue +45 -1
- package/app/components/usage/UsageRecordsTable.vue +4 -2
- package/app/components/usage/UsageSourceDonut.client.vue +4 -2
- package/app/components/usage/UsageTopConsumers.vue +4 -2
- package/app/composables/useChat.ts +3 -0
- package/package.json +1 -1
- package/server/api/conversations/[id].delete.ts +9 -0
- package/server/api/conversations/index.get.ts +2 -1
- package/server/api/usage/stats.get.ts +2 -0
- package/server/bridge/responder.ts +248 -0
- package/server/bridge/router.ts +6 -4
- package/server/db/schema.ts +3 -1
- package/server/drizzle/migrations/0015_great_mephistopheles.sql +2 -0
- package/server/drizzle/migrations/meta/0015_snapshot.json +1972 -0
- package/server/drizzle/migrations/meta/_journal.json +7 -0
- package/server/routes/_ws/chat.ts +2 -1
- package/server/utils/log-token-usage.ts +1 -1
- package/shared/types/index.ts +6 -0
- package/.output/public/_nuxt/1lOC4Si0.js +0 -1
- package/.output/public/_nuxt/AUdD1uDD.js +0 -1
- package/.output/public/_nuxt/DMy2sxuC.js +0 -1
- package/.output/public/_nuxt/DlSJ4TF_.js +0 -1
- package/.output/public/_nuxt/PzQHm02e.js +0 -1
- package/.output/public/_nuxt/builds/meta/613faa5d-8ace-45e0-8274-b611cc4fd1ad.json +0 -1
- package/.output/public/_nuxt/entry.DkvuF_CR.css +0 -1
- package/.output/public/_nuxt/svWfwR0T.js +0 -2
- 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
|
+
}
|
package/server/bridge/router.ts
CHANGED
|
@@ -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
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
/**
|
package/server/db/schema.ts
CHANGED
|
@@ -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(),
|