@swarmclawai/swarmclaw 0.6.4 → 0.6.6
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/README.md +5 -3
- package/package.json +5 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +41 -2
- package/src/app/api/chatrooms/[id]/route.ts +15 -1
- package/src/app/api/chatrooms/route.ts +15 -2
- package/src/app/api/schedules/[id]/run/route.ts +3 -0
- package/src/app/api/tasks/route.ts +24 -0
- package/src/app/api/wallets/[id]/approve/route.ts +62 -0
- package/src/app/api/wallets/[id]/balance-history/route.ts +18 -0
- package/src/app/api/wallets/[id]/route.ts +118 -0
- package/src/app/api/wallets/[id]/send/route.ts +118 -0
- package/src/app/api/wallets/[id]/transactions/route.ts +18 -0
- package/src/app/api/wallets/route.ts +74 -0
- package/src/app/globals.css +8 -0
- package/src/cli/index.js +15 -0
- package/src/cli/spec.js +14 -0
- package/src/components/agents/agent-avatar.tsx +15 -1
- package/src/components/agents/agent-card.tsx +1 -0
- package/src/components/agents/agent-chat-list.tsx +1 -1
- package/src/components/agents/agent-sheet.tsx +112 -26
- package/src/components/chat/chat-area.tsx +2 -2
- package/src/components/chat/chat-header.tsx +48 -19
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/delegation-banner.test.ts +27 -0
- package/src/components/chat/delegation-banner.tsx +109 -23
- package/src/components/chat/message-bubble.tsx +3 -2
- package/src/components/chat/message-list.tsx +5 -4
- package/src/components/chat/streaming-bubble.tsx +3 -2
- package/src/components/chat/thinking-indicator.tsx +3 -2
- package/src/components/chat/transfer-agent-picker.tsx +1 -1
- package/src/components/chatrooms/agent-hover-card.tsx +1 -1
- package/src/components/chatrooms/chatroom-input.tsx +1 -1
- package/src/components/chatrooms/chatroom-message.tsx +1 -1
- package/src/components/chatrooms/chatroom-sheet.tsx +1 -1
- package/src/components/chatrooms/chatroom-typing-bar.tsx +1 -1
- package/src/components/chatrooms/chatroom-view.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +1 -1
- package/src/components/home/home-view.tsx +2 -1
- package/src/components/knowledge/knowledge-list.tsx +1 -1
- package/src/components/knowledge/knowledge-sheet.tsx +1 -1
- package/src/components/layout/app-layout.tsx +18 -3
- package/src/components/memory/memory-agent-list.tsx +1 -1
- package/src/components/memory/memory-browser.tsx +1 -0
- package/src/components/memory/memory-card.tsx +3 -2
- package/src/components/memory/memory-detail.tsx +3 -3
- package/src/components/memory/memory-sheet.tsx +2 -2
- package/src/components/projects/project-detail.tsx +4 -4
- package/src/components/secrets/secret-sheet.tsx +1 -1
- package/src/components/secrets/secrets-list.tsx +1 -1
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +1 -1
- package/src/components/shared/agent-switch-dialog.tsx +1 -1
- package/src/components/shared/settings/section-user-preferences.tsx +4 -4
- package/src/components/skills/skill-list.tsx +1 -1
- package/src/components/skills/skill-sheet.tsx +1 -1
- package/src/components/tasks/task-board.tsx +3 -3
- package/src/components/tasks/task-sheet.tsx +21 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +99 -0
- package/src/components/wallets/wallet-panel.tsx +616 -0
- package/src/components/wallets/wallet-section.tsx +100 -0
- package/src/lib/server/agent-registry.ts +2 -2
- package/src/lib/server/chat-execution.ts +35 -3
- package/src/lib/server/chatroom-health.ts +60 -0
- package/src/lib/server/chatroom-helpers.test.ts +94 -0
- package/src/lib/server/chatroom-helpers.ts +64 -11
- package/src/lib/server/connectors/inbound-audio-transcription.test.ts +191 -0
- package/src/lib/server/connectors/inbound-audio-transcription.ts +261 -0
- package/src/lib/server/connectors/manager.ts +80 -2
- package/src/lib/server/connectors/whatsapp-text.test.ts +29 -0
- package/src/lib/server/connectors/whatsapp-text.ts +26 -0
- package/src/lib/server/connectors/whatsapp.ts +8 -5
- package/src/lib/server/orchestrator-lg.ts +12 -2
- package/src/lib/server/orchestrator.ts +6 -1
- package/src/lib/server/queue-followups.test.ts +224 -0
- package/src/lib/server/queue.ts +226 -24
- package/src/lib/server/scheduler.ts +3 -0
- package/src/lib/server/session-tools/chatroom.ts +11 -2
- package/src/lib/server/session-tools/context-mgmt.ts +2 -2
- package/src/lib/server/session-tools/index.ts +6 -2
- package/src/lib/server/session-tools/memory.ts +1 -1
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/wallet.ts +124 -0
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/solana.ts +122 -0
- package/src/lib/server/storage.ts +38 -0
- package/src/lib/server/stream-agent-chat.ts +126 -63
- package/src/lib/server/task-mention.test.ts +41 -0
- package/src/lib/server/task-mention.ts +3 -2
- package/src/lib/tool-definitions.ts +1 -0
- package/src/lib/view-routes.ts +1 -0
- package/src/stores/use-app-store.ts +8 -0
- package/src/types/index.ts +60 -1
|
@@ -187,7 +187,7 @@ export function buildMemoryTools(bctx: ToolBuildContext): StructuredToolInterfac
|
|
|
187
187
|
},
|
|
188
188
|
{
|
|
189
189
|
name: 'memory_tool',
|
|
190
|
-
description: '
|
|
190
|
+
description: 'My long-term memory — things I remember across conversations. I can store personal notes, recall past context, and build up knowledge over time. Memories can be private to me or shared with other agents. I can also attach files, link related memories, and contribute to a shared knowledge base. Actions: store, get, search, list, delete, link, unlink, knowledge_store, knowledge_search.',
|
|
191
191
|
schema: z.object({
|
|
192
192
|
action: z.enum(['store', 'get', 'search', 'list', 'delete', 'link', 'unlink', 'knowledge_store', 'knowledge_search']).describe('The action to perform'),
|
|
193
193
|
key: z.string().describe('For store: memory title. For get/delete/link/unlink: memory ID. For search: optional query fallback.'),
|
|
@@ -49,7 +49,7 @@ export function buildShellTools(bctx: ToolBuildContext): StructuredToolInterface
|
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
name: 'execute_command',
|
|
52
|
-
description: '
|
|
52
|
+
description: 'Run a shell command in my working directory. This is how I run servers, install packages, execute scripts, use git, and do anything hands-on. Use background=true for long-running processes like dev servers. Supports timeout/yield controls.',
|
|
53
53
|
schema: z.object({
|
|
54
54
|
command: z.string().describe('The shell command to execute'),
|
|
55
55
|
background: z.boolean().optional().describe('If true, start command in background immediately'),
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import type { ToolBuildContext } from './context'
|
|
4
|
+
import { loadWallets, loadWalletTransactions } from '../storage'
|
|
5
|
+
import type { AgentWallet, WalletTransaction } from '@/types'
|
|
6
|
+
|
|
7
|
+
export function buildWalletTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
8
|
+
if (!bctx.hasTool('wallet')) return []
|
|
9
|
+
|
|
10
|
+
const agentId = bctx.ctx?.agentId
|
|
11
|
+
|
|
12
|
+
function getAgentWallet(): AgentWallet | null {
|
|
13
|
+
if (!agentId) return null
|
|
14
|
+
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
15
|
+
return Object.values(wallets).find((w) => w.agentId === agentId) ?? null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return [
|
|
19
|
+
tool(
|
|
20
|
+
async ({ action, toAddress, amountSol, memo, limit }) => {
|
|
21
|
+
const wallet = getAgentWallet()
|
|
22
|
+
if (!wallet) {
|
|
23
|
+
return JSON.stringify({ error: 'No wallet linked to this agent. Ask the user to create one in the Wallets section.' })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
switch (action) {
|
|
27
|
+
case 'balance': {
|
|
28
|
+
try {
|
|
29
|
+
const { getBalance, lamportsToSol } = await import('../solana')
|
|
30
|
+
const balanceLamports = await getBalance(wallet.publicKey)
|
|
31
|
+
return JSON.stringify({
|
|
32
|
+
address: wallet.publicKey,
|
|
33
|
+
chain: wallet.chain,
|
|
34
|
+
balanceLamports,
|
|
35
|
+
balanceSol: lamportsToSol(balanceLamports),
|
|
36
|
+
})
|
|
37
|
+
} catch (err: unknown) {
|
|
38
|
+
return JSON.stringify({ error: `Failed to fetch balance: ${err instanceof Error ? err.message : String(err)}` })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
case 'address': {
|
|
43
|
+
return JSON.stringify({
|
|
44
|
+
address: wallet.publicKey,
|
|
45
|
+
chain: wallet.chain,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
case 'send': {
|
|
50
|
+
if (!toAddress) return JSON.stringify({ error: 'toAddress is required for send action' })
|
|
51
|
+
if (!amountSol || amountSol <= 0) return JSON.stringify({ error: 'amountSol must be positive' })
|
|
52
|
+
|
|
53
|
+
const { isValidSolanaAddress, solToLamports, lamportsToSol } = await import('../solana')
|
|
54
|
+
if (!isValidSolanaAddress(toAddress)) {
|
|
55
|
+
return JSON.stringify({ error: 'Invalid Solana address' })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const amountLamports = solToLamports(amountSol)
|
|
59
|
+
|
|
60
|
+
// Check per-tx limit
|
|
61
|
+
const perTxLimit = wallet.spendingLimitLamports ?? 100_000_000
|
|
62
|
+
if (amountLamports > perTxLimit) {
|
|
63
|
+
return JSON.stringify({
|
|
64
|
+
error: `Amount ${amountSol} SOL exceeds per-transaction limit of ${lamportsToSol(perTxLimit)} SOL`,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Send via API to enforce all limits and approval flow
|
|
69
|
+
try {
|
|
70
|
+
const baseUrl = process.env.NEXTAUTH_URL || `http://localhost:${process.env.PORT || 3456}`
|
|
71
|
+
const res = await fetch(`${baseUrl}/api/wallets/${wallet.id}/send`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
'X-Access-Key': process.env.ACCESS_KEY || '',
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({ toAddress, amountLamports, memo }),
|
|
78
|
+
})
|
|
79
|
+
const result = await res.json()
|
|
80
|
+
return JSON.stringify(result)
|
|
81
|
+
} catch (err: unknown) {
|
|
82
|
+
return JSON.stringify({ error: `Send failed: ${err instanceof Error ? err.message : String(err)}` })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case 'transactions': {
|
|
87
|
+
const allTxs = loadWalletTransactions() as Record<string, WalletTransaction>
|
|
88
|
+
const walletTxs = Object.values(allTxs)
|
|
89
|
+
.filter((tx) => tx.walletId === wallet.id)
|
|
90
|
+
.sort((a, b) => b.timestamp - a.timestamp)
|
|
91
|
+
.slice(0, limit ?? 10)
|
|
92
|
+
.map((tx) => ({
|
|
93
|
+
id: tx.id,
|
|
94
|
+
type: tx.type,
|
|
95
|
+
status: tx.status,
|
|
96
|
+
amountLamports: tx.amountLamports,
|
|
97
|
+
toAddress: tx.toAddress,
|
|
98
|
+
fromAddress: tx.fromAddress,
|
|
99
|
+
signature: tx.signature || undefined,
|
|
100
|
+
memo: tx.memo,
|
|
101
|
+
timestamp: tx.timestamp,
|
|
102
|
+
}))
|
|
103
|
+
|
|
104
|
+
return JSON.stringify({ transactions: walletTxs, count: walletTxs.length })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
return JSON.stringify({ error: `Unknown action: ${action}. Use balance, address, send, or transactions.` })
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'wallet_tool',
|
|
113
|
+
description: 'Manage your own crypto wallet. Actions: balance (check your SOL balance), address (get your wallet address), send (send SOL from your wallet — subject to your spending limits and user approval), transactions (view your recent transaction history).',
|
|
114
|
+
schema: z.object({
|
|
115
|
+
action: z.enum(['balance', 'address', 'send', 'transactions']).describe('Wallet action to perform'),
|
|
116
|
+
toAddress: z.string().optional().describe('Recipient Solana address (required for send)'),
|
|
117
|
+
amountSol: z.number().optional().describe('Amount in SOL to send (required for send)'),
|
|
118
|
+
memo: z.string().optional().describe('Reason or memo for the transaction'),
|
|
119
|
+
limit: z.number().optional().describe('Number of transactions to return (default 10, for transactions action)'),
|
|
120
|
+
}),
|
|
121
|
+
},
|
|
122
|
+
),
|
|
123
|
+
]
|
|
124
|
+
}
|
|
@@ -141,7 +141,7 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
name: 'web_search',
|
|
144
|
-
description: 'Search the web. Returns
|
|
144
|
+
description: 'Search the web for information. Returns results with title, url, and snippet.',
|
|
145
145
|
schema: z.object({
|
|
146
146
|
query: z.string().describe('Search query'),
|
|
147
147
|
maxResults: z.number().optional().describe('Maximum results to return (default 5, max 10)'),
|
|
@@ -179,7 +179,7 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
181
|
name: 'web_fetch',
|
|
182
|
-
description: 'Fetch a URL and
|
|
182
|
+
description: 'Fetch a URL and read its content (HTML stripped to text). How I read web pages and pull in external information.',
|
|
183
183
|
schema: z.object({
|
|
184
184
|
url: z.string().describe('The URL to fetch'),
|
|
185
185
|
}),
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Keypair, Connection, PublicKey, SystemProgram, Transaction, LAMPORTS_PER_SOL, sendAndConfirmTransaction } from '@solana/web3.js'
|
|
2
|
+
import bs58 from 'bs58'
|
|
3
|
+
import { encryptKey, decryptKey } from './storage'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com'
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Keypair generation & encryption
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export function generateSolanaKeypair(): { publicKey: string; encryptedPrivateKey: string } {
|
|
12
|
+
const keypair = Keypair.generate()
|
|
13
|
+
const secretKeyBase58 = bs58.encode(keypair.secretKey)
|
|
14
|
+
return {
|
|
15
|
+
publicKey: keypair.publicKey.toBase58(),
|
|
16
|
+
encryptedPrivateKey: encryptKey(secretKeyBase58),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getKeypairFromEncrypted(encryptedPrivateKey: string): Keypair {
|
|
21
|
+
const secretKeyBase58 = decryptKey(encryptedPrivateKey)
|
|
22
|
+
const secretKey = bs58.decode(secretKeyBase58)
|
|
23
|
+
return Keypair.fromSecretKey(secretKey)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Connection
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
export function getConnection(rpcUrl?: string): Connection {
|
|
31
|
+
return new Connection(rpcUrl || DEFAULT_RPC_URL, 'confirmed')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Balance
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
export async function getBalance(publicKey: string, rpcUrl?: string): Promise<number> {
|
|
39
|
+
const connection = getConnection(rpcUrl)
|
|
40
|
+
const pk = new PublicKey(publicKey)
|
|
41
|
+
return connection.getBalance(pk)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Send SOL
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
export async function sendSol(
|
|
49
|
+
encryptedPrivateKey: string,
|
|
50
|
+
toAddress: string,
|
|
51
|
+
lamports: number,
|
|
52
|
+
rpcUrl?: string,
|
|
53
|
+
): Promise<{ signature: string; fee: number }> {
|
|
54
|
+
const connection = getConnection(rpcUrl)
|
|
55
|
+
const fromKeypair = getKeypairFromEncrypted(encryptedPrivateKey)
|
|
56
|
+
const toPublicKey = new PublicKey(toAddress)
|
|
57
|
+
|
|
58
|
+
const transaction = new Transaction().add(
|
|
59
|
+
SystemProgram.transfer({
|
|
60
|
+
fromPubkey: fromKeypair.publicKey,
|
|
61
|
+
toPubkey: toPublicKey,
|
|
62
|
+
lamports,
|
|
63
|
+
}),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const signature = await sendAndConfirmTransaction(connection, transaction, [fromKeypair])
|
|
67
|
+
|
|
68
|
+
// Fetch fee from confirmed tx
|
|
69
|
+
let fee = 5000 // default fee estimate
|
|
70
|
+
try {
|
|
71
|
+
const txInfo = await connection.getTransaction(signature, { commitment: 'confirmed' })
|
|
72
|
+
if (txInfo?.meta?.fee) fee = txInfo.meta.fee
|
|
73
|
+
} catch {
|
|
74
|
+
// use default
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { signature, fee }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Recent transactions
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
export async function getRecentTransactions(
|
|
85
|
+
publicKey: string,
|
|
86
|
+
limit = 20,
|
|
87
|
+
rpcUrl?: string,
|
|
88
|
+
): Promise<Array<{ signature: string; blockTime: number | null; err: unknown }>> {
|
|
89
|
+
const connection = getConnection(rpcUrl)
|
|
90
|
+
const pk = new PublicKey(publicKey)
|
|
91
|
+
const signatures = await connection.getSignaturesForAddress(pk, { limit })
|
|
92
|
+
return signatures.map((s) => ({
|
|
93
|
+
signature: s.signature,
|
|
94
|
+
blockTime: s.blockTime ?? null,
|
|
95
|
+
err: s.err,
|
|
96
|
+
}))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Validate address
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
export function isValidSolanaAddress(address: string): boolean {
|
|
104
|
+
try {
|
|
105
|
+
new PublicKey(address)
|
|
106
|
+
return true
|
|
107
|
+
} catch {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Helpers
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
export function lamportsToSol(lamports: number): number {
|
|
117
|
+
return lamports / LAMPORTS_PER_SOL
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function solToLamports(sol: number): number {
|
|
121
|
+
return Math.round(sol * LAMPORTS_PER_SOL)
|
|
122
|
+
}
|
|
@@ -54,6 +54,9 @@ const COLLECTIONS = [
|
|
|
54
54
|
'webhook_retry_queue',
|
|
55
55
|
'notifications',
|
|
56
56
|
'chatrooms',
|
|
57
|
+
'wallets',
|
|
58
|
+
'wallet_transactions',
|
|
59
|
+
'wallet_balance_history',
|
|
57
60
|
] as const
|
|
58
61
|
|
|
59
62
|
for (const table of COLLECTIONS) {
|
|
@@ -826,6 +829,41 @@ export function markNotificationRead(id: string) {
|
|
|
826
829
|
}
|
|
827
830
|
}
|
|
828
831
|
|
|
832
|
+
// --- Wallets ---
|
|
833
|
+
export function loadWallets(): Record<string, unknown> {
|
|
834
|
+
return loadCollection('wallets')
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
export function upsertWallet(id: string, wallet: unknown) {
|
|
838
|
+
upsertCollectionItem('wallets', id, wallet)
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
export function deleteWallet(id: string) {
|
|
842
|
+
deleteCollectionItem('wallets', id)
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// --- Wallet Transactions ---
|
|
846
|
+
export function loadWalletTransactions(): Record<string, unknown> {
|
|
847
|
+
return loadCollection('wallet_transactions')
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
export function upsertWalletTransaction(id: string, tx: unknown) {
|
|
851
|
+
upsertCollectionItem('wallet_transactions', id, tx)
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export function deleteWalletTransaction(id: string) {
|
|
855
|
+
deleteCollectionItem('wallet_transactions', id)
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// --- Wallet Balance History ---
|
|
859
|
+
export function loadWalletBalanceHistory(): Record<string, unknown> {
|
|
860
|
+
return loadCollection('wallet_balance_history')
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
export function upsertWalletBalanceSnapshot(id: string, snapshot: unknown) {
|
|
864
|
+
upsertCollectionItem('wallet_balance_history', id, snapshot)
|
|
865
|
+
}
|
|
866
|
+
|
|
829
867
|
export function getSessionMessages(sessionId: string): Message[] {
|
|
830
868
|
const stmt = db.prepare('SELECT data FROM sessions WHERE id = ?')
|
|
831
869
|
const row = stmt.get(sessionId) as { data: string } | undefined
|