@swarmclawai/swarmclaw 0.2.0
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 +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import crypto from 'crypto'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
import Database from 'better-sqlite3'
|
|
6
|
+
|
|
7
|
+
import { DATA_DIR } from './data-dir'
|
|
8
|
+
export const UPLOAD_DIR = path.join(os.tmpdir(), 'swarmclaw-uploads')
|
|
9
|
+
|
|
10
|
+
// Ensure directories exist
|
|
11
|
+
for (const dir of [DATA_DIR, UPLOAD_DIR]) {
|
|
12
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// --- SQLite Database ---
|
|
16
|
+
const DB_PATH = path.join(DATA_DIR, 'swarmclaw.db')
|
|
17
|
+
const db = new Database(DB_PATH)
|
|
18
|
+
db.pragma('journal_mode = WAL')
|
|
19
|
+
db.pragma('foreign_keys = ON')
|
|
20
|
+
|
|
21
|
+
const collectionCacheKey = '__swarmclaw_storage_collection_cache__' as const
|
|
22
|
+
type StorageGlobals = typeof globalThis & {
|
|
23
|
+
[collectionCacheKey]?: Map<string, Map<string, string>>
|
|
24
|
+
}
|
|
25
|
+
const storageGlobals = globalThis as StorageGlobals
|
|
26
|
+
const collectionCache: Map<string, Map<string, string>> =
|
|
27
|
+
storageGlobals[collectionCacheKey]
|
|
28
|
+
?? (storageGlobals[collectionCacheKey] = new Map<string, Map<string, string>>())
|
|
29
|
+
|
|
30
|
+
// Collection tables (id → JSON blob)
|
|
31
|
+
const COLLECTIONS = [
|
|
32
|
+
'sessions',
|
|
33
|
+
'credentials',
|
|
34
|
+
'agents',
|
|
35
|
+
'schedules',
|
|
36
|
+
'tasks',
|
|
37
|
+
'secrets',
|
|
38
|
+
'provider_configs',
|
|
39
|
+
'skills',
|
|
40
|
+
'connectors',
|
|
41
|
+
'documents',
|
|
42
|
+
'webhooks',
|
|
43
|
+
'model_overrides',
|
|
44
|
+
'mcp_servers',
|
|
45
|
+
'webhook_logs',
|
|
46
|
+
] as const
|
|
47
|
+
|
|
48
|
+
for (const table of COLLECTIONS) {
|
|
49
|
+
db.exec(`CREATE TABLE IF NOT EXISTS ${table} (id TEXT PRIMARY KEY, data TEXT NOT NULL)`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Singleton tables (single row)
|
|
53
|
+
db.exec(`CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)`)
|
|
54
|
+
db.exec(`CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)`)
|
|
55
|
+
db.exec(`CREATE TABLE IF NOT EXISTS usage (session_id TEXT NOT NULL, data TEXT NOT NULL)`)
|
|
56
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_usage_session ON usage(session_id)`)
|
|
57
|
+
|
|
58
|
+
function readCollectionRaw(table: string): Map<string, string> {
|
|
59
|
+
const rows = db.prepare(`SELECT id, data FROM ${table}`).all() as { id: string; data: string }[]
|
|
60
|
+
const raw = new Map<string, string>()
|
|
61
|
+
for (const row of rows) {
|
|
62
|
+
raw.set(row.id, row.data)
|
|
63
|
+
}
|
|
64
|
+
return raw
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getCollectionRawCache(table: string): Map<string, string> {
|
|
68
|
+
const cached = collectionCache.get(table)
|
|
69
|
+
if (cached) return cached
|
|
70
|
+
const loaded = readCollectionRaw(table)
|
|
71
|
+
collectionCache.set(table, loaded)
|
|
72
|
+
return loaded
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function loadCollection(table: string): Record<string, any> {
|
|
76
|
+
const raw = getCollectionRawCache(table)
|
|
77
|
+
const result: Record<string, any> = {}
|
|
78
|
+
for (const [id, data] of raw.entries()) {
|
|
79
|
+
try {
|
|
80
|
+
result[id] = JSON.parse(data)
|
|
81
|
+
} catch {
|
|
82
|
+
// Ignore malformed records instead of crashing list endpoints.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function saveCollection(table: string, data: Record<string, any>) {
|
|
89
|
+
const current = getCollectionRawCache(table)
|
|
90
|
+
const toUpsert: Array<[string, string]> = []
|
|
91
|
+
|
|
92
|
+
for (const [id, val] of Object.entries(data)) {
|
|
93
|
+
const serialized = JSON.stringify(val)
|
|
94
|
+
if (typeof serialized !== 'string') continue
|
|
95
|
+
if (current.get(id) !== serialized) {
|
|
96
|
+
toUpsert.push([id, serialized])
|
|
97
|
+
}
|
|
98
|
+
current.set(id, serialized)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!toUpsert.length) return
|
|
102
|
+
|
|
103
|
+
const transaction = db.transaction(() => {
|
|
104
|
+
const upsert = db.prepare(`INSERT OR REPLACE INTO ${table} (id, data) VALUES (?, ?)`)
|
|
105
|
+
for (const [id, serialized] of toUpsert) {
|
|
106
|
+
upsert.run(id, serialized)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
transaction()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function deleteCollectionItem(table: string, id: string) {
|
|
113
|
+
db.prepare(`DELETE FROM ${table} WHERE id = ?`).run(id)
|
|
114
|
+
const cached = collectionCache.get(table)
|
|
115
|
+
if (cached) cached.delete(id)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Atomically insert or update a single item in a collection without
|
|
120
|
+
* loading/saving the entire collection. Prevents race conditions when
|
|
121
|
+
* concurrent processes are modifying different items.
|
|
122
|
+
*/
|
|
123
|
+
function upsertCollectionItem(table: string, id: string, value: any) {
|
|
124
|
+
const serialized = JSON.stringify(value)
|
|
125
|
+
db.prepare(`INSERT OR REPLACE INTO ${table} (id, data) VALUES (?, ?)`).run(id, serialized)
|
|
126
|
+
// Update the in-memory cache
|
|
127
|
+
const cached = collectionCache.get(table)
|
|
128
|
+
if (cached) {
|
|
129
|
+
cached.set(id, serialized)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function loadSingleton(table: string, fallback: any): any {
|
|
134
|
+
const row = db.prepare(`SELECT data FROM ${table} WHERE id = 1`).get() as { data: string } | undefined
|
|
135
|
+
return row ? JSON.parse(row.data) : fallback
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function saveSingleton(table: string, data: any) {
|
|
139
|
+
db.prepare(`INSERT OR REPLACE INTO ${table} (id, data) VALUES (1, ?)`).run(JSON.stringify(data))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- JSON Migration ---
|
|
143
|
+
// Auto-import from JSON files on first run, then leave them as backup
|
|
144
|
+
const JSON_FILES: Record<string, string> = {
|
|
145
|
+
sessions: path.join(DATA_DIR, 'sessions.json'),
|
|
146
|
+
credentials: path.join(DATA_DIR, 'credentials.json'),
|
|
147
|
+
agents: path.join(DATA_DIR, 'agents.json'),
|
|
148
|
+
schedules: path.join(DATA_DIR, 'schedules.json'),
|
|
149
|
+
tasks: path.join(DATA_DIR, 'tasks.json'),
|
|
150
|
+
secrets: path.join(DATA_DIR, 'secrets.json'),
|
|
151
|
+
provider_configs: path.join(DATA_DIR, 'providers.json'),
|
|
152
|
+
skills: path.join(DATA_DIR, 'skills.json'),
|
|
153
|
+
connectors: path.join(DATA_DIR, 'connectors.json'),
|
|
154
|
+
documents: path.join(DATA_DIR, 'documents.json'),
|
|
155
|
+
webhooks: path.join(DATA_DIR, 'webhooks.json'),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const MIGRATION_FLAG = path.join(DATA_DIR, '.sqlite_migrated')
|
|
159
|
+
|
|
160
|
+
function migrateFromJson() {
|
|
161
|
+
if (fs.existsSync(MIGRATION_FLAG)) return
|
|
162
|
+
|
|
163
|
+
console.log('[storage] Migrating from JSON files to SQLite...')
|
|
164
|
+
|
|
165
|
+
const transaction = db.transaction(() => {
|
|
166
|
+
for (const [table, jsonPath] of Object.entries(JSON_FILES)) {
|
|
167
|
+
if (fs.existsSync(jsonPath)) {
|
|
168
|
+
try {
|
|
169
|
+
const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
|
|
170
|
+
if (data && typeof data === 'object' && Object.keys(data).length > 0) {
|
|
171
|
+
const ins = db.prepare(`INSERT OR REPLACE INTO ${table} (id, data) VALUES (?, ?)`)
|
|
172
|
+
for (const [id, val] of Object.entries(data)) {
|
|
173
|
+
ins.run(id, JSON.stringify(val))
|
|
174
|
+
}
|
|
175
|
+
console.log(`[storage] Migrated ${table}: ${Object.keys(data).length} records`)
|
|
176
|
+
}
|
|
177
|
+
} catch { /* skip malformed files */ }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Settings (singleton)
|
|
182
|
+
const settingsPath = path.join(DATA_DIR, 'settings.json')
|
|
183
|
+
if (fs.existsSync(settingsPath)) {
|
|
184
|
+
try {
|
|
185
|
+
const data = JSON.parse(fs.readFileSync(settingsPath, 'utf8'))
|
|
186
|
+
if (data && Object.keys(data).length > 0) {
|
|
187
|
+
saveSingleton('settings', data)
|
|
188
|
+
console.log('[storage] Migrated settings')
|
|
189
|
+
}
|
|
190
|
+
} catch { /* skip */ }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Queue (singleton array)
|
|
194
|
+
const queuePath = path.join(DATA_DIR, 'queue.json')
|
|
195
|
+
if (fs.existsSync(queuePath)) {
|
|
196
|
+
try {
|
|
197
|
+
const data = JSON.parse(fs.readFileSync(queuePath, 'utf8'))
|
|
198
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
199
|
+
saveSingleton('queue', data)
|
|
200
|
+
console.log(`[storage] Migrated queue: ${data.length} items`)
|
|
201
|
+
}
|
|
202
|
+
} catch { /* skip */ }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Usage
|
|
206
|
+
const usagePath = path.join(DATA_DIR, 'usage.json')
|
|
207
|
+
if (fs.existsSync(usagePath)) {
|
|
208
|
+
try {
|
|
209
|
+
const data = JSON.parse(fs.readFileSync(usagePath, 'utf8'))
|
|
210
|
+
const ins = db.prepare(`INSERT INTO usage (session_id, data) VALUES (?, ?)`)
|
|
211
|
+
for (const [sessionId, records] of Object.entries(data)) {
|
|
212
|
+
if (Array.isArray(records)) {
|
|
213
|
+
for (const record of records) {
|
|
214
|
+
ins.run(sessionId, JSON.stringify(record))
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
console.log('[storage] Migrated usage records')
|
|
219
|
+
} catch { /* skip */ }
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
transaction()
|
|
224
|
+
fs.writeFileSync(MIGRATION_FLAG, new Date().toISOString())
|
|
225
|
+
console.log('[storage] Migration complete. JSON files preserved as backup.')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
migrateFromJson()
|
|
229
|
+
|
|
230
|
+
// Seed default agent if agents table is empty
|
|
231
|
+
{
|
|
232
|
+
const defaultStarterTools = [
|
|
233
|
+
'memory',
|
|
234
|
+
'files',
|
|
235
|
+
'web_search',
|
|
236
|
+
'web_fetch',
|
|
237
|
+
'browser',
|
|
238
|
+
'manage_agents',
|
|
239
|
+
'manage_tasks',
|
|
240
|
+
'manage_schedules',
|
|
241
|
+
'manage_skills',
|
|
242
|
+
'manage_connectors',
|
|
243
|
+
'manage_sessions',
|
|
244
|
+
'manage_secrets',
|
|
245
|
+
'manage_documents',
|
|
246
|
+
'manage_webhooks',
|
|
247
|
+
'claude_code',
|
|
248
|
+
'codex_cli',
|
|
249
|
+
'opencode_cli',
|
|
250
|
+
]
|
|
251
|
+
const count = (db.prepare('SELECT COUNT(*) as c FROM agents').get() as { c: number }).c
|
|
252
|
+
if (count === 0) {
|
|
253
|
+
const defaultAgent = {
|
|
254
|
+
id: 'default',
|
|
255
|
+
name: 'Assistant',
|
|
256
|
+
description: 'A general-purpose AI assistant',
|
|
257
|
+
provider: 'claude-cli',
|
|
258
|
+
model: '',
|
|
259
|
+
systemPrompt: `You are the default SwarmClaw assistant. SwarmClaw is a self-hosted AI agent orchestration dashboard.
|
|
260
|
+
|
|
261
|
+
Help users get started with the platform:
|
|
262
|
+
- **Agents**: Create specialized AI agents (Agents tab → "+"). Each agent has a provider, model, system prompt, and optional tools (shell, files, web search, browser). Use "Generate with AI" to scaffold agents from a description.
|
|
263
|
+
- **Orchestrators**: Toggle "Orchestrator" when creating an agent to let it delegate tasks to other agents. Orchestrators coordinate multi-agent workflows automatically.
|
|
264
|
+
- **Providers**: Configure LLM backends in Settings → Providers. Built-in providers: Claude Code CLI, OpenAI Codex CLI, OpenCode CLI, Anthropic, OpenAI, Google Gemini, DeepSeek, Groq, Together AI, Mistral AI, xAI (Grok), Fireworks AI, Ollama (local or cloud), and OpenClaw. You can also add custom OpenAI-compatible endpoints.
|
|
265
|
+
- **Tasks**: Use the Task Board to create, assign, and track work items. Agents can be assigned to tasks and will execute them autonomously.
|
|
266
|
+
- **Schedules**: Set up cron-based schedules to run agents or tasks on a recurring basis (Schedules tab).
|
|
267
|
+
- **Skills**: Create reusable skill files (markdown instructions) in the Skills tab and attach them to agents to specialize their behavior.
|
|
268
|
+
- **Connectors**: Bridge agents to Discord, Slack, Telegram, or WhatsApp so they can respond in chat platforms.
|
|
269
|
+
- **Secrets**: Store API keys securely in the encrypted vault (Settings → Secrets).
|
|
270
|
+
|
|
271
|
+
## Platform Tools
|
|
272
|
+
|
|
273
|
+
You have access to platform management tools. Here's how to use them:
|
|
274
|
+
|
|
275
|
+
- **manage_agents**: List, create, update, or delete agents. Use action "list" to see all agents, "create" with a JSON data payload to add new ones.
|
|
276
|
+
- **manage_tasks**: Create and manage task board items. Set "agentId" to assign a task to an agent, "status" to track progress (backlog → queued → running → completed/failed). Use action "create" with data like \`{"title": "...", "description": "...", "agentId": "...", "status": "backlog"}\`.
|
|
277
|
+
- **manage_schedules**: Create recurring or one-time scheduled jobs. Set "scheduleType" to "cron", "interval", or "once". Provide "taskPrompt" for what the agent should do and "agentId" for who runs it.
|
|
278
|
+
- **manage_skills**: List, create, or update reusable skill definitions that can be attached to agents.
|
|
279
|
+
- **manage_documents**: Upload/index/search long-lived docs (PDFs, markdown, notes) for retrieval.
|
|
280
|
+
- **manage_webhooks**: Register external webhook endpoints that trigger agent runs.
|
|
281
|
+
- **manage_connectors**: Manage chat platform bridges (Discord, Slack, Telegram, WhatsApp).
|
|
282
|
+
- **manage_sessions**: Session-level operations. Use \`sessions_tool\` to list sessions, send inter-session messages, spawn new agent sessions, and inspect status/history.
|
|
283
|
+
- **manage_secrets**: Store and retrieve encrypted service tokens/API credentials for durable reuse.
|
|
284
|
+
- **memory_tool**: Store and retrieve long-term memories. Use "store" to save knowledge, "search" to find relevant memories.
|
|
285
|
+
|
|
286
|
+
Be concise and helpful. When users ask how to do something, guide them to the specific UI location and explain the steps.`,
|
|
287
|
+
soul: '',
|
|
288
|
+
isOrchestrator: false,
|
|
289
|
+
tools: defaultStarterTools,
|
|
290
|
+
platformAssignScope: 'all',
|
|
291
|
+
skillIds: [],
|
|
292
|
+
subAgentIds: [],
|
|
293
|
+
createdAt: Date.now(),
|
|
294
|
+
updatedAt: Date.now(),
|
|
295
|
+
}
|
|
296
|
+
db.prepare(`INSERT OR REPLACE INTO agents (id, data) VALUES (?, ?)`).run('default', JSON.stringify(defaultAgent))
|
|
297
|
+
} else {
|
|
298
|
+
const row = db.prepare('SELECT data FROM agents WHERE id = ?').get('default') as { data: string } | undefined
|
|
299
|
+
if (row?.data) {
|
|
300
|
+
try {
|
|
301
|
+
const existing = JSON.parse(row.data) as Record<string, unknown>
|
|
302
|
+
const existingTools = Array.isArray(existing.tools) ? existing.tools : []
|
|
303
|
+
const mergedTools = Array.from(new Set([...existingTools, ...defaultStarterTools])).filter((t) => t !== 'delete_file')
|
|
304
|
+
if (JSON.stringify(existingTools) !== JSON.stringify(mergedTools)) {
|
|
305
|
+
existing.tools = mergedTools
|
|
306
|
+
existing.updatedAt = Date.now()
|
|
307
|
+
db.prepare('UPDATE agents SET data = ? WHERE id = ?').run(JSON.stringify(existing), 'default')
|
|
308
|
+
}
|
|
309
|
+
} catch {
|
|
310
|
+
// ignore malformed default agent payloads
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// --- .env loading ---
|
|
317
|
+
function loadEnv() {
|
|
318
|
+
const envPath = path.join(process.cwd(), '.env.local')
|
|
319
|
+
if (fs.existsSync(envPath)) {
|
|
320
|
+
fs.readFileSync(envPath, 'utf8').split('\n').forEach(line => {
|
|
321
|
+
const [k, ...v] = line.split('=')
|
|
322
|
+
if (k && v.length) process.env[k.trim()] = v.join('=').trim()
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
loadEnv()
|
|
327
|
+
|
|
328
|
+
// Auto-generate CREDENTIAL_SECRET if missing
|
|
329
|
+
if (!process.env.CREDENTIAL_SECRET) {
|
|
330
|
+
const secret = crypto.randomBytes(32).toString('hex')
|
|
331
|
+
const envPath = path.join(process.cwd(), '.env.local')
|
|
332
|
+
fs.appendFileSync(envPath, `\nCREDENTIAL_SECRET=${secret}\n`)
|
|
333
|
+
process.env.CREDENTIAL_SECRET = secret
|
|
334
|
+
console.log('[credentials] Generated CREDENTIAL_SECRET in .env.local')
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Auto-generate ACCESS_KEY if missing (used for simple auth)
|
|
338
|
+
const SETUP_FLAG = path.join(DATA_DIR, '.setup_pending')
|
|
339
|
+
if (!process.env.ACCESS_KEY) {
|
|
340
|
+
const key = crypto.randomBytes(16).toString('hex')
|
|
341
|
+
const envPath = path.join(process.cwd(), '.env.local')
|
|
342
|
+
fs.appendFileSync(envPath, `\nACCESS_KEY=${key}\n`)
|
|
343
|
+
process.env.ACCESS_KEY = key
|
|
344
|
+
fs.writeFileSync(SETUP_FLAG, key)
|
|
345
|
+
console.log(`\n${'='.repeat(50)}`)
|
|
346
|
+
console.log(` ACCESS KEY: ${key}`)
|
|
347
|
+
console.log(` Use this key to connect from the browser.`)
|
|
348
|
+
console.log(`${'='.repeat(50)}\n`)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function getAccessKey(): string {
|
|
352
|
+
return process.env.ACCESS_KEY || ''
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function validateAccessKey(key: string): boolean {
|
|
356
|
+
return key === process.env.ACCESS_KEY
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function isFirstTimeSetup(): boolean {
|
|
360
|
+
return fs.existsSync(SETUP_FLAG)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function markSetupComplete(): void {
|
|
364
|
+
if (fs.existsSync(SETUP_FLAG)) fs.unlinkSync(SETUP_FLAG)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// --- Sessions ---
|
|
368
|
+
export function loadSessions(): Record<string, any> {
|
|
369
|
+
return loadCollection('sessions')
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function saveSessions(s: Record<string, any>) {
|
|
373
|
+
saveCollection('sessions', s)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function disableAllSessionHeartbeats(): number {
|
|
377
|
+
const rows = db.prepare('SELECT id, data FROM sessions').all() as Array<{ id: string; data: string }>
|
|
378
|
+
if (!rows.length) return 0
|
|
379
|
+
|
|
380
|
+
const update = db.prepare('UPDATE sessions SET data = ? WHERE id = ?')
|
|
381
|
+
let changed = 0
|
|
382
|
+
|
|
383
|
+
const tx = db.transaction(() => {
|
|
384
|
+
for (const row of rows) {
|
|
385
|
+
let parsed: any
|
|
386
|
+
try {
|
|
387
|
+
parsed = JSON.parse(row.data)
|
|
388
|
+
} catch {
|
|
389
|
+
continue
|
|
390
|
+
}
|
|
391
|
+
if (!parsed || typeof parsed !== 'object') continue
|
|
392
|
+
if (parsed.heartbeatEnabled === false) continue
|
|
393
|
+
|
|
394
|
+
parsed.heartbeatEnabled = false
|
|
395
|
+
parsed.lastActiveAt = Date.now()
|
|
396
|
+
update.run(JSON.stringify(parsed), row.id)
|
|
397
|
+
changed += 1
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
tx()
|
|
401
|
+
|
|
402
|
+
return changed
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// --- Credentials ---
|
|
406
|
+
export function loadCredentials(): Record<string, any> {
|
|
407
|
+
return loadCollection('credentials')
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function saveCredentials(c: Record<string, any>) {
|
|
411
|
+
saveCollection('credentials', c)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function encryptKey(plaintext: string): string {
|
|
415
|
+
const key = Buffer.from(process.env.CREDENTIAL_SECRET!, 'hex')
|
|
416
|
+
const iv = crypto.randomBytes(12)
|
|
417
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)
|
|
418
|
+
let encrypted = cipher.update(plaintext, 'utf8', 'hex')
|
|
419
|
+
encrypted += cipher.final('hex')
|
|
420
|
+
const tag = cipher.getAuthTag().toString('hex')
|
|
421
|
+
return iv.toString('hex') + ':' + tag + ':' + encrypted
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function decryptKey(encrypted: string): string {
|
|
425
|
+
const key = Buffer.from(process.env.CREDENTIAL_SECRET!, 'hex')
|
|
426
|
+
const [ivHex, tagHex, data] = encrypted.split(':')
|
|
427
|
+
const iv = Buffer.from(ivHex, 'hex')
|
|
428
|
+
const tag = Buffer.from(tagHex, 'hex')
|
|
429
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)
|
|
430
|
+
decipher.setAuthTag(tag)
|
|
431
|
+
let decrypted = decipher.update(data, 'hex', 'utf8')
|
|
432
|
+
decrypted += decipher.final('utf8')
|
|
433
|
+
return decrypted
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// --- Agents ---
|
|
437
|
+
export function loadAgents(): Record<string, any> {
|
|
438
|
+
return loadCollection('agents')
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function saveAgents(p: Record<string, any>) {
|
|
442
|
+
saveCollection('agents', p)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// --- Schedules ---
|
|
446
|
+
export function loadSchedules(): Record<string, any> {
|
|
447
|
+
return loadCollection('schedules')
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function saveSchedules(s: Record<string, any>) {
|
|
451
|
+
saveCollection('schedules', s)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// --- Tasks ---
|
|
455
|
+
export function loadTasks(): Record<string, any> {
|
|
456
|
+
return loadCollection('tasks')
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function saveTasks(t: Record<string, any>) {
|
|
460
|
+
saveCollection('tasks', t)
|
|
461
|
+
}
|
|
462
|
+
export function upsertTask(id: string, task: any) {
|
|
463
|
+
upsertCollectionItem('tasks', id, task)
|
|
464
|
+
}
|
|
465
|
+
export function deleteTask(id: string) { deleteCollectionItem('tasks', id) }
|
|
466
|
+
export function deleteSession(id: string) { deleteCollectionItem('sessions', id) }
|
|
467
|
+
export function deleteAgent(id: string) { deleteCollectionItem('agents', id) }
|
|
468
|
+
export function deleteSchedule(id: string) { deleteCollectionItem('schedules', id) }
|
|
469
|
+
export function deleteSkill(id: string) { deleteCollectionItem('skills', id) }
|
|
470
|
+
|
|
471
|
+
// --- Queue ---
|
|
472
|
+
export function loadQueue(): string[] {
|
|
473
|
+
return loadSingleton('queue', [])
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function saveQueue(q: string[]) {
|
|
477
|
+
saveSingleton('queue', q)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// --- Settings ---
|
|
481
|
+
export function loadSettings(): Record<string, any> {
|
|
482
|
+
return loadSingleton('settings', {})
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function saveSettings(s: Record<string, any>) {
|
|
486
|
+
saveSingleton('settings', s)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// --- Secrets (service keys for orchestrators) ---
|
|
490
|
+
export function loadSecrets(): Record<string, any> {
|
|
491
|
+
return loadCollection('secrets')
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function saveSecrets(s: Record<string, any>) {
|
|
495
|
+
saveCollection('secrets', s)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export async function getSecret(key: string): Promise<{
|
|
499
|
+
id: string
|
|
500
|
+
name: string
|
|
501
|
+
service: string
|
|
502
|
+
value: string
|
|
503
|
+
scope: string
|
|
504
|
+
agentIds: string[]
|
|
505
|
+
createdAt: number
|
|
506
|
+
updatedAt: number
|
|
507
|
+
} | null> {
|
|
508
|
+
const needle = typeof key === 'string' ? key.trim().toLowerCase() : ''
|
|
509
|
+
if (!needle) return null
|
|
510
|
+
|
|
511
|
+
const secrets = loadSecrets()
|
|
512
|
+
const matches = Object.values(secrets).find((secret: any) => {
|
|
513
|
+
if (!secret || typeof secret !== 'object') return false
|
|
514
|
+
const id = typeof secret.id === 'string' ? secret.id.toLowerCase() : ''
|
|
515
|
+
const name = typeof secret.name === 'string' ? secret.name.toLowerCase() : ''
|
|
516
|
+
const service = typeof secret.service === 'string' ? secret.service.toLowerCase() : ''
|
|
517
|
+
return id === needle || name === needle || service === needle
|
|
518
|
+
}) as any | undefined
|
|
519
|
+
|
|
520
|
+
if (!matches) return null
|
|
521
|
+
|
|
522
|
+
try {
|
|
523
|
+
const decryptedValue =
|
|
524
|
+
typeof matches.encryptedValue === 'string'
|
|
525
|
+
? decryptKey(matches.encryptedValue)
|
|
526
|
+
: (typeof matches.value === 'string' ? matches.value : '')
|
|
527
|
+
if (!decryptedValue) return null
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
id: matches.id,
|
|
531
|
+
name: matches.name,
|
|
532
|
+
service: matches.service,
|
|
533
|
+
value: decryptedValue,
|
|
534
|
+
scope: matches.scope,
|
|
535
|
+
agentIds: Array.isArray(matches.agentIds) ? matches.agentIds : [],
|
|
536
|
+
createdAt: matches.createdAt,
|
|
537
|
+
updatedAt: matches.updatedAt,
|
|
538
|
+
}
|
|
539
|
+
} catch {
|
|
540
|
+
return null
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// --- Provider Configs (custom providers) ---
|
|
545
|
+
export function loadProviderConfigs(): Record<string, any> {
|
|
546
|
+
return loadCollection('provider_configs')
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export function saveProviderConfigs(p: Record<string, any>) {
|
|
550
|
+
saveCollection('provider_configs', p)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// --- Model Overrides (user-added models for built-in providers) ---
|
|
554
|
+
export function loadModelOverrides(): Record<string, string[]> {
|
|
555
|
+
return loadCollection('model_overrides') as Record<string, string[]>
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export function saveModelOverrides(m: Record<string, string[]>) {
|
|
559
|
+
saveCollection('model_overrides', m)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// --- Skills ---
|
|
563
|
+
export function loadSkills(): Record<string, any> {
|
|
564
|
+
return loadCollection('skills')
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export function saveSkills(s: Record<string, any>) {
|
|
568
|
+
saveCollection('skills', s)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// --- Usage ---
|
|
572
|
+
export function loadUsage(): Record<string, any[]> {
|
|
573
|
+
const stmt = db.prepare('SELECT session_id, data FROM usage')
|
|
574
|
+
const rows = stmt.all() as { session_id: string; data: string }[]
|
|
575
|
+
const result: Record<string, any[]> = {}
|
|
576
|
+
for (const row of rows) {
|
|
577
|
+
if (!result[row.session_id]) result[row.session_id] = []
|
|
578
|
+
result[row.session_id].push(JSON.parse(row.data))
|
|
579
|
+
}
|
|
580
|
+
return result
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export function saveUsage(u: Record<string, any[]>) {
|
|
584
|
+
const del = db.prepare('DELETE FROM usage')
|
|
585
|
+
const ins = db.prepare('INSERT INTO usage (session_id, data) VALUES (?, ?)')
|
|
586
|
+
const transaction = db.transaction(() => {
|
|
587
|
+
del.run()
|
|
588
|
+
for (const [sessionId, records] of Object.entries(u)) {
|
|
589
|
+
for (const record of records) {
|
|
590
|
+
ins.run(sessionId, JSON.stringify(record))
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
})
|
|
594
|
+
transaction()
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export function appendUsage(sessionId: string, record: any) {
|
|
598
|
+
const ins = db.prepare('INSERT INTO usage (session_id, data) VALUES (?, ?)')
|
|
599
|
+
ins.run(sessionId, JSON.stringify(record))
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// --- Connectors ---
|
|
603
|
+
export function loadConnectors(): Record<string, any> {
|
|
604
|
+
return loadCollection('connectors')
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export function saveConnectors(c: Record<string, any>) {
|
|
608
|
+
saveCollection('connectors', c)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// --- Documents ---
|
|
612
|
+
export function loadDocuments(): Record<string, any> {
|
|
613
|
+
return loadCollection('documents')
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function saveDocuments(d: Record<string, any>) {
|
|
617
|
+
saveCollection('documents', d)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// --- Webhooks ---
|
|
621
|
+
export function loadWebhooks(): Record<string, any> {
|
|
622
|
+
return loadCollection('webhooks')
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export function saveWebhooks(w: Record<string, any>) {
|
|
626
|
+
saveCollection('webhooks', w)
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// --- Active processes ---
|
|
630
|
+
export const active = new Map<string, any>()
|
|
631
|
+
export const devServers = new Map<string, { proc: any; url: string }>()
|
|
632
|
+
|
|
633
|
+
// --- Utilities ---
|
|
634
|
+
export function localIP(): string {
|
|
635
|
+
for (const ifaces of Object.values(os.networkInterfaces())) {
|
|
636
|
+
if (!ifaces) continue
|
|
637
|
+
for (const i of ifaces) {
|
|
638
|
+
if (i.family === 'IPv4' && !i.internal) return i.address
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return 'localhost'
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// --- MCP Servers ---
|
|
645
|
+
export function loadMcpServers(): Record<string, any> {
|
|
646
|
+
return loadCollection('mcp_servers')
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export function saveMcpServers(m: Record<string, any>) {
|
|
650
|
+
saveCollection('mcp_servers', m)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export function deleteMcpServer(id: string) { deleteCollectionItem('mcp_servers', id) }
|
|
654
|
+
|
|
655
|
+
// --- Webhook Logs ---
|
|
656
|
+
export function loadWebhookLogs(): Record<string, any> {
|
|
657
|
+
return loadCollection('webhook_logs')
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export function appendWebhookLog(id: string, entry: any) {
|
|
661
|
+
upsertCollectionItem('webhook_logs', id, entry)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export function getSessionMessages(sessionId: string): any[] {
|
|
665
|
+
const stmt = db.prepare('SELECT data FROM sessions WHERE id = ?')
|
|
666
|
+
const row = stmt.get(sessionId) as { data: string } | undefined
|
|
667
|
+
if (!row) return []
|
|
668
|
+
const session = JSON.parse(row.data)
|
|
669
|
+
return session?.messages || []
|
|
670
|
+
}
|